mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-17 22:11:30 +08:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4410e78e2 | ||
|
|
e3fcc6cce3 | ||
|
|
265d08fc3b | ||
|
|
7c8329c5c6 | ||
|
|
d3947c9a19 | ||
|
|
cd99331668 | ||
|
|
472e18b10a | ||
|
|
d443f5de28 | ||
|
|
3242d132f6 | ||
|
|
e66d2facd4 | ||
|
|
f15b8dc0da | ||
|
|
3275824aec | ||
|
|
965cb704ec | ||
|
|
938e165470 | ||
|
|
9058ef3344 | ||
|
|
ed39cc3038 | ||
|
|
a77752c4cb | ||
|
|
c9940957f0 | ||
|
|
c90d72d720 | ||
|
|
2c30bd9d24 | ||
|
|
f2dc8e21a8 | ||
|
|
6a0da9cf09 | ||
|
|
d55974c352 | ||
|
|
a898c22f4b | ||
|
|
b82e8bedfc | ||
|
|
7453cefd94 | ||
|
|
1ed6b958cb | ||
|
|
57896ab176 | ||
|
|
5c370b3914 | ||
|
|
182e35adc7 | ||
|
|
d0a360fd80 | ||
|
|
2fbc0625de | ||
|
|
d3d20a4e20 | ||
|
|
2c088d3504 | ||
|
|
6f9728f2d4 | ||
|
|
30552fd202 | ||
|
|
9826c4e943 | ||
|
|
bb9445bd0f | ||
|
|
1f7e66f4cb | ||
|
|
2a34e918a0 | ||
|
|
21c0d924ab | ||
|
|
c8d5ee6565 | ||
|
|
3d8fc7ca7b | ||
|
|
246b5b93f8 | ||
|
|
2183c0980b | ||
|
|
4ae301710d | ||
|
|
5f9390c210 | ||
|
|
0f3a03aab7 | ||
|
|
02f455b0cc | ||
|
|
ffddf60184 | ||
|
|
482840b8bb | ||
|
|
a3637cf2b6 | ||
|
|
48669cdb34 | ||
|
|
a953845ba7 | ||
|
|
8d71534839 | ||
|
|
d110118961 | ||
|
|
fa1ed2bc0c | ||
|
|
3f28978dad | ||
|
|
02cd121465 | ||
|
|
5481c300b2 | ||
|
|
7b75257a4a | ||
|
|
c02e5cad73 | ||
|
|
dee03c0f9f | ||
|
|
d1159764f6 | ||
|
|
eacb07988d | ||
|
|
a375766ac2 | ||
|
|
9b9276e752 | ||
|
|
753a2ab2b7 | ||
|
|
0cef5f79ee | ||
|
|
b11a8dfe54 | ||
|
|
2d1c94f1ef | ||
|
|
e14e850e10 |
18
.github/workflows/flutter-build.yml
vendored
18
.github/workflows/flutter-build.yml
vendored
@@ -39,7 +39,7 @@ env:
|
||||
# 2. Update the `VCPKG_COMMIT_ID` in `ci.yml` and `playground.yml`.
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836" # 2025.01.13, got "/opt/artifacts/vcpkg/vcpkg: No such file or directory" with latest version
|
||||
VERSION: "1.4.2"
|
||||
VERSION: "1.4.3"
|
||||
NDK_VERSION: "r27c"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
@@ -392,6 +392,13 @@ jobs:
|
||||
ls -l ./libs/portable/Runner.res;
|
||||
fi
|
||||
|
||||
- name: Upload unsigned
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: rustdesk-unsigned-windows-${{ matrix.job.arch }}
|
||||
path: Release
|
||||
|
||||
- name: Sign rustdesk files
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != ''
|
||||
shell: bash
|
||||
@@ -756,6 +763,7 @@ jobs:
|
||||
needs:
|
||||
- build-for-macOS
|
||||
- build-for-windows-flutter
|
||||
- build-for-windows-sciter
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ inputs.upload-artifact }}
|
||||
steps:
|
||||
@@ -777,9 +785,15 @@ jobs:
|
||||
name: rustdesk-unsigned-windows-x86_64
|
||||
path: ./windows-x86_64/
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@master
|
||||
with:
|
||||
name: rustdesk-unsigned-windows-x86
|
||||
path: ./windows-x86/
|
||||
|
||||
- name: Combine unsigned app
|
||||
run: |
|
||||
tar czf rustdesk-${{ env.VERSION }}-unsigned.tar.gz *.dmg windows-x86_64
|
||||
tar czf rustdesk-${{ env.VERSION }}-unsigned.tar.gz *.dmg windows-x86_64 windows-x86
|
||||
|
||||
- name: Publish unsigned app
|
||||
uses: softprops/action-gh-release@v1
|
||||
|
||||
2
.github/workflows/playground.yml
vendored
2
.github/workflows/playground.yml
vendored
@@ -17,7 +17,7 @@ env:
|
||||
TAG_NAME: "nightly"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
VERSION: "1.4.2"
|
||||
VERSION: "1.4.3"
|
||||
NDK_VERSION: "r26d"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
|
||||
4
.github/workflows/winget.yml
vendored
4
.github/workflows/winget.yml
vendored
@@ -10,6 +10,6 @@ jobs:
|
||||
- uses: vedantmgoyal9/winget-releaser@main
|
||||
with:
|
||||
identifier: RustDesk.RustDesk
|
||||
version: "1.4.2"
|
||||
release-tag: "1.4.2"
|
||||
version: "1.4.3"
|
||||
release-tag: "1.4.3"
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
||||
77
Cargo.lock
generated
77
Cargo.lock
generated
@@ -1145,7 +1145,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "clipboard-master"
|
||||
version = "4.0.0-beta.6"
|
||||
source = "git+https://github.com/rustdesk-org/clipboard-master#4fb62e5b62fb6350d82b571ec7ba94b3cd466695"
|
||||
source = "git+https://github.com/rustdesk-org/clipboard-master#ddc39f00a6211959489ae683aa6ae6eedf03a809"
|
||||
dependencies = [
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
@@ -4010,7 +4010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5426,7 +5426,7 @@ dependencies = [
|
||||
"libc",
|
||||
"redox_syscall 0.5.2",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6527,7 +6527,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
dependencies = [
|
||||
"android-wakelock",
|
||||
"android_logger",
|
||||
@@ -6642,13 +6642,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"dirs 5.0.1",
|
||||
"md5",
|
||||
"native-windows-gui",
|
||||
"winapi 0.3.9",
|
||||
"windows 0.61.1",
|
||||
"winres",
|
||||
]
|
||||
|
||||
@@ -9044,7 +9045,7 @@ dependencies = [
|
||||
"windows-core 0.52.0",
|
||||
"windows-implement 0.52.0",
|
||||
"windows-interface 0.52.0",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9054,7 +9055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
|
||||
dependencies = [
|
||||
"windows-core 0.54.0",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9094,7 +9095,7 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9104,7 +9105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
|
||||
dependencies = [
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9207,7 +9208,7 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9272,7 +9273,7 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9307,18 +9308,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.5",
|
||||
"windows_aarch64_msvc 0.52.5",
|
||||
"windows_i686_gnu 0.52.5",
|
||||
"windows_i686_gnullvm 0.52.5",
|
||||
"windows_i686_msvc 0.52.5",
|
||||
"windows_x86_64_gnu 0.52.5",
|
||||
"windows_x86_64_gnullvm 0.52.5",
|
||||
"windows_x86_64_msvc 0.52.5",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9343,7 +9344,7 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9369,9 +9370,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
@@ -9405,9 +9406,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@@ -9441,9 +9442,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
@@ -9453,9 +9454,9 @@ checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
@@ -9489,9 +9490,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
@@ -9525,9 +9526,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
@@ -9549,9 +9550,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
@@ -9585,9 +9586,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a>]<br>
|
||||
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a>] | [<a href="docs/README-RO.md">Română</a>]<br>
|
||||
<b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> to your native language</b>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.4.2
|
||||
version: 1.4.3
|
||||
exec: usr/share/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.4.2
|
||||
version: 1.4.3
|
||||
exec: usr/share/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
85
docs/CODE_OF_CONDUCT-RO.md
Normal file
85
docs/CODE_OF_CONDUCT-RO.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Codul de Conduită al Contributorilor
|
||||
|
||||
## Angajamentul Nostru
|
||||
|
||||
Noi, ca membri, contribuitori și lideri, ne angajăm să facem ca participarea în comunitatea noastră să fie o experiență fără hărțuire pentru toată lumea, indiferent de vârstă, dimensiunea corpului, dizabilități vizibile sau invizibile, etnie, caracteristici sexuale, identitate și exprimare de gen, nivel de experiență, educație, statut socio-economic, naționalitate, aspect personal, rasă, religie sau identitate și orientare sexuală.
|
||||
|
||||
Ne angajăm să acționăm și să interacționăm în moduri care contribuie la o comunitate deschisă, primitoare, diversă, incluzivă și sănătoasă.
|
||||
|
||||
## Standardele Noastre
|
||||
|
||||
Exemple de comportamente care contribuie la un mediu pozitiv pentru comunitatea noastră includ:
|
||||
|
||||
* Demonstrarea empatiei și a bunătății față de ceilalți
|
||||
* Respectarea opiniilor, punctelor de vedere și experiențelor diferite
|
||||
* Oferirea și acceptarea cu grație a feedback-ului constructiv
|
||||
* Asumarea responsabilității și cererea de scuze celor afectați de greșelile noastre și învățarea din experiență
|
||||
* Concentrarea pe ceea ce este cel mai bun nu doar pentru noi ca indivizi, ci pentru întreaga comunitate
|
||||
|
||||
Exemple de comportamente inacceptabile includ:
|
||||
|
||||
* Utilizarea limbajului sau imaginilor sexualizate, precum și atenția sau avansurile sexuale de orice fel
|
||||
* Trollare, insulte sau comentarii denigratoare și atacuri personale sau politice
|
||||
* Hărțuire publică sau privată
|
||||
* Publicarea informațiilor private ale altora, cum ar fi adresa fizică sau de e-mail, fără permisiunea explicită
|
||||
* Alte comportamente care ar putea fi considerate inadecvate într-un cadru profesional
|
||||
|
||||
## Responsabilități de Aplicare
|
||||
|
||||
Liderii comunității sunt responsabili pentru clarificarea și aplicarea standardelor noastre de comportament acceptabil și vor lua măsuri corective adecvate și echitabile ca răspuns la orice comportament pe care îl consideră inadecvat, amenințător, ofensator sau dăunător.
|
||||
|
||||
Liderii comunității au dreptul și responsabilitatea de a elimina, edita sau respinge comentarii, commit-uri, cod, editări wiki, probleme și alte contribuții care nu se aliniază acestui Cod de Conduită și vor comunica motivele pentru deciziile de moderare atunci când este cazul.
|
||||
|
||||
## Domeniu de Aplicare
|
||||
|
||||
Acest Cod de Conduită se aplică în toate spațiile comunității și se aplică și atunci când un individ reprezintă oficial comunitatea în spații publice.
|
||||
Exemple de reprezentare a comunității includ utilizarea unei adrese de e-mail oficiale, postarea printr-un cont oficial de social media sau acționarea ca reprezentant desemnat la un eveniment online sau offline.
|
||||
|
||||
## Aplicare
|
||||
|
||||
Cazurile de comportament abuziv, hărțuitor sau altfel inacceptabil pot fi raportate liderilor comunității responsabili pentru aplicare la [info@rustdesk.com](mailto:info@rustdesk.com).
|
||||
Toate plângerile vor fi revizuite și investigate prompt și corect.
|
||||
|
||||
Toți liderii comunității sunt obligați să respecte confidențialitatea și securitatea persoanei care raportează orice incident.
|
||||
|
||||
## Ghiduri de Aplicare
|
||||
|
||||
Liderii comunității vor urma aceste Ghiduri privind Impactul Comunității pentru a stabili consecințele pentru orice acțiune pe care o consideră o încălcare a acestui Cod de Conduită:
|
||||
|
||||
### 1. Corectare
|
||||
|
||||
**Impact asupra comunității**: Utilizarea limbajului neadecvat sau alte comportamente considerate neprofesionale sau nedorite în comunitate.
|
||||
|
||||
**Consecință**: O avertizare scrisă și privată din partea liderilor comunității, oferind claritate asupra naturii încălcării și o explicație despre motivul pentru care comportamentul a fost inadecvat. Poate fi cerută o scuză publică.
|
||||
|
||||
### 2. Avertisment
|
||||
|
||||
**Impact asupra comunității**: Încălcare printr-un incident singular sau o serie de acțiuni.
|
||||
|
||||
**Consecință**: Un avertisment cu consecințe pentru continuarea comportamentului. Nicio interacțiune cu persoanele implicate, inclusiv interacțiuni nesolicitate cu cei care aplică Codul de Conduită, pentru o perioadă specificată. Aceasta include evitarea interacțiunilor în spațiile comunității, precum și pe canale externe, cum ar fi rețelele sociale. Încălcarea acestor termeni poate duce la o suspendare temporară sau permanentă.
|
||||
|
||||
### 3. Suspendare Temporară
|
||||
|
||||
**Impact asupra comunității**: O încălcare serioasă a standardelor comunității, inclusiv comportament neadecvat susținut.
|
||||
|
||||
**Consecință**: Suspendare temporară de la orice tip de interacțiune sau comunicare publică cu comunitatea pentru o perioadă specificată. Nicio interacțiune publică sau privată cu persoanele implicate, inclusiv interacțiuni nesolicitate cu cei care aplică Codul de Conduită, nu este permisă în această perioadă. Încălcarea acestor termeni poate duce la o interdicție permanentă.
|
||||
|
||||
### 4. Interdicție Permanentă
|
||||
|
||||
**Impact asupra comunității**: Demonstrând un tipar de încălcare a standardelor comunității, inclusiv comportament neadecvat susținut, hărțuire a unei persoane sau agresiune față de sau denigrare a unor grupuri de persoane.
|
||||
|
||||
**Consecință**: Interdicție permanentă de la orice tip de interacțiune publică în cadrul comunității.
|
||||
|
||||
## Atribuire
|
||||
|
||||
Acest Cod de Conduită este adaptat din [Contributor Covenant][homepage], versiunea 2.0, disponibil la [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
||||
|
||||
Ghidurile privind Impactul Comunității au fost inspirate de [scara de aplicare a codului de conduită Mozilla][Mozilla CoC].
|
||||
|
||||
Pentru răspunsuri la întrebări frecvente despre acest cod de conduită, vezi FAQ la [https://www.contributor-covenant.org/faq][FAQ]. Traduceri sunt disponibile la [https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
31
docs/CONTRIBUTING-RO.md
Normal file
31
docs/CONTRIBUTING-RO.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Contribuții la RustDesk
|
||||
|
||||
RustDesk primește cu plăcere contribuții din partea tuturor. Iată ghidurile dacă te gândești să ne ajuți:
|
||||
|
||||
## Contribuții
|
||||
|
||||
Contribuțiile la RustDesk sau la dependențele sale ar trebui făcute sub forma de pull request-uri pe GitHub. Fiecare pull request va fi revizuit de un contributor principal (cineva cu permisiunea de a aplica patch-uri) și fie va fi integrat în arborele principal, fie vor fi oferite sugestii pentru modificările necesare. Toate contribuțiile trebuie să urmeze acest format, chiar și cele ale contributorilor principali.
|
||||
|
||||
Dacă dorești să lucrezi la o problemă, te rugăm să o revendici mai întâi comentând pe GitHub issue-ul pe care vrei să lucrezi. Aceasta previne eforturi duplicate din partea contributorilor asupra aceleiași probleme.
|
||||
|
||||
## Lista de verificare pentru Pull Request
|
||||
|
||||
- Creează un branch din branch-ul `master` și, dacă este necesar, fă rebase la branch-ul `master` curent înainte de a trimite pull request-ul. Dacă nu se poate integra curat cu `master`, ți se poate cere să faci rebase la modificările tale.
|
||||
|
||||
- Commit-urile ar trebui să fie cât mai mici posibil, asigurând totodată că fiecare commit este corect independent (adică fiecare commit ar trebui să compileze și să treacă testele).
|
||||
|
||||
- Commit-urile trebuie să fie însoțite de un semnătura Developer Certificate of Origin (http://developercertificate.org), care indică faptul că tu (și angajatorul tău, dacă este cazul) ești de acord să respecți termenii [licenței proiectului](../LICENCE). În git, aceasta este opțiunea `-s` la `git commit`.
|
||||
|
||||
- Dacă patch-ul tău nu este revizuit sau ai nevoie ca o anumită persoană să-l revizuiască, poți @-reply unui reviewer cerând o revizuire în pull request sau într-un comentariu, sau poți solicita o revizuire prin [email](mailto:info@rustdesk.com).
|
||||
|
||||
- Adaugă teste relevante pentru bug-ul corectat sau pentru funcționalitatea nouă.
|
||||
|
||||
Pentru instrucțiuni specifice git, vezi [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow).
|
||||
|
||||
## Conduită
|
||||
|
||||
[Codul de Conduită RustDesk](https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md)
|
||||
|
||||
## Comunicare
|
||||
|
||||
Contributorii RustDesk frecventează [Discord](https://discord.gg/nDceKgxnkV).
|
||||
181
docs/README-RO.md
Normal file
181
docs/README-RO.md
Normal file
@@ -0,0 +1,181 @@
|
||||
<p align="center">
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - desktopul tău la distanță"><br>
|
||||
<a href="../README.md#raw-steps-to-build">Construire</a> •
|
||||
<a href="../README.md#how-to-build-with-docker">Docker</a> •
|
||||
<a href="../README.md#file-structure">Structură</a> •
|
||||
<a href="../README.md#snapshot">Capturi</a><br>
|
||||
[<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-DA.md">Dansk</a>] | [<a href="README-GR.md">Ελληνικά</a>] | [<a href="README-TR.md">Türkçe</a>] | [<a href="README-NO.md">Norsk</a>] | [<a href="README-RO.md">Română</a>]<br>
|
||||
<b>Avem nevoie de ajutorul tău pentru a traduce acest README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> și <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> în limba ta maternă</b>
|
||||
</p>
|
||||
|
||||
> [!Atenție]
|
||||
> **Declinare de responsabilitate privind utilizarea abuzivă:** <br>
|
||||
> Dezvoltatorii RustDesk nu susțin sau aprobă utilizarea neetică sau ilegală a acestui software. Utilizarea abuzivă, cum ar fi accesul neautorizat, controlul sau invadarea intimității, este strict împotriva regulilor noastre. Autorii nu sunt responsabili pentru utilizarea necorespunzătoare a aplicației.
|
||||
|
||||
|
||||
Conversați cu noi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Încă o soluție de desktop la distanță scrisă în Rust. Funcționează imediat, fără configurare necesară. Ai control total asupra datelor tale, fără probleme de securitate. Poți folosi serverul nostru de rendezvous/relay, [să-ți configurezi propriul server](https://rustdesk.com/server) sau [să scrii propriul server de rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
|
||||
RustDesk primește contribuții de la oricine. Vezi [CONTRIBUTING.md](../docs/CONTRIBUTING.md) pentru ajutor la început.
|
||||
|
||||
[**ÎNTREBĂRI FRECVENTE (FAQ)**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||
|
||||
[**DESCĂRCARE BINARE**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
[**BUILD NIGHTLY**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||
[<img src="https://flathub.org/api/badge?svg&locale=en"
|
||||
alt="Get it on Flathub"
|
||||
height="80">](https://flathub.org/apps/com.rustdesk.RustDesk)
|
||||
|
||||
## Dependențe
|
||||
|
||||
Versiunile desktop folosesc Flutter sau Sciter (depreciat) pentru interfață; acest ghid este pentru Sciter doar, deoarece este mai ușor și mai prietenos pentru început. Vezi [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) pentru construire cu Flutter.
|
||||
|
||||
Te rugăm să descarci singur librăria dinamică Sciter.
|
||||
|
||||
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
|
||||
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
|
||||
[macOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
|
||||
|
||||
## Pași pentru construire (Raw Steps to build)
|
||||
|
||||
- Pregătește mediul de dezvoltare Rust și mediul de construire C++
|
||||
|
||||
- Instalează [vcpkg](https://github.com/microsoft/vcpkg) și setează corect variabila de mediu `VCPKG_ROOT`
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||
- Linux/macOS: vcpkg install libvpx libyuv opus aom
|
||||
|
||||
- rulează `cargo run`
|
||||
|
||||
## [Construire](https://rustdesk.com/docs/en/dev/build/)
|
||||
|
||||
## Cum se construiește pe Linux
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
|
||||
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
|
||||
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev
|
||||
```
|
||||
|
||||
### openSUSE Tumbleweed
|
||||
|
||||
```sh
|
||||
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
|
||||
```
|
||||
|
||||
### Instalează vcpkg
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2023.04.15
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus aom
|
||||
```
|
||||
|
||||
### Repară libvpx (Pentru Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone --recurse-submodules https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
## Cum să construiești cu Docker
|
||||
|
||||
Începe prin clonarea repository-ului și construirea imaginii Docker:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
git submodule update --init --recursive
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Apoi, de fiecare dată când trebuie să construiești aplicația, rulează comanda următoare:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||
```
|
||||
|
||||
Reține că prima construire poate dura mai mult până când dependențele sunt în cache; construirile ulterioare vor fi mai rapide. De asemenea, dacă trebuie să specifici argumente diferite comenzii de build, le poți adăuga la finalul comenzii în poziția `<OPTIONAL-ARGS>`. De exemplu, pentru a construi o versiune optimizată de release, adaugă `--release`. Executabilul rezultat va fi disponibil în folderul `target` pe sistemul tău, și poate fi rulat cu:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Sau, dacă rulezi un executabil release:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Asigură-te că rulezi aceste comenzi din rădăcina repository-ului RustDesk, altfel aplicația poate să nu găsească resursele necesare. De asemenea, reține că alte subcomenzi cargo, cum ar fi `install` sau `run`, nu sunt acceptate în prezent prin această metodă, deoarece ar instala sau rula programul în interiorul containerului în loc de gazdă.
|
||||
|
||||
## Structura fișierelor
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: codec video, config, wrapper tcp/udp, protobuf, funcții fs pentru transfer de fișiere și alte funcții utilitare
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: capturare ecran
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: control tastatură/mouse specific platformei
|
||||
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: implementare copy/paste pentru fișiere pentru Windows, Linux, macOS.
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: interfață Sciter învechită (depreciată)
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: servicii audio/clipboard/input/video și conexiuni de rețea
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: inițiază o conexiune peer
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: comunică cu [rustdesk-server](https://github.com/rustdesk/rustdesk-server), așteaptă conexiune directă remote (TCP hole punching) sau prin relay
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: cod specific platformei
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: cod Flutter pentru desktop și mobil
|
||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/v1/js)**: JavaScript pentru clientul Flutter web
|
||||
|
||||
## Capturi de ecran
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -7,34 +7,37 @@
|
||||
<a href="#file-structure">Dosya Yapısı</a> •
|
||||
<a href="#snapshot">Ekran Görüntüleri</a><br>
|
||||
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>]<br>
|
||||
<b>README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> ve <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Belge</a>'sini ana dilinize çevirmemiz için yardımınıza ihtiyacımız var</b>
|
||||
<b>README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> ve <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Dökümantasyonu</a>'nu ana dilinize çevirmemiz için yardımınıza ihtiyacımız var</b>
|
||||
</p>
|
||||
|
||||
|
||||
> [!Dikkat]
|
||||
> **Yanlış Kullanım Uyarısı:** <br>
|
||||
> RustDesk geliştiricileri, bu yazılımın etik olmayan veya yasa dışı kullanımını onaylamaz veya desteklemez. Yetkisiz erişim, kontrol veya gizlilik ihlali gibi kötüye kullanımlar kesinlikle yönergelerimize aykırıdır. Yazarlar, uygulamanın herhangi bir yanlış kullanımından sorumlu değildir.
|
||||
|
||||
Bizimle sohbet edin: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Başka bir uzak masaüstü yazılımı daha, Rust dilinde yazılmış. Hemen kullanıma hazır, hiçbir yapılandırma gerektirmez. Verilerinizin tam kontrolünü elinizde tutarsınız ve güvenlikle ilgili endişeleriniz olmaz. Kendi buluş/iletme sunucumuzu kullanabilirsiniz, [kendi sunucunuzu kurabilirsiniz](https://rustdesk.com/server) veya [kendi buluş/iletme sunucunuzu yazabilirsiniz](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
Rust dilinde yazılmış, başka bir uzak masaüstü yazılımı daha. Hiçbir yapılandırma gerekmeksizin, hemen kullanıma hazır. Güvenlik konusunda hiçbir endişe duymadan, verileriniz üzerinde tam kontrole sahip olun. Kendi rendezvous/relay sunucumuzu kullanabilirsiniz, [kendi sunucunuzu kurabilirsiniz](https://rustdesk.com/server) veya [kendi rendezvous/relay sunucunuzu yazabilirsiniz](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
|
||||
RustDesk, herkesten katkıyı kabul eder. Başlamak için [CONTRIBUTING.md](CONTRIBUTING-TR.md) belgesine göz atın.
|
||||
RustDesk, herkesin katkısına açıktır. Başlamak için [CONTRIBUTING.md](CONTRIBUTING-TR.md) belgesine göz atın.
|
||||
|
||||
[**SSS**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||
|
||||
[**BİNARİ İNDİR**](https://github.com/rustdesk/rustdesk/releases)
|
||||
[**BINARY İNDİR**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
[**NİGHTLY DERLEME**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||
[**NIGHTLY DERLEME**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="F-Droid'de Alın"
|
||||
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||
|
||||
## Bağımlılıklar
|
||||
## Gereksinimler
|
||||
|
||||
Masaüstü sürümleri GUI için
|
||||
|
||||
[Sciter](https://sciter.com/) veya Flutter kullanır, bu kılavuz sadece Sciter içindir.
|
||||
Masaüstü sürümleri GUI için; [Sciter](https://sciter.com/)(kaldırılacak) veya Flutter kullanır. Sciter daha kolay ve başlamak için daha dostcanlısı, bundan dolayı bu kılavuz sadece Sciter içindir. Flutter sürümünü derlemek için [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml)'ımıza bakın.
|
||||
|
||||
Lütfen Sciter dinamik kütüphanesini kendiniz indirin.
|
||||
|
||||
@@ -46,7 +49,7 @@ Lütfen Sciter dinamik kütüphanesini kendiniz indirin.
|
||||
|
||||
- Rust geliştirme ortamınızı ve C++ derleme ortamınızı hazırlayın.
|
||||
|
||||
- [vcpkg](https://github.com/microsoft/vcpkg) yükleyin ve `VCPKG_ROOT` çevresel değişkenini doğru bir şekilde ayarlayın.
|
||||
- [vcpkg](https://github.com/microsoft/vcpkg) yükleyin ve `VCPKG_ROOT` ortam değişkenini doğru bir şekilde ayarlayın.
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||
- Linux/macOS: vcpkg install libvpx libyuv opus aom
|
||||
@@ -123,7 +126,7 @@ VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
|
||||
## Docker ile Derleme Nasıl Yapılır
|
||||
|
||||
Öncelikle deposunu klonlayın ve Docker konteynerini oluşturun:
|
||||
Önce repository'i klonlayın ve Docker container'ını oluşturun.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
@@ -131,44 +134,40 @@ cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Ardından, uygulamayı derlemek için her seferinde aşağıdaki komutu çalıştırın:
|
||||
Ardından, uygulamayı her derlemeniz gerektiğinde aşağıdaki komutu çalıştırın:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||
```
|
||||
|
||||
İlk derleme, bağımlılıklar önbelleğe alınmadan önce daha uzun sürebilir, sonraki derlemeler daha hızlı olacaktır. Ayrıca, derleme komutuna isteğe bağlı argümanlar belirtmeniz gerekiyorsa, bunu
|
||||
|
||||
komutun sonunda `<İSTEĞE BAĞLI-ARGÜMANLAR>` pozisyonunda yapabilirsiniz. Örneğin, optimize edilmiş bir sürümü derlemek isterseniz, yukarıdaki komutu çalıştırdıktan sonra `--release` ekleyebilirsiniz. Oluşan yürütülebilir dosya sisteminizdeki hedef klasöründe bulunacak ve şu komutla çalıştırılabilir:
|
||||
Bilin ki ilk derlemeniz gereksinimlerin önbelleği yüklenmesinden ötürü uzun sürebilir, sonraki derlemeleriniz daha hızlı olacaktır. Ayrıca, derleme komutuna isteğe bağlı argümanlar belirtmeniz gerekiyorsa, bunu komutun sonunda ki `<OPTIONAL-ARGS>` yerine yazabilirsiniz. Örneğin, optimize edilmiş bir sürümü derlemek isterseniz, yukarıdaki komutu çalıştırdıktan sonra `--release` ekleyebilirsiniz. Oluşan çalıştırılabilir dosya sisteminizdeki hedef klasöründe bulunacak ve şu komutla çalıştırılabilir olacaktır:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Veya, yayın yürütülebilir dosyası çalıştırılıyorsa:
|
||||
Veya, yayım çalıştırılabilir dosyası için:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Lütfen bu komutları RustDesk deposunun kökünden çalıştırdığınızdan emin olun, aksi takdirde uygulama gereken kaynakları bulamayabilir. Ayrıca, `install` veya `run` gibi diğer cargo altkomutları şu anda bu yöntem aracılığıyla desteklenmemektedir, çünkü bunlar programı konteyner içinde kurar veya çalıştırır ve ana makinede değil.
|
||||
Lütfen bu komutları RustDesk reposunun root klasöründe çalıştırdığınızdan emin olun, aksi takdirde uygulama gereken kaynakları bulamayabilir. Ayrıca, `install` veya `run` gibi diğer cargo altkomutları şu anda bu yöntem aracılığıyla desteklenmemektedir, çünkü bunlar programı konteyner içinde kurar veya çalıştırır, ana makinede değil.
|
||||
|
||||
## Dosya Yapısı
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video kodlayıcı, yapılandırma, tcp/udp sarmalayıcı, protobuf, dosya transferi için fs işlevleri ve diğer bazı yardımcı işlevler
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, dosya transferi için fs fonksiyonları ve diğer bazı yardımcı işlevler
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: ekran yakalama
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platforma özgü klavye/fare kontrolü
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: ses/pasta/klavye/video hizmetleri ve ağ bağlantıları
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: bir eş bağlantısı başlatır
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server) ile iletişim kurar, uzak doğrudan (TCP delik vurma) veya iletme bağlantısını bekler
|
||||
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: platforma özgü kopyala/yapıştır implementasyonları.
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: Eski Sciter UI (kaldırılacak)
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: ses/pano/input/video servisleri ve ağ bağlantıları
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: Eşli bağlantı başlat
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server) ile iletişime gir, remote direct(TCP delik açma) yada relay bağlantısı için bekle
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platforma özgü kod
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: mobil için Flutter kodu
|
||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter web istemcisi için JavaScript
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Masaüstü ve mobil için Flutter kodu
|
||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/v1/js)**: Flutter web istemcisi için JavaScript
|
||||
|
||||
> [!Dikkat]
|
||||
> **Yanlış Kullanım Uyarısı:** <br>
|
||||
> RustDesk geliştiricileri, bu yazılımın etik olmayan veya yasa dışı kullanımını onaylamaz veya desteklemez. Yetkisiz erişim, kontrol veya gizlilik ihlali gibi kötüye kullanımlar kesinlikle yönergelerimize aykırıdır. Yazarlar, uygulamanın herhangi bir yanlış kullanımından sorumlu değildir.
|
||||
|
||||
## Ekran Görüntüleri
|
||||
|
||||
|
||||
9
docs/SECURITY-RO.md
Normal file
9
docs/SECURITY-RO.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Politica de Securitate
|
||||
|
||||
## Raportarea unei Vulnerabilități
|
||||
|
||||
Acordăm o mare importanță securității proiectului. Încurajăm toți utilizatorii să ne raporteze orice vulnerabilități pe care le descoperă.
|
||||
Dacă găsești o vulnerabilitate de securitate în proiectul RustDesk, te rugăm să o raportezi responsabil trimițând un e-mail la info@rustdesk.com.
|
||||
|
||||
În acest moment, nu avem un program de recompense pentru descoperirea de bug-uri. Suntem o echipă mică care încearcă să rezolve o problemă mare.
|
||||
Te rugăm să raportezi orice vulnerabilitate în mod responsabil, astfel încât să putem continua să construim o aplicație sigură pentru întreaga comunitate.
|
||||
@@ -62,7 +62,13 @@ class MainActivity : FlutterActivity() {
|
||||
channelTag
|
||||
)
|
||||
initFlutterChannel(flutterMethodChannel!!)
|
||||
thread { setCodecInfo() }
|
||||
thread {
|
||||
try {
|
||||
setCodecInfo()
|
||||
} catch (e: Exception) {
|
||||
Log.e("MainActivity", "Failed to setCodecInfo: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
@@ -60,6 +62,8 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<true/>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
||||
@@ -13,11 +13,13 @@ import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_hbb/utils/platform_channel.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uni_links/uni_links.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -75,6 +77,9 @@ bool _ignoreDevicePixelRatio = true;
|
||||
int windowsBuildNumber = 0;
|
||||
DesktopType? desktopType;
|
||||
|
||||
// Tolerance used for floating-point position comparisons to avoid precision errors.
|
||||
const double _kPositionEpsilon = 1e-6;
|
||||
|
||||
bool get isMainDesktopWindow =>
|
||||
desktopType == DesktopType.main || desktopType == DesktopType.cm;
|
||||
|
||||
@@ -106,6 +111,10 @@ enum DesktopType {
|
||||
portForward,
|
||||
}
|
||||
|
||||
bool isDoubleEqual(double a, double b) {
|
||||
return (a - b).abs() < _kPositionEpsilon;
|
||||
}
|
||||
|
||||
class IconFont {
|
||||
static const _family1 = 'Tabbar';
|
||||
static const _family2 = 'PeerSearchbar';
|
||||
@@ -1622,7 +1631,8 @@ bool mainGetPeerBoolOptionSync(String id, String key) {
|
||||
// Use `sessionGetToggleOption()` and `sessionToggleOption()` instead.
|
||||
// Because all session options use `Y` and `<Empty>` as values.
|
||||
|
||||
Future<bool> matchPeer(String searchText, Peer peer) async {
|
||||
Future<bool> matchPeer(
|
||||
String searchText, Peer peer, PeerTabIndex peerTabIndex) async {
|
||||
if (searchText.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
@@ -1633,11 +1643,14 @@ Future<bool> matchPeer(String searchText, Peer peer) async {
|
||||
peer.username.toLowerCase().contains(searchText)) {
|
||||
return true;
|
||||
}
|
||||
final alias = peer.alias;
|
||||
if (alias.isEmpty) {
|
||||
return false;
|
||||
if (peer.alias.toLowerCase().contains(searchText)) {
|
||||
return true;
|
||||
}
|
||||
return alias.toLowerCase().contains(searchText);
|
||||
if (peerTabShowNote(peerTabIndex) &&
|
||||
peer.note.toLowerCase().contains(searchText)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Get the image for the current [platform].
|
||||
@@ -1667,6 +1680,16 @@ class LastWindowPosition {
|
||||
LastWindowPosition(this.width, this.height, this.offsetWidth,
|
||||
this.offsetHeight, this.isMaximized, this.isFullscreen);
|
||||
|
||||
bool equals(LastWindowPosition other) {
|
||||
return (
|
||||
(width == other.width) &&
|
||||
(height == other.height) &&
|
||||
(offsetWidth == other.offsetWidth) &&
|
||||
(offsetHeight == other.offsetHeight) &&
|
||||
(isMaximized == other.isMaximized) &&
|
||||
(isFullscreen == other.isFullscreen));
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
"width": width,
|
||||
@@ -1706,24 +1729,36 @@ String get windowFramePrefix =>
|
||||
? "incoming_"
|
||||
: (bind.isOutgoingOnly() ? "outgoing_" : ""));
|
||||
|
||||
typedef WindowKey = ({WindowType type, int? windowId});
|
||||
|
||||
LastWindowPosition? _lastWindowPosition = null;
|
||||
final Debouncer _saveWindowDebounce = Debouncer(delay: Duration(seconds: 1));
|
||||
|
||||
/// Save window position and size on exit
|
||||
/// Note that windowId must be provided if it's subwindow
|
||||
Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
|
||||
Future<void> saveWindowPosition(WindowType type,
|
||||
{int? windowId, bool? flush}) async {
|
||||
if (type != WindowType.Main && windowId == null) {
|
||||
debugPrint(
|
||||
"Error: windowId cannot be null when saving positions for sub window");
|
||||
}
|
||||
|
||||
late Offset position;
|
||||
late Size sz;
|
||||
Offset? position;
|
||||
Size? sz;
|
||||
late bool isMaximized;
|
||||
bool isFullscreen = stateGlobal.fullscreen.isTrue;
|
||||
|
||||
setPreFrame() {
|
||||
final pos = bind.getLocalFlutterOption(k: windowFramePrefix + type.name);
|
||||
var lpos = LastWindowPosition.loadFromString(pos);
|
||||
position = Offset(
|
||||
lpos?.offsetWidth ?? position.dx, lpos?.offsetHeight ?? position.dy);
|
||||
sz = Size(lpos?.width ?? sz.width, lpos?.height ?? sz.height);
|
||||
if (lpos != null) {
|
||||
if (lpos.offsetWidth != null && lpos.offsetHeight != null) {
|
||||
position = Offset(lpos.offsetWidth!, lpos.offsetHeight!);
|
||||
}
|
||||
if (lpos.width != null && lpos.height != null) {
|
||||
sz = Size(lpos.width!, lpos.height!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
@@ -1763,30 +1798,54 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (isWindows) {
|
||||
if (isWindows && position != null) {
|
||||
const kMinOffset = -10000;
|
||||
const kMaxOffset = 10000;
|
||||
if (position.dx < kMinOffset ||
|
||||
position.dy < kMinOffset ||
|
||||
position.dx > kMaxOffset ||
|
||||
position.dy > kMaxOffset) {
|
||||
if (position!.dx < kMinOffset ||
|
||||
position!.dy < kMinOffset ||
|
||||
position!.dx > kMaxOffset ||
|
||||
position!.dy > kMaxOffset) {
|
||||
debugPrint("Invalid position: $position, ignore saving position");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final pos = LastWindowPosition(
|
||||
sz.width, sz.height, position.dx, position.dy, isMaximized, isFullscreen);
|
||||
debugPrint(
|
||||
"Saving frame: $windowId: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}, isMaximized:${pos.isMaximized}, isFullscreen:${pos.isFullscreen}");
|
||||
final pos = LastWindowPosition(sz?.width, sz?.height, position?.dx,
|
||||
position?.dy, isMaximized, isFullscreen);
|
||||
|
||||
await bind.setLocalFlutterOption(
|
||||
k: windowFramePrefix + type.name, v: pos.toString());
|
||||
final WindowKey key = (type: type, windowId: windowId);
|
||||
|
||||
if ((type == WindowType.RemoteDesktop || type == WindowType.ViewCamera) &&
|
||||
windowId != null) {
|
||||
await _saveSessionWindowPosition(
|
||||
type, windowId, isMaximized, isFullscreen, pos);
|
||||
final bool haveNewWindowPosition = (_lastWindowPosition == null) || !pos.equals(_lastWindowPosition!);
|
||||
final bool isPreviousNewWindowPositionPending = _saveWindowDebounce.isRunning;
|
||||
|
||||
if (haveNewWindowPosition || isPreviousNewWindowPositionPending) {
|
||||
_lastWindowPosition = pos;
|
||||
|
||||
if (flush ?? false) {
|
||||
// If a previous update is pending, replace it.
|
||||
_saveWindowDebounce.cancel();
|
||||
await _saveWindowPositionActual(key);
|
||||
} else if (haveNewWindowPosition) {
|
||||
_saveWindowDebounce.call(() => _saveWindowPositionActual(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveWindowPositionActual(WindowKey key) async {
|
||||
LastWindowPosition? pos = _lastWindowPosition;
|
||||
|
||||
if (pos != null) {
|
||||
debugPrint(
|
||||
"Saving frame: ${key.windowId}: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}, isMaximized:${pos.isMaximized}, isFullscreen:${pos.isFullscreen}");
|
||||
|
||||
await bind.setLocalFlutterOption(
|
||||
k: windowFramePrefix + key.type.name, v: pos.toString());
|
||||
|
||||
if ((key.type == WindowType.RemoteDesktop || key.type == WindowType.ViewCamera) &&
|
||||
key.windowId != null) {
|
||||
await _saveSessionWindowPosition(
|
||||
key.type, key.windowId!, pos.isMaximized ?? false, pos.isFullscreen ?? false, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1852,6 +1911,8 @@ Future<Size> _adjustRestoreMainWindowSize(double? width, double? height) async {
|
||||
return Size(restoreWidth, restoreHeight);
|
||||
}
|
||||
|
||||
// Consider using Rect.contains() instead,
|
||||
// though the implementation is not exactly the same.
|
||||
bool isPointInRect(Offset point, Rect rect) {
|
||||
return point.dx >= rect.left &&
|
||||
point.dx <= rect.right &&
|
||||
@@ -1949,8 +2010,24 @@ Future<bool> restoreWindowPosition(WindowType type,
|
||||
|
||||
var lpos = LastWindowPosition.loadFromString(pos);
|
||||
if (lpos == null) {
|
||||
debugPrint("no window position saved, ignoring position restoration");
|
||||
return false;
|
||||
debugPrint("No window position saved, trying to center the window.");
|
||||
switch (type) {
|
||||
case WindowType.Main:
|
||||
// Center the main window only if no position is saved (on first run).
|
||||
if (isWindows || isLinux) {
|
||||
await windowManager.center();
|
||||
}
|
||||
// For MacOS, the window is already centered by default.
|
||||
// See https://github.com/rustdesk/rustdesk/blob/9b9276e7524523d7f667fefcd0694d981443df0e/flutter/macos/Runner/Base.lproj/MainMenu.xib#L333
|
||||
// If `<windowPositionMask>` in `<window>` is not set, the window will be centered.
|
||||
break;
|
||||
default:
|
||||
// No need to change the position of a sub window if no position is saved,
|
||||
// since the default position is already centered.
|
||||
// https://github.com/rustdesk/rustdesk/blob/317639169359936f7f9f85ef445ec9774218772d/flutter/lib/utils/multi_window_manager.dart#L163
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (type == WindowType.RemoteDesktop || type == WindowType.ViewCamera) {
|
||||
if (!isRemotePeerPos && windowId != null) {
|
||||
@@ -3943,3 +4020,7 @@ String decode_http_response(http.Response resp) {
|
||||
return resp.body;
|
||||
}
|
||||
}
|
||||
|
||||
bool peerTabShowNote(PeerTabIndex peerTabIndex) {
|
||||
return peerTabIndex == PeerTabIndex.ab || peerTabIndex == PeerTabIndex.group;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ class PeerPayload {
|
||||
"platform": _platform(p.info['os']),
|
||||
"hostname": p.info['device_name'],
|
||||
"device_group_name": p.device_group_name,
|
||||
"note": p.note,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -466,6 +466,7 @@ class _AddressBookState extends State<AddressBook> {
|
||||
IDTextEditingController idController = IDTextEditingController(text: '');
|
||||
TextEditingController aliasController = TextEditingController(text: '');
|
||||
TextEditingController passwordController = TextEditingController(text: '');
|
||||
TextEditingController noteController = TextEditingController(text: '');
|
||||
final tags = List.of(gFFI.abModel.currentAbTags);
|
||||
var selectedTag = List<dynamic>.empty(growable: true).obs;
|
||||
final style = TextStyle(fontSize: 14.0);
|
||||
@@ -494,7 +495,11 @@ class _AddressBookState extends State<AddressBook> {
|
||||
password = passwordController.text;
|
||||
}
|
||||
String? errMsg2 = await gFFI.abModel.addIdToCurrent(
|
||||
id, aliasController.text.trim(), password, selectedTag);
|
||||
id,
|
||||
aliasController.text.trim(),
|
||||
password,
|
||||
selectedTag,
|
||||
noteController.text);
|
||||
if (errMsg2 != null) {
|
||||
setState(() {
|
||||
isInProgress = false;
|
||||
@@ -600,6 +605,24 @@ class _AddressBookState extends State<AddressBook> {
|
||||
),
|
||||
).workaroundFreezeLinuxMint(),
|
||||
)),
|
||||
row(
|
||||
label: Text(
|
||||
translate('Note'),
|
||||
style: style,
|
||||
),
|
||||
input: Obx(
|
||||
() => TextField(
|
||||
controller: noteController,
|
||||
maxLines: 3,
|
||||
minLines: 1,
|
||||
maxLength: 300,
|
||||
decoration: InputDecoration(
|
||||
labelText: stateGlobal.isPortrait.isFalse
|
||||
? null
|
||||
: translate('Note'),
|
||||
),
|
||||
).workaroundFreezeLinuxMint(),
|
||||
)),
|
||||
if (gFFI.abModel.currentAbTags.isNotEmpty)
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
|
||||
@@ -1783,6 +1783,49 @@ void editAbTagDialog(
|
||||
});
|
||||
}
|
||||
|
||||
void editAbPeerNoteDialog(String id) {
|
||||
var isInProgress = false;
|
||||
final currentNote = gFFI.abModel.getPeerNote(id);
|
||||
var controller = TextEditingController(text: currentNote);
|
||||
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
submit() async {
|
||||
setState(() {
|
||||
isInProgress = true;
|
||||
});
|
||||
await gFFI.abModel.changeNote(id: id, note: controller.text);
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Edit note")),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
maxLines: 3,
|
||||
minLines: 1,
|
||||
maxLength: 300,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('Note'),
|
||||
),
|
||||
).workaroundFreezeLinuxMint(),
|
||||
// NOT use Offstage to wrap LinearProgressIndicator
|
||||
if (isInProgress) const LinearProgressIndicator(),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: submit),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void renameDialog(
|
||||
{required String oldName,
|
||||
FormFieldValidator<String>? validator,
|
||||
@@ -2078,15 +2121,20 @@ void showWindowsSessionsDialog(
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: null,
|
||||
content: msgboxContent(type, title, text),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
msgboxContent(type, title, text).marginOnly(bottom: 12),
|
||||
ComboBox(
|
||||
keys: sids,
|
||||
values: names,
|
||||
initialKey: selectedUserValue,
|
||||
onChanged: (value) {
|
||||
selectedUserValue = value;
|
||||
}),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
ComboBox(
|
||||
keys: sids,
|
||||
values: names,
|
||||
initialKey: selectedUserValue,
|
||||
onChanged: (value) {
|
||||
selectedUserValue = value;
|
||||
}),
|
||||
dialogButton('Connect', onPressed: submit, isOutline: false),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_hbb/common/widgets/remote_input.dart';
|
||||
|
||||
enum GestureState {
|
||||
none,
|
||||
@@ -96,6 +97,12 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
|
||||
if (onTwoFingerScaleEnd != null) {
|
||||
onTwoFingerScaleEnd!(d);
|
||||
}
|
||||
if (isSpecialHoldDragActive) {
|
||||
// If we are in special drag mode, we need to reset the state.
|
||||
// Otherwise, the next `onTwoFingerScaleUpdate()` will handle a wrong `focalPoint`.
|
||||
_currentState = GestureState.none;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case GestureState.threeFingerVerticalDrag:
|
||||
debugPrint("ThreeFingerState.vertical onEnd");
|
||||
|
||||
@@ -127,6 +127,10 @@ class _PeerCardState extends State<_PeerCard>
|
||||
);
|
||||
}
|
||||
|
||||
bool _showNote(Peer peer) {
|
||||
return peerTabShowNote(widget.tab) && peer.note.isNotEmpty;
|
||||
}
|
||||
|
||||
makeChild(bool isPortrait, Peer peer) {
|
||||
final name = hideUsernameOnCard == true
|
||||
? peer.hostname
|
||||
@@ -134,6 +138,8 @@ class _PeerCardState extends State<_PeerCard>
|
||||
final greyStyle = TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
|
||||
final showNote = _showNote(peer);
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
@@ -185,14 +191,44 @@ class _PeerCardState extends State<_PeerCard>
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
)),
|
||||
]).marginOnly(top: isPortrait ? 0 : 2),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
name,
|
||||
style: isPortrait ? null : greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Tooltip(
|
||||
message: name,
|
||||
waitDuration: const Duration(seconds: 1),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
name,
|
||||
style: isPortrait ? null : greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showNote)
|
||||
Expanded(
|
||||
child: Tooltip(
|
||||
message: peer.note,
|
||||
waitDuration: const Duration(seconds: 1),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
peer.note,
|
||||
style: isPortrait ? null : greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).marginOnly(
|
||||
left: peerCardUiType.value ==
|
||||
PeerUiType.list
|
||||
? 32
|
||||
: 4),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
).marginOnly(top: 2),
|
||||
@@ -278,7 +314,7 @@ class _PeerCardState extends State<_PeerCard>
|
||||
padding: const EdgeInsets.all(6),
|
||||
child:
|
||||
getPlatformImage(peer.platform, size: 60),
|
||||
).marginOnly(top: 4),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -297,8 +333,26 @@ class _PeerCardState extends State<_PeerCard>
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_showNote(peer))
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Tooltip(
|
||||
message: peer.note,
|
||||
waitDuration: const Duration(seconds: 1),
|
||||
child: Text(
|
||||
peer.note,
|
||||
style: const TextStyle(
|
||||
color: Colors.white38,
|
||||
fontSize: 10),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
],
|
||||
).paddingAll(4.0),
|
||||
).paddingOnly(top: 4.0, left: 4.0, right: 4.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1134,6 +1188,7 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
if (gFFI.abModel.currentAbTags.isNotEmpty) {
|
||||
menuItems.add(_editTagAction(peer.id));
|
||||
}
|
||||
menuItems.add(_editNoteAction(peer.id));
|
||||
}
|
||||
final addressbooks = gFFI.abModel.addressBooksCanWrite();
|
||||
if (gFFI.peerTabModel.currentTab == PeerTabIndex.ab.index) {
|
||||
@@ -1173,6 +1228,21 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> _editNoteAction(String id) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Edit note'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
editAbPeerNoteDialog(id);
|
||||
},
|
||||
padding: super.menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
@override
|
||||
Future<String> _getAlias(String id) async =>
|
||||
|
||||
@@ -71,10 +71,12 @@ class _PeersView extends StatefulWidget {
|
||||
final Peers peers;
|
||||
final PeerFilter? peerFilter;
|
||||
final PeerCardBuilder peerCardBuilder;
|
||||
final PeerTabIndex peerTabIndex;
|
||||
|
||||
const _PeersView(
|
||||
{required this.peers,
|
||||
required this.peerCardBuilder,
|
||||
required this.peerTabIndex,
|
||||
this.peerFilter,
|
||||
Key? key})
|
||||
: super(key: key);
|
||||
@@ -395,8 +397,8 @@ class _PeersViewState extends State<_PeersView>
|
||||
return peers;
|
||||
}
|
||||
searchText = searchText.toLowerCase();
|
||||
final matches =
|
||||
await Future.wait(peers.map((peer) => matchPeer(searchText, peer)));
|
||||
final matches = await Future.wait(
|
||||
peers.map((peer) => matchPeer(searchText, peer, widget.peerTabIndex)));
|
||||
final filteredList = List<Peer>.empty(growable: true);
|
||||
for (var i = 0; i < peers.length; i++) {
|
||||
if (matches[i]) {
|
||||
@@ -441,7 +443,10 @@ abstract class BasePeersView extends StatelessWidget {
|
||||
break;
|
||||
}
|
||||
return _PeersView(
|
||||
peers: peers, peerFilter: peerFilter, peerCardBuilder: peerCardBuilder);
|
||||
peers: peers,
|
||||
peerFilter: peerFilter,
|
||||
peerCardBuilder: peerCardBuilder,
|
||||
peerTabIndex: peerTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,13 @@ class RawKeyFocusScope extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// For virtual mouse when using the mouse mode on mobile.
|
||||
// Special hold-drag mode: one finger holds a button (left/right button), another finger pans.
|
||||
// This flag is to override the scale gesture to a pan gesture.
|
||||
bool isSpecialHoldDragActive = false;
|
||||
// Cache the last focal point to calculate deltas in special hold-drag mode.
|
||||
Offset _lastSpecialHoldDragFocalPoint = Offset.zero;
|
||||
|
||||
class RawTouchGestureDetectorRegion extends StatefulWidget {
|
||||
final Widget child;
|
||||
final FFI ffi;
|
||||
@@ -97,6 +104,10 @@ class _RawTouchGestureDetectorRegionState
|
||||
bool _touchModePanStarted = false;
|
||||
Offset _doubleFinerTapPosition = Offset.zero;
|
||||
|
||||
// For mouse mode, we need to block the events when the cursor is in a blocked area.
|
||||
// So we need to cache the last tap down position.
|
||||
Offset? _lastTapDownPositionForMouseMode;
|
||||
|
||||
FFI get ffi => widget.ffi;
|
||||
FfiModel get ffiModel => widget.ffiModel;
|
||||
InputModel get inputModel => widget.inputModel;
|
||||
@@ -112,7 +123,15 @@ class _RawTouchGestureDetectorRegionState
|
||||
}
|
||||
|
||||
bool isNotTouchBasedDevice() {
|
||||
return !kTouchBasedDeviceKinds.contains(lastDeviceKind);
|
||||
return !kTouchBasedDeviceKinds.contains(lastDeviceKind);
|
||||
}
|
||||
|
||||
// Mobile, mouse mode.
|
||||
// Check if should block the mouse tap event (`_lastTapDownPositionForMouseMode`).
|
||||
bool shouldBlockMouseModeEvent() {
|
||||
return _lastTapDownPositionForMouseMode != null &&
|
||||
ffi.cursorModel.shouldBlock(_lastTapDownPositionForMouseMode!.dx,
|
||||
_lastTapDownPositionForMouseMode!.dy);
|
||||
}
|
||||
|
||||
onTapDown(TapDownDetails d) async {
|
||||
@@ -124,6 +143,8 @@ class _RawTouchGestureDetectorRegionState
|
||||
_lastPosOfDoubleTapDown = d.localPosition;
|
||||
// Desktop or mobile "Touch mode"
|
||||
_lastTapDownDetails = d;
|
||||
} else {
|
||||
_lastTapDownPositionForMouseMode = d.localPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +171,11 @@ class _RawTouchGestureDetectorRegionState
|
||||
return;
|
||||
}
|
||||
if (!handleTouch) {
|
||||
// Cannot use `_lastTapDownDetails` because Flutter calls `onTapUp` before `onTap`, clearing the cached details.
|
||||
// Using `_lastTapDownPositionForMouseMode` instead.
|
||||
if (shouldBlockMouseModeEvent()) {
|
||||
return;
|
||||
}
|
||||
// Mobile, "Mouse mode"
|
||||
await inputModel.tap(MouseButtons.left);
|
||||
}
|
||||
@@ -163,6 +189,8 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (handleTouch) {
|
||||
_lastPosOfDoubleTapDown = d.localPosition;
|
||||
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||
} else {
|
||||
_lastTapDownPositionForMouseMode = d.localPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +205,12 @@ class _RawTouchGestureDetectorRegionState
|
||||
!ffi.cursorModel.isInRemoteRect(_lastPosOfDoubleTapDown)) {
|
||||
return;
|
||||
}
|
||||
// Check if the position is in a blocked area when using the mouse mode.
|
||||
if (!handleTouch) {
|
||||
if (shouldBlockMouseModeEvent()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await inputModel.tap(MouseButtons.left);
|
||||
await inputModel.tap(MouseButtons.left);
|
||||
}
|
||||
@@ -198,6 +232,8 @@ class _RawTouchGestureDetectorRegionState
|
||||
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
|
||||
await inputModel.tapDown(MouseButtons.left);
|
||||
}
|
||||
} else {
|
||||
_lastTapDownPositionForMouseMode = d.localPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +258,10 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (!isMoved) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (shouldBlockMouseModeEvent()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await inputModel.tap(MouseButtons.right);
|
||||
} else {
|
||||
@@ -274,6 +314,7 @@ class _RawTouchGestureDetectorRegionState
|
||||
return;
|
||||
}
|
||||
if (!handleTouch) {
|
||||
if (isSpecialHoldDragActive) return;
|
||||
await inputModel.sendMouse('down', MouseButtons.left);
|
||||
}
|
||||
}
|
||||
@@ -283,6 +324,7 @@ class _RawTouchGestureDetectorRegionState
|
||||
return;
|
||||
}
|
||||
if (!handleTouch) {
|
||||
if (isSpecialHoldDragActive) return;
|
||||
await ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
|
||||
}
|
||||
}
|
||||
@@ -377,12 +419,26 @@ class _RawTouchGestureDetectorRegionState
|
||||
if (isNotTouchBasedDevice()) {
|
||||
return;
|
||||
}
|
||||
if (isSpecialHoldDragActive) {
|
||||
// Initialize the last focal point to calculate deltas manually.
|
||||
_lastSpecialHoldDragFocalPoint = d.focalPoint;
|
||||
}
|
||||
}
|
||||
|
||||
onTwoFingerScaleUpdate(ScaleUpdateDetails d) async {
|
||||
if (isNotTouchBasedDevice()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If in special drag mode, perform a pan instead of a scale.
|
||||
if (isSpecialHoldDragActive) {
|
||||
// Calculate delta manually to avoid the jumpy behavior.
|
||||
final delta = d.focalPoint - _lastSpecialHoldDragFocalPoint;
|
||||
_lastSpecialHoldDragFocalPoint = d.focalPoint;
|
||||
await ffi.cursorModel.updatePan(delta * 2.0, d.focalPoint, handleTouch);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((isDesktop || isWebDesktop)) {
|
||||
final scale = ((d.scale - _scale) * 1000).toInt();
|
||||
_scale = d.scale;
|
||||
@@ -420,7 +476,9 @@ class _RawTouchGestureDetectorRegionState
|
||||
// No idea why we need to set the view style to "" here.
|
||||
// bind.sessionSetViewStyle(sessionId: sessionId, value: "");
|
||||
}
|
||||
await inputModel.sendMouse('up', MouseButtons.left);
|
||||
if (!isSpecialHoldDragActive) {
|
||||
await inputModel.sendMouse('up', MouseButtons.left);
|
||||
}
|
||||
}
|
||||
|
||||
get onHoldDragCancel => null;
|
||||
|
||||
@@ -230,7 +230,6 @@ List<(String, String)> otherDefaultSettings() {
|
||||
('Disable clipboard', kOptionDisableClipboard),
|
||||
('Lock after session end', kOptionLockAfterSessionEnd),
|
||||
('Privacy mode', kOptionPrivacyMode),
|
||||
if (isMobile) ('Touch mode', kOptionTouchMode),
|
||||
('True color (4:4:4)', kOptionI444),
|
||||
('Reverse mouse wheel', kKeyReverseMouseWheel),
|
||||
('swap-left-right-mouse', kOptionSwapLeftRightMouse),
|
||||
|
||||
@@ -363,7 +363,13 @@ Future<List<TRadioMenu<String>>> toolbarViewStyle(
|
||||
child: Text(translate('Scale adaptive')),
|
||||
value: kRemoteViewStyleAdaptive,
|
||||
groupValue: groupValue,
|
||||
onChanged: onChanged)
|
||||
onChanged: onChanged),
|
||||
if (isDesktop || isWebDesktop)
|
||||
TRadioMenu<String>(
|
||||
child: Text(translate('Scale custom')),
|
||||
value: kRemoteViewStyleCustom,
|
||||
groupValue: groupValue,
|
||||
onChanged: onChanged)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -155,6 +155,9 @@ const String kOptionAllowRemoteCmModification = "allow-remote-cm-modification";
|
||||
const String kOptionEnableUdpPunch = "enable-udp-punch";
|
||||
const String kOptionEnableIpv6Punch = "enable-ipv6-punch";
|
||||
const String kOptionEnableTrustedDevices = "enable-trusted-devices";
|
||||
const String kOptionShowVirtualMouse = "show-virtual-mouse";
|
||||
const String kOptionVirtualMouseScale = "virtual-mouse-scale";
|
||||
const String kOptionShowVirtualJoystick = "show-virtual-joystick";
|
||||
|
||||
// network options
|
||||
const String kOptionAllowWebSocket = "allow-websocket";
|
||||
@@ -313,6 +316,10 @@ const kRemoteViewStyleOriginal = 'original';
|
||||
/// [kRemoteViewStyleAdaptive] Show remote image scaling by ratio factor.
|
||||
const kRemoteViewStyleAdaptive = 'adaptive';
|
||||
|
||||
/// [kRemoteViewStyleCustom] Show remote image at a user-defined scale percent.
|
||||
const kRemoteViewStyleCustom = 'custom';
|
||||
|
||||
|
||||
/// [kRemoteScrollStyleAuto] Scroll image auto by position.
|
||||
const kRemoteScrollStyleAuto = 'scrollauto';
|
||||
|
||||
@@ -345,6 +352,15 @@ const Set<PointerDeviceKind> kTouchBasedDeviceKinds = {
|
||||
PointerDeviceKind.invertedStylus,
|
||||
};
|
||||
|
||||
// Scale custom related constants
|
||||
const String kCustomScalePercentKey = 'custom_scale_percent'; // Flutter option key for storing custom scale percent (integer 5-1000)
|
||||
const int kScaleCustomMinPercent = 5;
|
||||
const int kScaleCustomPivotPercent = 100; // 100% should be at 1/3 of track
|
||||
const int kScaleCustomMaxPercent = 1000;
|
||||
const double kScaleCustomPivotPos = 1.0 / 3.0; // first 1/3 → up to 100%
|
||||
const double kScaleCustomDetentEpsilon = 0.006; // snap range around pivot (~0.6%)
|
||||
const Duration kDebounceCustomScaleDuration = Duration(milliseconds: 300);
|
||||
|
||||
// ================================ mobile ================================
|
||||
|
||||
// Magic numbers, maybe need to avoid it or use a better way to get them.
|
||||
|
||||
@@ -374,6 +374,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
rdpUsername: '',
|
||||
loginName: '',
|
||||
device_group_name: '',
|
||||
note: '',
|
||||
);
|
||||
_autocompleteOpts = [emptyPeer];
|
||||
} else {
|
||||
@@ -536,64 +537,68 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
builder: (context, setState) {
|
||||
var offset = Offset(0, 0);
|
||||
return Obx(() => InkWell(
|
||||
child: _menuOpen.value
|
||||
? Transform.rotate(
|
||||
angle: pi,
|
||||
child: Icon(IconFont.more, size: 14),
|
||||
child: _menuOpen.value
|
||||
? Transform.rotate(
|
||||
angle: pi,
|
||||
child: Icon(IconFont.more, size: 14),
|
||||
)
|
||||
: Icon(IconFont.more, size: 14),
|
||||
onTapDown: (e) {
|
||||
offset = e.globalPosition;
|
||||
},
|
||||
onTap: () async {
|
||||
_menuOpen.value = true;
|
||||
final x = offset.dx;
|
||||
final y = offset.dy;
|
||||
await mod_menu
|
||||
.showMenu(
|
||||
context: context,
|
||||
position: RelativeRect.fromLTRB(x, y, x, y),
|
||||
items: [
|
||||
(
|
||||
'Transfer file',
|
||||
() => onConnect(isFileTransfer: true)
|
||||
),
|
||||
(
|
||||
'View camera',
|
||||
() => onConnect(isViewCamera: true)
|
||||
),
|
||||
(
|
||||
'${translate('Terminal')} (beta)',
|
||||
() => onConnect(isTerminal: true)
|
||||
),
|
||||
]
|
||||
.map((e) => MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) =>
|
||||
Text(
|
||||
translate(e.$1),
|
||||
style: style,
|
||||
),
|
||||
proc: () => e.$2(),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
kDesktopMenuPadding.left),
|
||||
dismissOnClicked: true,
|
||||
))
|
||||
.map((e) => e.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: CustomPopupMenuTheme
|
||||
.commonColor,
|
||||
height:
|
||||
CustomPopupMenuTheme.height,
|
||||
dividerHeight:
|
||||
CustomPopupMenuTheme
|
||||
.dividerHeight)))
|
||||
.expand((i) => i)
|
||||
.toList(),
|
||||
elevation: 8,
|
||||
)
|
||||
: Icon(IconFont.more, size: 14),
|
||||
onTapDown: (e) {
|
||||
offset = e.globalPosition;
|
||||
},
|
||||
onTap: () async {
|
||||
_menuOpen.value = true;
|
||||
final x = offset.dx;
|
||||
final y = offset.dy;
|
||||
await mod_menu
|
||||
.showMenu(
|
||||
context: context,
|
||||
position: RelativeRect.fromLTRB(x, y, x, y),
|
||||
items: [
|
||||
(
|
||||
'Transfer file',
|
||||
() => onConnect(isFileTransfer: true)
|
||||
),
|
||||
(
|
||||
'View camera',
|
||||
() => onConnect(isViewCamera: true)
|
||||
),
|
||||
(
|
||||
'${translate('Terminal')} (beta)',
|
||||
() => onConnect(isTerminal: true)
|
||||
),
|
||||
]
|
||||
.map((e) => MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate(e.$1),
|
||||
style: style,
|
||||
),
|
||||
proc: () => e.$2(),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: kDesktopMenuPadding.left),
|
||||
dismissOnClicked: true,
|
||||
))
|
||||
.map((e) => e.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor:
|
||||
CustomPopupMenuTheme.commonColor,
|
||||
height: CustomPopupMenuTheme.height,
|
||||
dividerHeight: CustomPopupMenuTheme
|
||||
.dividerHeight)))
|
||||
.expand((i) => i)
|
||||
.toList(),
|
||||
elevation: 8,
|
||||
)
|
||||
.then((_) {
|
||||
_menuOpen.value = false;
|
||||
});
|
||||
},
|
||||
));
|
||||
.then((_) {
|
||||
_menuOpen.value = false;
|
||||
});
|
||||
},
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -61,9 +61,11 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
|
||||
String? connToken,
|
||||
}) {
|
||||
final tabKey = '${peerId}_$terminalId';
|
||||
final alias = bind.mainGetPeerOptionSync(id: peerId, key: 'alias');
|
||||
final tabLabel = alias.isNotEmpty ? '$alias #$terminalId' : '$peerId #$terminalId';
|
||||
return TabInfo(
|
||||
key: tabKey,
|
||||
label: '$peerId #$terminalId',
|
||||
label: tabLabel,
|
||||
selectedIcon: selectedIcon,
|
||||
unselectedIcon: unselectedIcon,
|
||||
onTabCloseButton: () async {
|
||||
|
||||
@@ -25,6 +25,7 @@ import '../../models/platform_model.dart';
|
||||
import '../../common/shared_state.dart';
|
||||
import './popup_menu.dart';
|
||||
import './kb_layout_type_chooser.dart';
|
||||
import 'package:flutter_hbb/utils/scale.dart';
|
||||
|
||||
class ToolbarState {
|
||||
late RxBool _pin;
|
||||
@@ -152,129 +153,6 @@ class _ToolbarTheme {
|
||||
typedef DismissFunc = void Function();
|
||||
|
||||
class RemoteMenuEntry {
|
||||
static MenuEntryRadios<String> viewStyle(
|
||||
String remoteId,
|
||||
FFI ffi,
|
||||
EdgeInsets padding, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
RxString? rxViewStyle,
|
||||
}) {
|
||||
return MenuEntryRadios<String>(
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale original'),
|
||||
value: kRemoteViewStyleOriginal,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale adaptive'),
|
||||
value: kRemoteViewStyleAdaptive,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
// null means peer id is not found, which there's no need to care about
|
||||
final viewStyle =
|
||||
await bind.sessionGetViewStyle(sessionId: ffi.sessionId) ?? '';
|
||||
if (rxViewStyle != null) {
|
||||
rxViewStyle.value = viewStyle;
|
||||
}
|
||||
return viewStyle;
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionSetViewStyle(
|
||||
sessionId: ffi.sessionId, value: newValue);
|
||||
if (rxViewStyle != null) {
|
||||
rxViewStyle.value = newValue;
|
||||
}
|
||||
ffi.canvasModel.updateViewStyle();
|
||||
if (dismissFunc != null) {
|
||||
dismissFunc();
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
|
||||
static MenuEntrySwitch2<String> showRemoteCursor(
|
||||
String remoteId,
|
||||
SessionID sessionId,
|
||||
EdgeInsets padding, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
}) {
|
||||
final state = ShowRemoteCursorState.find(remoteId);
|
||||
final optKey = 'show-remote-cursor';
|
||||
return MenuEntrySwitch2<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Show remote cursor'),
|
||||
getter: () {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(sessionId: sessionId, value: optKey);
|
||||
state.value =
|
||||
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: optKey);
|
||||
if (dismissFunc != null) {
|
||||
dismissFunc();
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
|
||||
static MenuEntrySwitch<String> disableClipboard(
|
||||
SessionID sessionId,
|
||||
EdgeInsets? padding, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
}) {
|
||||
return createSwitchMenuEntry(
|
||||
sessionId,
|
||||
'Disable clipboard',
|
||||
'disable-clipboard',
|
||||
padding,
|
||||
true,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
|
||||
static MenuEntrySwitch<String> createSwitchMenuEntry(
|
||||
SessionID sessionId,
|
||||
String text,
|
||||
String option,
|
||||
EdgeInsets? padding,
|
||||
bool dismissOnClicked, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
}) {
|
||||
return MenuEntrySwitch<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate(text),
|
||||
getter: () async {
|
||||
return bind.sessionGetToggleOptionSync(
|
||||
sessionId: sessionId, arg: option);
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
if (dismissFunc != null) {
|
||||
dismissFunc();
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
|
||||
static MenuEntryButton<String> insertLock(
|
||||
SessionID sessionId,
|
||||
EdgeInsets? padding, {
|
||||
@@ -1024,6 +902,7 @@ class _DisplayMenu extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
final RxInt _customPercent = 100.obs;
|
||||
late final ScreenAdjustor _screenAdjustor = ScreenAdjustor(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
@@ -1037,13 +916,27 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
FFI get ffi => widget.ffi;
|
||||
String get id => widget.id;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialize custom percent from stored option once
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
try {
|
||||
final v = await getSessionCustomScalePercent(widget.ffi.sessionId);
|
||||
if (_customPercent.value != v) {
|
||||
_customPercent.value = v;
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_screenAdjustor.updateScreen();
|
||||
menuChildrenGetter() {
|
||||
final menuChildren = <Widget>[
|
||||
_screenAdjustor.adjustWindow(context),
|
||||
viewStyle(),
|
||||
viewStyle(customPercent: _customPercent),
|
||||
scrollStyle(),
|
||||
imageQuality(),
|
||||
codec(),
|
||||
@@ -1108,30 +1001,69 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
);
|
||||
}
|
||||
|
||||
viewStyle() {
|
||||
viewStyle({required RxInt customPercent}) {
|
||||
return futureBuilder(
|
||||
future: toolbarViewStyle(context, widget.id, widget.ffi),
|
||||
hasData: (data) {
|
||||
final v = data as List<TRadioMenu<String>>;
|
||||
final bool isCustomSelected = v.isNotEmpty
|
||||
? v.first.groupValue == kRemoteViewStyleCustom
|
||||
: false;
|
||||
return Column(children: [
|
||||
...v
|
||||
.map((e) => RdoMenuButton<String>(
|
||||
value: e.value,
|
||||
groupValue: e.groupValue,
|
||||
onChanged: e.onChanged,
|
||||
child: e.child,
|
||||
ffi: ffi))
|
||||
.toList(),
|
||||
Divider(),
|
||||
...v.map((e) {
|
||||
final isCustom = e.value == kRemoteViewStyleCustom;
|
||||
final child = isCustom
|
||||
? Text(translate('Scale custom'))
|
||||
: e.child;
|
||||
// Whether the current selection is already custom
|
||||
final bool isGroupCustomSelected =
|
||||
e.groupValue == kRemoteViewStyleCustom;
|
||||
// Keep menu open when switching INTO custom so the slider is visible immediately
|
||||
final bool keepOpenForThisItem = isCustom && !isGroupCustomSelected;
|
||||
return RdoMenuButton<String>(
|
||||
value: e.value,
|
||||
groupValue: e.groupValue,
|
||||
onChanged: (value) {
|
||||
// Perform the original change
|
||||
e.onChanged?.call(value);
|
||||
// Only force a rebuild when we keep the menu open to reveal the slider
|
||||
if (keepOpenForThisItem) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
ffi: ffi,
|
||||
// When entering custom, keep submenu open to show the slider controls
|
||||
closeOnActivate: !keepOpenForThisItem);
|
||||
}).toList(),
|
||||
// Only show a divider when custom is NOT selected
|
||||
if (!isCustomSelected) Divider(),
|
||||
_customControlsIfCustomSelected(onChanged: (v) => customPercent.value = v),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _customControlsIfCustomSelected({ValueChanged<int>? onChanged}) {
|
||||
return futureBuilder(future: () async {
|
||||
final current = await bind.sessionGetViewStyle(sessionId: ffi.sessionId);
|
||||
return current == kRemoteViewStyleCustom;
|
||||
}(), hasData: (data) {
|
||||
final isCustom = data as bool;
|
||||
return AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 220),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
child: isCustom ? _CustomScaleMenuControls(ffi: ffi, onChanged: onChanged) : SizedBox.shrink(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
scrollStyle() {
|
||||
return futureBuilder(future: () async {
|
||||
final viewStyle =
|
||||
await bind.sessionGetViewStyle(sessionId: ffi.sessionId) ?? '';
|
||||
final visible = viewStyle == kRemoteViewStyleOriginal;
|
||||
final visible = viewStyle == kRemoteViewStyleOriginal ||
|
||||
viewStyle == kRemoteViewStyleCustom;
|
||||
final scrollStyle =
|
||||
await bind.sessionGetScrollStyle(sessionId: ffi.sessionId) ?? '';
|
||||
return {'visible': visible, 'scrollStyle': scrollStyle};
|
||||
@@ -1146,24 +1078,27 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
widget.ffi.canvasModel.updateScrollStyle();
|
||||
}
|
||||
|
||||
final enabled = widget.ffi.canvasModel.imageOverflow.value;
|
||||
return Column(children: [
|
||||
RdoMenuButton<String>(
|
||||
child: Text(translate('ScrollAuto')),
|
||||
value: kRemoteScrollStyleAuto,
|
||||
groupValue: groupValue,
|
||||
onChanged: enabled ? (value) => onChange(value) : null,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
RdoMenuButton<String>(
|
||||
child: Text(translate('Scrollbar')),
|
||||
value: kRemoteScrollStyleBar,
|
||||
groupValue: groupValue,
|
||||
onChanged: enabled ? (value) => onChange(value) : null,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
Divider(),
|
||||
]);
|
||||
return Obx(() => Column(children: [
|
||||
RdoMenuButton<String>(
|
||||
child: Text(translate('ScrollAuto')),
|
||||
value: kRemoteScrollStyleAuto,
|
||||
groupValue: groupValue,
|
||||
onChanged: widget.ffi.canvasModel.imageOverflow.value
|
||||
? (value) => onChange(value)
|
||||
: null,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
RdoMenuButton<String>(
|
||||
child: Text(translate('Scrollbar')),
|
||||
value: kRemoteScrollStyleBar,
|
||||
groupValue: groupValue,
|
||||
onChanged: widget.ffi.canvasModel.imageOverflow.value
|
||||
? (value) => onChange(value)
|
||||
: null,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
Divider(),
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1245,6 +1180,296 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomScaleMenuControls extends StatefulWidget {
|
||||
final FFI ffi;
|
||||
final ValueChanged<int>? onChanged;
|
||||
const _CustomScaleMenuControls({Key? key, required this.ffi, this.onChanged}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_CustomScaleMenuControls> createState() => _CustomScaleMenuControlsState();
|
||||
}
|
||||
|
||||
class _CustomScaleMenuControlsState extends State<_CustomScaleMenuControls> {
|
||||
late int _value;
|
||||
late final Debouncer<int> _debouncerScale;
|
||||
// Normalized slider position in [0, 1]. We map it nonlinearly to percent.
|
||||
double _pos = 0.0;
|
||||
|
||||
// Piecewise mapping constants (moved to consts.dart)
|
||||
static const int _minPercent = kScaleCustomMinPercent;
|
||||
static const int _pivotPercent = kScaleCustomPivotPercent; // 100% should be at 1/3 of track
|
||||
static const int _maxPercent = kScaleCustomMaxPercent;
|
||||
static const double _pivotPos = kScaleCustomPivotPos; // first 1/3 → up to 100%
|
||||
static const double _detentEpsilon = kScaleCustomDetentEpsilon; // snap range around pivot (~0.6%)
|
||||
|
||||
// Clamp helper for local use
|
||||
int _clamp(int v) => clampCustomScalePercent(v);
|
||||
|
||||
// Map normalized position [0,1] → percent [5,1000] with 100 at 1/3 width.
|
||||
int _mapPosToPercent(double p) {
|
||||
if (p <= 0.0) return _minPercent;
|
||||
if (p >= 1.0) return _maxPercent;
|
||||
if (p <= _pivotPos) {
|
||||
final q = p / _pivotPos; // 0..1
|
||||
final v = _minPercent + q * (_pivotPercent - _minPercent);
|
||||
return _clamp(v.round());
|
||||
} else {
|
||||
final q = (p - _pivotPos) / (1.0 - _pivotPos); // 0..1
|
||||
final v = _pivotPercent + q * (_maxPercent - _pivotPercent);
|
||||
return _clamp(v.round());
|
||||
}
|
||||
}
|
||||
|
||||
// Map percent [5,1000] → normalized position [0,1]
|
||||
double _mapPercentToPos(int percent) {
|
||||
final p = _clamp(percent);
|
||||
if (p <= _pivotPercent) {
|
||||
final q = (p - _minPercent) / (_pivotPercent - _minPercent);
|
||||
return q * _pivotPos;
|
||||
} else {
|
||||
final q = (p - _pivotPercent) / (_maxPercent - _pivotPercent);
|
||||
return _pivotPos + q * (1.0 - _pivotPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Snap normalized position to the pivot when close to it
|
||||
double _snapNormalizedPos(double p) {
|
||||
if ((p - _pivotPos).abs() <= _detentEpsilon) return _pivotPos;
|
||||
if (p < 0.0) return 0.0;
|
||||
if (p > 1.0) return 1.0;
|
||||
return p;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_value = 100;
|
||||
_debouncerScale = Debouncer<int>(
|
||||
kDebounceCustomScaleDuration,
|
||||
onChanged: (v) async {
|
||||
await _apply(v);
|
||||
},
|
||||
initialValue: _value,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
try {
|
||||
final v = await getSessionCustomScalePercent(widget.ffi.sessionId);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_value = v;
|
||||
_pos = _mapPercentToPos(v);
|
||||
});
|
||||
}
|
||||
} catch (e, st) {
|
||||
debugPrint('[CustomScale] Failed to get initial value: $e');
|
||||
debugPrintStack(stackTrace: st);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Future<void> _apply(int v) async {
|
||||
v = clampCustomScalePercent(v);
|
||||
setState(() {
|
||||
_value = v;
|
||||
});
|
||||
try {
|
||||
await bind.sessionSetFlutterOption(
|
||||
sessionId: widget.ffi.sessionId,
|
||||
k: kCustomScalePercentKey,
|
||||
v: v.toString());
|
||||
final curStyle = await bind.sessionGetViewStyle(sessionId: widget.ffi.sessionId);
|
||||
if (curStyle != kRemoteViewStyleCustom) {
|
||||
await bind.sessionSetViewStyle(
|
||||
sessionId: widget.ffi.sessionId, value: kRemoteViewStyleCustom);
|
||||
}
|
||||
await widget.ffi.canvasModel.updateViewStyle();
|
||||
if (isMobile) {
|
||||
HapticFeedback.selectionClick();
|
||||
}
|
||||
widget.onChanged?.call(v);
|
||||
} catch (e, st) {
|
||||
debugPrint('[CustomScale] Apply failed: $e');
|
||||
debugPrintStack(stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
void _nudge(int delta) {
|
||||
final next = _clamp(_value + delta);
|
||||
setState(() {
|
||||
_value = next;
|
||||
_pos = _mapPercentToPos(next);
|
||||
});
|
||||
widget.onChanged?.call(next);
|
||||
_debouncerScale.value = next;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debouncerScale.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
const smallBtnConstraints = BoxConstraints(minWidth: 28, minHeight: 28);
|
||||
|
||||
final sliderControl = Semantics(
|
||||
label: translate('Custom scale slider'),
|
||||
value: '$_value%',
|
||||
child: SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor: colorScheme.primary,
|
||||
thumbColor: colorScheme.primary,
|
||||
overlayColor: colorScheme.primary.withOpacity(0.1),
|
||||
showValueIndicator: ShowValueIndicator.never,
|
||||
thumbShape: _RectValueThumbShape(
|
||||
min: _minPercent.toDouble(),
|
||||
max: _maxPercent.toDouble(),
|
||||
width: 52,
|
||||
height: 24,
|
||||
radius: 4,
|
||||
// Display the mapped percent for the current normalized value
|
||||
displayValueForNormalized: (t) => _mapPosToPercent(t),
|
||||
),
|
||||
),
|
||||
child: Slider(
|
||||
value: _pos,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
// Use a wide range of divisions (calculated as (_maxPercent - _minPercent)) to provide ~1% precision increments.
|
||||
// This allows users to set precise scale values. Lower values would require more fine-tuning via the +/- buttons, which is undesirable for big ranges.
|
||||
divisions: (_maxPercent - _minPercent).round(),
|
||||
onChanged: (v) {
|
||||
final snapped = _snapNormalizedPos(v);
|
||||
final next = _mapPosToPercent(snapped);
|
||||
if (next != _value || snapped != _pos) {
|
||||
setState(() {
|
||||
_pos = snapped;
|
||||
_value = next;
|
||||
});
|
||||
widget.onChanged?.call(next);
|
||||
_debouncerScale.value = next;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Column(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(children: [
|
||||
Tooltip(
|
||||
message: translate('Decrease'),
|
||||
child: IconButton(
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(1),
|
||||
constraints: smallBtnConstraints,
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: () => _nudge(-1),
|
||||
),
|
||||
),
|
||||
Expanded(child: sliderControl),
|
||||
Tooltip(
|
||||
message: translate('Increase'),
|
||||
child: IconButton(
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(1),
|
||||
constraints: smallBtnConstraints,
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () => _nudge(1),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
Divider(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Lightweight rectangular thumb that paints the current percentage.
|
||||
// Stateless and uses only SliderTheme colors; avoids allocations beyond a TextPainter per frame.
|
||||
class _RectValueThumbShape extends SliderComponentShape {
|
||||
final double min;
|
||||
final double max;
|
||||
final double width;
|
||||
final double height;
|
||||
final double radius;
|
||||
// Optional mapper to compute display value from normalized position [0,1]
|
||||
// If null, falls back to linear interpolation between min and max.
|
||||
final int Function(double normalized)? displayValueForNormalized;
|
||||
|
||||
const _RectValueThumbShape({
|
||||
required this.min,
|
||||
required this.max,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.radius,
|
||||
this.displayValueForNormalized,
|
||||
});
|
||||
|
||||
@override
|
||||
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
|
||||
return Size(width, height);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(
|
||||
PaintingContext context,
|
||||
Offset center, {
|
||||
required Animation<double> activationAnimation,
|
||||
required Animation<double> enableAnimation,
|
||||
required bool isDiscrete,
|
||||
required TextPainter labelPainter,
|
||||
required RenderBox parentBox,
|
||||
required SliderThemeData sliderTheme,
|
||||
required TextDirection textDirection,
|
||||
required double value,
|
||||
required double textScaleFactor,
|
||||
required Size sizeWithOverflow,
|
||||
}) {
|
||||
final Canvas canvas = context.canvas;
|
||||
|
||||
// Resolve color based on enabled/disabled animation, with safe fallbacks.
|
||||
final ColorTween colorTween = ColorTween(
|
||||
begin: sliderTheme.disabledThumbColor,
|
||||
end: sliderTheme.thumbColor,
|
||||
);
|
||||
final Color? evaluatedColor = colorTween.evaluate(enableAnimation);
|
||||
final Color? thumbColor = sliderTheme.thumbColor;
|
||||
final Color fillColor = evaluatedColor ?? thumbColor ?? Colors.blueAccent;
|
||||
|
||||
final RRect rrect = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: center, width: width, height: height),
|
||||
Radius.circular(radius),
|
||||
);
|
||||
final Paint paint = Paint()..color = fillColor;
|
||||
canvas.drawRRect(rrect, paint);
|
||||
|
||||
// Compute displayed percent from normalized slider value.
|
||||
final int percent = displayValueForNormalized != null
|
||||
? displayValueForNormalized!(value)
|
||||
: (min + value * (max - min)).round();
|
||||
final TextSpan span = TextSpan(
|
||||
text: '$percent%',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
final TextPainter tp = TextPainter(
|
||||
text: span,
|
||||
textAlign: TextAlign.center,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
tp.layout(maxWidth: width - 4);
|
||||
tp.paint(canvas, Offset(center.dx - tp.width / 2, center.dy - tp.height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
class _ResolutionsMenu extends StatefulWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
@@ -2266,6 +2491,8 @@ class RdoMenuButton<T> extends StatelessWidget {
|
||||
final ValueChanged<T?>? onChanged;
|
||||
final Widget? child;
|
||||
final FFI? ffi;
|
||||
// When true, submenu will be dismissed on activate; when false, it stays open.
|
||||
final bool closeOnActivate;
|
||||
const RdoMenuButton({
|
||||
Key? key,
|
||||
required this.value,
|
||||
@@ -2273,6 +2500,7 @@ class RdoMenuButton<T> extends StatelessWidget {
|
||||
required this.child,
|
||||
this.ffi,
|
||||
this.onChanged,
|
||||
this.closeOnActivate = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -2281,9 +2509,10 @@ class RdoMenuButton<T> extends StatelessWidget {
|
||||
value: value,
|
||||
groupValue: groupValue,
|
||||
child: child,
|
||||
closeOnActivate: closeOnActivate,
|
||||
onChanged: onChanged != null
|
||||
? (T? value) {
|
||||
if (ffi != null) {
|
||||
if (ffi != null && closeOnActivate) {
|
||||
_menuDismissCallback(ffi!);
|
||||
}
|
||||
onChanged?.call(value);
|
||||
|
||||
@@ -292,7 +292,6 @@ class DesktopTab extends StatefulWidget {
|
||||
// ignore: must_be_immutable
|
||||
class _DesktopTabState extends State<DesktopTab>
|
||||
with MultiWindowListener, WindowListener {
|
||||
final _saveFrameDebounce = Debouncer(delay: Duration(seconds: 1));
|
||||
Timer? _macOSCheckRestoreTimer;
|
||||
int _macOSCheckRestoreCounter = 0;
|
||||
|
||||
@@ -370,7 +369,7 @@ class _DesktopTabState extends State<DesktopTab>
|
||||
|
||||
void _setMaximized(bool maximize) {
|
||||
stateGlobal.setMaximized(maximize);
|
||||
_saveFrameDebounce.call(_saveFrame);
|
||||
_saveFrame();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@@ -405,24 +404,29 @@ class _DesktopTabState extends State<DesktopTab>
|
||||
super.onWindowUnmaximize();
|
||||
}
|
||||
|
||||
_saveFrame() async {
|
||||
if (tabType == DesktopTabType.main) {
|
||||
await saveWindowPosition(WindowType.Main);
|
||||
} else if (kWindowType != null && kWindowId != null) {
|
||||
await saveWindowPosition(kWindowType!, windowId: kWindowId);
|
||||
_saveFrame({bool? flush}) async {
|
||||
try {
|
||||
if (tabType == DesktopTabType.main) {
|
||||
await saveWindowPosition(WindowType.Main, flush: flush);
|
||||
} else if (kWindowType != null && kWindowId != null) {
|
||||
await saveWindowPosition(kWindowType!,
|
||||
windowId: kWindowId, flush: flush);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error saving window position: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMoved() {
|
||||
_saveFrameDebounce.call(_saveFrame);
|
||||
_saveFrame();
|
||||
super.onWindowMoved();
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowResized() {
|
||||
_saveFrameDebounce.call(_saveFrame);
|
||||
super.onWindowMoved();
|
||||
_saveFrame();
|
||||
super.onWindowResized();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -460,6 +464,8 @@ class _DesktopTabState extends State<DesktopTab>
|
||||
});
|
||||
}
|
||||
|
||||
await _saveFrame(flush: true);
|
||||
|
||||
// hide window on close
|
||||
if (isMainWindow) {
|
||||
if (rustDeskWinManager.getActiveWindows().contains(kMainWindowId)) {
|
||||
@@ -1079,11 +1085,12 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
|
||||
child: Tooltip(
|
||||
message: widget.tabType == DesktopTabType.main
|
||||
? ''
|
||||
: translate(widget.label.value),
|
||||
message:
|
||||
widget.tabType == DesktopTabType.main ? '' : widget.label.value,
|
||||
child: Text(
|
||||
translate(widget.label.value),
|
||||
widget.tabType == DesktopTabType.main
|
||||
? translate(widget.label.value)
|
||||
: widget.label.value,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
|
||||
@@ -182,6 +182,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
rdpUsername: '',
|
||||
loginName: '',
|
||||
device_group_name: '',
|
||||
note: '',
|
||||
);
|
||||
_autocompleteOpts = [emptyPeer];
|
||||
} else {
|
||||
|
||||
@@ -92,6 +92,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
gFFI.dialogManager.dismissAll();
|
||||
WakelockPlus.disable();
|
||||
});
|
||||
model.jobController.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -424,6 +425,7 @@ class FileManagerView extends StatefulWidget {
|
||||
class _FileManagerViewState extends State<FileManagerView> {
|
||||
final _listScrollController = ScrollController();
|
||||
final _breadCrumbScroller = ScrollController();
|
||||
late final ascending = Rx<bool>(controller.sortAscending);
|
||||
|
||||
bool get isLocal => widget.controller.isLocal;
|
||||
FileController get controller => widget.controller;
|
||||
@@ -589,57 +591,67 @@ class _FileManagerViewState extends State<FileManagerView> {
|
||||
|
||||
Widget headTools() => Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Obx(() {
|
||||
final home = controller.options.value.home;
|
||||
final isWindows = controller.options.value.isWindows;
|
||||
return BreadCrumb(
|
||||
items: getPathBreadCrumbItems(controller.shortPath, isWindows,
|
||||
() => controller.goToHomeDirectory(), (list) {
|
||||
var path = "";
|
||||
if (home.startsWith(list[0])) {
|
||||
// absolute path
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, isWindows);
|
||||
}
|
||||
} else {
|
||||
path += home;
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, isWindows);
|
||||
}
|
||||
}
|
||||
controller.openDirectory(path);
|
||||
}),
|
||||
divider: Icon(Icons.chevron_right),
|
||||
overflow: ScrollableOverflow(controller: _breadCrumbScroller),
|
||||
);
|
||||
})),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: controller.goBack,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.arrow_upward),
|
||||
onPressed: controller.goToParentDirectory,
|
||||
),
|
||||
PopupMenuButton<SortBy>(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.sort),
|
||||
itemBuilder: (context) {
|
||||
return SortBy.values
|
||||
.map((e) => PopupMenuItem(
|
||||
child: Text(translate(e.toString())),
|
||||
value: e,
|
||||
))
|
||||
.toList();
|
||||
},
|
||||
onSelected: controller.changeSortStyle),
|
||||
Expanded(child: Obx(() {
|
||||
final home = controller.options.value.home;
|
||||
final isWindows = controller.options.value.isWindows;
|
||||
return BreadCrumb(
|
||||
items: getPathBreadCrumbItems(controller.shortPath, isWindows,
|
||||
() => controller.goToHomeDirectory(), (list) {
|
||||
var path = "";
|
||||
if (home.startsWith(list[0])) {
|
||||
// absolute path
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, isWindows);
|
||||
}
|
||||
} else {
|
||||
path += home;
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, isWindows);
|
||||
}
|
||||
}
|
||||
controller.openDirectory(path);
|
||||
}),
|
||||
divider: Icon(Icons.chevron_right),
|
||||
overflow: ScrollableOverflow(controller: _breadCrumbScroller),
|
||||
);
|
||||
})),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: controller.goBack,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.arrow_upward),
|
||||
onPressed: controller.goToParentDirectory,
|
||||
),
|
||||
PopupMenuButton<SortBy>(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.sort),
|
||||
itemBuilder: (context) {
|
||||
return SortBy.values
|
||||
.map((e) => PopupMenuItem(
|
||||
child: Text(translate(e.toString())),
|
||||
value: e,
|
||||
))
|
||||
.toList();
|
||||
},
|
||||
onSelected: (sortBy) {
|
||||
// If selecting the same sort option, flip the order
|
||||
// If selecting a different sort option, use ascending order
|
||||
if (controller.sortBy.value == sortBy) {
|
||||
ascending.value = !controller.sortAscending;
|
||||
} else {
|
||||
ascending.value = true;
|
||||
}
|
||||
controller.changeSortStyle(sortBy, ascending: ascending.value);
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
));
|
||||
));
|
||||
|
||||
Widget listTail() => Obx(() => Container(
|
||||
height: 100,
|
||||
|
||||
@@ -6,6 +6,8 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/common/widgets/toolbar.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/mobile/widgets/floating_mouse.dart';
|
||||
import 'package:flutter_hbb/mobile/widgets/floating_mouse_widgets.dart';
|
||||
import 'package:flutter_hbb/mobile/widgets/gesture_help.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||
@@ -617,6 +619,15 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||
if (showCursorPaint) {
|
||||
paints.add(CursorPaint(widget.id));
|
||||
}
|
||||
if (gFFI.ffiModel.touchMode) {
|
||||
paints.add(FloatingMouse(
|
||||
ffi: gFFI,
|
||||
));
|
||||
} else {
|
||||
paints.add(FloatingMouseWidgets(
|
||||
ffi: gFFI,
|
||||
));
|
||||
}
|
||||
return paints;
|
||||
}()));
|
||||
}
|
||||
@@ -789,13 +800,14 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||
controller: ScrollController(),
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: GestureHelp(
|
||||
touchMode: gFFI.ffiModel.touchMode,
|
||||
onTouchModeChange: (t) {
|
||||
gFFI.ffiModel.toggleTouchMode();
|
||||
final v = gFFI.ffiModel.touchMode ? 'Y' : '';
|
||||
bind.sessionPeerOption(
|
||||
sessionId: sessionId, name: kOptionTouchMode, value: v);
|
||||
})));
|
||||
touchMode: gFFI.ffiModel.touchMode,
|
||||
onTouchModeChange: (t) {
|
||||
gFFI.ffiModel.toggleTouchMode();
|
||||
final v = gFFI.ffiModel.touchMode ? 'Y' : 'N';
|
||||
bind.mainSetLocalOption(key: kOptionTouchMode, value: v);
|
||||
},
|
||||
virtualMouseMode: gFFI.ffiModel.virtualMouseMode,
|
||||
)));
|
||||
}
|
||||
|
||||
// * Currently mobile does not enable map mode
|
||||
|
||||
1209
flutter/lib/mobile/widgets/floating_mouse.dart
Normal file
1209
flutter/lib/mobile/widgets/floating_mouse.dart
Normal file
File diff suppressed because it is too large
Load Diff
880
flutter/lib/mobile/widgets/floating_mouse_widgets.dart
Normal file
880
flutter/lib/mobile/widgets/floating_mouse_widgets.dart
Normal file
@@ -0,0 +1,880 @@
|
||||
// These floating mouse widgets are used to simulate a physical mouse
|
||||
// when "mobile" -> "desktop" in mouse mode.
|
||||
// This file does not contain whole mouse widgets, it only contains
|
||||
// parts that help to control, such as wheel scroll and wheel button.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/common/widgets/remote_input.dart';
|
||||
import 'package:flutter_hbb/models/input_model.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
|
||||
// Used for the wheel button and wheel scroll widgets
|
||||
const double _kSpaceToHorizontalEdge = 25;
|
||||
const double _wheelWidth = 50;
|
||||
const double _wheelHeight = 162;
|
||||
// Used for the left/right button widgets
|
||||
const double _kSpaceToVerticalEdge = 15;
|
||||
const double _kSpaceBetweenLeftRightButtons = 40;
|
||||
const double _kLeftRightButtonWidth = 55;
|
||||
const double _kLeftRightButtonHeight = 40;
|
||||
const double _kBorderWidth = 1;
|
||||
final Color _kDefaultBorderColor = Colors.white.withOpacity(0.7);
|
||||
final Color _kDefaultColor = Colors.black.withOpacity(0.4);
|
||||
final Color _kTapDownColor = Colors.blue.withOpacity(0.7);
|
||||
final Color _kWidgetHighlightColor = Colors.white.withOpacity(0.9);
|
||||
const int _kInputTimerIntervalMillis = 100;
|
||||
|
||||
class FloatingMouseWidgets extends StatefulWidget {
|
||||
final FFI ffi;
|
||||
const FloatingMouseWidgets({
|
||||
super.key,
|
||||
required this.ffi,
|
||||
});
|
||||
|
||||
@override
|
||||
State<FloatingMouseWidgets> createState() => _FloatingMouseWidgetsState();
|
||||
}
|
||||
|
||||
class _FloatingMouseWidgetsState extends State<FloatingMouseWidgets> {
|
||||
InputModel get _inputModel => widget.ffi.inputModel;
|
||||
CursorModel get _cursorModel => widget.ffi.cursorModel;
|
||||
late final VirtualMouseMode _virtualMouseMode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_virtualMouseMode = widget.ffi.ffiModel.virtualMouseMode;
|
||||
_virtualMouseMode.addListener(_onVirtualMouseModeChanged);
|
||||
_cursorModel.blockEvents = false;
|
||||
isSpecialHoldDragActive = false;
|
||||
}
|
||||
|
||||
void _onVirtualMouseModeChanged() {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_virtualMouseMode.removeListener(_onVirtualMouseModeChanged);
|
||||
super.dispose();
|
||||
_cursorModel.blockEvents = false;
|
||||
isSpecialHoldDragActive = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final virtualMouseMode = _virtualMouseMode;
|
||||
if (!virtualMouseMode.showVirtualMouse) {
|
||||
return const Offstage();
|
||||
}
|
||||
return Stack(
|
||||
children: [
|
||||
FloatingWheel(
|
||||
inputModel: _inputModel,
|
||||
cursorModel: _cursorModel,
|
||||
),
|
||||
if (virtualMouseMode.showVirtualJoystick)
|
||||
VirtualJoystick(cursorModel: _cursorModel),
|
||||
FloatingLeftRightButton(
|
||||
isLeft: true,
|
||||
inputModel: _inputModel,
|
||||
cursorModel: _cursorModel,
|
||||
),
|
||||
FloatingLeftRightButton(
|
||||
isLeft: false,
|
||||
inputModel: _inputModel,
|
||||
cursorModel: _cursorModel,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingWheel extends StatefulWidget {
|
||||
final InputModel inputModel;
|
||||
final CursorModel cursorModel;
|
||||
const FloatingWheel(
|
||||
{super.key, required this.inputModel, required this.cursorModel});
|
||||
|
||||
@override
|
||||
State<FloatingWheel> createState() => _FloatingWheelState();
|
||||
}
|
||||
|
||||
class _FloatingWheelState extends State<FloatingWheel> {
|
||||
Offset _position = Offset.zero;
|
||||
bool _isInitialized = false;
|
||||
Rect? _lastBlockedRect;
|
||||
|
||||
bool _isUpDown = false;
|
||||
bool _isMidDown = false;
|
||||
bool _isDownDown = false;
|
||||
|
||||
Orientation? _previousOrientation;
|
||||
|
||||
Timer? _scrollTimer;
|
||||
|
||||
InputModel get _inputModel => widget.inputModel;
|
||||
CursorModel get _cursorModel => widget.cursorModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_resetPosition();
|
||||
});
|
||||
}
|
||||
|
||||
void _resetPosition() {
|
||||
final size = MediaQuery.of(context).size;
|
||||
setState(() {
|
||||
_position = Offset(
|
||||
size.width - _wheelWidth - _kSpaceToHorizontalEdge,
|
||||
(size.height - _wheelHeight) / 2,
|
||||
);
|
||||
_isInitialized = true;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _updateBlockedRect();
|
||||
});
|
||||
}
|
||||
|
||||
void _updateBlockedRect() {
|
||||
if (_lastBlockedRect != null) {
|
||||
_cursorModel.removeBlockedRect(_lastBlockedRect!);
|
||||
}
|
||||
final newRect =
|
||||
Rect.fromLTWH(_position.dx, _position.dy, _wheelWidth, _wheelHeight);
|
||||
_cursorModel.addBlockedRect(newRect);
|
||||
_lastBlockedRect = newRect;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollTimer?.cancel();
|
||||
if (_lastBlockedRect != null) {
|
||||
_cursorModel.removeBlockedRect(_lastBlockedRect!);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final currentOrientation = MediaQuery.of(context).orientation;
|
||||
if (_previousOrientation != null &&
|
||||
_previousOrientation != currentOrientation) {
|
||||
_resetPosition();
|
||||
}
|
||||
_previousOrientation = currentOrientation;
|
||||
}
|
||||
|
||||
Widget _buildUpDownButton(
|
||||
void Function(PointerDownEvent) onPointerDown,
|
||||
void Function(PointerUpEvent) onPointerUp,
|
||||
void Function(PointerCancelEvent) onPointerCancel,
|
||||
bool Function() flagGetter,
|
||||
BorderRadiusGeometry borderRadius,
|
||||
IconData iconData) {
|
||||
return Listener(
|
||||
onPointerDown: onPointerDown,
|
||||
onPointerUp: onPointerUp,
|
||||
onPointerCancel: onPointerCancel,
|
||||
child: Container(
|
||||
width: _wheelWidth,
|
||||
height: 55,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: _kDefaultColor,
|
||||
border: Border.all(
|
||||
color: flagGetter() ? _kTapDownColor : _kDefaultBorderColor,
|
||||
width: 1),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: Icon(iconData, color: _kDefaultBorderColor, size: 32),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_isInitialized) {
|
||||
return Positioned(child: Offstage());
|
||||
}
|
||||
return Positioned(
|
||||
left: _position.dx,
|
||||
top: _position.dy,
|
||||
child: _buildWidget(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWidget(BuildContext context) {
|
||||
return Container(
|
||||
width: _wheelWidth,
|
||||
height: _wheelHeight,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildUpDownButton(
|
||||
(event) {
|
||||
setState(() {
|
||||
_isUpDown = true;
|
||||
});
|
||||
_startScrollTimer(1);
|
||||
},
|
||||
(event) {
|
||||
setState(() {
|
||||
_isUpDown = false;
|
||||
});
|
||||
_stopScrollTimer();
|
||||
},
|
||||
(event) {
|
||||
setState(() {
|
||||
_isUpDown = false;
|
||||
});
|
||||
_stopScrollTimer();
|
||||
},
|
||||
() => _isUpDown,
|
||||
BorderRadius.vertical(top: Radius.circular(_wheelWidth * 0.5)),
|
||||
Icons.keyboard_arrow_up,
|
||||
),
|
||||
Listener(
|
||||
onPointerDown: (event) {
|
||||
setState(() {
|
||||
_isMidDown = true;
|
||||
});
|
||||
_inputModel.tapDown(MouseButtons.wheel);
|
||||
},
|
||||
onPointerUp: (event) {
|
||||
setState(() {
|
||||
_isMidDown = false;
|
||||
});
|
||||
_inputModel.tapUp(MouseButtons.wheel);
|
||||
},
|
||||
onPointerCancel: (event) {
|
||||
setState(() {
|
||||
_isMidDown = false;
|
||||
});
|
||||
_inputModel.tapUp(MouseButtons.wheel);
|
||||
},
|
||||
child: Container(
|
||||
width: _wheelWidth,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
color: _kDefaultColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color:
|
||||
_isMidDown ? _kTapDownColor : _kDefaultBorderColor,
|
||||
width: _kBorderWidth)),
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: _wheelWidth - 10,
|
||||
height: _wheelWidth - 10,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 18,
|
||||
height: 2,
|
||||
color: _kDefaultBorderColor,
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
Container(
|
||||
width: 24,
|
||||
height: 2,
|
||||
color: _kDefaultBorderColor,
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
Container(
|
||||
width: 18,
|
||||
height: 2,
|
||||
color: _kDefaultBorderColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildUpDownButton(
|
||||
(event) {
|
||||
setState(() {
|
||||
_isDownDown = true;
|
||||
});
|
||||
_startScrollTimer(-1);
|
||||
},
|
||||
(event) {
|
||||
setState(() {
|
||||
_isDownDown = false;
|
||||
});
|
||||
_stopScrollTimer();
|
||||
},
|
||||
(event) {
|
||||
setState(() {
|
||||
_isDownDown = false;
|
||||
});
|
||||
_stopScrollTimer();
|
||||
},
|
||||
() => _isDownDown,
|
||||
BorderRadius.vertical(bottom: Radius.circular(_wheelWidth * 0.5)),
|
||||
Icons.keyboard_arrow_down,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _startScrollTimer(int direction) {
|
||||
_scrollTimer?.cancel();
|
||||
_inputModel.scroll(direction);
|
||||
_scrollTimer = Timer.periodic(
|
||||
Duration(milliseconds: _kInputTimerIntervalMillis), (timer) {
|
||||
_inputModel.scroll(direction);
|
||||
});
|
||||
}
|
||||
|
||||
void _stopScrollTimer() {
|
||||
_scrollTimer?.cancel();
|
||||
_scrollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingLeftRightButton extends StatefulWidget {
|
||||
final bool isLeft;
|
||||
final InputModel inputModel;
|
||||
final CursorModel cursorModel;
|
||||
const FloatingLeftRightButton(
|
||||
{super.key,
|
||||
required this.isLeft,
|
||||
required this.inputModel,
|
||||
required this.cursorModel});
|
||||
|
||||
@override
|
||||
State<FloatingLeftRightButton> createState() =>
|
||||
_FloatingLeftRightButtonState();
|
||||
}
|
||||
|
||||
class _FloatingLeftRightButtonState extends State<FloatingLeftRightButton> {
|
||||
Offset _position = Offset.zero;
|
||||
bool _isInitialized = false;
|
||||
bool _isDown = false;
|
||||
Rect? _lastBlockedRect;
|
||||
|
||||
Orientation? _previousOrientation;
|
||||
Offset _preSavedPos = Offset.zero;
|
||||
|
||||
// Gesture ambiguity resolution
|
||||
Timer? _tapDownTimer;
|
||||
final Duration _pressTimeout = const Duration(milliseconds: 200);
|
||||
bool _isDragging = false;
|
||||
|
||||
bool get _isLeft => widget.isLeft;
|
||||
InputModel get _inputModel => widget.inputModel;
|
||||
CursorModel get _cursorModel => widget.cursorModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final currentOrientation = MediaQuery.of(context).orientation;
|
||||
_previousOrientation = currentOrientation;
|
||||
_resetPosition(currentOrientation);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_lastBlockedRect != null) {
|
||||
_cursorModel.removeBlockedRect(_lastBlockedRect!);
|
||||
}
|
||||
_tapDownTimer?.cancel();
|
||||
_trySavePosition();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final currentOrientation = MediaQuery.of(context).orientation;
|
||||
if (_previousOrientation == null ||
|
||||
_previousOrientation != currentOrientation) {
|
||||
_resetPosition(currentOrientation);
|
||||
}
|
||||
_previousOrientation = currentOrientation;
|
||||
}
|
||||
|
||||
double _getOffsetX(double w) {
|
||||
if (_isLeft) {
|
||||
return (w - _kLeftRightButtonWidth * 2 - _kSpaceBetweenLeftRightButtons) *
|
||||
0.5;
|
||||
} else {
|
||||
return (w + _kSpaceBetweenLeftRightButtons) * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
String _getPositionKey(Orientation ori) {
|
||||
final strLeftRight = _isLeft ? 'l' : 'r';
|
||||
final strOri = ori == Orientation.landscape ? 'l' : 'p';
|
||||
return '$strLeftRight$strOri-mouse-btn-pos';
|
||||
}
|
||||
|
||||
static Offset? _loadPositionFromString(String s) {
|
||||
if (s.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final m = jsonDecode(s);
|
||||
return Offset(m['x'], m['y']);
|
||||
} catch (e) {
|
||||
debugPrintStack(label: 'Failed to load position "$s" $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _trySavePosition() {
|
||||
if (_previousOrientation == null) return;
|
||||
if (((_position - _preSavedPos)).distanceSquared < 0.1) return;
|
||||
final pos = jsonEncode({
|
||||
'x': _position.dx,
|
||||
'y': _position.dy,
|
||||
});
|
||||
bind.setLocalFlutterOption(
|
||||
k: _getPositionKey(_previousOrientation!), v: pos);
|
||||
_preSavedPos = _position;
|
||||
}
|
||||
|
||||
void _restorePosition(Orientation ori) {
|
||||
final ps = bind.getLocalFlutterOption(k: _getPositionKey(ori));
|
||||
final pos = _loadPositionFromString(ps);
|
||||
if (pos == null) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
_position = Offset(_getOffsetX(size.width),
|
||||
size.height - _kSpaceToVerticalEdge - _kLeftRightButtonHeight);
|
||||
} else {
|
||||
_position = pos;
|
||||
_preSavedPos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
void _resetPosition(Orientation ori) {
|
||||
setState(() {
|
||||
_restorePosition(ori);
|
||||
_isInitialized = true;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _updateBlockedRect();
|
||||
});
|
||||
}
|
||||
|
||||
void _updateBlockedRect() {
|
||||
if (_lastBlockedRect != null) {
|
||||
_cursorModel.removeBlockedRect(_lastBlockedRect!);
|
||||
}
|
||||
final newRect = Rect.fromLTWH(_position.dx, _position.dy,
|
||||
_kLeftRightButtonWidth, _kLeftRightButtonHeight);
|
||||
_cursorModel.addBlockedRect(newRect);
|
||||
_lastBlockedRect = newRect;
|
||||
}
|
||||
|
||||
void _onMoveUpdateDelta(Offset delta) {
|
||||
final context = this.context;
|
||||
final size = MediaQuery.of(context).size;
|
||||
Offset newPosition = _position + delta;
|
||||
double minX = _kSpaceToHorizontalEdge;
|
||||
double minY = _kSpaceToVerticalEdge;
|
||||
double maxX = size.width - _kLeftRightButtonWidth - _kSpaceToHorizontalEdge;
|
||||
double maxY = size.height - _kLeftRightButtonHeight - _kSpaceToVerticalEdge;
|
||||
newPosition = Offset(
|
||||
newPosition.dx.clamp(minX, maxX),
|
||||
newPosition.dy.clamp(minY, maxY),
|
||||
);
|
||||
final isPositionChanged = !(isDoubleEqual(newPosition.dx, _position.dx) &&
|
||||
isDoubleEqual(newPosition.dy, _position.dy));
|
||||
setState(() {
|
||||
_position = newPosition;
|
||||
});
|
||||
if (isPositionChanged) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _updateBlockedRect();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onBodyPointerMoveUpdate(PointerMoveEvent event) {
|
||||
_cursorModel.blockEvents = true;
|
||||
// If move, it's a drag, not a tap.
|
||||
_isDragging = true;
|
||||
// Cancel the timer to prevent it from being recognized as a tap/hold.
|
||||
_tapDownTimer?.cancel();
|
||||
_tapDownTimer = null;
|
||||
_onMoveUpdateDelta(event.delta);
|
||||
}
|
||||
|
||||
Widget _buildButtonIcon() {
|
||||
final double w = _kLeftRightButtonWidth * 0.45;
|
||||
final double h = _kLeftRightButtonHeight * 0.75;
|
||||
final double borderRadius = w * 0.5;
|
||||
final double quarterCircleRadius = borderRadius * 0.9;
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: w,
|
||||
height: h,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(_kLeftRightButtonWidth * 0.225),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: _isLeft ? quarterCircleRadius * 0.25 : null,
|
||||
right: _isLeft ? null : quarterCircleRadius * 0.25,
|
||||
top: quarterCircleRadius * 0.25,
|
||||
child: CustomPaint(
|
||||
size: Size(quarterCircleRadius * 2, quarterCircleRadius * 2),
|
||||
painter: _QuarterCirclePainter(
|
||||
color: _kDefaultColor,
|
||||
isLeft: _isLeft,
|
||||
radius: quarterCircleRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_isInitialized) {
|
||||
return Positioned(child: Offstage());
|
||||
}
|
||||
return Positioned(
|
||||
left: _position.dx,
|
||||
top: _position.dy,
|
||||
// We can't use the GestureDetector here, because `onTapDown` may be
|
||||
// triggered sometimes when dragging.
|
||||
child: Listener(
|
||||
onPointerMove: _onBodyPointerMoveUpdate,
|
||||
onPointerDown: (event) async {
|
||||
_isDragging = false;
|
||||
setState(() {
|
||||
_isDown = true;
|
||||
});
|
||||
// Start a timer. If it fires, it's a hold.
|
||||
_tapDownTimer?.cancel();
|
||||
_tapDownTimer = Timer(_pressTimeout, () {
|
||||
isSpecialHoldDragActive = true;
|
||||
() async {
|
||||
await _cursorModel.syncCursorPosition();
|
||||
await _inputModel
|
||||
.tapDown(_isLeft ? MouseButtons.left : MouseButtons.right);
|
||||
}();
|
||||
_tapDownTimer = null;
|
||||
});
|
||||
},
|
||||
onPointerUp: (event) {
|
||||
_cursorModel.blockEvents = false;
|
||||
setState(() {
|
||||
_isDown = false;
|
||||
});
|
||||
// If timer is active, it's a quick tap.
|
||||
if (_tapDownTimer != null) {
|
||||
_tapDownTimer!.cancel();
|
||||
_tapDownTimer = null;
|
||||
// Fire tap down and up quickly.
|
||||
_inputModel
|
||||
.tapDown(_isLeft ? MouseButtons.left : MouseButtons.right)
|
||||
.then(
|
||||
(_) => Future.delayed(const Duration(milliseconds: 50), () {
|
||||
_inputModel.tapUp(
|
||||
_isLeft ? MouseButtons.left : MouseButtons.right);
|
||||
}));
|
||||
} else {
|
||||
// If it's not a quick tap, it could be a hold or drag.
|
||||
// If it was a hold, isSpecialHoldDragActive is true.
|
||||
if (isSpecialHoldDragActive) {
|
||||
_inputModel
|
||||
.tapUp(_isLeft ? MouseButtons.left : MouseButtons.right);
|
||||
}
|
||||
}
|
||||
|
||||
if (_isDragging) {
|
||||
_trySavePosition();
|
||||
}
|
||||
isSpecialHoldDragActive = false;
|
||||
},
|
||||
onPointerCancel: (event) {
|
||||
_cursorModel.blockEvents = false;
|
||||
setState(() {
|
||||
_isDown = false;
|
||||
});
|
||||
_tapDownTimer?.cancel();
|
||||
_tapDownTimer = null;
|
||||
if (isSpecialHoldDragActive) {
|
||||
_inputModel.tapUp(_isLeft ? MouseButtons.left : MouseButtons.right);
|
||||
}
|
||||
isSpecialHoldDragActive = false;
|
||||
if (_isDragging) {
|
||||
_trySavePosition();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: _kLeftRightButtonWidth,
|
||||
height: _kLeftRightButtonHeight,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: _kDefaultColor,
|
||||
border: Border.all(
|
||||
color: _isDown ? _kTapDownColor : _kDefaultBorderColor,
|
||||
width: _kBorderWidth),
|
||||
borderRadius: _isLeft
|
||||
? BorderRadius.horizontal(
|
||||
left: Radius.circular(_kLeftRightButtonHeight * 0.5))
|
||||
: BorderRadius.horizontal(
|
||||
right: Radius.circular(_kLeftRightButtonHeight * 0.5)),
|
||||
),
|
||||
child: _buildButtonIcon(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QuarterCirclePainter extends CustomPainter {
|
||||
final Color color;
|
||||
final bool isLeft;
|
||||
final double radius;
|
||||
_QuarterCirclePainter(
|
||||
{required this.color, required this.isLeft, required this.radius});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.fill;
|
||||
final rect = Rect.fromLTWH(0, 0, radius * 2, radius * 2);
|
||||
if (isLeft) {
|
||||
canvas.drawArc(rect, -pi, pi / 2, true, paint);
|
||||
} else {
|
||||
canvas.drawArc(rect, -pi / 2, pi / 2, true, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
// Virtual joystick sends the absolute movement for now.
|
||||
// Maybe we need to change it to relative movement in the future.
|
||||
class VirtualJoystick extends StatefulWidget {
|
||||
final CursorModel cursorModel;
|
||||
|
||||
const VirtualJoystick({super.key, required this.cursorModel});
|
||||
|
||||
@override
|
||||
State<VirtualJoystick> createState() => _VirtualJoystickState();
|
||||
}
|
||||
|
||||
class _VirtualJoystickState extends State<VirtualJoystick> {
|
||||
Offset _position = Offset.zero;
|
||||
bool _isInitialized = false;
|
||||
Offset _offset = Offset.zero;
|
||||
final double _joystickRadius = 50.0;
|
||||
final double _thumbRadius = 20.0;
|
||||
final double _moveStep = 3.0;
|
||||
final double _speed = 1.0;
|
||||
|
||||
// One-shot timer to detect a drag gesture
|
||||
Timer? _dragStartTimer;
|
||||
// Periodic timer for continuous movement
|
||||
Timer? _continuousMoveTimer;
|
||||
Size? _lastScreenSize;
|
||||
bool _isPressed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.cursorModel.blockEvents = false;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_lastScreenSize = MediaQuery.of(context).size;
|
||||
_resetPosition();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_stopSendEventTimer();
|
||||
widget.cursorModel.blockEvents = false;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final currentScreenSize = MediaQuery.of(context).size;
|
||||
if (_lastScreenSize != null && _lastScreenSize != currentScreenSize) {
|
||||
_resetPosition();
|
||||
}
|
||||
_lastScreenSize = currentScreenSize;
|
||||
}
|
||||
|
||||
void _resetPosition() {
|
||||
final size = MediaQuery.of(context).size;
|
||||
setState(() {
|
||||
_position = Offset(
|
||||
_kSpaceToHorizontalEdge + _joystickRadius,
|
||||
size.height * 0.5 + _joystickRadius * 1.5,
|
||||
);
|
||||
_isInitialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
Offset _offsetToPanDelta(Offset offset) {
|
||||
return Offset(
|
||||
offset.dx / _joystickRadius,
|
||||
offset.dy / _joystickRadius,
|
||||
);
|
||||
}
|
||||
|
||||
void _stopSendEventTimer() {
|
||||
_dragStartTimer?.cancel();
|
||||
_continuousMoveTimer?.cancel();
|
||||
_dragStartTimer = null;
|
||||
_continuousMoveTimer = null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_isInitialized) {
|
||||
return Positioned(child: Offstage());
|
||||
}
|
||||
return Positioned(
|
||||
left: _position.dx - _joystickRadius,
|
||||
top: _position.dy - _joystickRadius,
|
||||
child: GestureDetector(
|
||||
onPanStart: (details) {
|
||||
setState(() {
|
||||
_isPressed = true;
|
||||
});
|
||||
widget.cursorModel.blockEvents = true;
|
||||
_updateOffset(details.localPosition);
|
||||
|
||||
// 1. Send a single, small pan event immediately for responsiveness.
|
||||
// The movement is small for a gentle start.
|
||||
final initialDelta = _offsetToPanDelta(_offset);
|
||||
if (initialDelta.distance > 0) {
|
||||
widget.cursorModel.updatePan(initialDelta, Offset.zero, false);
|
||||
}
|
||||
|
||||
// 2. Start a one-shot timer to check if the user is holding for a drag.
|
||||
_dragStartTimer?.cancel();
|
||||
_dragStartTimer = Timer(const Duration(milliseconds: 120), () {
|
||||
// 3. If the timer fires, it's a drag. Start the continuous movement timer.
|
||||
_continuousMoveTimer?.cancel();
|
||||
_continuousMoveTimer =
|
||||
periodic_immediate(const Duration(milliseconds: 20), () async {
|
||||
if (_offset != Offset.zero) {
|
||||
widget.cursorModel.updatePan(
|
||||
_offsetToPanDelta(_offset) * _moveStep * _speed,
|
||||
Offset.zero,
|
||||
false);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
onPanUpdate: (details) {
|
||||
_updateOffset(details.localPosition);
|
||||
},
|
||||
onPanEnd: (details) {
|
||||
setState(() {
|
||||
_offset = Offset.zero;
|
||||
_isPressed = false;
|
||||
});
|
||||
widget.cursorModel.blockEvents = false;
|
||||
|
||||
// 4. Critical step: On pan end, cancel all timers.
|
||||
// If it was a flick, this cancels the drag detection before it fires.
|
||||
// If it was a drag, this stops the continuous movement.
|
||||
_stopSendEventTimer();
|
||||
},
|
||||
child: CustomPaint(
|
||||
size: Size(_joystickRadius * 2, _joystickRadius * 2),
|
||||
painter: _JoystickPainter(
|
||||
_offset, _joystickRadius, _thumbRadius, _isPressed),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateOffset(Offset localPosition) {
|
||||
final center = Offset(_joystickRadius, _joystickRadius);
|
||||
final offset = localPosition - center;
|
||||
final distance = offset.distance;
|
||||
|
||||
if (distance <= _joystickRadius) {
|
||||
setState(() {
|
||||
_offset = offset;
|
||||
});
|
||||
} else {
|
||||
final clampedOffset = offset / distance * _joystickRadius;
|
||||
setState(() {
|
||||
_offset = clampedOffset;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _JoystickPainter extends CustomPainter {
|
||||
final Offset _offset;
|
||||
final double _joystickRadius;
|
||||
final double _thumbRadius;
|
||||
final bool _isPressed;
|
||||
|
||||
_JoystickPainter(
|
||||
this._offset, this._joystickRadius, this._thumbRadius, this._isPressed);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final center = Offset(size.width / 2, size.height / 2);
|
||||
final joystickColor = _kDefaultColor;
|
||||
final borderColor = _isPressed ? _kTapDownColor : _kDefaultBorderColor;
|
||||
final thumbColor = _kWidgetHighlightColor;
|
||||
|
||||
final joystickPaint = Paint()
|
||||
..color = joystickColor
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final borderPaint = Paint()
|
||||
..color = borderColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5;
|
||||
|
||||
final thumbPaint = Paint()
|
||||
..color = thumbColor
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
// Draw joystick base and border
|
||||
canvas.drawCircle(center, _joystickRadius, joystickPaint);
|
||||
canvas.drawCircle(center, _joystickRadius, borderPaint);
|
||||
|
||||
// Draw thumb
|
||||
final thumbCenter = center + _offset;
|
||||
canvas.drawCircle(thumbCenter, _thumbRadius, thumbPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _JoystickPainter oldDelegate) {
|
||||
return oldDelegate._offset != _offset ||
|
||||
oldDelegate._isPressed != _isPressed;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:toggle_switch/toggle_switch.dart';
|
||||
|
||||
class GestureIcons {
|
||||
@@ -35,20 +36,27 @@ typedef OnTouchModeChange = void Function(bool);
|
||||
|
||||
class GestureHelp extends StatefulWidget {
|
||||
GestureHelp(
|
||||
{Key? key, required this.touchMode, required this.onTouchModeChange})
|
||||
{Key? key,
|
||||
required this.touchMode,
|
||||
required this.onTouchModeChange,
|
||||
required this.virtualMouseMode})
|
||||
: super(key: key);
|
||||
final bool touchMode;
|
||||
final OnTouchModeChange onTouchModeChange;
|
||||
final VirtualMouseMode virtualMouseMode;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _GestureHelpState(touchMode);
|
||||
State<StatefulWidget> createState() =>
|
||||
_GestureHelpState(touchMode, virtualMouseMode);
|
||||
}
|
||||
|
||||
class _GestureHelpState extends State<GestureHelp> {
|
||||
late int _selectedIndex;
|
||||
late bool _touchMode;
|
||||
final VirtualMouseMode _virtualMouseMode;
|
||||
|
||||
_GestureHelpState(bool touchMode) {
|
||||
_GestureHelpState(bool touchMode, VirtualMouseMode virtualMouseMode)
|
||||
: _virtualMouseMode = virtualMouseMode {
|
||||
_touchMode = touchMode;
|
||||
_selectedIndex = _touchMode ? 1 : 0;
|
||||
}
|
||||
@@ -68,31 +76,144 @@ class _GestureHelpState extends State<GestureHelp> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
ToggleSwitch(
|
||||
initialLabelIndex: _selectedIndex,
|
||||
activeFgColor: Colors.white,
|
||||
inactiveFgColor: Colors.white60,
|
||||
activeBgColor: [MyTheme.accent],
|
||||
inactiveBgColor: Theme.of(context).hintColor,
|
||||
totalSwitches: 2,
|
||||
minWidth: 150,
|
||||
fontSize: 15,
|
||||
iconSize: 18,
|
||||
labels: [translate("Mouse mode"), translate("Touch mode")],
|
||||
icons: [Icons.mouse, Icons.touch_app],
|
||||
onToggle: (index) {
|
||||
setState(() {
|
||||
if (_selectedIndex != index) {
|
||||
_selectedIndex = index ?? 0;
|
||||
_touchMode = index == 0 ? false : true;
|
||||
widget.onTouchModeChange(_touchMode);
|
||||
}
|
||||
});
|
||||
},
|
||||
Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ToggleSwitch(
|
||||
initialLabelIndex: _selectedIndex,
|
||||
activeFgColor: Colors.white,
|
||||
inactiveFgColor: Colors.white60,
|
||||
activeBgColor: [MyTheme.accent],
|
||||
inactiveBgColor: Theme.of(context).hintColor,
|
||||
totalSwitches: 2,
|
||||
minWidth: 150,
|
||||
fontSize: 15,
|
||||
iconSize: 18,
|
||||
labels: [
|
||||
translate("Mouse mode"),
|
||||
translate("Touch mode")
|
||||
],
|
||||
icons: [Icons.mouse, Icons.touch_app],
|
||||
onToggle: (index) {
|
||||
setState(() {
|
||||
if (_selectedIndex != index) {
|
||||
_selectedIndex = index ?? 0;
|
||||
_touchMode = index == 0 ? false : true;
|
||||
widget.onTouchModeChange(_touchMode);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
Transform.translate(
|
||||
offset: const Offset(-10.0, 0.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _virtualMouseMode.showVirtualMouse,
|
||||
onChanged: (value) async {
|
||||
if (value == null) return;
|
||||
await _virtualMouseMode.toggleVirtualMouse();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
await _virtualMouseMode.toggleVirtualMouse();
|
||||
setState(() {});
|
||||
},
|
||||
child: Text(translate('Show virtual mouse')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_touchMode && _virtualMouseMode.showVirtualMouse)
|
||||
Padding(
|
||||
// Indent "Virtual mouse size"
|
||||
padding: const EdgeInsets.only(left: 24.0),
|
||||
child: SizedBox(
|
||||
width: 260,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 0.0, bottom: 0),
|
||||
child: Text(translate('Virtual mouse size')),
|
||||
),
|
||||
Transform.translate(
|
||||
offset: Offset(-0.0, -6.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 0.0),
|
||||
child: Text(translate('Small')),
|
||||
),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: _virtualMouseMode
|
||||
.virtualMouseScale,
|
||||
min: 0.8,
|
||||
max: 1.8,
|
||||
divisions: 10,
|
||||
onChanged: (value) {
|
||||
_virtualMouseMode
|
||||
.setVirtualMouseScale(value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(right: 16.0),
|
||||
child: Text(translate('Large')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!_touchMode && _virtualMouseMode.showVirtualMouse)
|
||||
Transform.translate(
|
||||
offset: const Offset(-10.0, -12.0),
|
||||
child: Padding(
|
||||
// Indent "Show virtual joystick"
|
||||
padding: const EdgeInsets.only(left: 24.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Checkbox(
|
||||
value:
|
||||
_virtualMouseMode.showVirtualJoystick,
|
||||
onChanged: (value) async {
|
||||
if (value == null) return;
|
||||
await _virtualMouseMode
|
||||
.toggleVirtualJoystick();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
await _virtualMouseMode
|
||||
.toggleVirtualJoystick();
|
||||
setState(() {});
|
||||
},
|
||||
child: Text(
|
||||
translate("Show virtual joystick")),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Container(
|
||||
child: Wrap(
|
||||
spacing: space,
|
||||
|
||||
@@ -319,8 +319,8 @@ class AbModel {
|
||||
// #endregion
|
||||
|
||||
// #region peer
|
||||
Future<String?> addIdToCurrent(
|
||||
String id, String alias, String password, List<dynamic> tags) async {
|
||||
Future<String?> addIdToCurrent(String id, String alias, String password,
|
||||
List<dynamic> tags, String note) async {
|
||||
if (currentAbPeers.where((element) => element.id == id).isNotEmpty) {
|
||||
return "$id already exists in address book $_currentName";
|
||||
}
|
||||
@@ -333,6 +333,9 @@ class AbModel {
|
||||
if (password.isNotEmpty) {
|
||||
peer['password'] = password;
|
||||
}
|
||||
if (note.isNotEmpty) {
|
||||
peer['note'] = note;
|
||||
}
|
||||
final ret = await addPeersTo([peer], _currentName.value);
|
||||
_syncAllFromRecent = true;
|
||||
return ret;
|
||||
@@ -376,6 +379,14 @@ class AbModel {
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<bool> changeNote({required String id, required String note}) async {
|
||||
bool res = await current.changeNote(id: id, note: note);
|
||||
await pullNonLegacyAfterChange();
|
||||
currentAbPeers.refresh();
|
||||
// no need to save cache
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<bool> changePersonalHashPassword(String id, String hash) async {
|
||||
var ret = false;
|
||||
final personalAb = addressbooks[_personalAddressBookName];
|
||||
@@ -658,6 +669,15 @@ class AbModel {
|
||||
}
|
||||
}
|
||||
|
||||
String getPeerNote(String id) {
|
||||
final it = currentAbPeers.where((p0) => p0.id == id);
|
||||
if (it.isEmpty) {
|
||||
return '';
|
||||
} else {
|
||||
return it.first.note;
|
||||
}
|
||||
}
|
||||
|
||||
Color getCurrentAbTagColor(String tag) {
|
||||
if (tag == kUntagged) {
|
||||
return MyTheme.accent;
|
||||
@@ -863,6 +883,8 @@ abstract class BaseAb {
|
||||
|
||||
Future<bool> changeAlias({required String id, required String alias});
|
||||
|
||||
Future<bool> changeNote({required String id, required String note});
|
||||
|
||||
Future<bool> changePersonalHashPassword(String id, String hash);
|
||||
|
||||
Future<bool> changeSharedPassword(String id, String password);
|
||||
@@ -1090,6 +1112,12 @@ class LegacyAb extends BaseAb {
|
||||
return await pushAb();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> changeNote({required String id, required String note}) async {
|
||||
// no need to implement
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> changeSharedPassword(String id, String password) async {
|
||||
// no need to implement
|
||||
@@ -1549,6 +1577,27 @@ class Ab extends BaseAb {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> changeNote({required String id, required String note}) async {
|
||||
try {
|
||||
final api =
|
||||
"${await bind.mainGetApiServer()}/api/ab/peer/update/${profile.guid}";
|
||||
var headers = getHttpHeaders();
|
||||
headers['Content-Type'] = "application/json";
|
||||
final body = jsonEncode({"id": id, "note": note});
|
||||
final resp = await http.put(Uri.parse(api), headers: headers, body: body);
|
||||
final errMsg = _jsonDecodeActionResp(resp);
|
||||
if (errMsg.isNotEmpty) {
|
||||
BotToast.showText(contentColor: Colors.red, text: errMsg);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
debugPrint('changeNote err: ${err.toString()}');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _setPassword(Object bodyContent) async {
|
||||
try {
|
||||
final api =
|
||||
@@ -1815,6 +1864,11 @@ class DummyAb extends BaseAb {
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> changeNote({required String id, required String note}) async {
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> changePersonalHashPassword(String id, String hash) async {
|
||||
return false;
|
||||
|
||||
@@ -1033,30 +1033,54 @@ class JobController {
|
||||
await bind.sessionCancelJob(sessionId: sessionId, actId: id);
|
||||
}
|
||||
|
||||
void loadLastJob(Map<String, dynamic> evt) {
|
||||
Future<void> loadLastJob(Map<String, dynamic> evt) async {
|
||||
debugPrint("load last job: $evt");
|
||||
Map<String, dynamic> jobDetail = json.decode(evt['value']);
|
||||
// int id = int.parse(jobDetail['id']);
|
||||
String remote = jobDetail['remote'];
|
||||
String to = jobDetail['to'];
|
||||
bool showHidden = jobDetail['show_hidden'];
|
||||
int fileNum = jobDetail['file_num'];
|
||||
bool isRemote = jobDetail['is_remote'];
|
||||
final currJobId = JobController.jobID.next();
|
||||
String fileName = path.basename(isRemote ? remote : to);
|
||||
var jobProgress = JobProgress()
|
||||
..type = JobType.transfer
|
||||
..fileName = fileName
|
||||
..jobName = isRemote ? remote : to
|
||||
..id = currJobId
|
||||
..isRemoteToLocal = isRemote
|
||||
..fileNum = fileNum
|
||||
..remote = remote
|
||||
..to = to
|
||||
..showHidden = showHidden
|
||||
..state = JobState.paused;
|
||||
jobTable.add(jobProgress);
|
||||
bind.sessionAddJob(
|
||||
bool isAutoStart = jobDetail['auto_start'] == true;
|
||||
int currJobId = -1;
|
||||
if (isAutoStart) {
|
||||
// Ensure jobDetail['id'] exists and is an int
|
||||
if (jobDetail.containsKey('id') &&
|
||||
jobDetail['id'] != null &&
|
||||
jobDetail['id'] is int) {
|
||||
currJobId = jobDetail['id'];
|
||||
}
|
||||
}
|
||||
if (currJobId < 0) {
|
||||
// If id is missing or invalid, disable auto-start and assign a new job id
|
||||
isAutoStart = false;
|
||||
currJobId = JobController.jobID.next();
|
||||
}
|
||||
|
||||
if (!isAutoStart) {
|
||||
if (!(isDesktop || isWebDesktop)) {
|
||||
// Don't add to job table if not auto start on mobile.
|
||||
// Because mobile does not support job list view now.
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to job table if not auto start on desktop.
|
||||
String fileName = path.basename(isRemote ? remote : to);
|
||||
final jobProgress = JobProgress()
|
||||
..type = JobType.transfer
|
||||
..fileName = fileName
|
||||
..jobName = isRemote ? remote : to
|
||||
..id = currJobId
|
||||
..isRemoteToLocal = isRemote
|
||||
..fileNum = fileNum
|
||||
..remote = remote
|
||||
..to = to
|
||||
..showHidden = showHidden
|
||||
..state = JobState.paused;
|
||||
jobTable.add(jobProgress);
|
||||
}
|
||||
|
||||
await bind.sessionAddJob(
|
||||
sessionId: sessionId,
|
||||
isRemote: isRemote,
|
||||
includeHidden: showHidden,
|
||||
@@ -1065,6 +1089,11 @@ class JobController {
|
||||
to: isRemote ? to : remote,
|
||||
fileNum: fileNum,
|
||||
);
|
||||
|
||||
if (isAutoStart) {
|
||||
await bind.sessionResumeJob(
|
||||
sessionId: sessionId, actId: currJobId, isRemote: isRemote);
|
||||
}
|
||||
}
|
||||
|
||||
void resumeJob(int jobId) {
|
||||
@@ -1095,6 +1124,11 @@ class JobController {
|
||||
}
|
||||
debugPrint("update folder files: $info");
|
||||
}
|
||||
|
||||
void clear() {
|
||||
jobTable.clear();
|
||||
jobResultListener.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class JobResultListener<T> {
|
||||
|
||||
@@ -766,6 +766,11 @@ class InputModel {
|
||||
command: command);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> getMouseEventMove() => {
|
||||
'type': _kMouseEventMove,
|
||||
'buttons': 0,
|
||||
};
|
||||
|
||||
Map<String, dynamic> _getMouseEvent(PointerEvent evt, String type) {
|
||||
final Map<String, dynamic> out = {};
|
||||
|
||||
@@ -1222,16 +1227,17 @@ class InputModel {
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleMouse(
|
||||
Map<String, dynamic>? processEventToPeer(
|
||||
Map<String, dynamic> evt,
|
||||
Offset offset, {
|
||||
bool onExit = false,
|
||||
bool moveCanvas = true,
|
||||
}) {
|
||||
if (isViewCamera) return;
|
||||
if (isViewCamera) return null;
|
||||
double x = offset.dx;
|
||||
double y = max(0.0, offset.dy);
|
||||
if (_checkPeerControlProtected(x, y)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
var type = kMouseEventTypeDefault;
|
||||
@@ -1248,7 +1254,7 @@ class InputModel {
|
||||
isMove = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
evt['type'] = type;
|
||||
|
||||
@@ -1266,9 +1272,10 @@ class InputModel {
|
||||
type,
|
||||
onExit: onExit,
|
||||
buttons: evt['buttons'],
|
||||
moveCanvas: moveCanvas,
|
||||
);
|
||||
if (pos == null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
if (type != '') {
|
||||
evt['x'] = '0';
|
||||
@@ -1286,7 +1293,22 @@ class InputModel {
|
||||
kForwardMouseButton: 'forward'
|
||||
};
|
||||
evt['buttons'] = mapButtons[evt['buttons']] ?? '';
|
||||
bind.sessionSendMouse(sessionId: sessionId, msg: json.encode(modify(evt)));
|
||||
return evt;
|
||||
}
|
||||
|
||||
Map<String, dynamic>? handleMouse(
|
||||
Map<String, dynamic> evt,
|
||||
Offset offset, {
|
||||
bool onExit = false,
|
||||
bool moveCanvas = true,
|
||||
}) {
|
||||
final evtToPeer =
|
||||
processEventToPeer(evt, offset, onExit: onExit, moveCanvas: moveCanvas);
|
||||
if (evtToPeer != null) {
|
||||
bind.sessionSendMouse(
|
||||
sessionId: sessionId, msg: json.encode(modify(evtToPeer)));
|
||||
}
|
||||
return evtToPeer;
|
||||
}
|
||||
|
||||
Point? handlePointerDevicePos(
|
||||
@@ -1297,6 +1319,7 @@ class InputModel {
|
||||
String evtType, {
|
||||
bool onExit = false,
|
||||
int buttons = kPrimaryMouseButton,
|
||||
bool moveCanvas = true,
|
||||
}) {
|
||||
final ffiModel = parent.target!.ffiModel;
|
||||
CanvasCoords canvas =
|
||||
@@ -1325,7 +1348,7 @@ class InputModel {
|
||||
|
||||
y -= CanvasModel.topToEdge;
|
||||
x -= CanvasModel.leftToEdge;
|
||||
if (isMove) {
|
||||
if (isMove && moveCanvas) {
|
||||
parent.target!.canvasModel.moveDesktopMouse(x, y);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import '../utils/image.dart' as img;
|
||||
import '../common/widgets/dialog.dart';
|
||||
import 'input_model.dart';
|
||||
import 'platform_model.dart';
|
||||
import 'package:flutter_hbb/utils/scale.dart';
|
||||
|
||||
import 'package:flutter_hbb/generated_bridge.dart'
|
||||
if (dart.library.html) 'package:flutter_hbb/web/bridge.dart';
|
||||
@@ -113,6 +114,7 @@ class FfiModel with ChangeNotifier {
|
||||
bool? _secure;
|
||||
bool? _direct;
|
||||
bool _touchMode = false;
|
||||
late VirtualMouseMode virtualMouseMode;
|
||||
Timer? _timer;
|
||||
var _reconnects = 1;
|
||||
bool _viewOnly = false;
|
||||
@@ -165,6 +167,7 @@ class FfiModel with ChangeNotifier {
|
||||
clear();
|
||||
sessionId = parent.target!.sessionId;
|
||||
cachedPeerData.permissions = _permissions;
|
||||
virtualMouseMode = VirtualMouseMode(this);
|
||||
}
|
||||
|
||||
Rect? globalDisplaysRect() => _getDisplaysRect(_pi.displays, true);
|
||||
@@ -1104,9 +1107,23 @@ class FfiModel with ChangeNotifier {
|
||||
if (isPeerAndroid) {
|
||||
_touchMode = true;
|
||||
} else {
|
||||
_touchMode = await bind.sessionGetOption(
|
||||
sessionId: sessionId, arg: kOptionTouchMode) !=
|
||||
'';
|
||||
// `kOptionTouchMode` is originally peer option, but it is moved to local option later.
|
||||
// We check local option first, if not set, then check peer option.
|
||||
// Because if local option is not empty:
|
||||
// 1. User has set the touch mode explicitly.
|
||||
// 2. The advanced option (custom client) is set.
|
||||
// Then we choose to use the local option.
|
||||
final optLocal = bind.mainGetLocalOption(key: kOptionTouchMode);
|
||||
if (optLocal != '') {
|
||||
_touchMode = optLocal == 'Y';
|
||||
} else {
|
||||
final optSession = await bind.sessionGetOption(
|
||||
sessionId: sessionId, arg: kOptionTouchMode);
|
||||
_touchMode = optSession != '';
|
||||
}
|
||||
}
|
||||
if (isMobile) {
|
||||
virtualMouseMode.loadOptions();
|
||||
}
|
||||
if (connType == ConnType.fileTransfer) {
|
||||
parent.target?.fileModel.onReady();
|
||||
@@ -1507,6 +1524,72 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
class VirtualMouseMode with ChangeNotifier {
|
||||
bool _showVirtualMouse = false;
|
||||
double _virtualMouseScale = 1.0;
|
||||
bool _showVirtualJoystick = false;
|
||||
|
||||
bool get showVirtualMouse => _showVirtualMouse;
|
||||
double get virtualMouseScale => _virtualMouseScale;
|
||||
bool get showVirtualJoystick => _showVirtualJoystick;
|
||||
|
||||
FfiModel ffiModel;
|
||||
|
||||
VirtualMouseMode(this.ffiModel);
|
||||
|
||||
bool _shouldShow() => !ffiModel.isPeerAndroid;
|
||||
|
||||
setShowVirtualMouse(bool b) {
|
||||
if (b == _showVirtualMouse) return;
|
||||
if (_shouldShow()) {
|
||||
_showVirtualMouse = b;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
setVirtualMouseScale(double s) {
|
||||
if (s <= 0) return;
|
||||
if (s == _virtualMouseScale) return;
|
||||
_virtualMouseScale = s;
|
||||
bind.mainSetLocalOption(key: kOptionVirtualMouseScale, value: s.toString());
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
setShowVirtualJoystick(bool b) {
|
||||
if (b == _showVirtualJoystick) return;
|
||||
if (_shouldShow()) {
|
||||
_showVirtualJoystick = b;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void loadOptions() {
|
||||
_showVirtualMouse =
|
||||
bind.mainGetLocalOption(key: kOptionShowVirtualMouse) == 'Y';
|
||||
_virtualMouseScale = double.tryParse(
|
||||
bind.mainGetLocalOption(key: kOptionVirtualMouseScale)) ??
|
||||
1.0;
|
||||
_showVirtualJoystick =
|
||||
bind.mainGetLocalOption(key: kOptionShowVirtualJoystick) == 'Y';
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> toggleVirtualMouse() async {
|
||||
await bind.mainSetLocalOption(
|
||||
key: kOptionShowVirtualMouse, value: showVirtualMouse ? 'N' : 'Y');
|
||||
setShowVirtualMouse(
|
||||
bind.mainGetLocalOption(key: kOptionShowVirtualMouse) == 'Y');
|
||||
}
|
||||
|
||||
Future<void> toggleVirtualJoystick() async {
|
||||
await bind.mainSetLocalOption(
|
||||
key: kOptionShowVirtualJoystick,
|
||||
value: showVirtualJoystick ? 'N' : 'Y');
|
||||
setShowVirtualJoystick(
|
||||
bind.mainGetLocalOption(key: kOptionShowVirtualJoystick) == 'Y');
|
||||
}
|
||||
}
|
||||
|
||||
class ImageModel with ChangeNotifier {
|
||||
ui.Image? _image;
|
||||
|
||||
@@ -1699,6 +1782,8 @@ class ViewStyle {
|
||||
final s2 = height / displayHeight;
|
||||
s = s1 < s2 ? s1 : s2;
|
||||
}
|
||||
} else if (style == kRemoteViewStyleCustom) {
|
||||
// Custom scale is session-scoped and applied in CanvasModel.updateViewStyle()
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -1815,7 +1900,13 @@ class CanvasModel with ChangeNotifier {
|
||||
displayWidth: displayWidth,
|
||||
displayHeight: displayHeight,
|
||||
);
|
||||
if (_lastViewStyle == viewStyle) {
|
||||
// If only the Custom scale percent changed, proceed to update even if
|
||||
// the basic ViewStyle fields are equal.
|
||||
// In Custom scale mode, the scale percent can change independently of the other
|
||||
// ViewStyle fields and is not captured by the equality check. Therefore, we must
|
||||
// allow updates to proceed when style == kRemoteViewStyleCustom, even if the
|
||||
// rest of the ViewStyle fields are unchanged.
|
||||
if (_lastViewStyle == viewStyle && style != kRemoteViewStyleCustom) {
|
||||
return;
|
||||
}
|
||||
if (_lastViewStyle.style != viewStyle.style) {
|
||||
@@ -1824,12 +1915,30 @@ class CanvasModel with ChangeNotifier {
|
||||
_lastViewStyle = viewStyle;
|
||||
_scale = viewStyle.scale;
|
||||
|
||||
// Apply custom scale percent when in Custom mode
|
||||
if (style == kRemoteViewStyleCustom) {
|
||||
try {
|
||||
_scale = await getSessionCustomScale(sessionId);
|
||||
} catch (e, stack) {
|
||||
debugPrint('Error in getSessionCustomScale: $e');
|
||||
debugPrintStack(stackTrace: stack);
|
||||
_scale = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
_devicePixelRatio = ui.window.devicePixelRatio;
|
||||
if (kIgnoreDpi && style == kRemoteViewStyleOriginal) {
|
||||
_scale = 1.0 / _devicePixelRatio;
|
||||
if (kIgnoreDpi) {
|
||||
if (style == kRemoteViewStyleOriginal) {
|
||||
_scale = 1.0 / _devicePixelRatio;
|
||||
} else if (_scale != 0 && style == kRemoteViewStyleCustom) {
|
||||
_scale /= _devicePixelRatio;
|
||||
}
|
||||
}
|
||||
_resetCanvasOffset(displayWidth, displayHeight);
|
||||
_imageOverflow.value = _x < 0 || y < 0;
|
||||
final overflow = _x < 0 || y < 0;
|
||||
if (_imageOverflow.value != overflow) {
|
||||
_imageOverflow.value = overflow;
|
||||
}
|
||||
if (notify) {
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -1850,7 +1959,7 @@ class CanvasModel with ChangeNotifier {
|
||||
tryUpdateScrollStyle(Duration duration, String? style) async {
|
||||
if (_scrollStyle != ScrollStyle.scrollbar) return;
|
||||
style ??= await bind.sessionGetViewStyle(sessionId: sessionId);
|
||||
if (style != kRemoteViewStyleOriginal) {
|
||||
if (style != kRemoteViewStyleOriginal && style != kRemoteViewStyleCustom) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2266,9 +2375,25 @@ class CursorModel with ChangeNotifier {
|
||||
|
||||
Rect? get keyHelpToolsRectToAdjustCanvas =>
|
||||
_lastKeyboardIsVisible ? _keyHelpToolsRect : null;
|
||||
keyHelpToolsVisibilityChanged(Rect? r, bool keyboardIsVisible) {
|
||||
_keyHelpToolsRect = r;
|
||||
if (r == null) {
|
||||
// The blocked rect is used to block the pointer/touch events in the remote page.
|
||||
final List<Rect> _blockedRects = [];
|
||||
// Used in shouldBlock().
|
||||
// _blockEvents is a flag to block pointer/touch events on the remote image.
|
||||
// It is set to true to prevent accidental touch events in the following scenarios:
|
||||
// 1. In floating mouse mode, when the scroll circle is shown.
|
||||
// 2. In floating mouse widgets mode, when the left/right buttons are moving.
|
||||
// 3. In floating mouse widgets mode, when using the virtual joystick.
|
||||
// When _blockEvents is true, all pointer/touch events are blocked regardless of the contents of _blockedRects.
|
||||
// _blockedRects contains specific rectangular regions where events are blocked; these are checked when _blockEvents is false.
|
||||
// In summary: _blockEvents acts as a global block, while _blockedRects provides fine-grained blocking.
|
||||
bool _blockEvents = false;
|
||||
List<Rect> get blockedRects => List.unmodifiable(_blockedRects);
|
||||
|
||||
set blockEvents(bool v) => _blockEvents = v;
|
||||
|
||||
keyHelpToolsVisibilityChanged(Rect? rect, bool keyboardIsVisible) {
|
||||
_keyHelpToolsRect = rect;
|
||||
if (rect == null) {
|
||||
_lastIsBlocked = false;
|
||||
} else {
|
||||
// Block the touch event is safe here.
|
||||
@@ -2283,6 +2408,14 @@ class CursorModel with ChangeNotifier {
|
||||
_lastKeyboardIsVisible = keyboardIsVisible;
|
||||
}
|
||||
|
||||
addBlockedRect(Rect rect) {
|
||||
_blockedRects.add(rect);
|
||||
}
|
||||
|
||||
removeBlockedRect(Rect rect) {
|
||||
_blockedRects.remove(rect);
|
||||
}
|
||||
|
||||
get lastIsBlocked => _lastIsBlocked;
|
||||
|
||||
ui.Image? get image => _image;
|
||||
@@ -2349,13 +2482,22 @@ class CursorModel with ChangeNotifier {
|
||||
|
||||
// mobile Soft keyboard, block touch event from the KeyHelpTools
|
||||
shouldBlock(double x, double y) {
|
||||
if (_blockEvents) {
|
||||
return true;
|
||||
}
|
||||
final offset = Offset(x, y);
|
||||
for (final rect in _blockedRects) {
|
||||
if (isPointInRect(offset, rect)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// For help tools rectangle, only block touch event when in touch mode.
|
||||
if (!(parent.target?.ffiModel.touchMode ?? false)) {
|
||||
return false;
|
||||
}
|
||||
if (_keyHelpToolsRect == null) {
|
||||
return false;
|
||||
}
|
||||
if (isPointInRect(Offset(x, y), _keyHelpToolsRect!)) {
|
||||
if (_keyHelpToolsRect != null &&
|
||||
isPointInRect(offset, _keyHelpToolsRect!)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -2375,6 +2517,10 @@ class CursorModel with ChangeNotifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> syncCursorPosition() async {
|
||||
await parent.target?.inputModel.moveMouse(_x, _y);
|
||||
}
|
||||
|
||||
bool isInRemoteRect(Offset offset) {
|
||||
return getRemotePosInRect(offset) != null;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class Peer {
|
||||
bool online = false;
|
||||
String loginName; //login username
|
||||
String device_group_name;
|
||||
String note;
|
||||
bool? sameServer;
|
||||
|
||||
String getId() {
|
||||
@@ -43,6 +44,7 @@ class Peer {
|
||||
rdpUsername = json['rdpUsername'] ?? '',
|
||||
loginName = json['loginName'] ?? '',
|
||||
device_group_name = json['device_group_name'] ?? '',
|
||||
note = json['note'] is String ? json['note'] : '',
|
||||
sameServer = json['same_server'];
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -60,6 +62,7 @@ class Peer {
|
||||
"rdpUsername": rdpUsername,
|
||||
'loginName': loginName,
|
||||
'device_group_name': device_group_name,
|
||||
'note': note,
|
||||
'same_server': sameServer,
|
||||
};
|
||||
}
|
||||
@@ -104,6 +107,7 @@ class Peer {
|
||||
required this.rdpUsername,
|
||||
required this.loginName,
|
||||
required this.device_group_name,
|
||||
required this.note,
|
||||
this.sameServer,
|
||||
});
|
||||
|
||||
@@ -122,6 +126,7 @@ class Peer {
|
||||
rdpUsername: '',
|
||||
loginName: '',
|
||||
device_group_name: '',
|
||||
note: '',
|
||||
);
|
||||
bool equal(Peer other) {
|
||||
return id == other.id &&
|
||||
@@ -136,7 +141,8 @@ class Peer {
|
||||
rdpPort == other.rdpPort &&
|
||||
rdpUsername == other.rdpUsername &&
|
||||
device_group_name == other.device_group_name &&
|
||||
loginName == other.loginName;
|
||||
loginName == other.loginName &&
|
||||
note == other.note;
|
||||
}
|
||||
|
||||
Peer.copy(Peer other)
|
||||
@@ -154,6 +160,7 @@ class Peer {
|
||||
rdpUsername: other.rdpUsername,
|
||||
loginName: other.loginName,
|
||||
device_group_name: other.device_group_name,
|
||||
note: other.note,
|
||||
sameServer: other.sameServer);
|
||||
}
|
||||
|
||||
|
||||
@@ -475,7 +475,11 @@ class RustDeskMultiWindowManager {
|
||||
final shouldSavePos = type != WindowType.Terminal || i == windows.length - 1;
|
||||
if (shouldSavePos) {
|
||||
debugPrint("closing multi window, type: ${type.toString()} id: $wId");
|
||||
await saveWindowPosition(type, windowId: wId);
|
||||
try {
|
||||
await saveWindowPosition(type, windowId: wId);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to save window position of $wId, $e');
|
||||
}
|
||||
}
|
||||
try {
|
||||
await WindowController.fromWindowId(wId).setPreventClose(false);
|
||||
|
||||
34
flutter/lib/utils/scale.dart
Normal file
34
flutter/lib/utils/scale.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
/// Clamp custom scale percent to supported bounds.
|
||||
/// Keep this in sync with the slider's minimum in the desktop toolbar UI.
|
||||
///
|
||||
/// This function exists to ensure consistent clamping behavior across the app
|
||||
/// and to provide a single point of reference for the valid scale range.
|
||||
int clampCustomScalePercent(int percent) {
|
||||
return percent.clamp(kScaleCustomMinPercent, kScaleCustomMaxPercent);
|
||||
}
|
||||
|
||||
/// Parse a string percent and clamp. Defaults to 100 when invalid.
|
||||
int parseCustomScalePercent(String? s, {int defaultPercent = 100}) {
|
||||
final parsed = int.tryParse(s ?? '') ?? defaultPercent;
|
||||
return clampCustomScalePercent(parsed);
|
||||
}
|
||||
|
||||
/// Convert a percent value to scale factor after clamping.
|
||||
double percentToScale(int percent) => clampCustomScalePercent(percent) / 100.0;
|
||||
|
||||
/// Fetch, parse and clamp the custom scale percent for a session.
|
||||
Future<int> getSessionCustomScalePercent(UuidValue sessionId) async {
|
||||
final opt = await bind.sessionGetFlutterOption(
|
||||
sessionId: sessionId, k: kCustomScalePercentKey);
|
||||
return parseCustomScalePercent(opt);
|
||||
}
|
||||
|
||||
/// Fetch and compute the custom scale factor for a session.
|
||||
Future<double> getSessionCustomScale(UuidValue sessionId) async {
|
||||
final p = await getSessionCustomScalePercent(sessionId);
|
||||
return percentToScale(p);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
||||
version: 1.4.2+60
|
||||
version: 1.4.3+61
|
||||
|
||||
environment:
|
||||
sdk: '^3.1.0'
|
||||
|
||||
@@ -10,6 +10,7 @@ add_executable(${BINARY_NAME} WIN32
|
||||
"flutter_window.cpp"
|
||||
"main.cpp"
|
||||
"utils.cpp"
|
||||
"win32_desktop.cpp"
|
||||
"win32_window.cpp"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
"Runner.rc"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "win32_desktop.h"
|
||||
#include "flutter_window.h"
|
||||
#include "utils.h"
|
||||
|
||||
@@ -126,8 +127,22 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(800, 600);
|
||||
|
||||
// Get primary monitor's work area.
|
||||
Win32Window::Point workarea_origin(0, 0);
|
||||
Win32Window::Size workarea_size(0, 0);
|
||||
|
||||
Win32Desktop::GetWorkArea(workarea_origin, workarea_size);
|
||||
|
||||
// Compute window bounds for default main window position: (10, 10) x(800, 600)
|
||||
Win32Window::Point relative_origin(10, 10);
|
||||
|
||||
Win32Window::Point origin(workarea_origin.x + relative_origin.x, workarea_origin.y + relative_origin.y);
|
||||
Win32Window::Size size(800u, 600u);
|
||||
|
||||
// Fit the window to the monitor's work area.
|
||||
Win32Desktop::FitToWorkArea(origin, size);
|
||||
|
||||
std::wstring window_title;
|
||||
if (is_cm_page) {
|
||||
window_title = app_name + L" - Connection Manager";
|
||||
|
||||
69
flutter/windows/runner/win32_desktop.cpp
Normal file
69
flutter/windows/runner/win32_desktop.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "win32_desktop.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Win32Desktop
|
||||
{
|
||||
void GetWorkArea(Win32Window::Point& origin, Win32Window::Size& size)
|
||||
{
|
||||
RECT windowRect;
|
||||
|
||||
windowRect.left = origin.x;
|
||||
windowRect.top = origin.y;
|
||||
windowRect.right = origin.x + size.width;
|
||||
windowRect.bottom = origin.y + size.height;
|
||||
|
||||
HMONITOR hMonitor = MonitorFromRect(&windowRect, MONITOR_DEFAULTTONEAREST);
|
||||
|
||||
if (hMonitor == NULL)
|
||||
hMonitor = MonitorFromWindow(NULL, MONITOR_DEFAULTTOPRIMARY);
|
||||
|
||||
RECT workAreaRect;
|
||||
workAreaRect.left = 0;
|
||||
workAreaRect.top = 0;
|
||||
workAreaRect.right = 1280;
|
||||
workAreaRect.bottom = 1024 - 40; // default Windows 10 task bar height
|
||||
|
||||
if (hMonitor != NULL)
|
||||
{
|
||||
MONITORINFO monitorInfo = {0};
|
||||
|
||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||
|
||||
if (GetMonitorInfoW(hMonitor, &monitorInfo))
|
||||
{
|
||||
workAreaRect = monitorInfo.rcWork;
|
||||
}
|
||||
}
|
||||
|
||||
origin.x = workAreaRect.left;
|
||||
origin.y = workAreaRect.top;
|
||||
|
||||
size.width = workAreaRect.right - workAreaRect.left;
|
||||
size.height = workAreaRect.bottom - workAreaRect.top;
|
||||
}
|
||||
|
||||
void FitToWorkArea(Win32Window::Point& origin, Win32Window::Size& size)
|
||||
{
|
||||
// Retrieve the work area of the monitor that contains or
|
||||
// is closed to the supplied window bounds.
|
||||
Win32Window::Point workarea_origin = origin;
|
||||
Win32Window::Size workarea_size = size;
|
||||
|
||||
GetWorkArea(workarea_origin, workarea_size);
|
||||
|
||||
// Translate the window so that its top/left is inside the work area.
|
||||
origin.x = std::max(origin.x, workarea_origin.x);
|
||||
origin.y = std::max(origin.y, workarea_origin.y);
|
||||
|
||||
// Crop the window if it extends past the bottom/right of the work area.
|
||||
Win32Window::Point workarea_bottom_right(
|
||||
workarea_origin.x + workarea_size.width,
|
||||
workarea_origin.y + workarea_size.height);
|
||||
|
||||
size.width = std::min(size.width, workarea_bottom_right.x - origin.x);
|
||||
size.height = std::min(size.height, workarea_bottom_right.y - origin.y);
|
||||
}
|
||||
}
|
||||
12
flutter/windows/runner/win32_desktop.h
Normal file
12
flutter/windows/runner/win32_desktop.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef RUNNER_WIN32_DESKTOP_H_
|
||||
#define RUNNER_WIN32_DESKTOP_H_
|
||||
|
||||
#include "win32_window.h"
|
||||
|
||||
namespace Win32Desktop
|
||||
{
|
||||
void GetWorkArea(Win32Window::Point& origin, Win32Window::Size& size);
|
||||
void FitToWorkArea(Win32Window::Point& origin, Win32Window::Size& size);
|
||||
}
|
||||
|
||||
#endif // RUNNER_WIN32_DESKTOP_H_
|
||||
Submodule libs/hbb_common updated: 334641686c...5ed0afde08
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
edition = "2021"
|
||||
description = "RustDesk Remote Desktop"
|
||||
|
||||
@@ -15,6 +15,14 @@ md5 = "0.7"
|
||||
winapi = { version = "0.3", features = ["winbase"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows = { version = "0.61", features = [
|
||||
"Wdk",
|
||||
"Wdk_System",
|
||||
"Wdk_System_SystemServices",
|
||||
"Win32",
|
||||
"Win32_System",
|
||||
"Win32_System_SystemInformation",
|
||||
] }
|
||||
native-windows-gui = {version = "1.0", default-features = false, features = ["animation-timer", "image-decoder"]}
|
||||
|
||||
[package.metadata.winres]
|
||||
|
||||
@@ -92,12 +92,46 @@ fn setup(
|
||||
}
|
||||
write_meta(&dir, ts);
|
||||
#[cfg(windows)]
|
||||
windows::copy_runtime_broker(&dir);
|
||||
win::copy_runtime_broker(&dir);
|
||||
#[cfg(linux)]
|
||||
reader.configure_permission(&dir);
|
||||
Some(dir.join(&reader.exe))
|
||||
}
|
||||
|
||||
fn use_null_stdio() -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// When running in CMD on Windows 7, using Stdio::inherit() with spawn returns an "invalid handle" error.
|
||||
// Since using Stdio::null() didn’t cause any issues, and determining whether the program is launched from CMD or by double-clicking would require calling more APIs during startup, we also use Stdio::null() when launched by double-clicking on Windows 7.
|
||||
let is_windows_7 = is_windows_7();
|
||||
println!("is windows7: {}", is_windows_7);
|
||||
return is_windows_7;
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_windows_7() -> bool {
|
||||
use windows::Wdk::System::SystemServices::RtlGetVersion;
|
||||
use windows::Win32::System::SystemInformation::OSVERSIONINFOW;
|
||||
|
||||
unsafe {
|
||||
let mut version_info = OSVERSIONINFOW::default();
|
||||
version_info.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOW>() as u32;
|
||||
|
||||
if RtlGetVersion(&mut version_info).is_ok() {
|
||||
// Windows 7 is version 6.1
|
||||
println!(
|
||||
"Windows version: {}.{}",
|
||||
version_info.dwMajorVersion, version_info.dwMinorVersion
|
||||
);
|
||||
return version_info.dwMajorVersion == 6 && version_info.dwMinorVersion == 1;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn execute(path: PathBuf, args: Vec<String>, _ui: bool) {
|
||||
println!("executing {}", path.display());
|
||||
// setup env
|
||||
@@ -114,12 +148,18 @@ fn execute(path: PathBuf, args: Vec<String>, _ui: bool) {
|
||||
cmd.env(SET_FOREGROUND_WINDOW_ENV_KEY, "1");
|
||||
}
|
||||
}
|
||||
let _child = cmd
|
||||
.env(APPNAME_RUNTIME_ENV_KEY, exe_name)
|
||||
.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn();
|
||||
|
||||
cmd.env(APPNAME_RUNTIME_ENV_KEY, exe_name);
|
||||
if use_null_stdio() {
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
} else {
|
||||
cmd.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
}
|
||||
let _child = cmd.spawn();
|
||||
|
||||
#[cfg(windows)]
|
||||
if _ui {
|
||||
@@ -168,7 +208,7 @@ fn main() {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows {
|
||||
mod win {
|
||||
use std::{fs, os::windows::process::CommandExt, path::Path, process::Command};
|
||||
|
||||
// Used for privacy mode(magnifier impl).
|
||||
|
||||
@@ -268,12 +268,12 @@ impl TraitCapturer for CameraCapturer {
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_gdi(&self) -> bool {
|
||||
false
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn set_gdi(&mut self) -> bool {
|
||||
false
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(feature = "vram")]
|
||||
|
||||
@@ -687,7 +687,7 @@ pub fn check_available_hwcodec() -> String {
|
||||
height: 720,
|
||||
pixfmt: DEFAULT_PIXFMT,
|
||||
align: HW_STRIDE_ALIGN as _,
|
||||
kbs: 0,
|
||||
kbs: 1000,
|
||||
fps: DEFAULT_FPS,
|
||||
gop: DEFAULT_GOP,
|
||||
quality: DEFAULT_HW_QUALITY,
|
||||
|
||||
@@ -661,7 +661,9 @@ fn on_create_session_response(
|
||||
Variant(Box::new("u3".to_string())),
|
||||
);
|
||||
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html
|
||||
// args.insert("multiple".into(), Variant(Box::new(true)));
|
||||
if is_server_running() {
|
||||
args.insert("multiple".into(), Variant(Box::new(true)));
|
||||
}
|
||||
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32)));
|
||||
|
||||
let path = portal.select_sources(ses.clone(), args)?;
|
||||
@@ -725,7 +727,9 @@ fn on_select_devices_response(
|
||||
Variant(Box::new("u3".to_string())),
|
||||
);
|
||||
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html
|
||||
// args.insert("multiple".into(), Variant(Box::new(true)));
|
||||
if is_server_running() {
|
||||
args.insert("multiple".into(), Variant(Box::new(true)));
|
||||
}
|
||||
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32)));
|
||||
|
||||
let session = session.clone();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pkgname=rustdesk
|
||||
pkgver=1.4.2
|
||||
pkgver=1.4.3
|
||||
pkgrel=0
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
|
||||
771
res/ab.py
Normal file
771
res/ab.py
Normal file
@@ -0,0 +1,771 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def get_personal_ab(url, token):
|
||||
"""Get personal address book GUID"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
response = requests.get(f"{url}/api/ab/personal", headers=headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
return f"Error: {response.status_code} - {response.text}"
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def view_shared_abs(url, token, name=None):
|
||||
"""View all shared address books (excluding personal ones)"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
pageSize = 30
|
||||
params = {
|
||||
"name": name,
|
||||
}
|
||||
|
||||
filtered_params = {
|
||||
k: "%" + v + "%" if (v != "-" and "%" not in v and k != "name") else v
|
||||
for k, v in params.items()
|
||||
if v is not None
|
||||
}
|
||||
filtered_params["pageSize"] = pageSize
|
||||
|
||||
abs = []
|
||||
current = 1
|
||||
|
||||
while True:
|
||||
filtered_params["current"] = current
|
||||
response = requests.get(f"{url}/api/ab/shared/profiles", headers=headers, params=filtered_params)
|
||||
response_json = response.json()
|
||||
|
||||
data = response_json.get("data", [])
|
||||
abs.extend(data)
|
||||
|
||||
total = response_json.get("total", 0)
|
||||
current += pageSize
|
||||
if len(data) < pageSize or current > total:
|
||||
break
|
||||
|
||||
return abs
|
||||
|
||||
|
||||
def get_ab_by_name(url, token, ab_name):
|
||||
"""Get address book by name"""
|
||||
abs = view_shared_abs(url, token, ab_name)
|
||||
for ab in abs:
|
||||
if ab["name"] == ab_name:
|
||||
return ab
|
||||
return None
|
||||
|
||||
|
||||
def view_ab_peers(url, token, ab_guid, peer_id=None, alias=None):
|
||||
"""View peers in an address book"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
pageSize = 30
|
||||
params = {
|
||||
"ab": ab_guid,
|
||||
"id": peer_id,
|
||||
"alias": alias,
|
||||
}
|
||||
|
||||
filtered_params = {
|
||||
k: "%" + v + "%" if (v != "-" and "%" not in v and k not in ["ab"]) else v
|
||||
for k, v in params.items()
|
||||
if v is not None
|
||||
}
|
||||
filtered_params["pageSize"] = pageSize
|
||||
|
||||
peers = []
|
||||
current = 1
|
||||
|
||||
while True:
|
||||
filtered_params["current"] = current
|
||||
response = requests.get(f"{url}/api/ab/peers", headers=headers, params=filtered_params)
|
||||
response_json = response.json()
|
||||
|
||||
data = response_json.get("data", [])
|
||||
peers.extend(data)
|
||||
|
||||
total = response_json.get("total", 0)
|
||||
current += pageSize
|
||||
if len(data) < pageSize or current > total:
|
||||
break
|
||||
|
||||
return peers
|
||||
|
||||
|
||||
def view_ab_tags(url, token, ab_guid):
|
||||
"""View tags in an address book"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{url}/api/ab/tags/{ab_guid}", headers=headers)
|
||||
response_json = check_response(response)
|
||||
|
||||
# Handle error responses
|
||||
if isinstance(response_json, tuple) and response_json[0] == "Failed":
|
||||
print(f"Error: {response_json[1]} - {response_json[2]}")
|
||||
return []
|
||||
|
||||
# Format color values as hex
|
||||
if response_json:
|
||||
for tag in response_json:
|
||||
if "color" in tag and tag["color"] is not None:
|
||||
# Convert color to hex format
|
||||
color_value = tag["color"]
|
||||
if isinstance(color_value, int):
|
||||
tag["color"] = f"0x{color_value:08X}"
|
||||
|
||||
return response_json if response_json else []
|
||||
|
||||
|
||||
def check_response(response):
|
||||
"""Check API response and return result"""
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
response_json = response.json()
|
||||
return response_json
|
||||
except ValueError:
|
||||
return response.text or "Success"
|
||||
else:
|
||||
return "Failed", response.status_code, response.text
|
||||
|
||||
|
||||
def add_peer(url, token, ab_guid, peer_id, alias=None, note=None, tags=None, password=None):
|
||||
"""Add a peer to address book"""
|
||||
print(f"Adding peer {peer_id} to address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"id": peer_id,
|
||||
"note": note,
|
||||
}
|
||||
|
||||
# Add peer info if provided
|
||||
info = {}
|
||||
if alias:
|
||||
info["alias"] = alias
|
||||
if tags:
|
||||
info["tags"] = tags if isinstance(tags, list) else [tags]
|
||||
if password:
|
||||
info["password"] = password
|
||||
|
||||
if info:
|
||||
payload.update(info)
|
||||
|
||||
response = requests.post(f"{url}/api/ab/peer/add/{ab_guid}", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def delete_peer(url, token, ab_guid, peer_ids):
|
||||
"""Delete peers from address book by IDs"""
|
||||
if isinstance(peer_ids, str):
|
||||
peer_ids = [peer_ids]
|
||||
|
||||
print(f"Deleting peers {peer_ids} from address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/ab/peer/{ab_guid}", headers=headers, json=peer_ids)
|
||||
return check_response(response)
|
||||
|
||||
def update_peer(url, token, ab_guid, peer_id, alias=None, note=None, tags=None, password=None):
|
||||
"""Update a peer in address book"""
|
||||
print(f"Updating peer {peer_id} in address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# Check if at least one parameter is provided for update
|
||||
update_params = [alias, note, tags, password]
|
||||
if all(param is None for param in update_params):
|
||||
return "Error: At least one parameter must be specified for update"
|
||||
|
||||
payload = {
|
||||
"id": peer_id,
|
||||
}
|
||||
|
||||
# Add fields to update
|
||||
info = {}
|
||||
if alias is not None:
|
||||
info["alias"] = alias
|
||||
if tags is not None:
|
||||
info["tags"] = tags if isinstance(tags, list) else [tags]
|
||||
if password is not None:
|
||||
info["password"] = password
|
||||
|
||||
if info:
|
||||
payload.update(info)
|
||||
|
||||
if note is not None:
|
||||
payload["note"] = note
|
||||
|
||||
response = requests.put(f"{url}/api/ab/peer/update/{ab_guid}", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def str2color(tag_name, existing_colors=None):
|
||||
"""Generate color for tag name similar to str2color2 function"""
|
||||
if existing_colors is None:
|
||||
existing_colors = []
|
||||
|
||||
color_map = {
|
||||
"red": 0xFFFF0000,
|
||||
"green": 0xFF008000,
|
||||
"blue": 0xFF0000FF,
|
||||
"orange": 0xFFFF9800,
|
||||
"purple": 0xFF9C27B0,
|
||||
"grey": 0xFF9E9E9E,
|
||||
"cyan": 0xFF00BCD4,
|
||||
"lime": 0xFFCDDC39,
|
||||
"teal": 0xFF009688,
|
||||
"pink": 0xFFF48FB1,
|
||||
"indigo": 0xFF3F51B5,
|
||||
"brown": 0xFF795548,
|
||||
}
|
||||
|
||||
lower_name = tag_name.lower()
|
||||
|
||||
# Check if tag name matches a predefined color
|
||||
if lower_name in color_map:
|
||||
return color_map[lower_name]
|
||||
|
||||
# Special case for yellow
|
||||
if lower_name == "yellow":
|
||||
return 0xFFFFFF00
|
||||
|
||||
# Generate hash-based color
|
||||
hash_value = 0
|
||||
for char in tag_name:
|
||||
hash_value += ord(char)
|
||||
|
||||
color_list = list(color_map.values())
|
||||
hash_value = hash_value % len(color_list)
|
||||
result = color_list[hash_value]
|
||||
|
||||
# If color is already used, try to find an unused one
|
||||
if result in existing_colors:
|
||||
for color in color_list:
|
||||
if color not in existing_colors:
|
||||
result = color
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def add_tag(url, token, ab_guid, tag_name, color=None):
|
||||
"""Add a tag to address book"""
|
||||
print(f"Adding tag '{tag_name}' to address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# If no color specified, generate one based on tag name
|
||||
if color is None:
|
||||
# Get existing tags to avoid color conflicts
|
||||
try:
|
||||
existing_tags = view_ab_tags(url, token, ab_guid)
|
||||
existing_colors = [tag.get("color", 0) for tag in existing_tags]
|
||||
color = str2color(tag_name, existing_colors)
|
||||
except:
|
||||
# Fallback to default color if we can't get existing tags
|
||||
color = str2color(tag_name)
|
||||
|
||||
payload = {
|
||||
"name": tag_name,
|
||||
"color": color,
|
||||
}
|
||||
|
||||
response = requests.post(f"{url}/api/ab/tag/add/{ab_guid}", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def update_tag(url, token, ab_guid, tag_name, color):
|
||||
"""Update a tag in address book"""
|
||||
print(f"Updating tag '{tag_name}' in address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"name": tag_name,
|
||||
"color": color,
|
||||
}
|
||||
|
||||
response = requests.put(f"{url}/api/ab/tag/update/{ab_guid}", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def delete_tags(url, token, ab_guid, tag_names):
|
||||
"""Delete tags from address book"""
|
||||
if isinstance(tag_names, str):
|
||||
tag_names = [tag_names]
|
||||
|
||||
print(f"Deleting tags {tag_names} from address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/ab/tag/{ab_guid}", headers=headers, json=tag_names)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def add_shared_ab(url, token, name, note=None, password=None):
|
||||
"""Add a new shared address book"""
|
||||
print(f"Adding shared address book '{name}'")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"name": name,
|
||||
"note": note,
|
||||
}
|
||||
|
||||
# Add info if password is provided
|
||||
if password:
|
||||
payload["info"] = {
|
||||
"password": password
|
||||
}
|
||||
|
||||
response = requests.post(f"{url}/api/ab/shared/add", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def update_shared_ab(url, token, ab_guid, name=None, note=None, owner=None, password=None):
|
||||
"""Update a shared address book"""
|
||||
print(f"Updating shared address book {ab_guid}")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# Check if at least one parameter is provided for update
|
||||
update_params = [name, note, owner, password]
|
||||
if all(param is None for param in update_params):
|
||||
return "Error: At least one parameter must be specified for update"
|
||||
|
||||
payload = {
|
||||
"guid": ab_guid,
|
||||
}
|
||||
|
||||
if name is not None:
|
||||
payload["name"] = name
|
||||
if note is not None:
|
||||
payload["note"] = note
|
||||
if owner is not None:
|
||||
payload["owner"] = owner
|
||||
if password is not None:
|
||||
payload["info"] = {
|
||||
"password": password
|
||||
}
|
||||
|
||||
response = requests.put(f"{url}/api/ab/shared/update/profile", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def delete_shared_abs(url, token, ab_guids):
|
||||
"""Delete shared address books"""
|
||||
if isinstance(ab_guids, str):
|
||||
ab_guids = [ab_guids]
|
||||
|
||||
print(f"Deleting shared address books {ab_guids}")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/ab/shared", headers=headers, json=ab_guids)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def permission_to_string(permission):
|
||||
"""Convert numeric permission to string representation"""
|
||||
permission_map = {
|
||||
1: "ro", # Read
|
||||
2: "rw", # ReadWrite
|
||||
3: "full" # FullControl
|
||||
}
|
||||
return permission_map.get(permission, str(permission))
|
||||
|
||||
|
||||
def string_to_permission(permission_str):
|
||||
"""Convert string permission to numeric representation"""
|
||||
permission_map = {
|
||||
"ro": 1, # Read
|
||||
"rw": 2, # ReadWrite
|
||||
"full": 3 # FullControl
|
||||
}
|
||||
return permission_map.get(permission_str.lower(), None)
|
||||
|
||||
|
||||
def view_ab_rules(url, token, ab_guid):
|
||||
"""View rules in an address book"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
pageSize = 30
|
||||
params = {
|
||||
"ab": ab_guid,
|
||||
"pageSize": pageSize,
|
||||
}
|
||||
|
||||
rules = []
|
||||
current = 1
|
||||
|
||||
while True:
|
||||
params["current"] = current
|
||||
response = requests.get(f"{url}/api/ab/rules", headers=headers, params=params)
|
||||
response_json = response.json()
|
||||
|
||||
data = response_json.get("data", [])
|
||||
rules.extend(data)
|
||||
|
||||
total = response_json.get("total", 0)
|
||||
current += pageSize
|
||||
if len(data) < pageSize or current > total:
|
||||
break
|
||||
|
||||
# Convert numeric permissions to string format
|
||||
for rule in rules:
|
||||
if "rule" in rule:
|
||||
rule["rule"] = permission_to_string(rule["rule"])
|
||||
|
||||
return rules
|
||||
|
||||
|
||||
def add_ab_rule(url, token, ab_guid, rule_type, user=None, group=None, rule=1):
|
||||
"""Add a rule to address book"""
|
||||
print(f"Adding {rule_type} rule to address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"guid": ab_guid,
|
||||
"rule": rule,
|
||||
}
|
||||
|
||||
if rule_type == "user" and user:
|
||||
payload["user"] = user
|
||||
elif rule_type == "group" and group:
|
||||
payload["group"] = group
|
||||
elif rule_type == "everyone":
|
||||
# For everyone, both user and group are None (not included in payload)
|
||||
pass
|
||||
|
||||
response = requests.post(f"{url}/api/ab/rule", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def update_ab_rule(url, token, rule_guid, rule):
|
||||
"""Update an address book rule"""
|
||||
print(f"Updating rule {rule_guid}")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"guid": rule_guid,
|
||||
"rule": rule,
|
||||
}
|
||||
|
||||
response = requests.patch(f"{url}/api/ab/rule", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def delete_ab_rules(url, token, rule_guids):
|
||||
"""Delete address book rules"""
|
||||
if isinstance(rule_guids, str):
|
||||
rule_guids = [rule_guids]
|
||||
|
||||
print(f"Deleting rules {rule_guids}")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/ab/rules", headers=headers, json=rule_guids)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def main():
|
||||
def parse_color(value):
|
||||
"""Parse color value - supports both hex (0xFF00FF00) and decimal"""
|
||||
if value.startswith('0x') or value.startswith('0X'):
|
||||
return int(value, 16)
|
||||
else:
|
||||
return int(value)
|
||||
|
||||
def parse_permission(value):
|
||||
"""Parse permission value - supports both string (ro/rw/full) and numeric (1/2/3)"""
|
||||
# Try to parse as string first
|
||||
permission_num = string_to_permission(value)
|
||||
if permission_num is not None:
|
||||
return permission_num
|
||||
|
||||
# Try to parse as integer for backward compatibility
|
||||
try:
|
||||
num_value = int(value)
|
||||
if num_value in [1, 2, 3]:
|
||||
return num_value
|
||||
else:
|
||||
raise argparse.ArgumentTypeError(f"Invalid permission value: {value}. Must be one of: ro, rw, full, 1, 2, 3")
|
||||
except ValueError:
|
||||
raise argparse.ArgumentTypeError(f"Invalid permission value: {value}. Must be one of: ro, rw, full, 1, 2, 3")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Address Book manager")
|
||||
|
||||
# Required arguments
|
||||
parser.add_argument(
|
||||
"command",
|
||||
choices=["view-ab", "add-ab", "update-ab", "delete-ab", "get-personal-ab",
|
||||
"view-peer", "add-peer", "update-peer", "delete-peer",
|
||||
"view-tag", "add-tag", "update-tag", "delete-tag",
|
||||
"view-rule", "add-rule", "update-rule", "delete-rule"],
|
||||
help="Command to execute",
|
||||
)
|
||||
|
||||
# Global arguments (used by all commands)
|
||||
parser.add_argument("--url", required=True, help="URL of the API")
|
||||
parser.add_argument("--token", required=True, help="Bearer token for authentication")
|
||||
|
||||
# Address book identification (used by most commands except get-personal-ab)
|
||||
parser.add_argument("--ab-name", help="Address book name (for identification)")
|
||||
parser.add_argument("--ab-guid", help="Address book GUID (alternative to ab-name)")
|
||||
|
||||
# Address book management arguments
|
||||
parser.add_argument("--ab-update-name", help="New address book name (for update)")
|
||||
parser.add_argument("--note", help="Note field")
|
||||
parser.add_argument("--password", help="Password field")
|
||||
parser.add_argument("--owner", help="Address book owner (username)")
|
||||
|
||||
# Peer management arguments
|
||||
parser.add_argument("--peer-id", help="Peer ID")
|
||||
parser.add_argument("--alias", help="Peer alias")
|
||||
parser.add_argument("--tags", help="Peer tags (supports both 'tag1,tag2' and '[tag1,tag2]' formats, use '[]' to clear tags)")
|
||||
|
||||
# Tag management arguments
|
||||
parser.add_argument("--tag-name", help="Tag name")
|
||||
parser.add_argument("--tag-color", type=parse_color, help="Tag color (hex number like 0xFF00FF00 or decimal, auto-generated if not specified)")
|
||||
|
||||
# Rule management arguments
|
||||
parser.add_argument("--rule-type", choices=["user", "group", "everyone"], help="Rule type (auto-detected if not specified)")
|
||||
parser.add_argument("--rule-user", help="Rule target user name (auto-sets rule-type=user)")
|
||||
parser.add_argument("--rule-group", help="Rule target group name (auto-sets rule-type=group)")
|
||||
parser.add_argument("--rule-permission", type=parse_permission, help="Rule permission (ro=Read, rw=ReadWrite, full=FullControl, or numeric 1/2/3)")
|
||||
parser.add_argument("--rule-guid", help="Rule GUID (for update/delete)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Remove trailing slashes from URL
|
||||
while args.url.endswith("/"):
|
||||
args.url = args.url[:-1]
|
||||
|
||||
if args.command == "view-ab":
|
||||
# View all shared address books
|
||||
abs = view_shared_abs(args.url, args.token, args.ab_name)
|
||||
print(json.dumps(abs, indent=2))
|
||||
|
||||
elif args.command == "get-personal-ab":
|
||||
# Get personal address book GUID
|
||||
personal_ab = get_personal_ab(args.url, args.token)
|
||||
print(json.dumps(personal_ab, indent=2))
|
||||
|
||||
elif args.command in ["add-ab", "update-ab", "delete-ab"]:
|
||||
# Address book management commands
|
||||
if args.command == "add-ab":
|
||||
if not args.ab_name:
|
||||
print("Error: --ab-name is required for add-ab command")
|
||||
return
|
||||
|
||||
result = add_shared_ab(args.url, args.token, args.ab_name, args.note, args.password)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command in ["update-ab", "delete-ab"]:
|
||||
# Commands that need ab-name or ab-guid
|
||||
if not args.ab_name and not args.ab_guid:
|
||||
print("Error: --ab-name or --ab-guid is required for this command")
|
||||
return
|
||||
|
||||
if args.ab_name and args.ab_guid:
|
||||
print("Error: Cannot specify both --ab-name and --ab-guid")
|
||||
return
|
||||
|
||||
if args.ab_guid:
|
||||
ab_guid = args.ab_guid
|
||||
print(f"Working with address book GUID: {ab_guid}")
|
||||
else:
|
||||
# Get address book by name
|
||||
ab = get_ab_by_name(args.url, args.token, args.ab_name)
|
||||
if not ab:
|
||||
print(f"Error: Address book '{args.ab_name}' not found")
|
||||
return
|
||||
ab_guid = ab["guid"]
|
||||
print(f"Working with address book: {args.ab_name} (GUID: {ab_guid})")
|
||||
|
||||
if args.command == "update-ab":
|
||||
result = update_shared_ab(args.url, args.token, ab_guid, args.ab_update_name, args.note, args.owner, args.password)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "delete-ab":
|
||||
result = delete_shared_abs(args.url, args.token, ab_guid)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command in ["view-peer", "add-peer", "update-peer", "delete-peer", "view-tag", "add-tag", "update-tag", "delete-tag", "view-rule", "add-rule", "update-rule", "delete-rule"]:
|
||||
if not args.ab_name and not args.ab_guid:
|
||||
print("Error: --ab-name or --ab-guid is required for this command")
|
||||
return
|
||||
|
||||
if args.ab_name and args.ab_guid:
|
||||
print("Error: Cannot specify both --ab-name and --ab-guid")
|
||||
return
|
||||
|
||||
if args.ab_guid:
|
||||
ab_guid = args.ab_guid
|
||||
print(f"Working with address book GUID: {ab_guid}")
|
||||
else:
|
||||
# Get address book by name
|
||||
ab = get_ab_by_name(args.url, args.token, args.ab_name)
|
||||
if not ab:
|
||||
print(f"Error: Address book '{args.ab_name}' not found")
|
||||
return
|
||||
|
||||
ab_guid = ab["guid"]
|
||||
print(f"Working with address book: {args.ab_name} (GUID: {ab_guid})")
|
||||
|
||||
if args.command == "view-peer":
|
||||
peers = view_ab_peers(args.url, args.token, ab_guid, args.peer_id, args.alias)
|
||||
print(json.dumps(peers, indent=2))
|
||||
|
||||
elif args.command == "add-peer":
|
||||
if not args.peer_id:
|
||||
print("Error: --peer-id is required for add-peer command")
|
||||
return
|
||||
|
||||
# Handle tags parsing - support both [tag1,tag2] and tag1,tag2 formats
|
||||
tags = None
|
||||
if args.tags is not None:
|
||||
if args.tags == "[]":
|
||||
tags = [] # Empty list to clear tags
|
||||
else:
|
||||
# Remove brackets if present and split by comma
|
||||
tags_str = args.tags.strip()
|
||||
if tags_str.startswith('[') and tags_str.endswith(']'):
|
||||
tags_str = tags_str[1:-1] # Remove brackets
|
||||
tags = [tag.strip() for tag in tags_str.split(",") if tag.strip()]
|
||||
|
||||
result = add_peer(
|
||||
args.url,
|
||||
args.token,
|
||||
ab_guid,
|
||||
args.peer_id,
|
||||
args.alias,
|
||||
args.note,
|
||||
tags,
|
||||
args.password
|
||||
)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "update-peer":
|
||||
if not args.peer_id:
|
||||
print("Error: --peer-id is required for update-peer command")
|
||||
return
|
||||
|
||||
# Handle tags parsing - support both [tag1,tag2] and tag1,tag2 formats
|
||||
tags = None
|
||||
if args.tags is not None:
|
||||
if args.tags == "[]":
|
||||
tags = [] # Empty list to clear tags
|
||||
else:
|
||||
# Remove brackets if present and split by comma
|
||||
tags_str = args.tags.strip()
|
||||
if tags_str.startswith('[') and tags_str.endswith(']'):
|
||||
tags_str = tags_str[1:-1] # Remove brackets
|
||||
tags = [tag.strip() for tag in tags_str.split(",") if tag.strip()]
|
||||
|
||||
result = update_peer(
|
||||
args.url,
|
||||
args.token,
|
||||
ab_guid,
|
||||
args.peer_id,
|
||||
args.alias,
|
||||
args.note,
|
||||
tags,
|
||||
args.password
|
||||
)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "delete-peer":
|
||||
if not args.peer_id:
|
||||
print("Error: --peer-id is required for delete-peer command")
|
||||
return
|
||||
|
||||
result = delete_peer(args.url, args.token, ab_guid, args.peer_id)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "view-tag":
|
||||
tags = view_ab_tags(args.url, args.token, ab_guid)
|
||||
print(json.dumps(tags, indent=2))
|
||||
|
||||
elif args.command == "add-tag":
|
||||
if not args.tag_name:
|
||||
print("Error: --tag-name is required for add-tag command")
|
||||
return
|
||||
|
||||
result = add_tag(args.url, args.token, ab_guid, args.tag_name, args.tag_color)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "update-tag":
|
||||
if not args.tag_name:
|
||||
print("Error: --tag-name is required for update-tag command")
|
||||
return
|
||||
|
||||
result = update_tag(args.url, args.token, ab_guid, args.tag_name, args.tag_color)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "delete-tag":
|
||||
if not args.tag_name:
|
||||
print("Error: --tag-name is required for delete-tag command")
|
||||
return
|
||||
|
||||
result = delete_tags(args.url, args.token, ab_guid, args.tag_name)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "view-rule":
|
||||
rules = view_ab_rules(args.url, args.token, ab_guid)
|
||||
print(json.dumps(rules, indent=2))
|
||||
|
||||
elif args.command == "add-rule":
|
||||
if not args.rule_permission:
|
||||
print("Error: --rule-permission is required for add-rule command")
|
||||
return
|
||||
|
||||
# Auto-detect rule type if not explicitly specified
|
||||
if not args.rule_type:
|
||||
if args.rule_user and args.rule_group:
|
||||
print("Error: Cannot specify both --rule-user and --rule-group")
|
||||
return
|
||||
elif args.rule_user:
|
||||
rule_type = "user"
|
||||
elif args.rule_group:
|
||||
rule_type = "group"
|
||||
else:
|
||||
print("Error: Must specify --rule-type=everyone, --rule-user, or --rule-group")
|
||||
return
|
||||
else:
|
||||
rule_type = args.rule_type
|
||||
|
||||
# Validate explicit rule type with parameters
|
||||
if rule_type == "user" and not args.rule_user:
|
||||
print("Error: --rule-user is required when rule-type=user")
|
||||
return
|
||||
elif rule_type == "group" and not args.rule_group:
|
||||
print("Error: --rule-group is required when rule-type=group")
|
||||
return
|
||||
elif rule_type == "user" and args.rule_group:
|
||||
print("Error: Cannot specify --rule-group when rule-type=user")
|
||||
return
|
||||
elif rule_type == "group" and args.rule_user:
|
||||
print("Error: Cannot specify --rule-user when rule-type=group")
|
||||
return
|
||||
elif rule_type == "everyone" and (args.rule_user or args.rule_group):
|
||||
print("Error: Cannot specify --rule-user or --rule-group when rule-type=everyone")
|
||||
return
|
||||
|
||||
result = add_ab_rule(args.url, args.token, ab_guid, rule_type, args.rule_user, args.rule_group, args.rule_permission)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "update-rule":
|
||||
if not args.rule_guid:
|
||||
print("Error: --rule-guid is required for update-rule command")
|
||||
return
|
||||
if not args.rule_permission:
|
||||
print("Error: --rule-permission is required for update-rule command")
|
||||
return
|
||||
|
||||
result = update_ab_rule(args.url, args.token, args.rule_guid, args.rule_permission)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "delete-rule":
|
||||
if not args.rule_guid:
|
||||
print("Error: --rule-guid is required for delete-rule command")
|
||||
return
|
||||
|
||||
result = delete_ab_rules(args.url, args.token, args.rule_guid)
|
||||
print(f"Result: {result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
370
res/audits.py
Normal file
370
res/audits.py
Normal file
@@ -0,0 +1,370 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
||||
def format_timestamp(timestamp):
|
||||
"""Convert Unix timestamp to readable local datetime"""
|
||||
if timestamp is None:
|
||||
return None
|
||||
try:
|
||||
# Convert to local time
|
||||
local_dt = datetime.fromtimestamp(timestamp)
|
||||
return local_dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
except (ValueError, TypeError):
|
||||
return timestamp
|
||||
|
||||
|
||||
def parse_local_time_to_utc_string(time_str):
|
||||
"""Parse local time string to UTC time string for API filtering"""
|
||||
try:
|
||||
# Parse the local time string
|
||||
local_dt = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S.%f")
|
||||
# Make the datetime object timezone-aware using system's local timezone
|
||||
local_dt = local_dt.replace(tzinfo=datetime.now().astimezone().tzinfo)
|
||||
utc_dt = local_dt.astimezone(timezone.utc)
|
||||
return utc_dt.strftime("%Y-%m-%d %H:%M:%S.000")
|
||||
except ValueError:
|
||||
try:
|
||||
# Try without microseconds
|
||||
local_dt = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
|
||||
# Make the datetime object timezone-aware using system's local timezone
|
||||
local_dt = local_dt.replace(tzinfo=datetime.now().astimezone().tzinfo)
|
||||
utc_dt = local_dt.astimezone(timezone.utc)
|
||||
return utc_dt.strftime("%Y-%m-%d %H:%M:%S.000")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def get_connection_type_name(conn_type):
|
||||
"""Convert connection type number to readable name"""
|
||||
type_map = {
|
||||
0: "Remote Desktop",
|
||||
1: "File Transfer",
|
||||
2: "Port Transfer",
|
||||
3: "View Camera",
|
||||
4: "Terminal"
|
||||
}
|
||||
return type_map.get(conn_type, f"Unknown ({conn_type})")
|
||||
|
||||
|
||||
def get_console_type_name(console_type):
|
||||
"""Convert console audit type number to readable name"""
|
||||
type_map = {
|
||||
0: "Group Management",
|
||||
1: "User Management",
|
||||
2: "Device Management",
|
||||
3: "Address Book Management"
|
||||
}
|
||||
return type_map.get(console_type, f"Unknown ({console_type})")
|
||||
|
||||
|
||||
def get_console_operation_name(operation_code):
|
||||
"""Convert console operation code to readable name"""
|
||||
operation_map = {
|
||||
0: "User Login",
|
||||
1: "Add Group",
|
||||
2: "Add User",
|
||||
3: "Add Device",
|
||||
4: "Delete Groups",
|
||||
5: "Disconnect Device",
|
||||
6: "Enable Users",
|
||||
7: "Disable Users",
|
||||
8: "Enable Devices",
|
||||
9: "Disable Devices",
|
||||
10: "Update Group",
|
||||
11: "Update User",
|
||||
12: "Update Device",
|
||||
13: "Delete User",
|
||||
14: "Delete Device",
|
||||
15: "Add Address Book",
|
||||
16: "Delete Address Book",
|
||||
17: "Change Address Book Name",
|
||||
18: "Delete Devices in the Address Book Recycle Bin",
|
||||
19: "Empty Address Book Recycle Bin",
|
||||
20: "Add Address Book Permission",
|
||||
21: "Delete Address Book Permission",
|
||||
22: "Update Address Book Permission"
|
||||
}
|
||||
return operation_map.get(operation_code, f"Unknown ({operation_code})")
|
||||
|
||||
|
||||
def get_alarm_type_name(alarm_type):
|
||||
"""Convert alarm type number to readable name"""
|
||||
type_map = {
|
||||
0: "Access attempt outside the IP whiltelist",
|
||||
1: "Over 30 consecutive access attempts",
|
||||
2: "Multiple access attempts within one minute",
|
||||
3: "Over 30 consecutive login attempts",
|
||||
4: "Multiple login attempts within one minute",
|
||||
5: "Multiple login attempts within one hour"
|
||||
}
|
||||
return type_map.get(alarm_type, f"Unknown ({alarm_type})")
|
||||
|
||||
|
||||
def enhance_audit_data(data, audit_type):
|
||||
"""Enhance audit data with readable formats"""
|
||||
if not data:
|
||||
return data
|
||||
|
||||
enhanced_data = []
|
||||
for item in data:
|
||||
enhanced_item = item.copy()
|
||||
|
||||
# Convert timestamps - replace original values
|
||||
if 'created_at' in enhanced_item:
|
||||
enhanced_item['created_at'] = format_timestamp(enhanced_item['created_at'])
|
||||
if 'end_time' in enhanced_item:
|
||||
enhanced_item['end_time'] = format_timestamp(enhanced_item['end_time'])
|
||||
|
||||
# Add type-specific enhancements - replace original values
|
||||
if audit_type == 'conn':
|
||||
if 'conn_type' in enhanced_item:
|
||||
enhanced_item['conn_type'] = get_connection_type_name(enhanced_item['conn_type'])
|
||||
else:
|
||||
enhanced_item['conn_type'] = "Not Logged In"
|
||||
|
||||
elif audit_type == 'console':
|
||||
if 'typ' in enhanced_item:
|
||||
# Replace typ field with type and convert to readable name
|
||||
enhanced_item['type'] = get_console_type_name(enhanced_item['typ'])
|
||||
del enhanced_item['typ']
|
||||
if 'iop' in enhanced_item:
|
||||
# Replace iop field with operation and convert to readable name
|
||||
enhanced_item['operation'] = get_console_operation_name(enhanced_item['iop'])
|
||||
del enhanced_item['iop']
|
||||
|
||||
elif audit_type == 'alarm' and 'typ' in enhanced_item:
|
||||
# Replace typ field with type and convert to readable name
|
||||
enhanced_item['type'] = get_alarm_type_name(enhanced_item['typ'])
|
||||
del enhanced_item['typ']
|
||||
|
||||
enhanced_data.append(enhanced_item)
|
||||
|
||||
return enhanced_data
|
||||
|
||||
|
||||
def check_response(response):
|
||||
"""Check API response and return result"""
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
response_json = response.json()
|
||||
return response_json
|
||||
except ValueError:
|
||||
return response.text or "Success"
|
||||
else:
|
||||
return "Failed", response.status_code, response.text
|
||||
|
||||
|
||||
def view_audits_common(url, token, endpoint, filters=None, page_size=None, current=None,
|
||||
created_at=None, days_ago=None, non_wildcard_fields=None):
|
||||
"""Common function for viewing audits"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# Set default page size and current page
|
||||
if page_size is None:
|
||||
page_size = 10
|
||||
if current is None:
|
||||
current = 1
|
||||
|
||||
params = {
|
||||
"pageSize": page_size,
|
||||
"current": current
|
||||
}
|
||||
|
||||
# Add filter parameters if provided
|
||||
if filters:
|
||||
for key, value in filters.items():
|
||||
if value is not None:
|
||||
params[key] = value
|
||||
|
||||
# Handle time filters
|
||||
if days_ago is not None:
|
||||
# Calculate datetime from days ago
|
||||
target_time = datetime.now() - timedelta(days=days_ago)
|
||||
# Convert to UTC time string using system timezone
|
||||
utc_timestamp = target_time.timestamp()
|
||||
utc_dt = datetime.fromtimestamp(utc_timestamp, timezone.utc)
|
||||
params["created_at"] = utc_dt.strftime("%Y-%m-%d %H:%M:%S.000")
|
||||
elif created_at:
|
||||
# Parse local time string and convert to UTC time string
|
||||
utc_time_str = parse_local_time_to_utc_string(created_at)
|
||||
if utc_time_str is not None:
|
||||
params["created_at"] = utc_time_str
|
||||
else:
|
||||
# If parsing fails, pass the original value
|
||||
params["created_at"] = created_at
|
||||
|
||||
# Apply wildcard patterns for string fields (excluding specific fields)
|
||||
if non_wildcard_fields is None:
|
||||
non_wildcard_fields = set()
|
||||
|
||||
# Always exclude these fields from wildcard treatment
|
||||
non_wildcard_fields.update(["created_at", "pageSize", "current"])
|
||||
|
||||
string_params = {}
|
||||
for k, v in params.items():
|
||||
if isinstance(v, str) and k not in non_wildcard_fields:
|
||||
if v != "-" and "%" not in v:
|
||||
string_params[k] = "%" + v + "%"
|
||||
else:
|
||||
string_params[k] = v
|
||||
else:
|
||||
string_params[k] = v
|
||||
|
||||
response = requests.get(f"{url}/api/audits/{endpoint}", headers=headers, params=string_params)
|
||||
response_json = response.json()
|
||||
|
||||
# Enhance the data with readable formats
|
||||
data = enhance_audit_data(response_json.get("data", []), endpoint)
|
||||
|
||||
return {
|
||||
"data": data,
|
||||
"total": response_json.get("total", 0),
|
||||
"current": current,
|
||||
"pageSize": page_size
|
||||
}
|
||||
|
||||
|
||||
def view_conn_audits(url, token, remote=None, conn_type=None,
|
||||
page_size=None, current=None, created_at=None, days_ago=None):
|
||||
"""View connection audits"""
|
||||
filters = {
|
||||
"remote": remote,
|
||||
"conn_type": conn_type
|
||||
}
|
||||
non_wildcard_fields = {"conn_type"}
|
||||
|
||||
return view_audits_common(
|
||||
url, token, "conn", filters, page_size, current, created_at, days_ago, non_wildcard_fields
|
||||
)
|
||||
|
||||
|
||||
def view_file_audits(url, token, remote=None,
|
||||
page_size=None, current=None, created_at=None, days_ago=None):
|
||||
"""View file audits"""
|
||||
filters = {
|
||||
"remote": remote
|
||||
}
|
||||
non_wildcard_fields = set()
|
||||
|
||||
return view_audits_common(
|
||||
url, token, "file", filters, page_size, current, created_at, days_ago, non_wildcard_fields
|
||||
)
|
||||
|
||||
|
||||
def view_alarm_audits(url, token, device=None,
|
||||
page_size=None, current=None, created_at=None, days_ago=None):
|
||||
"""View alarm audits"""
|
||||
filters = {
|
||||
"device": device
|
||||
}
|
||||
non_wildcard_fields = set()
|
||||
|
||||
return view_audits_common(
|
||||
url, token, "alarm", filters, page_size, current, created_at, days_ago, non_wildcard_fields
|
||||
)
|
||||
|
||||
|
||||
def view_console_audits(url, token, operator=None,
|
||||
page_size=None, current=None, created_at=None, days_ago=None):
|
||||
"""View console audits"""
|
||||
filters = {
|
||||
"operator": operator
|
||||
}
|
||||
non_wildcard_fields = set()
|
||||
|
||||
return view_audits_common(
|
||||
url, token, "console", filters, page_size, current, created_at, days_ago, non_wildcard_fields
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Audits manager")
|
||||
parser.add_argument(
|
||||
"command",
|
||||
choices=["view-conn", "view-file", "view-alarm", "view-console"],
|
||||
help="Command to execute",
|
||||
)
|
||||
parser.add_argument("--url", required=True, help="URL of the API")
|
||||
parser.add_argument("--token", required=True, help="Bearer token for authentication")
|
||||
|
||||
# Pagination parameters
|
||||
parser.add_argument("--page-size", type=int, default=10, help="Number of records per page (default: 10)")
|
||||
parser.add_argument("--current", type=int, default=1, help="Current page number (default: 1)")
|
||||
|
||||
# Time filtering parameters
|
||||
parser.add_argument("--created-at", help="Filter by creation time in local time (format: 2025-09-16 14:15:57 or 2025-09-16 14:15:57.000)")
|
||||
parser.add_argument("--days-ago", type=int, help="Filter by days ago (e.g., 7 for last 7 days)")
|
||||
|
||||
# Audit filters (simplified)
|
||||
parser.add_argument("--remote", help="Remote peer ID filter (for conn/file audits)")
|
||||
parser.add_argument("--device", help="Device ID filter (for alarm audits)")
|
||||
parser.add_argument("--conn-type", type=int, help="Connection type filter (for conn audits only): 0=Remote Desktop, 1=File Transfer, 2=Port Transfer, 3=View Camera, 4=Terminal")
|
||||
parser.add_argument("--operator", help="Operator filter (for console audits only)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Remove trailing slashes from URL
|
||||
while args.url.endswith("/"):
|
||||
args.url = args.url[:-1]
|
||||
|
||||
if args.command == "view-conn":
|
||||
# View connection audits
|
||||
result = view_conn_audits(
|
||||
args.url,
|
||||
args.token,
|
||||
args.remote,
|
||||
args.conn_type,
|
||||
args.page_size,
|
||||
args.current,
|
||||
args.created_at,
|
||||
args.days_ago
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
elif args.command == "view-file":
|
||||
# View file audits
|
||||
result = view_file_audits(
|
||||
args.url,
|
||||
args.token,
|
||||
args.remote,
|
||||
args.page_size,
|
||||
args.current,
|
||||
args.created_at,
|
||||
args.days_ago
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
elif args.command == "view-alarm":
|
||||
# View alarm audits
|
||||
result = view_alarm_audits(
|
||||
args.url,
|
||||
args.token,
|
||||
args.device,
|
||||
args.page_size,
|
||||
args.current,
|
||||
args.created_at,
|
||||
args.days_ago
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
elif args.command == "view-console":
|
||||
# View console audits
|
||||
result = view_console_audits(
|
||||
args.url,
|
||||
args.token,
|
||||
args.operator,
|
||||
args.page_size,
|
||||
args.current,
|
||||
args.created_at,
|
||||
args.days_ago
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -95,8 +95,17 @@ def delete(url, token, guid, id):
|
||||
|
||||
def assign(url, token, guid, id, type, value):
|
||||
print("assign", id, type, value)
|
||||
if type != "ab" and type != "strategy_name" and type != "user_name":
|
||||
print("Invalid type, it must be 'ab', 'strategy_name' or 'user_name'")
|
||||
valid_types = [
|
||||
"ab",
|
||||
"strategy_name",
|
||||
"user_name",
|
||||
"device_group_name",
|
||||
"note",
|
||||
"device_username",
|
||||
"device_name",
|
||||
]
|
||||
if type not in valid_types:
|
||||
print(f"Invalid type, it must be one of: {', '.join(valid_types)}")
|
||||
return
|
||||
data = {"type": type, "value": value}
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
@@ -124,7 +133,7 @@ def main():
|
||||
parser.add_argument("--device_group_name", help="Device group name")
|
||||
parser.add_argument(
|
||||
"--assign_to",
|
||||
help="<type>=<value>, e.g. user_name=mike, strategy_name=test, ab=ab1, ab=ab1,tag1",
|
||||
help="<type>=<value>, e.g. user_name=mike, strategy_name=test, device_group_name=group1, note=note1, device_username=username1, device_name=name1, ab=ab1, ab=ab1,tag1,alias1,password1,note1"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--offline_days", type=int, help="Offline duration in days, e.g., 7"
|
||||
@@ -148,28 +157,37 @@ def main():
|
||||
if args.command == "view":
|
||||
for device in devices:
|
||||
print(device)
|
||||
elif args.command == "disable":
|
||||
for device in devices:
|
||||
response = disable(args.url, args.token, device["guid"], device["id"])
|
||||
print(response)
|
||||
elif args.command == "enable":
|
||||
for device in devices:
|
||||
response = enable(args.url, args.token, device["guid"], device["id"])
|
||||
print(response)
|
||||
elif args.command == "delete":
|
||||
for device in devices:
|
||||
response = delete(args.url, args.token, device["guid"], device["id"])
|
||||
print(response)
|
||||
elif args.command == "assign":
|
||||
if "=" not in args.assign_to:
|
||||
print("Invalid assign_to format, it must be <type>=<value>")
|
||||
return
|
||||
type, value = args.assign_to.split("=", 1)
|
||||
for device in devices:
|
||||
response = assign(
|
||||
args.url, args.token, device["guid"], device["id"], type, value
|
||||
)
|
||||
print(response)
|
||||
elif args.command in ["disable", "enable", "delete", "assign"]:
|
||||
# Check if we need user confirmation for multiple devices
|
||||
if len(devices) > 1:
|
||||
print(f"Found {len(devices)} devices. Do you want to proceed with {args.command} operation on the devices? (Y/N)")
|
||||
confirmation = input("Type 'Y' to confirm: ").strip()
|
||||
if confirmation.upper() != 'Y':
|
||||
print("Operation cancelled.")
|
||||
return
|
||||
|
||||
if args.command == "disable":
|
||||
for device in devices:
|
||||
response = disable(args.url, args.token, device["guid"], device["id"])
|
||||
print(response)
|
||||
elif args.command == "enable":
|
||||
for device in devices:
|
||||
response = enable(args.url, args.token, device["guid"], device["id"])
|
||||
print(response)
|
||||
elif args.command == "delete":
|
||||
for device in devices:
|
||||
response = delete(args.url, args.token, device["guid"], device["id"])
|
||||
print(response)
|
||||
elif args.command == "assign":
|
||||
if "=" not in args.assign_to:
|
||||
print("Invalid assign_to format, it must be <type>=<value>")
|
||||
return
|
||||
type, value = args.assign_to.split("=", 1)
|
||||
for device in devices:
|
||||
response = assign(
|
||||
args.url, args.token, device["guid"], device["id"], type, value
|
||||
)
|
||||
print(response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.2
|
||||
Version: 1.4.3
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.2
|
||||
Version: 1.4.3
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.2
|
||||
Version: 1.4.3
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -177,6 +177,7 @@ elseif(VCPKG_CMAKE_SYSTEM_NAME STREQUAL "Android")
|
||||
string(APPEND OPTIONS "\
|
||||
--target-os=android \
|
||||
--disable-asm \
|
||||
--disable-iconv \
|
||||
--enable-jni \
|
||||
--enable-mediacodec \
|
||||
--disable-hwaccels \
|
||||
|
||||
@@ -2027,7 +2027,7 @@ impl LoginConfigHandler {
|
||||
///
|
||||
// It's Ok to check the option empty in this function.
|
||||
// `toggle_option()` is only called in a session.
|
||||
// Custom client advanced settings will not affact this function.
|
||||
// Custom client advanced settings will not effect this function.
|
||||
pub fn toggle_option(&mut self, name: String) -> Option<Message> {
|
||||
let mut option = OptionMessage::default();
|
||||
let mut config = self.load_config();
|
||||
@@ -2302,7 +2302,7 @@ impl LoginConfigHandler {
|
||||
///
|
||||
// It's Ok to check the option empty in this function.
|
||||
// `get_toggle_option()` is only called in a session.
|
||||
// Custom client advanced settings will not affact this function.
|
||||
// Custom client advanced settings will not effect this function.
|
||||
pub fn get_toggle_option(&self, name: &str) -> bool {
|
||||
if name == "show-remote-cursor" {
|
||||
self.config.show_remote_cursor.v
|
||||
|
||||
@@ -257,7 +257,7 @@ impl ClipboardContext {
|
||||
let mut i = 1;
|
||||
loop {
|
||||
// Try 5 times to create clipboard
|
||||
// Arboard::new() connect to X server or Wayland compositor, which shoud be ok at most time
|
||||
// Arboard::new() connect to X server or Wayland compositor, which should be OK most times
|
||||
// But sometimes, the connection may fail, so we retry here.
|
||||
match arboard::Clipboard::new() {
|
||||
Ok(x) => {
|
||||
@@ -314,7 +314,7 @@ impl ClipboardContext {
|
||||
|
||||
pub fn get(&mut self, side: ClipboardSide, force: bool) -> ResultType<Vec<ClipboardData>> {
|
||||
let data = self.get_formats_filter(SUPPORTED_FORMATS, side, force)?;
|
||||
// We have a seperate service named `file-clipboard` to handle file copy-paste.
|
||||
// We have a separate service named `file-clipboard` to handle file copy-paste.
|
||||
// We need to read the file urls because file copy may set the other clipboard formats such as text.
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
{
|
||||
@@ -757,7 +757,7 @@ pub fn get_clipboards_msg(client: bool) -> Option<Message> {
|
||||
}
|
||||
|
||||
// We need this mod to notify multiple subscribers when the clipboard changes.
|
||||
// Because only one clipboard master(listener) can tigger the clipboard change event multiple listeners are created on Linux(x11).
|
||||
// Because only one clipboard master(listener) can trigger the clipboard change event multiple listeners are created on Linux(x11).
|
||||
// https://github.com/rustdesk-org/clipboard-master/blob/4fb62e5b62fb6350d82b571ec7ba94b3cd466695/src/master/x11.rs#L226
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub mod clipboard_listener {
|
||||
|
||||
@@ -55,6 +55,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
"--file-transfer",
|
||||
"--view-camera",
|
||||
"--port-forward",
|
||||
"--terminal",
|
||||
"--rdp",
|
||||
]
|
||||
.contains(&arg.as_str())
|
||||
@@ -462,51 +463,25 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
let token = args[pos + 1].to_owned();
|
||||
let id = crate::ipc::get_id();
|
||||
let uuid = crate::encode64(hbb_common::get_uuid());
|
||||
let mut user_name = None;
|
||||
let pos = args.iter().position(|x| x == "--user_name").unwrap_or(max);
|
||||
if pos < max {
|
||||
user_name = Some(args[pos + 1].to_owned());
|
||||
}
|
||||
let mut strategy_name = None;
|
||||
let pos = args
|
||||
.iter()
|
||||
.position(|x| x == "--strategy_name")
|
||||
.unwrap_or(max);
|
||||
if pos < max {
|
||||
strategy_name = Some(args[pos + 1].to_owned());
|
||||
}
|
||||
let mut address_book_name = None;
|
||||
let pos = args
|
||||
.iter()
|
||||
.position(|x| x == "--address_book_name")
|
||||
.unwrap_or(max);
|
||||
if pos < max {
|
||||
address_book_name = Some(args[pos + 1].to_owned());
|
||||
}
|
||||
let mut address_book_tag = None;
|
||||
let pos = args
|
||||
.iter()
|
||||
.position(|x| x == "--address_book_tag")
|
||||
.unwrap_or(max);
|
||||
if pos < max {
|
||||
address_book_tag = Some(args[pos + 1].to_owned());
|
||||
}
|
||||
let mut address_book_alias = None;
|
||||
let pos = args
|
||||
.iter()
|
||||
.position(|x| x == "--address_book_alias")
|
||||
.unwrap_or(max);
|
||||
if pos < max {
|
||||
address_book_alias = Some(args[pos + 1].to_owned());
|
||||
}
|
||||
let mut device_group_name = None;
|
||||
let pos = args
|
||||
.iter()
|
||||
.position(|x| x == "--device_group_name")
|
||||
.unwrap_or(max);
|
||||
if pos < max {
|
||||
device_group_name = Some(args[pos + 1].to_owned());
|
||||
}
|
||||
let get_value = |c: &str| {
|
||||
let pos = args.iter().position(|x| x == c).unwrap_or(max);
|
||||
if pos < max {
|
||||
Some(args[pos + 1].to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let user_name = get_value("--user_name");
|
||||
let strategy_name = get_value("--strategy_name");
|
||||
let address_book_name = get_value("--address_book_name");
|
||||
let address_book_tag = get_value("--address_book_tag");
|
||||
let address_book_alias = get_value("--address_book_alias");
|
||||
let address_book_password = get_value("--address_book_password");
|
||||
let address_book_note = get_value("--address_book_note");
|
||||
let device_group_name = get_value("--device_group_name");
|
||||
let note = get_value("--note");
|
||||
let device_username = get_value("--device_username");
|
||||
let device_name = get_value("--device_name");
|
||||
let mut body = serde_json::json!({
|
||||
"id": id,
|
||||
"uuid": uuid,
|
||||
@@ -516,9 +491,19 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
&& strategy_name.is_none()
|
||||
&& address_book_name.is_none()
|
||||
&& device_group_name.is_none()
|
||||
&& note.is_none()
|
||||
&& device_username.is_none()
|
||||
&& device_name.is_none()
|
||||
{
|
||||
println!(
|
||||
"--user_name or --strategy_name or --address_book_name or --device_group_name is required!"
|
||||
r#"At least one of the following options is required:
|
||||
--user_name
|
||||
--strategy_name
|
||||
--address_book_name
|
||||
--device_group_name
|
||||
--note
|
||||
--device_username
|
||||
--device_name"#
|
||||
);
|
||||
} else {
|
||||
if let Some(name) = user_name {
|
||||
@@ -535,10 +520,25 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
if let Some(name) = address_book_alias {
|
||||
body["address_book_alias"] = serde_json::json!(name);
|
||||
}
|
||||
if let Some(name) = address_book_password {
|
||||
body["address_book_password"] = serde_json::json!(name);
|
||||
}
|
||||
if let Some(name) = address_book_note {
|
||||
body["address_book_note"] = serde_json::json!(name);
|
||||
}
|
||||
}
|
||||
if let Some(name) = device_group_name {
|
||||
body["device_group_name"] = serde_json::json!(name);
|
||||
}
|
||||
if let Some(name) = note {
|
||||
body["note"] = serde_json::json!(name);
|
||||
}
|
||||
if let Some(name) = device_username {
|
||||
body["device_username"] = serde_json::json!(name);
|
||||
}
|
||||
if let Some(name) = device_name {
|
||||
body["device_name"] = serde_json::json!(name);
|
||||
}
|
||||
let url = crate::ui_interface::get_api_server() + "/api/devices/cli";
|
||||
match crate::post_request_sync(url, body.to_string(), &header) {
|
||||
Err(err) => println!("{}", err),
|
||||
@@ -668,7 +668,7 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<Strin
|
||||
let mut param_array = vec![];
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--connect" | "--play" | "--file-transfer" | "--view-camera" | "--port-forward"
|
||||
"--connect" | "--play" | "--file-transfer" | "--view-camera" | "--port-forward" | "--terminal"
|
||||
| "--rdp" => {
|
||||
authority = Some((&arg.to_string()[2..]).to_owned());
|
||||
id = args.next();
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::{
|
||||
os::raw::{c_char, c_int, c_void},
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
Arc, RwLock,
|
||||
},
|
||||
};
|
||||
@@ -756,7 +756,7 @@ impl InvokeUiSession for FlutterHandler {
|
||||
// unused in flutter
|
||||
fn clear_all_jobs(&self) {}
|
||||
|
||||
fn load_last_job(&self, _cnt: i32, job_json: &str) {
|
||||
fn load_last_job(&self, _cnt: i32, job_json: &str, _auto_start: bool) {
|
||||
self.push_event("load_last_job", &[("value", job_json)], &[]);
|
||||
}
|
||||
|
||||
@@ -1328,6 +1328,7 @@ pub fn session_add(
|
||||
server_keyboard_enabled: Arc::new(RwLock::new(true)),
|
||||
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
|
||||
server_clipboard_enabled: Arc::new(RwLock::new(true)),
|
||||
reconnect_count: Arc::new(AtomicUsize::new(0)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -1980,7 +1981,7 @@ pub(super) fn session_update_virtual_display(session: &FlutterSession, index: i3
|
||||
let mut vdisplays = displays.split(',').collect::<Vec<_>>();
|
||||
let len = vdisplays.len();
|
||||
if index == 0 {
|
||||
// 0 means we cann't toggle the virtual display by index.
|
||||
// 0 means we can't toggle the virtual display by index.
|
||||
vdisplays.remove(vdisplays.len() - 1);
|
||||
} else {
|
||||
if let Some(i) = vdisplays.iter().position(|&x| x == index.to_string()) {
|
||||
|
||||
@@ -140,6 +140,18 @@ async fn start_hbbs_sync_async() {
|
||||
if !ab_tag.is_empty() {
|
||||
v[keys::OPTION_PRESET_ADDRESS_BOOK_TAG] = json!(ab_tag);
|
||||
}
|
||||
let ab_alias = Config::get_option(keys::OPTION_PRESET_ADDRESS_BOOK_ALIAS);
|
||||
if !ab_alias.is_empty() {
|
||||
v[keys::OPTION_PRESET_ADDRESS_BOOK_ALIAS] = json!(ab_alias);
|
||||
}
|
||||
let ab_password = Config::get_option(keys::OPTION_PRESET_ADDRESS_BOOK_PASSWORD);
|
||||
if !ab_password.is_empty() {
|
||||
v[keys::OPTION_PRESET_ADDRESS_BOOK_PASSWORD] = json!(ab_password);
|
||||
}
|
||||
let ab_note = Config::get_option(keys::OPTION_PRESET_ADDRESS_BOOK_NOTE);
|
||||
if !ab_note.is_empty() {
|
||||
v[keys::OPTION_PRESET_ADDRESS_BOOK_NOTE] = json!(ab_note);
|
||||
}
|
||||
let username = get_builtin_option(keys::OPTION_PRESET_USERNAME);
|
||||
if !username.is_empty() {
|
||||
v[keys::OPTION_PRESET_USERNAME] = json!(username);
|
||||
@@ -152,6 +164,18 @@ async fn start_hbbs_sync_async() {
|
||||
if !device_group_name.is_empty() {
|
||||
v[keys::OPTION_PRESET_DEVICE_GROUP_NAME] = json!(device_group_name);
|
||||
}
|
||||
let device_username = Config::get_option(keys::OPTION_PRESET_DEVICE_USERNAME);
|
||||
if !device_username.is_empty() {
|
||||
v["username"] = json!(device_username);
|
||||
}
|
||||
let device_name = Config::get_option(keys::OPTION_PRESET_DEVICE_NAME);
|
||||
if !device_name.is_empty() {
|
||||
v["hostname"] = json!(device_name);
|
||||
}
|
||||
let note = Config::get_option(keys::OPTION_PRESET_NOTE);
|
||||
if !note.is_empty() {
|
||||
v[keys::OPTION_PRESET_NOTE] = json!(note);
|
||||
}
|
||||
let v = v.to_string();
|
||||
let mut hash = "".to_owned();
|
||||
if crate::is_public(&url) {
|
||||
|
||||
@@ -271,7 +271,7 @@ pub enum Data {
|
||||
CheckHwcodec,
|
||||
#[cfg(feature = "flutter")]
|
||||
VideoConnCount(Option<usize>),
|
||||
// Although the key is not neccessary, it is used to avoid hardcoding the key.
|
||||
// Although the key is not necessary, it is used to avoid hardcoding the key.
|
||||
WaylandScreencastRestoreToken((String, String)),
|
||||
HwCodecConfig(Option<String>),
|
||||
RemoveTrustedDevices(Vec<Bytes>),
|
||||
|
||||
@@ -903,7 +903,7 @@ fn _map_keyboard_mode(_peer: &str, event: &Event, mut key_event: KeyEvent) -> Op
|
||||
let keycode = match _peer {
|
||||
OS_LOWER_WINDOWS => {
|
||||
// https://github.com/rustdesk/rustdesk/issues/1371
|
||||
// Filter scancodes that are greater than 255 and the hight word is not 0xE0.
|
||||
// Filter scancodes that are greater than 255 and the height word is not 0xE0.
|
||||
if event.position_code > 255 && (event.position_code >> 8) != 0xE0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ mod uk;
|
||||
mod vi;
|
||||
mod ta;
|
||||
mod ge;
|
||||
mod fi;
|
||||
|
||||
pub const LANGS: &[(&str, &str)] = &[
|
||||
("en", "English"),
|
||||
@@ -93,6 +94,7 @@ pub const LANGS: &[(&str, &str)] = &[
|
||||
("sc", "Sardu"),
|
||||
("ta", "தமிழ்"),
|
||||
("ge", "ქართული"),
|
||||
("fi", "Suomi"),
|
||||
];
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -152,6 +154,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
|
||||
"kz" => kz::T.deref(),
|
||||
"uk" => uk::T.deref(),
|
||||
"fa" => fa::T.deref(),
|
||||
"fi" => fi::T.deref(),
|
||||
"ca" => ca::T.deref(),
|
||||
"el" => el::T.deref(),
|
||||
"sv" => sv::T.deref(),
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "يرجى إدخال اسم مستخدم بصلاحيات المسؤول للمتابعة."),
|
||||
("Preparing for installation ...", "جارٍ التحضير للتثبيت..."),
|
||||
("Show my cursor", "إظهار المؤشر الخاص بي"),
|
||||
("Scale custom", "مقياس مخصص"),
|
||||
("Custom scale slider", "شريط تمرير المقياس المخصص"),
|
||||
("Decrease", "تصغير"),
|
||||
("Increase", "تكبير"),
|
||||
("Show virtual mouse", "إظهار الفأرة الافتراضية"),
|
||||
("Virtual mouse size", "حجم الفأرة الافتراضية"),
|
||||
("Small", "صغير"),
|
||||
("Large", "كبير"),
|
||||
("Show virtual joystick", "إظهار عصا التحكم الافتراضية"),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -652,18 +652,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Untagged", "Sense etiquetar"),
|
||||
("new-version-of-{}-tip", ""),
|
||||
("Accessible devices", "Dispositius accessibles"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "Veuillez mettre à niveau le client RustDesk vers la version {} ou plus récente du côté distant !"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
|
||||
("d3d_render_tip", ""),
|
||||
("Use D3D rendering", ""),
|
||||
("Printer", ""),
|
||||
("Use D3D rendering", "Utilitza renderització D3D"),
|
||||
("Printer", "Impressora"),
|
||||
("printer-os-requirement-tip", ""),
|
||||
("printer-requires-installed-{}-client-tip", ""),
|
||||
("printer-{}-not-installed-tip", ""),
|
||||
("printer-{}-ready-tip", ""),
|
||||
("Install {} Printer", ""),
|
||||
("Outgoing Print Jobs", ""),
|
||||
("Incoming Print Jobs", ""),
|
||||
("Incoming Print Job", ""),
|
||||
("Install {} Printer", "Instal·la {} impressora"),
|
||||
("Outgoing Print Jobs", "Treballs d'impressió sortints"),
|
||||
("Incoming Print Jobs", "Treballs d'impressió entrants"),
|
||||
("Incoming Print Job", "Treballs d'impressió entrant"),
|
||||
("use-the-default-printer-tip", ""),
|
||||
("use-the-selected-printer-tip", ""),
|
||||
("auto-print-tip", ""),
|
||||
@@ -672,43 +672,54 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("remote-printing-disallowed-text-tip", ""),
|
||||
("save-settings-tip", ""),
|
||||
("dont-show-again-tip", ""),
|
||||
("Take screenshot", ""),
|
||||
("Taking screenshot", ""),
|
||||
("Take screenshot", "Fes una captura de pantalla"),
|
||||
("Taking screenshot", "Fent la captura de pantalla"),
|
||||
("screenshot-merged-screen-not-supported-tip", ""),
|
||||
("screenshot-action-tip", ""),
|
||||
("Save as", ""),
|
||||
("Copy to clipboard", ""),
|
||||
("Enable remote printer", ""),
|
||||
("Downloading {}", ""),
|
||||
("{} Update", ""),
|
||||
("Save as", "Anomena i desa"),
|
||||
("Copy to clipboard", "Copia al porta-retalls"),
|
||||
("Enable remote printer", "Habilita l'impressora remota"),
|
||||
("Downloading {}", "Descarregant {}"),
|
||||
("{} Update", "{} Actualitza"),
|
||||
("{}-to-update-tip", ""),
|
||||
("download-new-version-failed-tip", ""),
|
||||
("Auto update", ""),
|
||||
("Auto update", "Actualització automàtica"),
|
||||
("update-failed-check-msi-tip", ""),
|
||||
("websocket_tip", ""),
|
||||
("Use WebSocket", ""),
|
||||
("Trackpad speed", ""),
|
||||
("Default trackpad speed", ""),
|
||||
("Numeric one-time password", ""),
|
||||
("Enable IPv6 P2P connection", ""),
|
||||
("Enable UDP hole punching", ""),
|
||||
("Trackpad speed", "Velocitat del trackpad"),
|
||||
("Default trackpad speed", "Velocitat per defecte del trackpad"),
|
||||
("Numeric one-time password", "Contrasenya numèrica d'un sol ús"),
|
||||
("Enable IPv6 P2P connection", "Habilita la connexió IPv6 P2P"),
|
||||
("Enable UDP hole punching", "Activa la perforació UDP"),
|
||||
("View camera", "Mostra la càmera"),
|
||||
("Enable camera", ""),
|
||||
("No cameras", ""),
|
||||
("Enable camera", "Habilita la càmera"),
|
||||
("No cameras", "No hi ha càmeres"),
|
||||
("view_camera_unsupported_tip", ""),
|
||||
("Terminal", ""),
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("Terminal", "Terminal"),
|
||||
("Enable terminal", "Habilita el terminal"),
|
||||
("New tab", "Nova finestra"),
|
||||
("Keep terminal sessions on disconnect", "Mantingues les sessions de terminal desconnectades"),
|
||||
("Terminal (Run as administrator)", "Terminal (executa com a administrador"),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only in the installed version.", ""),
|
||||
("Failed to get user token.", "No s'ha pogut obtenir el token d'usuari."),
|
||||
("Incorrect username or password.", "Nom d'usuari o contrasenya incorrecte"),
|
||||
("The user is not an administrator.", "Aquest usuari no és administrador"),
|
||||
("Failed to check if the user is an administrator.", "No s'ha pogut comprovar si l'usuari és administrador."),
|
||||
("Supported only in the installed version.", "Només compatible amb la versió instal·lada."),
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Preparing for installation ...", "Preparant per a l'instal·lació..."),
|
||||
("Show my cursor", "Mostra el meu punter"),
|
||||
("Scale custom", "Escala personalitzada"),
|
||||
("Custom scale slider", "Control lliscant d'escala personalitzada"),
|
||||
("Decrease", "Disminueix"),
|
||||
("Increase", "Augmenta"),
|
||||
("Show virtual mouse", "Mostra el ratolí virtual"),
|
||||
("Virtual mouse size", "Mida del ratolí virtual"),
|
||||
("Small", "Petita"),
|
||||
("Large", "Gran"),
|
||||
("Show virtual joystick", "Mostra el joystick virtual"),
|
||||
("Edit note", "Edita la nota"),
|
||||
("Alias", "Alias"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "输入用户名或域名\\用户名"),
|
||||
("Preparing for installation ...", "准备安装..."),
|
||||
("Show my cursor", "显示我的光标"),
|
||||
("Scale custom", "自定义缩放"),
|
||||
("Custom scale slider", "自定义缩放滑块"),
|
||||
("Decrease", "缩小"),
|
||||
("Increase", "放大"),
|
||||
("Show virtual mouse", "显示虚拟鼠标"),
|
||||
("Virtual mouse size", "虚拟鼠标大小"),
|
||||
("Small", "小"),
|
||||
("Large", "大"),
|
||||
("Show virtual joystick", "显示虚拟摇杆"),
|
||||
("Edit note", "编辑备注"),
|
||||
("Alias", "别名"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Geben Sie Benutzername oder Domäne\\Benutzername ein"),
|
||||
("Preparing for installation ...", "Installation wird vorbereitet …"),
|
||||
("Show my cursor", "Meinen Cursor anzeigen"),
|
||||
("Scale custom", "Benutzerdefinierte Skalierung"),
|
||||
("Custom scale slider", "Schieberegler für benutzerdefinierte Skalierung"),
|
||||
("Decrease", "Verringern"),
|
||||
("Increase", "Erhöhen"),
|
||||
("Show virtual mouse", "Virtuelle Maus anzeigen"),
|
||||
("Virtual mouse size", "Virtuelle Mausgröße"),
|
||||
("Small", "Klein"),
|
||||
("Large", "Groß"),
|
||||
("Show virtual joystick", "Virtuellen Joystick anzeigen"),
|
||||
("Edit note", "Hinweis bearbeiten"),
|
||||
("Alias", "Alias"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -679,7 +679,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Save as", "Guardar como"),
|
||||
("Copy to clipboard", "Copiar al portapapeles"),
|
||||
("Enable remote printer", "Habilitar impresora remota"),
|
||||
("Downloading {}", "Descarngando {}"),
|
||||
("Downloading {}", "Descargando {}"),
|
||||
("{} Update", "{} Actualizar"),
|
||||
("{}-to-update-tip", "{} Se cerrará ahora e instalará la nueva versión."),
|
||||
("download-new-version-failed-tip", "Descarga fallida. Puedes volver a intentarlo o hacer clic en el botón \"Download\" para descargar desde la página de lanzamientos y actualizar manualmente."),
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Introduzca el nombre de usuario o dominio\\NombreDeUsuario"),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", "Escala personalizada"),
|
||||
("Custom scale slider", "Control deslizante de escala personalizada"),
|
||||
("Decrease", "Disminuir"),
|
||||
("Increase", "Aumentar"),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "لطفاً نام کاربری مدیریتی را برای ارتقاء دسترسی وارد کنید."),
|
||||
("Preparing for installation ...", "در حال آمادهسازی برای نصب..."),
|
||||
("Show my cursor", "نمایش نشانگر من"),
|
||||
("Scale custom", "مقیاس سفارشی"),
|
||||
("Custom scale slider", "نوار لغزنده مقیاس سفارشی"),
|
||||
("Decrease", "کاهش"),
|
||||
("Increase", "افزایش"),
|
||||
("Show virtual mouse", "نمایش ماوس مجازی"),
|
||||
("Virtual mouse size", "اندازه ماوس مجازی"),
|
||||
("Small", "کوچک"),
|
||||
("Large", "بزرگ"),
|
||||
("Show virtual joystick", "نمایش جویاستیک مجازی"),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
725
src/lang/fi.rs
Normal file
725
src/lang/fi.rs
Normal file
@@ -0,0 +1,725 @@
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Tila"),
|
||||
("Your Desktop", "Oma työpöytä"),
|
||||
("desk_tip", "Työpöytääsi voidaan käyttää tällä tunnuksella ja salasanalla."),
|
||||
("Password", "Salasana"),
|
||||
("Ready", "Valmis"),
|
||||
("Established", "Yhdistetty"),
|
||||
("connecting_status", "Yhdistetään RustDesk verkkoon..."),
|
||||
("Enable service", "Ota palvelu käyttöön"),
|
||||
("Start service", "Käynnistä palvelu"),
|
||||
("Service is running", "Palvelu on käynnissä"),
|
||||
("Service is not running", "Palvelu ei ole käynnissä"),
|
||||
("not_ready_status", "Ei valmis tarkista yhteys."),
|
||||
("Control Remote Desktop", "Hallitse etätyöpöytää"),
|
||||
("Transfer file", "Siirrä tiedosto"),
|
||||
("Connect", "Yhdistä"),
|
||||
("Recent sessions", "Viimeisimmät istunnot"),
|
||||
("Address book", "Osoitekirja"),
|
||||
("Confirmation", "Vahvistus"),
|
||||
("TCP tunneling", "TCP tunnelointi"),
|
||||
("Remove", "Poista"),
|
||||
("Refresh random password", "Päivitä satunnainen salasana"),
|
||||
("Set your own password", "Aseta oma salasana"),
|
||||
("Enable keyboard/mouse", "Salli näppäimistö ja hiiri"),
|
||||
("Enable clipboard", "Salli leikepöytä"),
|
||||
("Enable file transfer", "Salli tiedostonsiirto"),
|
||||
("Enable TCP tunneling", "Salli TCP tunnelointi"),
|
||||
("IP Whitelisting", "IP osoitteiden sallintalista"),
|
||||
("ID/Relay Server", "ID/Välityspalvelin"),
|
||||
("Import server config", "Tuo palvelimen asetukset"),
|
||||
("Export Server Config", "Vie palvelimen asetukset"),
|
||||
("Import server configuration successfully", "Palvelimen asetukset tuotu onnistuneesti"),
|
||||
("Export server configuration successfully", "Palvelimen asetukset viety onnistuneesti"),
|
||||
("Invalid server configuration", "Virheellinen palvelimen määritys"),
|
||||
("Clipboard is empty", "Leikepöytä on tyhjä"),
|
||||
("Stop service", "Pysäytä palvelu"),
|
||||
("Change ID", "Vaihda ID"),
|
||||
("Your new ID", "Uusi ID"),
|
||||
("length %min% to %max%", "pituus %min%–%max%"),
|
||||
("starts with a letter", "alkaa kirjaimella"),
|
||||
("allowed characters", "sallitut merkit"),
|
||||
("id_change_tip", "Sallitut merkit: a–z, A–Z, 0–9, - ja _. Ensimmäisen merkin on oltava kirjain. Pituus 6–16 merkkiä."),
|
||||
("Website", "Verkkosivusto"),
|
||||
("About", "Tietoa"),
|
||||
("Slogan_tip", "Tehty sydämellä tässä kaoottisessa maailmassa!"),
|
||||
("Privacy Statement", "Tietosuojaseloste"),
|
||||
("Mute", "Mykistä"),
|
||||
("Build Date", "Koontipäivä"),
|
||||
("Version", "Versio"),
|
||||
("Home", "Etusivu"),
|
||||
("Audio Input", "Äänitulo"),
|
||||
("Enhancements", "Parannukset"),
|
||||
("Hardware Codec", "Laitteistokoodekki"),
|
||||
("Adaptive bitrate", "Mukautuva bittinopeus"),
|
||||
("ID Server", "ID palvelin"),
|
||||
("Relay Server", "Välityspalvelin"),
|
||||
("API Server", "API palvelin"),
|
||||
("invalid_http", "Osoitteen on alettava http:// tai https://"),
|
||||
("Invalid IP", "Virheellinen IP osoite"),
|
||||
("Invalid format", "Virheellinen muoto"),
|
||||
("server_not_support", "Palvelin ei tue tätä ominaisuutta"),
|
||||
("Not available", "Ei saatavilla"),
|
||||
("Too frequent", "Liian tiheä pyyntö"),
|
||||
("Cancel", "Peruuta"),
|
||||
("Skip", "Ohita"),
|
||||
("Close", "Sulje"),
|
||||
("Retry", "Yritä uudelleen"),
|
||||
("OK", "OK"),
|
||||
("Password Required", "Salasana vaaditaan"),
|
||||
("Please enter your password", "Syötä salasanasi"),
|
||||
("Remember password", "Muista salasana"),
|
||||
("Wrong Password", "Väärä salasana"),
|
||||
("Do you want to enter again?", "Haluatko yrittää uudelleen?"),
|
||||
("Connection Error", "Yhteysvirhe"),
|
||||
("Error", "Virhe"),
|
||||
("Reset by the peer", "Yhteys katkaistu vastapuolen toimesta"),
|
||||
("Connecting...", "Yhdistetään..."),
|
||||
("Connection in progress. Please wait.", "Yhdistetään – odota hetki."),
|
||||
("Please try 1 minute later", "Yritä uudelleen minuutin kuluttua"),
|
||||
("Login Error", "Kirjautumisvirhe"),
|
||||
("Successful", "Onnistui"),
|
||||
("Connected, waiting for image...", "Yhdistetty, odotetaan kuvaa..."),
|
||||
("Name", "Nimi"),
|
||||
("Type", "Tyyppi"),
|
||||
("Modified", "Muokattu"),
|
||||
("Size", "Koko"),
|
||||
("Show Hidden Files", "Näytä piilotetut tiedostot"),
|
||||
("Receive", "Vastaanota"),
|
||||
("Send", "Lähetä"),
|
||||
("Refresh File", "Päivitä tiedosto"),
|
||||
("Local", "Paikallinen"),
|
||||
("Remote", "Etä"),
|
||||
("Remote Computer", "Etätietokone"),
|
||||
("Local Computer", "Paikallinen tietokone"),
|
||||
("Confirm Delete", "Vahvista poisto"),
|
||||
("Delete", "Poista"),
|
||||
("Properties", "Ominaisuudet"),
|
||||
("Multi Select", "Monivalinta"),
|
||||
("Select All", "Valitse kaikki"),
|
||||
("Unselect All", "Poista kaikki valinnat"),
|
||||
("Empty Directory", "Tyhjä kansio"),
|
||||
("Not an empty directory", "Hakemisto ei ole tyhjä"),
|
||||
("Are you sure you want to delete this file?", "Haluatko varmasti poistaa tämän tiedoston?"),
|
||||
("Are you sure you want to delete this empty directory?", "Haluatko varmasti poistaa tämän tyhjän hakemiston?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Haluatko varmasti poistaa tämän hakemiston tiedoston?"),
|
||||
("Do this for all conflicts", "Tee sama kaikille ristiriidoille"),
|
||||
("This is irreversible!", "Tätä toimintoa ei voi perua!"),
|
||||
("Deleting", "Poistetaan"),
|
||||
("files", "tiedostoa"),
|
||||
("Waiting", "Odotetaan"),
|
||||
("Finished", "Valmis"),
|
||||
("Speed", "Nopeus"),
|
||||
("Custom Image Quality", "Mukautettu kuvanlaatu"),
|
||||
("Privacy mode", "Yksityisyystila"),
|
||||
("Block user input", "Estä käyttäjän toiminta"),
|
||||
("Unblock user input", "Salli käyttäjän toiminta"),
|
||||
("Adjust Window", "Sovita ikkuna"),
|
||||
("Original", "Alkuperäinen"),
|
||||
("Shrink", "Pienennä"),
|
||||
("Stretch", "Venytä"),
|
||||
("Scrollbar", "Vierityspalkki"),
|
||||
("ScrollAuto", "Automaattinen vieritys"),
|
||||
("Good image quality", "Hyvä kuvanlaatu"),
|
||||
("Balanced", "Tasapainotettu"),
|
||||
("Optimize reaction time", "Optimoi vasteaika"),
|
||||
("Custom", "Mukautettu"),
|
||||
("Show remote cursor", "Näytä etäkursori"),
|
||||
("Show quality monitor", "Näytä laadunvalvonta"),
|
||||
("Disable clipboard", "Poista leikepöytä käytöstä"),
|
||||
("Lock after session end", "Lukitse istunnon päätyttyä"),
|
||||
("Insert Ctrl + Alt + Del", "Lähetä Ctrl + Alt + Del"),
|
||||
("Insert Lock", "Aseta lukitse"),
|
||||
("Refresh", "Päivitä"),
|
||||
("ID does not exist", "Tunnusta ei ole olemassa"),
|
||||
("Failed to connect to rendezvous server", "Yhteys tapaamispalvelimeen epäonnistui"),
|
||||
("Please try later", "Yritä myöhemmin uudelleen"),
|
||||
("Remote desktop is offline", "Etätyöpöytä ei ole online tilassa"),
|
||||
("Key mismatch", "Avaimet eivät täsmää"),
|
||||
("Timeout", "Aikakatkaisu"),
|
||||
("Failed to connect to relay server", "Yhteys välityspalvelimeen epäonnistui"),
|
||||
("Failed to connect via rendezvous server", "Yhteys tapaamispalvelimen kautta epäonnistui"),
|
||||
("Failed to connect via relay server", "Yhteys välityspalvelimen kautta epäonnistui"),
|
||||
("Failed to make direct connection to remote desktop", "Suora yhteys etätyöpöytään epäonnistui"),
|
||||
("Set Password", "Aseta salasana"),
|
||||
("OS Password", "Käyttöjärjestelmän salasana"),
|
||||
("install_tip", "Joissain tapauksissa RustDesk ei toimi oikein etäpuolella UAC:n vuoksi. Välttääksesi tämän, napsauta alla olevaa painiketta asentaaksesi RustDeskin järjestelmään."),
|
||||
("Click to upgrade", "Päivitä napsauttamalla"),
|
||||
("Configure", "Määritä"),
|
||||
("config_acc", "Etätyöpöydän hallintaa varten sinun on annettava RustDeskille ”Esteettömyys”-oikeudet."),
|
||||
("config_screen", "Etätyöpöydän käyttöä varten sinun on annettava RustDeskille ”Näytön tallennus” oikeudet."),
|
||||
("Installing ...", "Asennetaan ..."),
|
||||
("Install", "Asenna"),
|
||||
("Installation", "Asennus"),
|
||||
("Installation Path", "Asennuspolku"),
|
||||
("Create start menu shortcuts", "Luo pikakuvakkeet Käynnistä valikkoon"),
|
||||
("Create desktop icon", "Luo kuvake työpöydälle"),
|
||||
("agreement_tip", "Aloittamalla asennuksen hyväksyt käyttöoikeussopimuksen."),
|
||||
("Accept and Install", "Hyväksy ja asenna"),
|
||||
("End-user license agreement", "Käyttöoikeussopimus"),
|
||||
("Generating ...", "Luodaan ..."),
|
||||
("Your installation is lower version.", "Asennettu versio on vanhempi."),
|
||||
("not_close_tcp_tip", "Älä sulje tätä ikkunaa tunnelin ollessa käytössä"),
|
||||
("Listening ...", "Kuunnellaan ..."),
|
||||
("Remote Host", "Etätietokone"),
|
||||
("Remote Port", "Etäportti"),
|
||||
("Action", "Toiminto"),
|
||||
("Add", "Lisää"),
|
||||
("Local Port", "Paikallinen portti"),
|
||||
("Local Address", "Paikallinen osoite"),
|
||||
("Change Local Port", "Vaihda paikallinen porttia"),
|
||||
("setup_server_tip", "Nopeampaa yhteyttä varten voit asettaa oman palvelimen"),
|
||||
("Too short, at least 6 characters.", "Liian lyhyt, vähintään 6 merkkiä."),
|
||||
("The confirmation is not identical.", "Vahvistus ei täsmää."),
|
||||
("Permissions", "Oikeudet"),
|
||||
("Accept", "Hyväksy"),
|
||||
("Dismiss", "Hylkää"),
|
||||
("Disconnect", "Katkaise yhteys"),
|
||||
("Enable file copy and paste", "Salli tiedostojen kopiointi ja liittäminen"),
|
||||
("Connected", "Yhdistetty"),
|
||||
("Direct and encrypted connection", "Suora ja salattu yhteys"),
|
||||
("Relayed and encrypted connection", "Välitetty ja salattu yhteys"),
|
||||
("Direct and unencrypted connection", "Suora ja salaamaton yhteys"),
|
||||
("Relayed and unencrypted connection", "Välitetty ja salaamaton yhteys"),
|
||||
("Enter Remote ID", "Anna ID"),
|
||||
("Enter your password", "Syötä salasanasi"),
|
||||
("Logging in...", "Kirjaudutaan sisään..."),
|
||||
("Enable RDP session sharing", "Salli RDP istunnon jakaminen"),
|
||||
("Auto Login", "Automaattinen kirjautuminen"),
|
||||
("Enable direct IP access", "Salli suora IP yhteys"),
|
||||
("Rename", "Nimeä uudelleen"),
|
||||
("Space", "Välilyönti"),
|
||||
("Create desktop shortcut", "Luo työpöydän pikakuvake"),
|
||||
("Change Path", "Vaihda polku"),
|
||||
("Create Folder", "Luo kansio"),
|
||||
("Please enter the folder name", "Anna kansion nimi"),
|
||||
("Fix it", "Korjaa"),
|
||||
("Warning", "Varoitus"),
|
||||
("Login screen using Wayland is not supported", "Kirjautumisnäyttö Waylandilla ei ole tuettu"),
|
||||
("Reboot required", "Uudelleenkäynnistys vaaditaan"),
|
||||
("Unsupported display server", "Näyttöpalvelin ei ole tuettu"),
|
||||
("x11 expected", "X11 odotettu"),
|
||||
("Port", "Portti"),
|
||||
("Settings", "Asetukset"),
|
||||
("Username", "Käyttäjänimi"),
|
||||
("Invalid port", "Virheellinen portti"),
|
||||
("Closed manually by the peer", "Suljettu vastapuolen toimesta"),
|
||||
("Enable remote configuration modification", "Salli etäasetusten muokkaus"),
|
||||
("Run without install", "Suorita ilman asennusta"),
|
||||
("Connect via relay", "Yhdistä välityspalvelimen kautta"),
|
||||
("Always connect via relay", "Yhdistä aina välityspalvelimen kautta"),
|
||||
("whitelist_tip", "Vain sallitut IP osoitteet voivat muodostaa yhteyden"),
|
||||
("Login", "Kirjaudu sisään"),
|
||||
("Verify", "Vahvista"),
|
||||
("Remember me", "Muista minut"),
|
||||
("Trust this device", "Luota tähän laitteeseen"),
|
||||
("Verification code", "Vahvistuskoodi"),
|
||||
("verification_tip", "Vahvistuskoodi on lähetetty rekisteröityyn sähköpostiosoitteeseen. Syötä koodi jatkaaksesi kirjautumista."),
|
||||
("Logout", "Kirjaudu ulos"),
|
||||
("Tags", "Tunnisteet"),
|
||||
("Search ID", "Hae ID"),
|
||||
("whitelist_sep", "Valkoisen listan erotin"),
|
||||
("Add ID", "Lisää ID"),
|
||||
("Add Tag", "Lisää tunniste"),
|
||||
("Unselect all tags", "Poista kaikki tunnistevalinnat"),
|
||||
("Network error", "Verkkovirhe"),
|
||||
("Username missed", "Käyttäjänimi puuttuu"),
|
||||
("Password missed", "Salasana puuttuu"),
|
||||
("Wrong credentials", "Virheelliset kirjautumistiedot"),
|
||||
("The verification code is incorrect or has expired", "Vahvistuskoodi on virheellinen tai vanhentunut"),
|
||||
("Edit Tag", "Muokkaa tunnistetta"),
|
||||
("Forget Password", "Unohditko salasanasi"),
|
||||
("Favorites", "Suosikit"),
|
||||
("Add to Favorites", "Lisää suosikkeihin"),
|
||||
("Remove from Favorites", "Poista suosikeista"),
|
||||
("Empty", "Tyhjä"),
|
||||
("Invalid folder name", "Virheellinen kansion nimi"),
|
||||
("Socks5 Proxy", "Socks5 välityspalvelin"),
|
||||
("Socks5/Http(s) Proxy", "Socks5/HTTP(s)-välityspalvelin"),
|
||||
("Discovered", "Löydetty"),
|
||||
("install_daemon_tip", "Palvelun automaattista käynnistystä varten RustDesk daemon on asennettava järjestelmään."),
|
||||
("Remote ID", "Etätunnus"),
|
||||
("Paste", "Liitä"),
|
||||
("Paste here?", "Liitä tähän?"),
|
||||
("Are you sure to close the connection?", "Haluatko varmasti katkaista yhteyden?"),
|
||||
("Download new version", "Lataa uusi versio"),
|
||||
("Touch mode", "Kosketustila"),
|
||||
("Mouse mode", "Hiiritila"),
|
||||
("One-Finger Tap", "Yksi sormipainallus"),
|
||||
("Left Mouse", "Vasen hiiren painike"),
|
||||
("One-Long Tap", "Pitkä painallus yhdellä sormella"),
|
||||
("Two-Finger Tap", "Kahden sormen napautus"),
|
||||
("Right Mouse", "Oikea hiiren painike"),
|
||||
("One-Finger Move", "Yhden sormen liike"),
|
||||
("Double Tap & Move", "Kaksoisnapautus ja liike"),
|
||||
("Mouse Drag", "Vedä hiirellä"),
|
||||
("Three-Finger vertically", "Kolmen sormen pystysuora liike"),
|
||||
("Mouse Wheel", "Hiiren rulla"),
|
||||
("Two-Finger Move", "Kahden sormen liike"),
|
||||
("Canvas Move", "Siirrä näkymää"),
|
||||
("Pinch to Zoom", "Lähennä tai loitonna"),
|
||||
("Canvas Zoom", "Suurennus"),
|
||||
("Reset canvas", "Palauta näkymä"),
|
||||
("No permission of file transfer", "Ei oikeutta tiedostonsiirtoon"),
|
||||
("Note", "Huomautus"),
|
||||
("Connection", "Yhteys"),
|
||||
("Share screen", "Jaa näyttö"),
|
||||
("Chat", "Keskustelu"),
|
||||
("Total", "Yhteensä"),
|
||||
("items", "kohdetta"),
|
||||
("Selected", "Valittu"),
|
||||
("Screen Capture", "Näytön kaappaus"),
|
||||
("Input Control", "Tulon hallinta"),
|
||||
("Audio Capture", "Äänen tallennus"),
|
||||
("Do you accept?", "Hyväksytkö?"),
|
||||
("Open System Setting", "Avaa järjestelmäasetukset"),
|
||||
("How to get Android input permission?", "Kuinka myöntää Androidin oikeudet?"),
|
||||
("android_input_permission_tip1", "Siirry Androidin asetuksiin ja ota RustDeskille käyttöön 'Syötteen ohjaus' oikeus."),
|
||||
("android_input_permission_tip2", "Jos et löydä asetusta, etsi 'Esteettömyys' ja salli RustDesk ohjelman käyttö."),
|
||||
("android_new_connection_tip", "Uusi yhteyspyyntö vastaanotettu."),
|
||||
("android_service_will_start_tip", "RustDesk palvelu käynnistyy taustalla."),
|
||||
("android_stop_service_tip", "Pysäytä taustapalvelu tarvittaessa RustDeskin asetuksista."),
|
||||
("android_version_audio_tip", "Äänensiirto vaatii Android 10:n tai uudemman."),
|
||||
("android_start_service_tip", "RustDesk palvelu käynnistetään..."),
|
||||
("android_permission_may_not_change_tip", "Oikeudet eivät ehkä päivity heti. Käynnistä sovellus uudelleen, jos muutokset eivät tule voimaan."),
|
||||
("Account", "Tili"),
|
||||
("Overwrite", "Korvaa"),
|
||||
("This file exists, skip or overwrite this file?", "Tämä tiedosto on jo olemassa, ohitetaanko vai korvataanko se?"),
|
||||
("Quit", "Poistu"),
|
||||
("Help", "Ohje"),
|
||||
("Failed", "Epäonnistui"),
|
||||
("Succeeded", "Onnistui"),
|
||||
("Someone turns on privacy mode, exit", "Yksityisyystila otettu käyttöön, poistutaan"),
|
||||
("Unsupported", "Ei tuettu"),
|
||||
("Peer denied", "Vastapuoli hylkäsi pyynnön"),
|
||||
("Please install plugins", "Asenna tarvittavat lisäosat"),
|
||||
("Peer exit", "Vastapuoli sulki yhteyden"),
|
||||
("Failed to turn off", "Sammutus epäonnistui"),
|
||||
("Turned off", "Sammutettu"),
|
||||
("Language", "Kieli"),
|
||||
("Keep RustDesk background service", "Pidä RustDeskin taustapalvelu käynnissä"),
|
||||
("Ignore Battery Optimizations", "Ohita akun optimoinnit"),
|
||||
("android_open_battery_optimizations_tip", "Poista RustDeskin akkuoptimointi, jotta yhteys pysyy vakaana taustalla."),
|
||||
("Start on boot", "Käynnistä automaattisesti laitteen käynnistyessä"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Käynnistä näytönjakopalvelu laitteen käynnistyessä (vaatii erityisoikeudet)"),
|
||||
("Connection not allowed", "Yhteyttä ei sallita"),
|
||||
("Legacy mode", "Perinteinen tila"),
|
||||
("Map mode", "Karttatila"),
|
||||
("Translate mode", "Käännöstila"),
|
||||
("Use permanent password", "Käytä pysyvää salasanaa"),
|
||||
("Use both passwords", "Käytä molempia salasanoja"),
|
||||
("Set permanent password", "Aseta pysyvä salasana"),
|
||||
("Enable remote restart", "Salli etäuudelleenkäynnistys"),
|
||||
("Restart remote device", "Käynnistä etälaite uudelleen"),
|
||||
("Are you sure you want to restart", "Haluatko varmasti käynnistää laitteen uudelleen?"),
|
||||
("Restarting remote device", "Etälaitetta käynnistetään uudelleen"),
|
||||
("remote_restarting_tip", "Odota, kunnes etälaite käynnistyy uudelleen ja muodostaa yhteyden."),
|
||||
("Copied", "Kopioitu"),
|
||||
("Exit Fullscreen", "Poistu koko näytöstä"),
|
||||
("Fullscreen", "Koko näyttö"),
|
||||
("Mobile Actions", "Puhelin toiminnot"),
|
||||
("Select Monitor", "Valitse näyttö"),
|
||||
("Control Actions", "Ohjaustoiminnot"),
|
||||
("Display Settings", "Näyttöasetukset"),
|
||||
("Ratio", "Suhde"),
|
||||
("Image Quality", "Kuvanlaatu"),
|
||||
("Scroll Style", "Vieritystyyli"),
|
||||
("Show Toolbar", "Näytä työkalupalkki"),
|
||||
("Hide Toolbar", "Piilota työkalupalkki"),
|
||||
("Direct Connection", "Suora yhteys"),
|
||||
("Relay Connection", "Välitetty yhteys"),
|
||||
("Secure Connection", "Suojattu yhteys"),
|
||||
("Insecure Connection", "Suojaamaton yhteys"),
|
||||
("Scale original", "Skaalaa alkuperäinen"),
|
||||
("Scale adaptive", "Mukautuva skaalaus"),
|
||||
("General", "Yleiset"),
|
||||
("Security", "Turvallisuus"),
|
||||
("Theme", "Teema"),
|
||||
("Dark Theme", "Tumma teema"),
|
||||
("Light Theme", "Vaalea teema"),
|
||||
("Dark", "Tumma"),
|
||||
("Light", "Vaalea"),
|
||||
("Follow System", "Seuraa järjestelmän teemaa"),
|
||||
("Enable hardware codec", "Käytä laitteistokoodausta"),
|
||||
("Unlock Security Settings", "Avaa suojausasetukset"),
|
||||
("Enable audio", "Ota ääni käyttöön"),
|
||||
("Unlock Network Settings", "Avaa verkkoasetukset"),
|
||||
("Server", "Palvelin"),
|
||||
("Direct IP Access", "Suora IP yhteys"),
|
||||
("Proxy", "Välityspalvelin"),
|
||||
("Apply", "Käytä"),
|
||||
("Disconnect all devices?", "Katkaistaanko yhteys kaikkiin laitteisiin?"),
|
||||
("Clear", "Tyhjennä"),
|
||||
("Audio Input Device", "Äänitulolaite"),
|
||||
("Use IP Whitelisting", "Käytä IP sallitut listaa"),
|
||||
("Network", "Verkko"),
|
||||
("Pin Toolbar", "Kiinnitä työkalupalkki"),
|
||||
("Unpin Toolbar", "Irrota työkalupalkki"),
|
||||
("Recording", "Tallennus"),
|
||||
("Directory", "Hakemisto"),
|
||||
("Automatically record incoming sessions", "Tallenna saapuvat istunnot automaattisesti"),
|
||||
("Automatically record outgoing sessions", "Tallenna lähtevät istunnot automaattisesti"),
|
||||
("Change", "Vaihda"),
|
||||
("Start session recording", "Aloita istunnon tallennus"),
|
||||
("Stop session recording", "Lopeta istunnon tallennus"),
|
||||
("Enable recording session", "Ota istunnon tallennus käyttöön"),
|
||||
("Enable LAN discovery", "Ota LAN havaitseminen käyttöön"),
|
||||
("Deny LAN discovery", "Estä LAN havaitseminen"),
|
||||
("Write a message", "Kirjoita viesti"),
|
||||
("Prompt", "Kehote"),
|
||||
("Please wait for confirmation of UAC...", "Odota UAC hyväksyntää..."),
|
||||
("elevated_foreground_window_tip", "Käyttäjävalvontaikkuna on etualalla, hyväksy pyyntö etäkäytön jatkamiseksi."),
|
||||
("Disconnected", "Yhteys katkaistu"),
|
||||
("Other", "Muu"),
|
||||
("Confirm before closing multiple tabs", "Vahvista ennen useiden välilehtien sulkemista"),
|
||||
("Keyboard Settings", "Näppäimistöasetukset"),
|
||||
("Full Access", "Täysi käyttöoikeus"),
|
||||
("Screen Share", "Näytönjako"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland vaatii Ubuntu 21.04:n tai uudemman version."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vaatii uudemman Linux jakelun version. Kokeile X11 työpöytää tai vaihda käyttöjärjestelmää."),
|
||||
("JumpLink", "Pikalinkki"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Valitse jaettava näyttö (toiminto etäpäässä)."),
|
||||
("Show RustDesk", "Näytä RustDesk"),
|
||||
("This PC", "Tämä tietokone"),
|
||||
("or", "tai"),
|
||||
("Continue with", "Jatka käyttäen"),
|
||||
("Elevate", "Korota oikeudet"),
|
||||
("Zoom cursor", "Suurennusosoitin"),
|
||||
("Accept sessions via password", "Hyväksy istunnot salasanalla"),
|
||||
("Accept sessions via click", "Hyväksy istunnot napsauttamalla"),
|
||||
("Accept sessions via both", "Hyväksy istunnot kummallakin tavalla"),
|
||||
("Please wait for the remote side to accept your session request...", "Odota, että etäpää hyväksyy istuntopyyntösi..."),
|
||||
("One-time Password", "Kertakäyttösalasana"),
|
||||
("Use one-time password", "Käytä kertakäyttösalasanaa"),
|
||||
("One-time password length", "Kertakäyttösalasanan pituus"),
|
||||
("Request access to your device", "Pyydä pääsyä laitteeseesi"),
|
||||
("Hide connection management window", "Piilota yhteydenhallintaikkuna"),
|
||||
("hide_cm_tip", "Yhteydenhallintaikkuna voidaan piilottaa, jotta etäistunto ei keskeydy."),
|
||||
("wayland_experiment_tip", "Wayland tuki on kokeellinen ja saattaa aiheuttaa yhteysongelmia."),
|
||||
("Right click to select tabs", "Valitse välilehti hiiren oikealla painikkeella"),
|
||||
("Skipped", "Ohitettu"),
|
||||
("Add to address book", "Lisää osoitekirjaan"),
|
||||
("Group", "Ryhmä"),
|
||||
("Search", "Haku"),
|
||||
("Closed manually by web console", "Suljettu manuaalisesti verkkokonsolista"),
|
||||
("Local keyboard type", "Paikallinen näppäimistötyyppi"),
|
||||
("Select local keyboard type", "Valitse paikallinen näppäimistötyyppi"),
|
||||
("software_render_tip", "Jos laitteistokiihdytys ei toimi oikein, voit käyttää ohjelmistopohjaista renderöintiä."),
|
||||
("Always use software rendering", "Käytä aina ohjelmistopohjaista renderöintiä"),
|
||||
("config_input", "Syöteasetukset"),
|
||||
("config_microphone", "Mikrofoni"),
|
||||
("request_elevation_tip", "Etätoiminto vaatii järjestelmänvalvojan oikeudet."),
|
||||
("Wait", "Odota"),
|
||||
("Elevation Error", "Oikeuksien korotus epäonnistui"),
|
||||
("Ask the remote user for authentication", "Pyydä etäkäyttäjää vahvistamaan oikeudet"),
|
||||
("Choose this if the remote account is administrator", "Valitse tämä, jos etätili on järjestelmänvalvoja"),
|
||||
("Transmit the username and password of administrator", "Lähetä järjestelmänvalvojan käyttäjätunnus ja salasana"),
|
||||
("still_click_uac_tip", "Etäkäyttäjän on edelleen hyväksyttävä UAC kehote omalla koneellaan."),
|
||||
("Request Elevation", "Pyydä oikeuksien korotusta"),
|
||||
("wait_accept_uac_tip", "Odota, että etäkäyttäjä hyväksyy UAC pyynnön..."),
|
||||
("Elevate successfully", "Oikeuksien korotus onnistui"),
|
||||
("uppercase", "iso kirjain"),
|
||||
("lowercase", "pieni kirjain"),
|
||||
("digit", "numero"),
|
||||
("special character", "erikoismerkki"),
|
||||
("length>=8", "vähintään 8 merkkiä"),
|
||||
("Weak", "Heikko"),
|
||||
("Medium", "Keskitaso"),
|
||||
("Strong", "Vahva"),
|
||||
("Switch Sides", "Vaihda puolia"),
|
||||
("Please confirm if you want to share your desktop?", "Haluatko varmasti jakaa työpöytäsi?"),
|
||||
("Display", "Näyttö"),
|
||||
("Default View Style", "Oletusnäkymän tyyli"),
|
||||
("Default Scroll Style", "Oletusvieritys tyyli"),
|
||||
("Default Image Quality", "Oletuskuvanlaatu"),
|
||||
("Default Codec", "Oletuskoodekki"),
|
||||
("Bitrate", "Bittinopeus"),
|
||||
("FPS", "Kuvataajuus (FPS)"),
|
||||
("Auto", "Automaattinen"),
|
||||
("Other Default Options", "Muut oletusasetukset"),
|
||||
("Voice call", "Äänipuhelu"),
|
||||
("Text chat", "Tekstikeskustelu"),
|
||||
("Stop voice call", "Lopeta äänipuhelu"),
|
||||
("relay_hint_tip", "Jos suora yhteys ei toimi, käytetään automaattisesti välityspalvelinta."),
|
||||
("Reconnect", "Yhdistä uudelleen"),
|
||||
("Codec", "Koodekki"),
|
||||
("Resolution", "Resoluutio"),
|
||||
("No transfers in progress", "Ei käynnissä olevia siirtoja"),
|
||||
("Set one-time password length", "Aseta kertakäyttösalasanan pituus"),
|
||||
("RDP Settings", "RDP asetukset"),
|
||||
("Sort by", "Järjestä"),
|
||||
("New Connection", "Uusi yhteys"),
|
||||
("Restore", "Palauta"),
|
||||
("Minimize", "Pienennä"),
|
||||
("Maximize", "Suurenna"),
|
||||
("Your Device", "Sinun laitteesi"),
|
||||
("empty_recent_tip", "Ei äskettäisiä istuntoja"),
|
||||
("empty_favorite_tip", "Ei suosikkeja"),
|
||||
("empty_lan_tip", "LAN laitteita ei löytynyt"),
|
||||
("empty_address_book_tip", "Osoitekirja on tyhjä"),
|
||||
("Empty Username", "Tyhjä käyttäjänimi"),
|
||||
("Empty Password", "Tyhjä salasana"),
|
||||
("Me", "Minä"),
|
||||
("identical_file_tip", "Saman niminen tiedosto on jo olemassa"),
|
||||
("show_monitors_tip", "Näytä kaikki käytettävissä olevat näytöt"),
|
||||
("View Mode", "Näkymätila"),
|
||||
("login_linux_tip", "Kirjaudu sisään Linux käyttäjätunnuksellasi"),
|
||||
("verify_rustdesk_password_tip", "Vahvista RustDesk salasanasi kirjautumista varten"),
|
||||
("remember_account_tip", "Muista tilini kirjautumista varten"),
|
||||
("os_account_desk_tip", "Käytä käyttöjärjestelmän käyttäjätiliä kirjautumiseen"),
|
||||
("OS Account", "Käyttöjärjestelmän tili"),
|
||||
("another_user_login_title_tip", "Toinen käyttäjä on kirjautunut sisään"),
|
||||
("another_user_login_text_tip", "Etäistunto keskeytetään, koska toinen käyttäjä on ottanut hallinnan."),
|
||||
("xorg_not_found_title_tip", "Xorg ei löydy"),
|
||||
("xorg_not_found_text_tip", "X11 palvelinta ei löydetty. Vaihda Xorg ympäristöön jatkaaksesi."),
|
||||
("no_desktop_title_tip", "Työpöytää ei havaittu"),
|
||||
("no_desktop_text_tip", "Työpöytäympäristöä ei löydy. Asenna esimerkiksi GNOME tai XFCE."),
|
||||
("No need to elevate", "Oikeuksien korotusta ei tarvita"),
|
||||
("System Sound", "Järjestelmän ääni"),
|
||||
("Default", "Oletus"),
|
||||
("New RDP", "Uusi RDP yhteys"),
|
||||
("Fingerprint", "Sormenjälki"),
|
||||
("Copy Fingerprint", "Kopioi sormenjälki"),
|
||||
("no fingerprints", "Ei sormenjälkiä"),
|
||||
("Select a peer", "Valitse vastapää"),
|
||||
("Select peers", "Valitse useita vastapään laitteita"),
|
||||
("Plugins", "Laajennukset"),
|
||||
("Uninstall", "Poista asennus"),
|
||||
("Update", "Päivitä"),
|
||||
("Enable", "Ota käyttöön"),
|
||||
("Disable", "Poista käytöstä"),
|
||||
("Options", "Asetukset"),
|
||||
("resolution_original_tip", "Näytä alkuperäisessä resoluutiossa ilman skaalausta"),
|
||||
("resolution_fit_local_tip", "Sovita etänäyttö paikalliseen näkymään"),
|
||||
("resolution_custom_tip", "Käytä mukautettua resoluutiota"),
|
||||
("Collapse toolbar", "Tiivistä työkalupalkki"),
|
||||
("Accept and Elevate", "Hyväksy ja korota oikeudet"),
|
||||
("accept_and_elevate_btn_tooltip", "Hyväksy ja korota oikeudet järjestelmänvalvojaksi"),
|
||||
("clipboard_wait_response_timeout_tip", "Leikepöydän pyyntö aikakatkaistiin – ei vastausta etäpäästä."),
|
||||
("Incoming connection", "Saapuva yhteys"),
|
||||
("Outgoing connection", "Lähtevä yhteys"),
|
||||
("Exit", "Poistu"),
|
||||
("Open", "Avaa"),
|
||||
("logout_tip", "Haluatko varmasti kirjautua ulos?"),
|
||||
("Service", "Palvelu"),
|
||||
("Start", "Käynnistä"),
|
||||
("Stop", "Pysäytä"),
|
||||
("exceed_max_devices", "Olet saavuttanut hallittavien laitteiden enimmäismäärän."),
|
||||
("Sync with recent sessions", "Synkronoi viimeisimpiin istuntoihin"),
|
||||
("Sort tags", "Järjestä tunnisteet"),
|
||||
("Open connection in new tab", "Avaa yhteys uuteen välilehteen"),
|
||||
("Move tab to new window", "Siirrä välilehti uuteen ikkunaan"),
|
||||
("Can not be empty", "Ei voi olla tyhjä"),
|
||||
("Already exists", "On jo olemassa"),
|
||||
("Change Password", "Vaihda salasana"),
|
||||
("Refresh Password", "Päivitä salasana"),
|
||||
("ID", "Tunnus"),
|
||||
("Grid View", "Ruudukkonäkymä"),
|
||||
("List View", "Luettelonäkymä"),
|
||||
("Select", "Valitse"),
|
||||
("Toggle Tags", "Näytä/piilota tunnisteet"),
|
||||
("pull_ab_failed_tip", "Osoitekirjan lataus epäonnistui palvelimelta."),
|
||||
("push_ab_failed_tip", "Osoitekirjan lähetys palvelimelle epäonnistui."),
|
||||
("synced_peer_readded_tip", "Synkronoitu laite lisättiin uudelleen."),
|
||||
("Change Color", "Vaihda väri"),
|
||||
("Primary Color", "Pääväri"),
|
||||
("HSV Color", "HSV väriarvot"),
|
||||
("Installation Successful!", "Asennus onnistui!"),
|
||||
("Installation failed!", "Asennus epäonnistui!"),
|
||||
("Reverse mouse wheel", "Käänteinen hiiren rullaussuunta"),
|
||||
("{} sessions", "{} istuntoa"),
|
||||
("scam_title", "Huijausvaroitus"),
|
||||
("scam_text1", "Älä anna tuntemattomille henkilöille pääsyä tietokoneeseesi."),
|
||||
("scam_text2", "RustDesk ei koskaan pyydä maksua tai etäkäyttöä ilman lupaasi."),
|
||||
("Don't show again", "Älä näytä uudelleen"),
|
||||
("I Agree", "Hyväksyn"),
|
||||
("Decline", "Hylkää"),
|
||||
("Timeout in minutes", "Aikakatkaisu minuuteissa"),
|
||||
("auto_disconnect_option_tip", "Katkaise yhteys automaattisesti, jos ei aktiivisuutta määräaikaan mennessä."),
|
||||
("Connection failed due to inactivity", "Yhteys epäonnistui toimettomuuden vuoksi"),
|
||||
("Check for software update on startup", "Tarkista ohjelmistopäivitykset käynnistyksen yhteydessä"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Päivitä RustDesk Server Pro versioon {} jatkaaksesi."),
|
||||
("pull_group_failed_tip", "Ryhmäasetusten nouto epäonnistui."),
|
||||
("Filter by intersection", "Suodata leikkausten perusteella"),
|
||||
("Remove wallpaper during incoming sessions", "Poista taustakuva saapuvien istuntojen ajaksi"),
|
||||
("Test", "Testaa"),
|
||||
("display_is_plugged_out_msg", "Näyttö on irrotettu"),
|
||||
("No displays", "Ei näyttöjä"),
|
||||
("Open in new window", "Avaa uudessa ikkunassa"),
|
||||
("Show displays as individual windows", "Näytä näytöt erillisinä ikkunoina"),
|
||||
("Use all my displays for the remote session", "Käytä kaikkia näyttöjä etäistunnossa"),
|
||||
("selinux_tip", "SELinux saattaa estää etäyhteyden toiminnan. Tarkista asetukset."),
|
||||
("Change view", "Vaihda näkymä"),
|
||||
("Big tiles", "Suuret ruudut"),
|
||||
("Small tiles", "Pienet ruudut"),
|
||||
("List", "Lista"),
|
||||
("Virtual display", "Virtuaalinäyttö"),
|
||||
("Plug out all", "Irrota kaikki"),
|
||||
("True color (4:4:4)", "Tarkka väri (4:4:4)"),
|
||||
("Enable blocking user input", "Estä käyttäjän syöte etäpäässä"),
|
||||
("id_input_tip", "Anna etätunnus muodossa tunnus@palvelin"),
|
||||
("privacy_mode_impl_mag_tip", "Yksityisyystila käyttää suurennustekniikkaa piilottaakseen sisällön."),
|
||||
("privacy_mode_impl_virtual_display_tip", "Yksityisyystila käyttää virtuaalinäyttöä tietosuojan takaamiseksi."),
|
||||
("Enter privacy mode", "Siirry yksityisyystilaan"),
|
||||
("Exit privacy mode", "Poistu yksityisyystilasta"),
|
||||
("idd_not_support_under_win10_2004_tip", "Virtuaalinäyttöä ei tueta Windows 10 2004 versiota vanhemmissa järjestelmissä."),
|
||||
("input_source_1_tip", "Valitse syöte 1: fyysinen näppäimistö tai hiiri"),
|
||||
("input_source_2_tip", "Valitse syöte 2: virtuaalinen syöte"),
|
||||
("Swap control-command key", "Vaihda Ctrl ja Command näppäinten paikkaa"),
|
||||
("swap-left-right-mouse", "Vaihda hiiren vasen ja oikea painike"),
|
||||
("2FA code", "2FA koodi"),
|
||||
("More", "Lisää"),
|
||||
("enable-2fa-title", "Ota kaksivaiheinen todennus käyttöön"),
|
||||
("enable-2fa-desc", "Lisää turvallisuutta vahvistamalla kirjautumisesi 2FA koodilla."),
|
||||
("wrong-2fa-code", "Väärä 2FA koodi"),
|
||||
("enter-2fa-title", "Syötä 2FA koodi"),
|
||||
("Email verification code must be 6 characters.", "Sähköpostivarmennuskoodin on oltava 6 merkkiä pitkä."),
|
||||
("2FA code must be 6 digits.", "2FA koodin on oltava 6 numeroa."),
|
||||
("Multiple Windows sessions found", "Useita Windows istuntoja havaittu"),
|
||||
("Please select the session you want to connect to", "Valitse istunto, johon haluat muodostaa yhteyden"),
|
||||
("powered_by_me", "Ylpeästi kehitetty omavaraisesti"),
|
||||
("outgoing_only_desk_tip", "Tämä asennus tukee vain lähteviä yhteyksiä."),
|
||||
("preset_password_warning", "Esiasetettu salasana voi olla turvaton — vaihda se suojataksesi yhteytesi."),
|
||||
("Security Alert", "Turvailmoitus"),
|
||||
("My address book", "Oma osoitekirja"),
|
||||
("Personal", "Henkilökohtainen"),
|
||||
("Owner", "Omistaja"),
|
||||
("Set shared password", "Aseta jaettu salasana"),
|
||||
("Exist in", "Sisältyy kohteeseen"),
|
||||
("Read-only", "Vain luku"),
|
||||
("Read/Write", "Luku ja kirjoitus"),
|
||||
("Full Control", "Täysi hallinta"),
|
||||
("share_warning_tip", "Jakaminen antaa muille pääsyn laitteeseesi. Varmista, että luotat käyttäjään."),
|
||||
("Everyone", "Kaikki"),
|
||||
("ab_web_console_tip", "Osoitekirjaa voidaan hallita myös verkkokonsolin kautta."),
|
||||
("allow-only-conn-window-open-tip", "Salli vain yksi yhteyshallintaikkuna kerrallaan."),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Yksityisyystilaa ei tarvita, koska fyysisiä näyttöjä ei ole."),
|
||||
("Follow remote cursor", "Seuraa etäosoitinta"),
|
||||
("Follow remote window focus", "Seuraa etäikkunan kohdistusta"),
|
||||
("default_proxy_tip", "Käytetään oletusarvoista välityspalvelinta, ellei muuta määritetty."),
|
||||
("no_audio_input_device_tip", "Äänitulolaitetta ei löydy."),
|
||||
("Incoming", "Saapuva"),
|
||||
("Outgoing", "Lähtevä"),
|
||||
("Clear Wayland screen selection", "Tyhjennä Wayland näyttövalinta"),
|
||||
("clear_Wayland_screen_selection_tip", "Tyhjentää nykyisen Wayland näytön valinnan."),
|
||||
("confirm_clear_Wayland_screen_selection_tip", "Haluatko varmasti tyhjentää Wayland näyttövalinnan?"),
|
||||
("android_new_voice_call_tip", "Uusi äänipuhelu aloitettu"),
|
||||
("texture_render_tip", "Käytä tekstuuripohjaista renderöintiä paremman suorituskyvyn saavuttamiseksi."),
|
||||
("Use texture rendering", "Käytä tekstuurirenderöintiä"),
|
||||
("Floating window", "Kelluva ikkuna"),
|
||||
("floating_window_tip", "Kelluva ikkuna pysyy muiden sovellusten päällä etäistunnon aikana."),
|
||||
("Keep screen on", "Pidä näyttö päällä"),
|
||||
("Never", "Ei koskaan"),
|
||||
("During controlled", "Kun etäohjattuna"),
|
||||
("During service is on", "Kun palvelu on käynnissä"),
|
||||
("Capture screen using DirectX", "Kaappaa näyttö käyttämällä DirectX"),
|
||||
("Back", "Takaisin"),
|
||||
("Apps", "Sovellukset"),
|
||||
("Volume up", "Lisää äänenvoimakkuutta"),
|
||||
("Volume down", "Vähennä äänenvoimakkuutta"),
|
||||
("Power", "Virta"),
|
||||
("Telegram bot", "Telegram-botti"),
|
||||
("enable-bot-tip", "Ota Telegram botti käyttöön etähallintaa varten."),
|
||||
("enable-bot-desc", "Mahdollistaa ilmoitukset ja etätoiminnot Telegramin kautta."),
|
||||
("cancel-2fa-confirm-tip", "Haluatko varmasti poistaa kaksivaiheisen todennuksen käytöstä?"),
|
||||
("cancel-bot-confirm-tip", "Haluatko varmasti poistaa Telegram-botin käytöstä?"),
|
||||
("About RustDesk", "Tietoa RustDeskistä"),
|
||||
("Send clipboard keystrokes", "Lähetä leikepöydän näppäinsyötteet"),
|
||||
("network_error_tip", "Verkkovirhe – tarkista yhteys ja yritä uudelleen."),
|
||||
("Unlock with PIN", "Avaa PIN-koodilla"),
|
||||
("Requires at least {} characters", "Vaatii vähintään {} merkkiä"),
|
||||
("Wrong PIN", "Väärä PIN-koodi"),
|
||||
("Set PIN", "Aseta PIN-koodi"),
|
||||
("Enable trusted devices", "Ota luotetut laitteet käyttöön"),
|
||||
("Manage trusted devices", "Hallitse luotettuja laitteita"),
|
||||
("Platform", "Alusta"),
|
||||
("Days remaining", "Päiviä jäljellä"),
|
||||
("enable-trusted-devices-tip", "Vain luotetut laitteet voivat muodostaa yhteyden ilman lisävahvistusta."),
|
||||
("Parent directory", "Ylähakemisto"),
|
||||
("Resume", "Jatka"),
|
||||
("Invalid file name", "Virheellinen tiedostonimi"),
|
||||
("one-way-file-transfer-tip", "Tiedostonsiirto on yksisuuntainen – vain lähetys tai vastaanotto."),
|
||||
("Authentication Required", "Tunnistautuminen vaaditaan"),
|
||||
("Authenticate", "Tunnistaudu"),
|
||||
("web_id_input_tip", "Anna etätunnus verkkoliittymässä muodossa tunnus@palvelin"),
|
||||
("Download", "Lataa"),
|
||||
("Upload folder", "Lataa kansio"),
|
||||
("Upload files", "Lataa tiedostoja"),
|
||||
("Clipboard is synchronized", "Leikepöytä on synkronoitu"),
|
||||
("Update client clipboard", "Päivitä asiakkaan leikepöytä"),
|
||||
("Untagged", "Tunnisteeton"),
|
||||
("new-version-of-{}-tip", "Uusi versio sovelluksesta {} on saatavilla"),
|
||||
("Accessible devices", "Käytettävissä olevat laitteet"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "Päivitä etä-RustDesk-asiakasversioon {} yhteensopivuuden takaamiseksi"),
|
||||
("d3d_render_tip", "Käytä Direct3D-renderöintiä paremman suorituskyvyn saavuttamiseksi"),
|
||||
("Use D3D rendering", "Käytä D3D-renderöintiä"),
|
||||
("Printer", "Tulostin"),
|
||||
("printer-os-requirement-tip", "Tulostustoiminto vaatii yhteensopivan käyttöjärjestelmän"),
|
||||
("printer-requires-installed-{}-client-tip", "Tulostus vaatii, että {} asiakas on asennettu"),
|
||||
("printer-{}-not-installed-tip", "{} tulostinta ei ole asennettu"),
|
||||
("printer-{}-ready-tip", "{}-tulostin on valmis"),
|
||||
("Install {} Printer", "Asenna {} tulostin"),
|
||||
("Outgoing Print Jobs", "Lähtevät tulostustyöt"),
|
||||
("Incoming Print Jobs", "Saapuvat tulostustyöt"),
|
||||
("Incoming Print Job", "Saapuva tulostustyö"),
|
||||
("use-the-default-printer-tip", "Käytä oletustulostinta"),
|
||||
("use-the-selected-printer-tip", "Käytä valittua tulostinta"),
|
||||
("auto-print-tip", "Tulosta saapuvat työt automaattisesti"),
|
||||
("print-incoming-job-confirm-tip", "Hyväksytäänkö saapuvan tulostustyön tulostus?"),
|
||||
("remote-printing-disallowed-tile-tip", "Etätulostus estetty"),
|
||||
("remote-printing-disallowed-text-tip", "Etätulostus ei ole sallittu tässä laitteessa tai yhteydessä."),
|
||||
("save-settings-tip", "Tallenna asetukset"),
|
||||
("dont-show-again-tip", "Älä näytä uudelleen"),
|
||||
("Take screenshot", "Ota kuvakaappaus"),
|
||||
("Taking screenshot", "Otetaan kuvakaappausta"),
|
||||
("screenshot-merged-screen-not-supported-tip", "Yhdistetyn näytön kuvakaappaus ei ole tuettu"),
|
||||
("screenshot-action-tip", "Valitse, mitä haluat tehdä kuvakaappaukselle"),
|
||||
("Save as", "Tallenna nimellä"),
|
||||
("Copy to clipboard", "Kopioi leikepöydälle"),
|
||||
("Enable remote printer", "Ota etätulostin käyttöön"),
|
||||
("Downloading {}", "Ladataan {}"),
|
||||
("{} Update", "{} päivitys"),
|
||||
("{}-to-update-tip", "Päivitä sovellus {} jatkaaksesi"),
|
||||
("download-new-version-failed-tip", "Uuden version lataus epäonnistui"),
|
||||
("Auto update", "Automaattinen päivitys"),
|
||||
("update-failed-check-msi-tip", "Päivitys epäonnistui – tarkista MSI asennuspaketti"),
|
||||
("websocket_tip", "Käytä WebSocket protokollaa yhteyden muodostamiseen"),
|
||||
("Use WebSocket", "Käytä WebSocketia"),
|
||||
("Trackpad speed", "Kosketuslevyn nopeus"),
|
||||
("Default trackpad speed", "Oletusnopeus kosketuslevylle"),
|
||||
("Numeric one-time password", "Numeerinen kertakäyttösalasana"),
|
||||
("Enable IPv6 P2P connection", "Ota IPv6 P2P yhteys käyttöön"),
|
||||
("Enable UDP hole punching", "Ota käyttöön UDP hole punching tekniikka"),
|
||||
("View camera", "Näytä kamera"),
|
||||
("Enable camera", "Ota kamera käyttöön"),
|
||||
("No cameras", "Ei kameroita"),
|
||||
("view_camera_unsupported_tip", "Kameranäkymä ei ole tuettu tällä alustalla"),
|
||||
("Terminal", "Pääte"),
|
||||
("Enable terminal", "Ota pääte käyttöön"),
|
||||
("New tab", "Uusi välilehti"),
|
||||
("Keep terminal sessions on disconnect", "Säilytä pääteistunnot yhteyden katketessa"),
|
||||
("Terminal (Run as administrator)", "Pääte (Suorita järjestelmänvalvojana)"),
|
||||
("terminal-admin-login-tip", "Kirjaudu järjestelmänvalvojana käyttääksesi tätä päätettä"),
|
||||
("Failed to get user token.", "Käyttäjätunnuksen hakeminen epäonnistui."),
|
||||
("Incorrect username or password.", "Virheellinen käyttäjätunnus tai salasana."),
|
||||
("The user is not an administrator.", "Käyttäjä ei ole järjestelmänvalvoja."),
|
||||
("Failed to check if the user is an administrator.", "Järjestelmänvalvojan tarkistus epäonnistui."),
|
||||
("Supported only in the installed version.", "Tuettu vain asennetussa versiossa."),
|
||||
("elevation_username_tip", "Anna järjestelmänvalvojan käyttäjätunnus oikeuksien korotusta varten"),
|
||||
("Preparing for installation ...", "Valmistellaan asennusta..."),
|
||||
("Show my cursor", "Näytä osoittimeni"),
|
||||
("Scale custom", "Mukautettu skaalaus"),
|
||||
("Custom scale slider", "Mukautetun skaalauksen liukusäädin"),
|
||||
("Decrease", "Pienennä"),
|
||||
("Increase", "Suurenna"),
|
||||
("Show virtual mouse", "Näytä virtuaalinen hiiri"),
|
||||
("Virtual mouse size", "Virtuaalihiiren koko"),
|
||||
("Small", "Pieni"),
|
||||
("Large", "Suuri"),
|
||||
("Show virtual joystick", "Näytä virtuaalinen ohjain"),
|
||||
("Edit note", "Muokkaa muistiinpanoa"),
|
||||
("Alias", "Alias"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
@@ -332,8 +332,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Relay Connection", "Connexion via relais"),
|
||||
("Secure Connection", "Connexion sécurisée"),
|
||||
("Insecure Connection", "Connexion non sécurisée"),
|
||||
("Scale original", "Échelle 100 %"),
|
||||
("Scale adaptive", "Mise à l’échelle auto"),
|
||||
("Scale original", "Échelle originale"),
|
||||
("Scale adaptive", "Échelle adaptative"),
|
||||
("General", "Général"),
|
||||
("Security", "Sécurité"),
|
||||
("Theme", "Thème"),
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Saisissez un nom d’utilisateur ou un domaine\\utilisateur"),
|
||||
("Preparing for installation ...", "Préparation de l’installation…"),
|
||||
("Show my cursor", "Afficher mon curseur"),
|
||||
("Scale custom", "Échelle personnalisée"),
|
||||
("Custom scale slider", "Curseur d’échelle personnalisée"),
|
||||
("Decrease", "Diminuer"),
|
||||
("Increase", "Augmenter"),
|
||||
("Show virtual mouse", "Afficher la souris virtuelle"),
|
||||
("Virtual mouse size", "Taille de la souris virtuelle"),
|
||||
("Small", "Petite"),
|
||||
("Large", "Grande"),
|
||||
("Show virtual joystick", "Afficher le joystick virtuel"),
|
||||
("Edit note", "Modifier la note"),
|
||||
("Alias", "Alias"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "רמז_ליוזר_להעלאת_הרשאה"),
|
||||
("Preparing for installation ...", "הכנה להתקנה..."),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
115
src/lang/hu.rs
115
src/lang/hu.rs
@@ -36,13 +36,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Invalid server configuration", "Érvénytelen kiszolgáló-konfiguráció"),
|
||||
("Clipboard is empty", "A vágólap üres"),
|
||||
("Stop service", "Szolgáltatás leállítása"),
|
||||
("Change ID", "Azonosító megváltoztatása"),
|
||||
("Your new ID", "Az új azonosítója"),
|
||||
("Change ID", "Azonosító módosítása"),
|
||||
("Your new ID", "Az új azonosító"),
|
||||
("length %min% to %max%", "hossz %min% és %max% között"),
|
||||
("starts with a letter", "betűvel kezdődik"),
|
||||
("allowed characters", "engedélyezett karakterek"),
|
||||
("id_change_tip", "Csak a-z, A-Z, 0-9, - (kötőjel) csoportokba tartozó karakterek, illetve a _ karakter van engedélyezve. Az első karakternek mindenképpen a-z, A-Z csoportokba kell esnie. Az azonosító hosszúsága 6-tól, 16 karakter."),
|
||||
("Website", "Webhely"),
|
||||
("Website", "Weboldal"),
|
||||
("About", "Névjegy"),
|
||||
("Slogan_tip", "Szenvedéllyel programozva - egy káoszba süllyedő világban!"),
|
||||
("Privacy Statement", "Adatvédelmi nyilatkozat"),
|
||||
@@ -54,9 +54,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enhancements", "Fejlesztések"),
|
||||
("Hardware Codec", "Hardveres kodek"),
|
||||
("Adaptive bitrate", "Adaptív bitráta"),
|
||||
("ID Server", "ID kiszolgáló"),
|
||||
("ID Server", "ID-kiszolgáló"),
|
||||
("Relay Server", "Továbbító-kiszolgáló"),
|
||||
("API Server", "API kiszolgáló"),
|
||||
("API Server", "API-kiszolgáló"),
|
||||
("invalid_http", "A címnek mindenképpen http(s)://-el kell kezdődnie."),
|
||||
("Invalid IP", "A megadott IP-cím érvénytelen"),
|
||||
("Invalid format", "Érvénytelen formátum"),
|
||||
@@ -105,10 +105,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to delete this file?", "Biztosan törli ezt a fájlt?"),
|
||||
("Are you sure you want to delete this empty directory?", "Biztosan törli ezt az üres könyvtárat?"),
|
||||
("Are you sure you want to delete the file of this directory?", "Biztosan törli a könyvtár tartalmát?"),
|
||||
("Do this for all conflicts", "Tegye ezt minden ütközéskor"),
|
||||
("Do this for all conflicts", "Tegye ezt minden ütközés esetén"),
|
||||
("This is irreversible!", "Ez a művelet nem vonható vissza!"),
|
||||
("Deleting", "Törlés folyamatban"),
|
||||
("files", "fájlok"),
|
||||
("files", "fájl"),
|
||||
("Waiting", "Várakozás"),
|
||||
("Finished", "Befejezve"),
|
||||
("Speed", "Sebesség"),
|
||||
@@ -130,14 +130,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Show quality monitor", "Kijelző minőségének ellenőrzése"),
|
||||
("Disable clipboard", "Közös vágólap kikapcsolása"),
|
||||
("Lock after session end", "Távoli fiók zárolása a munkamenet végén"),
|
||||
("Insert Ctrl + Alt + Del", "Illessze be a Ctrl + Alt + Del"),
|
||||
("Insert Ctrl + Alt + Del", "Illessze be a Ctrl + Alt + Del billentyűzetkombinációt"),
|
||||
("Insert Lock", "Távoli fiók zárolása"),
|
||||
("Refresh", "Frissítés"),
|
||||
("ID does not exist", "Az azonosító nem létezik"),
|
||||
("Failed to connect to rendezvous server", "Nem sikerült kapcsolódni a kiszolgálóhoz"),
|
||||
("Please try later", "Próbálja meg később"),
|
||||
("Remote desktop is offline", "A távoli számítógép offline állapotban van"),
|
||||
("Key mismatch", "Eltérés a kulcsokban"),
|
||||
("Key mismatch", "Kulcseltérés"),
|
||||
("Timeout", "Időtúllépés"),
|
||||
("Failed to connect to relay server", "Nem sikerült kapcsolódni a továbbító-kiszolgálóhoz"),
|
||||
("Failed to connect via rendezvous server", "Nem sikerült kapcsolódni a kiszolgálón keresztül"),
|
||||
@@ -149,16 +149,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Click to upgrade", "Kattintson ide a frissítés telepítéséhez"),
|
||||
("Configure", "Beállítás"),
|
||||
("config_acc", "A számítógép távoli vezérléséhez a RustDesknek hozzáférési jogokat kell biztosítania."),
|
||||
("config_screen", "Ahhoz, hogy távolról hozzáférhessen számítógépéhez, meg kell adnia a RustDesknek a \"Képernyőfelvétel\" jogosultságot."),
|
||||
("config_screen", "Ahhoz, hogy távolról hozzáférhessen számítógépéhez, meg kell adnia a RustDesknek a „Képernyőfelvétel” jogosultságot."),
|
||||
("Installing ...", "Telepítés…"),
|
||||
("Install", "Telepítés"),
|
||||
("Installation", "Telepítés"),
|
||||
("Installation Path", "Telepítési útvonal"),
|
||||
("Create start menu shortcuts", "Start menü parancsikonok létrehozása"),
|
||||
("Create desktop icon", "Ikon létrehozása az asztalon"),
|
||||
("agreement_tip", "A telepítés folytatásával automatikusan elfogadásra kerül a licensz szerződés."),
|
||||
("agreement_tip", "A telepítés folytatásával automatikusan elfogadásra kerül a licenc szerződés."),
|
||||
("Accept and Install", "Elfogadás és telepítés"),
|
||||
("End-user license agreement", "Végfelhasználói licensz szerződés"),
|
||||
("End-user license agreement", "Végfelhasználói licenc szerződés"),
|
||||
("Generating ...", "Létrehozás…"),
|
||||
("Your installation is lower version.", "A telepített verzió alacsonyabb."),
|
||||
("not_close_tcp_tip", "Ne zárja be ezt az ablakot, amíg TCP-alagutat használ"),
|
||||
@@ -169,7 +169,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add", "Hozzáadás"),
|
||||
("Local Port", "Helyi port"),
|
||||
("Local Address", "Helyi cím"),
|
||||
("Change Local Port", "Helyi port megváltoztatása"),
|
||||
("Change Local Port", "Helyi port módosítása"),
|
||||
("setup_server_tip", "Gyorsabb kapcsolat érdekében, hozzon létre saját kiszolgálót"),
|
||||
("Too short, at least 6 characters.", "Túl rövid, legalább 6 karakter."),
|
||||
("The confirmation is not identical.", "A megerősítés nem volt azonos"),
|
||||
@@ -197,15 +197,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please enter the folder name", "Adja meg a mappa nevét"),
|
||||
("Fix it", "Javítás"),
|
||||
("Warning", "Figyelmeztetés"),
|
||||
("Login screen using Wayland is not supported", "Bejelentkezéskori Wayland használata nem támogatott"),
|
||||
("Login screen using Wayland is not supported", "A Wayland használatával történő bejelentkezési képernyő nem támogatott"),
|
||||
("Reboot required", "Újraindítás szükséges"),
|
||||
("Unsupported display server", "Nem támogatott megjelenítő kiszolgáló"),
|
||||
("x11 expected", "x11-re számítottt"),
|
||||
("x11 expected", "x11-re számított"),
|
||||
("Port", "Port"),
|
||||
("Settings", "Beállítások"),
|
||||
("Username", "Felhasználónév"),
|
||||
("Invalid port", "Érvénytelen port"),
|
||||
("Closed manually by the peer", "A kapcsolatot a másik fél kézileg bezárta"),
|
||||
("Closed manually by the peer", "A kapcsolatot a másik fél saját kezűleg bezárta"),
|
||||
("Enable remote configuration modification", "Távoli konfiguráció-módosítás engedélyezése"),
|
||||
("Run without install", "Futtatás telepítés nélkül"),
|
||||
("Connect via relay", "Kapcsolódás továbbító-kiszolgálón keresztül"),
|
||||
@@ -230,7 +230,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "Hibás felhasználónév vagy jelszó"),
|
||||
("The verification code is incorrect or has expired", "A hitelesítőkód érvénytelen vagy lejárt"),
|
||||
("Edit Tag", "Címke szerkesztése"),
|
||||
("Forget Password", "A jelszó megjegyzésének megszüntetése"),
|
||||
("Forget Password", "Jelszó elfelejtése"),
|
||||
("Favorites", "Kedvencek"),
|
||||
("Add to Favorites", "Hozzáadás a kedvencekhez"),
|
||||
("Remove from Favorites", "Eltávolítás a kedvencekből"),
|
||||
@@ -253,7 +253,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Two-Finger Tap", "Kétujjas érintés"),
|
||||
("Right Mouse", "Jobb egér gomb"),
|
||||
("One-Finger Move", "Egyujjas mozgatás"),
|
||||
("Double Tap & Move", "Dupla érintés, és mozgatás"),
|
||||
("Double Tap & Move", "Dupla érintés és mozgatás"),
|
||||
("Mouse Drag", "Mozgatás egérrel"),
|
||||
("Three-Finger vertically", "Három ujj függőlegesen"),
|
||||
("Mouse Wheel", "Egérgörgő"),
|
||||
@@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Share screen", "Képernyőmegosztás"),
|
||||
("Chat", "Csevegés"),
|
||||
("Total", "Összes"),
|
||||
("items", "elemek"),
|
||||
("items", "elem"),
|
||||
("Selected", "Kijelölve"),
|
||||
("Screen Capture", "Képernyőrögzítés"),
|
||||
("Input Control", "Távoli vezérlés"),
|
||||
@@ -276,13 +276,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Do you accept?", "Elfogadás?"),
|
||||
("Open System Setting", "Rendszerbeállítások megnyitása"),
|
||||
("How to get Android input permission?", "Hogyan állítható be az Androidos beviteli engedély?"),
|
||||
("android_input_permission_tip1", "Ahhoz, hogy egy távoli eszköz vezérelhesse Android készülékét, engedélyeznie kell a RustDesk számára a \"Hozzáférhetőség\" szolgáltatás használatát."),
|
||||
("android_input_permission_tip1", "Ahhoz, hogy egy távoli eszköz vezérelhesse Android készülékét, engedélyeznie kell a RustDesk számára a „Hozzáférhetőség” szolgáltatás használatát."),
|
||||
("android_input_permission_tip2", "A következő rendszerbeállítások oldalon a letöltött alkalmazások menüponton belül, kapcsolja be a [RustDesk Input] szolgáltatást."),
|
||||
("android_new_connection_tip", "Új kérés érkezett, mely vezérelni szeretné az eszközét"),
|
||||
("android_service_will_start_tip", "A képernyőmegosztás aktiválása automatikusan elindítja a szolgáltatást, így más eszközök is vezérelhetik ezt az Android-eszközt."),
|
||||
("android_stop_service_tip", "A szolgáltatás leállítása automatikusan szétkapcsol minden létező kapcsolatot."),
|
||||
("android_version_audio_tip", "A jelenlegi Android verzió nem támogatja a hangrögzítést, frissítsen legalább Android 10-re, vagy egy újabb verzióra."),
|
||||
("android_start_service_tip", "A képernyőmegosztó szolgáltatás elindításához koppintson a \"Kapcsolási szolgáltatás indítása\" gombra, vagy aktiválja a \"Képernyőfelvétel\" engedélyt."),
|
||||
("android_start_service_tip", "A képernyőmegosztó szolgáltatás elindításához koppintson a „Kapcsolási szolgáltatás indítása” gombra, vagy aktiválja a „Képernyőfelvétel” engedélyt."),
|
||||
("android_permission_may_not_change_tip", "A meglévő kapcsolatok engedélyei csak új kapcsolódás után módosulnak."),
|
||||
("Account", "Fiók"),
|
||||
("Overwrite", "Felülírás"),
|
||||
@@ -303,7 +303,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívül hagyása"),
|
||||
("android_open_battery_optimizations_tip", "Ha le szeretné tiltani ezt a funkciót, lépjen a RustDesk alkalmazás beállításaiba, keresse meg az [Akkumulátorkímélő] lehetőséget és válassza a nincs korlátozás lehetőséget."),
|
||||
("Start on boot", "Indítás bekapcsoláskor"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Indítsa el a képernyőmegosztó szolgáltatást rendszerindításkor, speciális engedélyeket igényel"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "Indítsa el a képernyőmegosztó szolgáltatást rendszerindításkor, mely speciális engedélyeket is igényel"),
|
||||
("Connection not allowed", "A kapcsolódás nem engedélyezett"),
|
||||
("Legacy mode", "Kompatibilitási mód"),
|
||||
("Map mode", "Hozzárendelési mód"),
|
||||
@@ -352,7 +352,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Apply", "Alkalmaz"),
|
||||
("Disconnect all devices?", "Leválasztja az összes eszközt?"),
|
||||
("Clear", "Tisztítás"),
|
||||
("Audio Input Device", "Audio bemeneti eszköz"),
|
||||
("Audio Input Device", "Hangbemeneti eszköz"),
|
||||
("Use IP Whitelisting", "Engedélyezési lista használata"),
|
||||
("Network", "Hálózat"),
|
||||
("Pin Toolbar", "Eszköztár kitűzése"),
|
||||
@@ -361,10 +361,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Directory", "Könyvtár"),
|
||||
("Automatically record incoming sessions", "A bejövő munkamenetek automatikus rögzítése"),
|
||||
("Automatically record outgoing sessions", "A kimenő munkamenetek automatikus rögzítése"),
|
||||
("Change", "Változtatás"),
|
||||
("Start session recording", "Munkamenet rögzítés indítása"),
|
||||
("Stop session recording", "Munkamenet rögzítés leállítása"),
|
||||
("Enable recording session", "Munkamenet rögzítés engedélyezése"),
|
||||
("Change", "Módosítás"),
|
||||
("Start session recording", "Munkamenet-rögzítés indítása"),
|
||||
("Stop session recording", "Munkamenet-rögzítés leállítása"),
|
||||
("Enable recording session", "Munkamenet-rögzítés engedélyezése"),
|
||||
("Enable LAN discovery", "Felfedezés engedélyezése"),
|
||||
("Deny LAN discovery", "Felfedezés tiltása"),
|
||||
("Write a message", "Üzenet írása"),
|
||||
@@ -378,7 +378,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Full Access", "Teljes hozzáférés"),
|
||||
("Screen Share", "Képernyőmegosztás"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "A Waylandhez Ubuntu 21.04 vagy újabb verzió szükséges."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "A Wayland a Linux disztribúció magasabb verzióját igényli. Próbálja ki az X11 desktopot, vagy változtassa meg az operációs rendszert."),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "A Wayland a Linux disztribúció magasabb verzióját igényli. Próbálja ki az X11 asztali környezetet, vagy változtassa meg az operációs rendszert."),
|
||||
("JumpLink", "Hiperhivatkozás"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Válassza ki a megosztani kívánt képernyőt."),
|
||||
("Show RustDesk", "A RustDesk megjelenítése"),
|
||||
@@ -403,20 +403,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Add to address book", "Hozzáadás a címjegyzékhez"),
|
||||
("Group", "Csoport"),
|
||||
("Search", "Keresés"),
|
||||
("Closed manually by web console", "Kézzel bezárva a webkonzolon keresztül"),
|
||||
("Closed manually by web console", "Saját kezűleg bezárva a webkonzolon keresztül"),
|
||||
("Local keyboard type", "Helyi billentyűzet típusa"),
|
||||
("Select local keyboard type", "Helyi billentyűzet típusának kiválasztása"),
|
||||
("software_render_tip", "Ha Nvidia grafikus kártyát használ Linux alatt, és a távoli ablak a kapcsolat létrehozása után azonnal bezáródik, akkor a Nouveau nyílt forráskódú illesztőprogramra való váltás és a szoftveres renderelés használata segíthet. A szoftvert újra kell indítani."),
|
||||
("Always use software rendering", "Mindig szoftveres renderelést használjon"),
|
||||
("config_input", "Ahhoz, hogy a távoli asztalt a billentyűzettel vezérelhesse, a RustDesknek meg kell adnia a \"Bemenet figyelése\" jogosultságot."),
|
||||
("config_microphone", "Ahhoz, hogy távolról beszélhessen, meg kell adnia a RustDesknek a \"Hangfelvétel\" jogosultságot."),
|
||||
("software_render_tip", "Ha Nvidia grafikus kártyát használ Linux alatt, és a távoli ablak a kapcsolat létrehozása után azonnal bezáródik, akkor a Nouveau nyílt forráskódú illesztőprogramra való váltás és a szoftveres leképezés alkalmazása segíthet. A szoftvert újra kell indítani."),
|
||||
("Always use software rendering", "Mindig szoftveres leképezést használjon"),
|
||||
("config_input", "Ahhoz, hogy a távoli asztalt a billentyűzettel vezérelhesse, a RustDesknek meg kell adnia a „Bemenet figyelése” jogosultságot."),
|
||||
("config_microphone", "Ahhoz, hogy távolról beszélhessen, meg kell adnia a RustDesknek a „Hangfelvétel” jogosultságot."),
|
||||
("request_elevation_tip", "Akkor is kérhet megnövelt jogokat, ha valaki a partneroldalon van."),
|
||||
("Wait", "Várjon"),
|
||||
("Elevation Error", "Emelt szintű hozzáférési hiba"),
|
||||
("Ask the remote user for authentication", "Hitelesítés kérése a távoli felhasználótól"),
|
||||
("Choose this if the remote account is administrator", "Akkor válassza ezt, ha a távoli fiók rendszergazda"),
|
||||
("Transmit the username and password of administrator", "Küldje el a rendszergazda felhasználónevét és jelszavát"),
|
||||
("still_click_uac_tip", "A távoli felhasználónak továbbra is az \"Igen\" gombra kell kattintania a RustDesk UAC ablakában. Kattintson!"),
|
||||
("still_click_uac_tip", "A távoli felhasználónak továbbra is az „Igen” gombra kell kattintania a RustDesk UAC ablakában. Kattintson!"),
|
||||
("Request Elevation", "Emelt szintű jogok igénylése"),
|
||||
("wait_accept_uac_tip", "Várjon, amíg a távoli felhasználó elfogadja az UAC párbeszédet."),
|
||||
("Elevate successfully", "Emelt szintű jogok megadva"),
|
||||
@@ -442,7 +442,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Voice call", "Hanghívás"),
|
||||
("Text chat", "Szöveges csevegés"),
|
||||
("Stop voice call", "Hanghívás leállítása"),
|
||||
("relay_hint_tip", "Ha a közvetlen kapcsolat nem lehetséges, megpróbálhat kapcsolatot létesíteni egy továbbító-kiszolgálón keresztül.\nHa az első próbálkozáskor továbbító-kiszolgálón keresztüli kapcsolatot szeretne létrehozni, használhatja az \"/r\" utótagot. az azonosítóhoz vagy a \"Mindig továbbító-kiszolgálón keresztül kapcsolódom\" opcióhoz a legutóbbi munkamenetek listájában, ha van ilyen."),
|
||||
("relay_hint_tip", "Ha a közvetlen kapcsolat nem lehetséges, megpróbálhat kapcsolatot létesíteni egy továbbító-kiszolgálón keresztül.\nHa az első próbálkozáskor továbbító-kiszolgálón keresztüli kapcsolatot szeretne létrehozni, használhatja az „/r” utótagot. Az azonosítóhoz vagy a „Mindig továbbító-kiszolgálón keresztül kapcsolódom” opcióhoz a legutóbbi munkamenetek listájában, ha van ilyen."),
|
||||
("Reconnect", "Újrakapcsolódás"),
|
||||
("Codec", "Kodek"),
|
||||
("Resolution", "Felbontás"),
|
||||
@@ -456,7 +456,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Maximize", "Maximalizálás"),
|
||||
("Your Device", "Az Ön eszköze"),
|
||||
("empty_recent_tip", "Nincsenek aktuális munkamenetek!\nIdeje ütemezni egy újat."),
|
||||
("empty_favorite_tip", "Még nincs kedvenc távoli állomása?\nHagyja, hogy találjunk valakit, akivel kapcsolatba tud lépni, és add hozzá a kedvenceidhez!"),
|
||||
("empty_favorite_tip", "Még nincs kedvenc távoli állomása?\nHagyja, hogy találjunk valakit, akivel kapcsolatba tud lépni, és adja hozzá a kedvencekhez!"),
|
||||
("empty_lan_tip", "Úgy tűnik, még nem adott hozzá egyetlen távoli helyszínt sem."),
|
||||
("empty_address_book_tip", "Úgy tűnik, hogy jelenleg nincsenek távoli állomások a címjegyzékében."),
|
||||
("Empty Username", "Üres felhasználónév"),
|
||||
@@ -550,7 +550,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Open in new window", "Megnyitás új ablakban"),
|
||||
("Show displays as individual windows", "Kijelzők megjelenítése egyedi ablakokként"),
|
||||
("Use all my displays for the remote session", "Az összes kijelzőm használata a távoli munkamenethez"),
|
||||
("selinux_tip", "A SELinux engedélyezve van az eszközén, ami azt okozhatja, hogy a RustDesk nem fut megfelelően, mint ellenőrzött webhely."),
|
||||
("selinux_tip", "A SELinux engedélyezve van az eszközén, ami azt okozhatja, hogy a RustDesk nem fut megfelelően, mint ellenőrzött."),
|
||||
("Change view", "Nézet módosítása"),
|
||||
("Big tiles", "Nagy csempék"),
|
||||
("Small tiles", "Kis csempék"),
|
||||
@@ -559,7 +559,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Plug out all", "Kapcsolja ki az összeset"),
|
||||
("True color (4:4:4)", "Valódi szín (4:4:4)"),
|
||||
("Enable blocking user input", "Engedélyezze a felhasználói bevitel blokkolását"),
|
||||
("id_input_tip", "Megadhat egy azonosítót, egy közvetlen IP-címet vagy egy tartományt egy porttal (<domain>:<port>).\nHa egy másik kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a kiszolgáló címét (<id>@<kiszolgáló_cím>?key=<key_value>), például\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nHa egy nyilvános kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a \"<id>@public\" lehetőséget. in. A kulcsra nincs szükség nyilvános kiszolgálók esetén.\n\nHa az első kapcsolathoz továbbító-kiszolgálón keresztüli kapcsolatot akar kényszeríteni, adja hozzá az \"/r\" az azonosítót a végén, például \"9123456234/r\"."),
|
||||
("id_input_tip", "Megadhat egy azonosítót, egy közvetlen IP-címet vagy egy tartományt egy porttal (<domain>:<port>).\nHa egy másik kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a kiszolgáló címét (<id>@<kiszolgáló_cím>?key=<key_value>), például\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nHa egy nyilvános kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a „<id>@public” lehetőséget. A kulcsra nincs szükség nyilvános kiszolgálók esetén.\n\nHa az első kapcsolathoz továbbító-kiszolgálón keresztüli kapcsolatot akar kényszeríteni, adja hozzá az „/r” az azonosítót a végén, például „9123456234/r”."),
|
||||
("privacy_mode_impl_mag_tip", "1. mód"),
|
||||
("privacy_mode_impl_virtual_display_tip", "2. mód"),
|
||||
("Enter privacy mode", "Lépjen be az adatvédelmi módba"),
|
||||
@@ -577,7 +577,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("enter-2fa-title", "Kétfaktoros hitelesítés"),
|
||||
("Email verification code must be 6 characters.", "Az e-mailben kapott ellenőrző-kódnak 6 karakterből kell állnia."),
|
||||
("2FA code must be 6 digits.", "A 2FA-kódnak 6 számjegyűnek kell lennie."),
|
||||
("Multiple Windows sessions found", "Több Windows munkamenet található"),
|
||||
("Multiple Windows sessions found", "Több Windows-munkamenet található"),
|
||||
("Please select the session you want to connect to", "Válassza ki a munkamenetet, amelyhez kapcsolódni szeretne"),
|
||||
("powered_by_me", "Üzemeltető: RustDesk"),
|
||||
("outgoing_only_desk_tip", "Ez a RustDesk testre szabott kimenete.\nMás eszközökhöz kapcsolódhat, de más eszközök nem kapcsolódhatnak az Ön eszközéhez."),
|
||||
@@ -594,10 +594,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("share_warning_tip", "A fenti mezők megosztottak és mások számára is láthatóak."),
|
||||
("Everyone", "Mindenki"),
|
||||
("ab_web_console_tip", "További információk a webes konzolról"),
|
||||
("allow-only-conn-window-open-tip", "Csak akkor engedélyezze a kapcsolódást, ha a RustDesk ablak nyitva van."),
|
||||
("allow-only-conn-window-open-tip", "Csak akkor engedélyezze a kapcsolódást, ha a RustDesk ablaka nyitva van."),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Nincsenek fizikai képernyők; Nincs szükség az adatvédelmi üzemmód használatára."),
|
||||
("Follow remote cursor", "Kövesse a távoli kurzort"),
|
||||
("Follow remote window focus", "Kövesse a távoli ablak fókuszt"),
|
||||
("Follow remote window focus", "Kövesse a távoli ablakfókuszt"),
|
||||
("default_proxy_tip", "A szabványos protokoll és port SOCKS5 és 1080"),
|
||||
("no_audio_input_device_tip", "Nem található hangbemeneti eszköz."),
|
||||
("Incoming", "Bejövő"),
|
||||
@@ -606,8 +606,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("clear_Wayland_screen_selection_tip", "A képernyőválasztás törlése után újra kiválaszthatja a megosztandó képernyőt."),
|
||||
("confirm_clear_Wayland_screen_selection_tip", "Biztos, hogy törölni szeretné a Wayland képernyő kiválasztását?"),
|
||||
("android_new_voice_call_tip", "Új hanghívás-kérés érkezett. Ha elfogadja a megkeresést, a hang átvált hangkommunikációra."),
|
||||
("texture_render_tip", "Használja a textúra renderelést a képek simábbá tételéhez. Ezt az opciót kikapcsolhatja, ha renderelési problémái vannak."),
|
||||
("Use texture rendering", "Textúra renderelés használata"),
|
||||
("texture_render_tip", "Használja a textúra leképezést a képek simábbá tételéhez. Ezt az opciót kikapcsolhatja, ha leképezési problémái vannak."),
|
||||
("Use texture rendering", "Textúra leképezés használata"),
|
||||
("Floating window", "Lebegő ablak"),
|
||||
("floating_window_tip", "Segít, ha a RustDesk a háttérben fut."),
|
||||
("Keep screen on", "Tartsa a képernyőt bekapcsolva"),
|
||||
@@ -622,10 +622,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Power", "Teljesítmény"),
|
||||
("Telegram bot", "Telegram bot"),
|
||||
("enable-bot-tip", "Ha aktiválja ezt a funkciót, akkor a 2FA-kódot a botjától kaphatja meg. Kapcsolati értesítésként is használható."),
|
||||
("enable-bot-desc", "1. Nyisson csevegést @BotFather.\n2. Küldje el a \"/newbot\" parancsot. Miután ezt a lépést elvégezte, kap egy tokent.\n3. Indítson csevegést az újonnan létrehozott botjával. Küldjön egy olyan üzenetet, amely egy perjel (\"/\") kezdetű, pl. \"/hello\" az aktiváláshoz.\n"),
|
||||
("enable-bot-desc", "1. Nyisson csevegést @BotFather.\n2. Küldje el a „/newbot” parancsot. Miután ezt a lépést elvégezte, kap egy tokent.\n3. Indítson csevegést az újonnan létrehozott botjával. Küldjön egy olyan üzenetet, amely egy perjel („/”) kezdetű, pl. „/hello” az aktiváláshoz.\n"),
|
||||
("cancel-2fa-confirm-tip", "Biztosan le akarja mondani a 2FA-t?"),
|
||||
("cancel-bot-confirm-tip", "Biztosan le akarja mondani a Telegram botot?"),
|
||||
("About RustDesk", "RustDesk névjegye"),
|
||||
("About RustDesk", "A RustDesk névjegye"),
|
||||
("Send clipboard keystrokes", "Billentyűleütések küldése a vágólapra"),
|
||||
("network_error_tip", "Ellenőrizze a hálózati kapcsolatot, majd próbálja meg újra."),
|
||||
("Unlock with PIN", "Feloldás PIN-kóddal"),
|
||||
@@ -643,7 +643,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("one-way-file-transfer-tip", "Az egyirányú fájlátvitel engedélyezve van a vezérelt oldalon."),
|
||||
("Authentication Required", "Hitelesítés szükséges"),
|
||||
("Authenticate", "Hitelesítés"),
|
||||
("web_id_input_tip", "Azonos kiszolgálón lévő azonosítót adhat meg, a közvetlen IP elérés nem támogatott a webkliensben.\nHa egy másik kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a kiszolgáló címét (<id>@<kiszolgáló_cím>?key=<key_value>), például\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nHa egy nyilvános kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a \"<id>@public\" betűt. in. A kulcsra nincs szükség a nyilvános kiszolgálók esetében."),
|
||||
("web_id_input_tip", "Azonos kiszolgálón lévő azonosítót adhat meg, a közvetlen IP elérés nem támogatott a webkliensben.\nHa egy másik kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a kiszolgáló címét (<id>@<kiszolgáló_cím>?key=<key_value>), például\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nHa egy nyilvános kiszolgálón lévő eszközhöz szeretne hozzáférni, adja meg a „<id>@public” betűt. A kulcsra nincs szükség a nyilvános kiszolgálók esetében."),
|
||||
("Download", "Letöltés"),
|
||||
("Upload folder", "Mappa feltöltése"),
|
||||
("Upload files", "Fájlok feltöltése"),
|
||||
@@ -653,14 +653,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("new-version-of-{}-tip", "A(z) {} új verziója"),
|
||||
("Accessible devices", "Hozzáférhető eszközök"),
|
||||
("upgrade_remote_rustdesk_client_to_{}_tip", "Frissítse a RustDesk klienst {} vagy újabb verziójára a távoli oldalon!"),
|
||||
("d3d_render_tip", "D3D renderelés"),
|
||||
("Use D3D rendering", "D3D renderelés használata"),
|
||||
("d3d_render_tip", "D3D leképezés"),
|
||||
("Use D3D rendering", "D3D leképezés használata"),
|
||||
("Printer", "Nyomtató"),
|
||||
("printer-os-requirement-tip", "Nyomtató operációs rendszerének minimális rendszerkövetelménye"),
|
||||
("printer-requires-installed-{}-client-tip", "A nyomtatóhoz szükséges a(z) {} kliens telepítése"),
|
||||
("printer-{}-not-installed-tip", "A(z) {} nyomtató nincs telepítve"),
|
||||
("printer-{}-ready-tip", "A(z) {} nyomtató készen áll"),
|
||||
("Install {} Printer", "A(z) {} nyomtató nyomtató telepítése"),
|
||||
("Install {} Printer", "A(z) {} nyomtató telepítése"),
|
||||
("Outgoing Print Jobs", "Kimenő nyomtatási feladatok"),
|
||||
("Incoming Print Jobs", "Bejövő nyomtatási feladatok"),
|
||||
("Incoming Print Job", "Bejövő nyomtatási feladat"),
|
||||
@@ -682,9 +682,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Downloading {}", "Letöltés {}"),
|
||||
("{} Update", "{} Frissítés"),
|
||||
("{}-to-update-tip", "A {} bezárása és az új verzió telepítése."),
|
||||
("download-new-version-failed-tip", "Ha a letöltés sikertelen, akkor vagy újrapróbálkozhat, vagy a \"Letöltés\" gombra kattintva letöltheti a kiadási oldalról, és manuálisan frissíthet."),
|
||||
("download-new-version-failed-tip", "Ha a letöltés sikertelen, akkor vagy újrapróbálkozhat, vagy a „Letöltés” gombra kattintva letöltheti a kiadási oldalról, és manuálisan frissíthet."),
|
||||
("Auto update", "Automatikus frissítés"),
|
||||
("update-failed-check-msi-tip", "A telepítési módszer felismerése nem sikerült. Kérjük, kattintson a \"Letöltés\" gombra, hogy letöltse a kiadási oldalról, és manuálisan frissítse."),
|
||||
("update-failed-check-msi-tip", "A telepítési módszer felismerése nem sikerült. Kérjük, kattintson a „Letöltés” gombra, hogy letöltse a kiadási oldalról, és manuálisan frissítse."),
|
||||
("websocket_tip", "WebSocket használatakor csak a relé-kapcsolatok támogatottak."),
|
||||
("Use WebSocket", "WebSocket használata"),
|
||||
("Trackpad speed", "Érintőpad sebessége"),
|
||||
@@ -709,6 +709,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Supported only in the installed version.", "Csak a telepített változatban támogatott."),
|
||||
("elevation_username_tip", "Felhasználónév vagy tartománynév megadása\\felhasználónév"),
|
||||
("Preparing for installation ...", "Felkészülés a telepítésre ..."),
|
||||
("Show my cursor", ""),
|
||||
("Show my cursor", "Kurzor megjelenítése"),
|
||||
("Scale custom", "Egyéni méretarány"),
|
||||
("Custom scale slider", "Egyéni méretarány-csúszka"),
|
||||
("Decrease", "Csökkentés"),
|
||||
("Increase", "Növelés"),
|
||||
("Show virtual mouse", "Virtuális egér megjelenítése"),
|
||||
("Virtual mouse size", "Virtuális egér mérete"),
|
||||
("Small", "Kicsi"),
|
||||
("Large", "Nagy"),
|
||||
("Show virtual joystick", "Virtuális vezérlő megjelenítése"),
|
||||
("Edit note", "Jegyzet szerkesztése"),
|
||||
("Alias", "Álnév"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "Status"),
|
||||
("Your Desktop", "Desktop Anda"),
|
||||
("desk_tip", "Akses desktop anda dengan ID & Kata sandi ini"),
|
||||
("desk_tip", "Akses desktop kamu dengan ID & Kata sandi ini"),
|
||||
("Password", "Kata sandi"),
|
||||
("Ready", "Sudah siap"),
|
||||
("Established", "Didirikan"),
|
||||
@@ -244,7 +244,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Paste", "Tempel"),
|
||||
("Paste here?", "Tempel disini?"),
|
||||
("Are you sure to close the connection?", "Apakah kamu yakin akan menutup koneksi?"),
|
||||
("Download new version", "Unduh versi baru"),
|
||||
("Download new version", "Download versi baru"),
|
||||
("Touch mode", "Mode Layar Sentuh"),
|
||||
("Mouse mode", "Mode Mouse"),
|
||||
("One-Finger Tap", "Ketuk Satu Jari"),
|
||||
@@ -273,10 +273,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Screen Capture", "Tangkapan Layar"),
|
||||
("Input Control", "Kontrol input"),
|
||||
("Audio Capture", "Rekam Suara"),
|
||||
("Do you accept?", "Apakah anda setuju?"),
|
||||
("Do you accept?", "Apakah kamu setuju?"),
|
||||
("Open System Setting", "Buka Pengaturan Sistem"),
|
||||
("How to get Android input permission?", "Bagaimana cara mendapatkan izin input dari Android?"),
|
||||
("android_input_permission_tip1", "Agar perangkat jarak jauh dapat mengontrol perangkat Android Anda melalui mouse atau sentuhan, Anda harus mengizinkan RustDesk untuk menggunakan layanan \"Aksesibilitas\"."),
|
||||
("android_input_permission_tip1", "Agar perangkat jarak jauh dapat mengontrol perangkat Android melalui mouse atau sentuhan, Kamu harus memberikan izin/permission kd RustDesk untuk menggunakan layanan \"Aksesibilitas\"."),
|
||||
("android_input_permission_tip2", "Silakan buka halaman pengaturan sistem berikutnya, temukan dan masuk ke [Layanan Terinstal], aktifkan layanan [Input RustDesk]."),
|
||||
("android_new_connection_tip", "Permintaan akses remote telah diterima"),
|
||||
("android_service_will_start_tip", "Mengaktifkan \"Tangkapan Layar\" akan memulai secara otomatis, memungkinkan perangkat lain untuk meminta koneksi ke perangkat Anda."),
|
||||
@@ -620,7 +620,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Volume up", "Naikkan volume"),
|
||||
("Volume down", "Turunkan volume"),
|
||||
("Power", ""),
|
||||
("Telegram bot", ""),
|
||||
("Telegram bot", "Bot Telegram"),
|
||||
("enable-bot-tip", "Jika fitur ini diaktifkan, Kamu dapat menerima kode 2FA dari bot, serta mendapatkan notifikasi tentang koneksi."),
|
||||
("enable-bot-desc", "1. Buka chat dengan @BotFather.\n2. Kirim perintah \"/newbot\". Setelah menyelesaikan langkah ini, Kamu akan mendapatkan token\n3. Mulai percakapan dengan bot yang baru dibuat. Kirim pesan yang dimulai dengan garis miring (\"/\") seperti \"/hello\" untuk mengaktifkannya."),
|
||||
("cancel-2fa-confirm-tip", "Apakah Kamu yakin ingin membatalkan 2FA?"),
|
||||
@@ -647,7 +647,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Download", "Download"),
|
||||
("Upload folder", "Upload folder"),
|
||||
("Upload files", "Upload file"),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Clipboard is synchronized", "Clipboard disinkronisasi"),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", "Versi {} sudah tersedia."),
|
||||
@@ -668,47 +668,58 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("use-the-selected-printer-tip", ""),
|
||||
("auto-print-tip", ""),
|
||||
("print-incoming-job-confirm-tip", ""),
|
||||
("remote-printing-disallowed-tile-tip", ""),
|
||||
("remote-printing-disallowed-text-tip", ""),
|
||||
("save-settings-tip", ""),
|
||||
("dont-show-again-tip", ""),
|
||||
("Take screenshot", ""),
|
||||
("Taking screenshot", ""),
|
||||
("remote-printing-disallowed-tile-tip", "Remote Printing tidak diizinkan"),
|
||||
("remote-printing-disallowed-text-tip", "Komputer yang diakses tidak mengizinkan Remote Printing."),
|
||||
("save-settings-tip", "Simpan pengaturan"),
|
||||
("dont-show-again-tip", "Jangan tampilkan lagi"),
|
||||
("Take screenshot", "Ambil tangkapan layar"),
|
||||
("Taking screenshot", "Mengambil tangkapan layar"),
|
||||
("screenshot-merged-screen-not-supported-tip", ""),
|
||||
("screenshot-action-tip", ""),
|
||||
("Save as", ""),
|
||||
("Copy to clipboard", ""),
|
||||
("Enable remote printer", ""),
|
||||
("Downloading {}", ""),
|
||||
("{} Update", ""),
|
||||
("{}-to-update-tip", ""),
|
||||
("download-new-version-failed-tip", ""),
|
||||
("Auto update", ""),
|
||||
("Save as", "Simpan sebagai"),
|
||||
("Copy to clipboard", "Salin ke papan klip"),
|
||||
("Enable remote printer", "Aktifkan printer jarak jauh"),
|
||||
("Downloading {}", "Mendownload {}"),
|
||||
("{} Update", "Perbarui {}"),
|
||||
("{}-to-update-tip", "{} akan ditutup dan menginstal versi baru"),
|
||||
("download-new-version-failed-tip", "Gagal mendownload. Kamu bisa mencoba lagi nanti atau klik tombol \"Download\" melakukan download dari halaman rilis dan meningkatkan versi secara manual."),
|
||||
("Auto update", "Pembaruan otomatis"),
|
||||
("update-failed-check-msi-tip", ""),
|
||||
("websocket_tip", ""),
|
||||
("Use WebSocket", ""),
|
||||
("Trackpad speed", ""),
|
||||
("Default trackpad speed", ""),
|
||||
("Numeric one-time password", ""),
|
||||
("Enable IPv6 P2P connection", ""),
|
||||
("Enable UDP hole punching", ""),
|
||||
("Use WebSocket", "Gunakan WebSocket"),
|
||||
("Trackpad speed", "Kecepatan trackpad"),
|
||||
("Default trackpad speed", "Kecepatan default trackpad"),
|
||||
("Numeric one-time password", "Kata sandi sekali pakai numerik"),
|
||||
("Enable IPv6 P2P connection", "Aktifkan koneksi P2P IPv6"),
|
||||
("Enable UDP hole punching", "Aktifkan UDP hole punching"),
|
||||
("View camera", "Lihat Kamera"),
|
||||
("Enable camera", "Aktifkan kamera"),
|
||||
("No cameras", "Tidak ada kamera"),
|
||||
("view_camera_unsupported_tip", "Perangkat yang terhubung tidak mendukung tampilan kamera."),
|
||||
("Terminal", ""),
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("Terminal", "Terminal"),
|
||||
("Enable terminal", "Aktifkan terminal"),
|
||||
("New tab", "Tab baru"),
|
||||
("Keep terminal sessions on disconnect", "Pertahankan sesi terminal saat terputus"),
|
||||
("Terminal (Run as administrator)", "Terminal (Jalankan sebagai administrator)"),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only in the installed version.", ""),
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Failed to get user token.", "Gagal mendapatkan token pengguna."),
|
||||
("Incorrect username or password.", "Nama pengguna atau kata sandi salah."),
|
||||
("The user is not an administrator.", "Pengguna bukanlah administrator."),
|
||||
("Failed to check if the user is an administrator.", "Gagal memeriksa apakah pengguna adalah administrator."),
|
||||
("Supported only in the installed version.", "Hanya didukung pada versi yang terinstal."),
|
||||
("elevation_username_tip", "panduan_elevasi_nama_pengguna"),
|
||||
("Preparing for installation ...", "Mempersiapkan instalasi ..."),
|
||||
("Show my cursor", "Tampilkan kursor saya"),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -708,7 +708,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to check if the user is an administrator.", "Impossibile verificare se l'utente è un amministratore."),
|
||||
("Supported only in the installed version.", "Supportato solo nella versione installata."),
|
||||
("elevation_username_tip", "Inserisci Nome utente o dominio sorgente\\nome Utente"),
|
||||
("Preparing for installation ...", "Preparazione per l'installazione..."),
|
||||
("Preparing for installation ...", "Preparazione installazione..."),
|
||||
("Show my cursor", "Visualizza il mio cursore"),
|
||||
("Scale custom", "Scala personalizzata"),
|
||||
("Custom scale slider", "Cursore scala personalizzata"),
|
||||
("Decrease", "Diminuisci"),
|
||||
("Increase", "Aumenta"),
|
||||
("Show virtual mouse", "Visualizza mouse virtuale"),
|
||||
("Virtual mouse size", "Dimensione mouse virtuale"),
|
||||
("Small", "Piccola"),
|
||||
("Large", "Grande"),
|
||||
("Show virtual joystick", "Visualizza joystick virtuale"),
|
||||
("Edit note", "Modifica nota"),
|
||||
("Alias", "Alias"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
147
src/lang/ja.rs
147
src/lang/ja.rs
@@ -3,12 +3,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "状態"),
|
||||
("Your Desktop", "あなたのコンピューター"),
|
||||
("desk_tip", "下記のIDとパスワードであなたのコンピューターにアクセスできます。"),
|
||||
("desk_tip", "下記の ID とパスワードでこのコンピューターにアクセスできます。"),
|
||||
("Password", "パスワード"),
|
||||
("Ready", "準備完了"),
|
||||
("Established", "接続完了"),
|
||||
("connecting_status", "RuskDesk ネットワークに接続中..."),
|
||||
("Enable service", "サービスを有効化"),
|
||||
("connecting_status", "RustDesk ネットワークに接続中..."),
|
||||
("Enable service", "サービスを有効化する"),
|
||||
("Start service", "サービスを開始"),
|
||||
("Service is running", "サービスが実行されています"),
|
||||
("Service is not running", "サービスは停止しています"),
|
||||
@@ -23,10 +23,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Remove", "削除"),
|
||||
("Refresh random password", "ランダムパスワードを再生成"),
|
||||
("Set your own password", "パスワードを設定"),
|
||||
("Enable keyboard/mouse", "キーボード/マウスを有効化"),
|
||||
("Enable clipboard", "クリップボードを有効化"),
|
||||
("Enable file transfer", "ファイル転送を有効化"),
|
||||
("Enable TCP tunneling", "TCP トンネリングを有効化"),
|
||||
("Enable keyboard/mouse", "キーボード/マウスを有効化する"),
|
||||
("Enable clipboard", "クリップボードを有効化する"),
|
||||
("Enable file transfer", "ファイル転送を有効化する"),
|
||||
("Enable TCP tunneling", "TCP トンネリングを有効化する"),
|
||||
("IP Whitelisting", "IP ホワイトリスト"),
|
||||
("ID/Relay Server", "認証/中継サーバー"),
|
||||
("Import server config", "サーバー設定をインポート"),
|
||||
@@ -39,8 +39,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Change ID", "ID を変更"),
|
||||
("Your new ID", "新しい ID"),
|
||||
("length %min% to %max%", "%min%~%max% 文字の長さ"),
|
||||
("starts with a letter", "始まりがアルファベット"),
|
||||
("allowed characters", "使用可能な文字のみ"),
|
||||
("starts with a letter", "アルファベットで始まる"),
|
||||
("allowed characters", "使用可能な文字"),
|
||||
("id_change_tip", "使用できるのは大文字・小文字のアルファベット、数字、アンダースコア (_) のみです。先頭の文字はアルファベット、長さは 6 文字から 16 文字である必要があります。"),
|
||||
("Website", "公式サイト"),
|
||||
("About", "RustDesk について"),
|
||||
@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Audio Input", "オーディオ入力"),
|
||||
("Enhancements", "拡張機能"),
|
||||
("Hardware Codec", "ハードウェアコーデック"),
|
||||
("Adaptive bitrate", "可変ビットレート"),
|
||||
("Adaptive bitrate", "可変ビットレートを使用する"),
|
||||
("ID Server", "認証サーバー"),
|
||||
("Relay Server", "中継サーバー"),
|
||||
("API Server", "API サーバー"),
|
||||
@@ -86,7 +86,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Type", "種類"),
|
||||
("Modified", "最終更新日"),
|
||||
("Size", "サイズ"),
|
||||
("Show Hidden Files", "隠しファイルを表示"),
|
||||
("Show Hidden Files", "隠しファイルを表示する"),
|
||||
("Receive", "受信"),
|
||||
("Send", "送信"),
|
||||
("Refresh File", "ファイルを更新"),
|
||||
@@ -112,7 +112,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Waiting", "待機中"),
|
||||
("Finished", "完了"),
|
||||
("Speed", "速度"),
|
||||
("Custom Image Quality", "画質をカスタムする"),
|
||||
("Custom Image Quality", "カスタム画質"),
|
||||
("Privacy mode", "プライバシーモード"),
|
||||
("Block user input", "ユーザーの入力をブロック"),
|
||||
("Unblock user input", "ユーザーの入力を許可"),
|
||||
@@ -122,13 +122,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stretch", "伸縮"),
|
||||
("Scrollbar", "スクロールバー"),
|
||||
("ScrollAuto", "自動スクロール"),
|
||||
("Good image quality", "画質優先"),
|
||||
("Good image quality", "画質を優先"),
|
||||
("Balanced", "バランス"),
|
||||
("Optimize reaction time", "速度優先"),
|
||||
("Optimize reaction time", "速度を優先"),
|
||||
("Custom", "カスタム"),
|
||||
("Show remote cursor", "リモートコンピューターのカーソルを表示"),
|
||||
("Show quality monitor", "品質ディスプレイを表示"),
|
||||
("Disable clipboard", "クリップボードを無効化"),
|
||||
("Show remote cursor", "リモートコンピューターのカーソルを表示する"),
|
||||
("Show quality monitor", "ディスプレイの品質を表示する"),
|
||||
("Disable clipboard", "クリップボードを無効化する"),
|
||||
("Lock after session end", "セッション終了後にロックする"),
|
||||
("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del を送信"),
|
||||
("Insert Lock", "ロック命令を送信"),
|
||||
@@ -136,7 +136,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ID does not exist", "ID が存在しません"),
|
||||
("Failed to connect to rendezvous server", "ランデブーサーバーに接続できませんでした"),
|
||||
("Please try later", "後でもう一度お試しください"),
|
||||
("Remote desktop is offline", "リモートデスクトッはオフラインです"),
|
||||
("Remote desktop is offline", "リモートデスクトップはオフラインです"),
|
||||
("Key mismatch", "キーが一致しません"),
|
||||
("Timeout", "タイムアウト"),
|
||||
("Failed to connect to relay server", "中継サーバーに接続できませんでした"),
|
||||
@@ -167,17 +167,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Remote Port", "リモートポート"),
|
||||
("Action", "操作"),
|
||||
("Add", "追加"),
|
||||
("Local Port", "ローカルのポート"),
|
||||
("Local Address", "ローカルポート"),
|
||||
("Local Port", "ローカルポート"),
|
||||
("Local Address", "ローカルアドレス"),
|
||||
("Change Local Port", "ローカルポートを変更"),
|
||||
("setup_server_tip", "より高速に接続したい場合は、自分のサーバーをセットアップすることをおすすめします"),
|
||||
("setup_server_tip", "より高速に接続したい場合は、自分のサーバーをセットアップすることを推奨します。"),
|
||||
("Too short, at least 6 characters.", "文字数が短すぎます。最低文字数は 6 文字です。"),
|
||||
("The confirmation is not identical.", "確認欄と入力が一致しません。"),
|
||||
("Permissions", "権限"),
|
||||
("Accept", "承諾"),
|
||||
("Dismiss", "無視"),
|
||||
("Dismiss", "却下"),
|
||||
("Disconnect", "切断"),
|
||||
("Enable file copy and paste", "ファイルのコピーと貼り付けを許可"),
|
||||
("Enable file copy and paste", "ファイルのコピーと貼り付けを許可する"),
|
||||
("Connected", "接続済み"),
|
||||
("Direct and encrypted connection", "直接接続: 接続は暗号化されています"),
|
||||
("Relayed and encrypted connection", "中継接続: 接続は暗号化されています"),
|
||||
@@ -186,9 +186,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enter Remote ID", "リモート ID を入力"),
|
||||
("Enter your password", "パスワードを入力"),
|
||||
("Logging in...", "ログイン中..."),
|
||||
("Enable RDP session sharing", "RDP セッション共有を有効化"),
|
||||
("Enable RDP session sharing", "RDP セッション共有を有効化する"),
|
||||
("Auto Login", "自動ログイン"),
|
||||
("Enable direct IP access", "直接 IP アクセスを有効化"),
|
||||
("Enable direct IP access", "直接 IP アクセスを有効化する"),
|
||||
("Rename", "名前の変更"),
|
||||
("Space", "スペース"),
|
||||
("Create desktop shortcut", "デスクトップにショートカットを作成する"),
|
||||
@@ -206,7 +206,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username", "ユーザー名"),
|
||||
("Invalid port", "無効なポート"),
|
||||
("Closed manually by the peer", "リモートホストによって切断されました"),
|
||||
("Enable remote configuration modification", "リモート設定の変更を有効化"),
|
||||
("Enable remote configuration modification", "リモート設定の変更を有効化する"),
|
||||
("Run without install", "インストールせずに実行"),
|
||||
("Connect via relay", "中継サーバー経由で接続"),
|
||||
("Always connect via relay", "常に中継サーバー経由で接続"),
|
||||
@@ -230,7 +230,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Wrong credentials", "資格情報が間違っています"),
|
||||
("The verification code is incorrect or has expired", "認証コードが間違っているか、有効期限が切れています"),
|
||||
("Edit Tag", "タグを編集"),
|
||||
("Forget Password", "パスワードを忘れる"),
|
||||
("Forget Password", "パスワードを忘れた"),
|
||||
("Favorites", "お気に入り"),
|
||||
("Add to Favorites", "お気に入りに追加"),
|
||||
("Remove from Favorites", "お気に入りから削除"),
|
||||
@@ -301,17 +301,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Language", "言語"),
|
||||
("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"),
|
||||
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
|
||||
("android_open_battery_optimizations_tip", "この機能を使わない場合は、RestDesk アプリの設定ページから「バッテリー」に進み、「制限しない」を選択してください。"),
|
||||
("android_open_battery_optimizations_tip", "この機能を使わない場合は、RustDesk アプリの設定ページから「バッテリー」に進み、「制限しない」を選択してください。"),
|
||||
("Start on boot", "起動時に自動実行する"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "起動時に画面共有サービスを開始します。これには特別な権限が必要です。"),
|
||||
("Connection not allowed", "接続が許可されていません"),
|
||||
("Legacy mode", "レガシーモード"),
|
||||
("Map mode", "マップモード"),
|
||||
("Translate mode", "変換モード"),
|
||||
("Use permanent password", "固定パスワードを使用"),
|
||||
("Use both passwords", "どちらのパスワードも使用"),
|
||||
("Use permanent password", "固定パスワードを使用する"),
|
||||
("Use both passwords", "両方のパスワードを使用する"),
|
||||
("Set permanent password", "固定パスワードを設定"),
|
||||
("Enable remote restart", "リモートからの再起動を有効化"),
|
||||
("Enable remote restart", "リモートからの再起動を有効化する"),
|
||||
("Restart remote device", "リモートの端末を再起動"),
|
||||
("Are you sure you want to restart", "本当に再起動しますか"),
|
||||
("Restarting remote device", "リモートデバイスを再起動中"),
|
||||
@@ -342,9 +342,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Dark", "ダーク"),
|
||||
("Light", "ライト"),
|
||||
("Follow System", "システム設定に従う"),
|
||||
("Enable hardware codec", "ハードウェアコーデックを有効化"),
|
||||
("Enable hardware codec", "ハードウェアコーデックを有効化する"),
|
||||
("Unlock Security Settings", "セキュリティ設定のロックを解除"),
|
||||
("Enable audio", "オーディオを有効化"),
|
||||
("Enable audio", "オーディオを有効化する"),
|
||||
("Unlock Network Settings", "ネットワーク設定のロックを解除"),
|
||||
("Server", "サーバー"),
|
||||
("Direct IP Access", "直接 IP 接続"),
|
||||
@@ -364,9 +364,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Change", "変更"),
|
||||
("Start session recording", "セッションの録画を開始"),
|
||||
("Stop session recording", "セッションの録画を停止"),
|
||||
("Enable recording session", "セッションの録画を有効化"),
|
||||
("Enable LAN discovery", "LAN の探索を有効化"),
|
||||
("Deny LAN discovery", "LAN の探索を拒否"),
|
||||
("Enable recording session", "セッションの録画を有効化する"),
|
||||
("Enable LAN discovery", "LAN の探索を有効化する"),
|
||||
("Deny LAN discovery", "LAN の探索を拒否する"),
|
||||
("Write a message", "メッセージを書き込む"),
|
||||
("Prompt", "必須"),
|
||||
("Please wait for confirmation of UAC...", "UAC の承認を待機しています..."),
|
||||
@@ -374,7 +374,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Disconnected", "切断しました"),
|
||||
("Other", "その他"),
|
||||
("Confirm before closing multiple tabs", "複数のタブを閉じる前に確認する"),
|
||||
("Keyboard Settings", "キーボード設定"),
|
||||
("Keyboard Settings", "キーボードの設定"),
|
||||
("Full Access", "フルアクセス"),
|
||||
("Screen Share", "画面共有"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland を使用するには、Ubuntu 21.04 以降のバージョンが必要です。"),
|
||||
@@ -386,10 +386,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("or", "または"),
|
||||
("Continue with", "で続行"),
|
||||
("Elevate", "昇格"),
|
||||
("Zoom cursor", "カーソルを拡大"),
|
||||
("Accept sessions via password", "パスワードによるセッションの許可"),
|
||||
("Accept sessions via click", "クリックによるセッションの承認"),
|
||||
("Accept sessions via both", "両方の方法でセッションを許可する"),
|
||||
("Zoom cursor", "カーソルを拡大する"),
|
||||
("Accept sessions via password", "パスワードでセッションを承認"),
|
||||
("Accept sessions via click", "クリックでセッションを承認"),
|
||||
("Accept sessions via both", "両方の方法でセッションを承認"),
|
||||
("Please wait for the remote side to accept your session request...", "リモートコンピューターがあなたのセッション要求を受け入れるまでお待ちください..."),
|
||||
("One-time Password", "ワンタイムパスワード"),
|
||||
("Use one-time password", "ワンタイムパスワードを使用する"),
|
||||
@@ -398,7 +398,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Hide connection management window", "接続管理画面を隠す"),
|
||||
("hide_cm_tip", "パスワードによるセッションを許可し、固定パスワードを使用する場合にのみ、管理画面の非表示を許可する。"),
|
||||
("wayland_experiment_tip", "Wayland のサポートは試験的なものです。無人アクセスを使用する場合はX11デスクトップをご利用ください。"),
|
||||
("Right click to select tabs", "右クリックでタフを選択"),
|
||||
("Right click to select tabs", "右クリックでタブを選択"),
|
||||
("Skipped", "スキップ"),
|
||||
("Add to address book", "アドレス帳に追加"),
|
||||
("Group", "グループ"),
|
||||
@@ -463,7 +463,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Empty Password", "空のパスワード"),
|
||||
("Me", "あなた"),
|
||||
("identical_file_tip", "このファイルはリモートコンピューターと同一です。"),
|
||||
("show_monitors_tip", "ツールバーにディスプレイを表示します"),
|
||||
("show_monitors_tip", "ツールバーにディスプレイを表示する"),
|
||||
("View Mode", "表示モード"),
|
||||
("login_linux_tip", "X デスクトップのセッションにログインするには、リモートコンピューターのLinuxアカウントにログインする必要があります。"),
|
||||
("verify_rustdesk_password_tip", "RustDesk のパスワードを確認する"),
|
||||
@@ -539,7 +539,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Timeout in minutes", "タイムアウトまでの時間 (分)"),
|
||||
("auto_disconnect_option_tip", "ユーザーが非アクティブの場合、自動的に受信したセッションを閉じる"),
|
||||
("Connection failed due to inactivity", "リモートデスクトップユーザーが非アクティブなため、接続に失敗しました"),
|
||||
("Check for software update on startup", "起動時にソフトウェアの更新をチェック"),
|
||||
("Check for software update on startup", "起動時にソフトウェアの更新を確認する"),
|
||||
("upgrade_rustdesk_server_pro_to_{}_tip", "RustDesk Server Pro をバージョン {} 以上にアップグレードしてください!"),
|
||||
("pull_group_failed_tip", "グループの更新に失敗しました"),
|
||||
("Filter by intersection", "交差位置でフィルター"),
|
||||
@@ -548,17 +548,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("display_is_plugged_out_msg", "ディスプレイが接続されていません。最初のディスプレイを選択してください。"),
|
||||
("No displays", "ディスプレイがありません"),
|
||||
("Open in new window", "新しいウィンドウで開く"),
|
||||
("Show displays as individual windows", "ディスプレイを別々のウィンドウとして表示"),
|
||||
("Show displays as individual windows", "ディスプレイを個別のウィンドウとして表示する"),
|
||||
("Use all my displays for the remote session", "すべてのディスプレイをセッションで使用する"),
|
||||
("selinux_tip", "SELinuxが有効になっているため、RustDesk が正常に動作しない可能性があります。"),
|
||||
("Change view", "表示変更"),
|
||||
("Change view", "表示を変更"),
|
||||
("Big tiles", "大きなタイル"),
|
||||
("Small tiles", "小さなタイル"),
|
||||
("List", "リスト"),
|
||||
("Virtual display", "仮想ディスプレイ"),
|
||||
("Plug out all", "すべて切断する"),
|
||||
("True color (4:4:4)", "True color (4:4:4)"),
|
||||
("Enable blocking user input", "ユーザー入力のブロックを有効化"),
|
||||
("Plug out all", "すべて切断"),
|
||||
("True color (4:4:4)", "True Color (4:4:4)"),
|
||||
("Enable blocking user input", "ユーザー入力のブロックを有効化する"),
|
||||
("id_input_tip", "ID、IPアドレス、またはドメインとポート番号(<ドメイン>:<ポート>)を使用できます。\n他のサーバーのデバイスにアクセスしたい場合は、サーバーアドレス(<id>@<サーバーアドレス>?key=<キーの値>)を追加してください。 \n(例: 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=)\nパブリックサーバーのデバイスに接続したい場合は、「<id>@public」のように入力してください。パブリックサーバーの場合、キーは不要です。\n\n初回接続で中継接続を行いたい場合は、「9123456234/r」のように末尾に「/r」を付けてください。"),
|
||||
("privacy_mode_impl_mag_tip", "モード 1"),
|
||||
("privacy_mode_impl_virtual_display_tip", "モード 2"),
|
||||
@@ -571,7 +571,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("swap-left-right-mouse", "マウスのクリックを入れ替える"),
|
||||
("2FA code", "二要素認証コード"),
|
||||
("More", "詳細"),
|
||||
("enable-2fa-title", "二要素認証を有効化"),
|
||||
("enable-2fa-title", "二要素認証を有効化する"),
|
||||
("enable-2fa-desc", "認証アプリをセットアップします。Authy、Microsoft または Google 認証システムなどが PC またはスマートフォンで利用できます。\n\nQR コードをスキャンし、アプリが表示するコードを入力することで二要素認証が有効になります。"),
|
||||
("wrong-2fa-code", "コードが違います。コードと端末の時刻設定が正しいかをご確認ください。"),
|
||||
("enter-2fa-title", "二要素認証"),
|
||||
@@ -596,8 +596,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Web コンソールの詳細"),
|
||||
("allow-only-conn-window-open-tip", "RustDesk のウィンドウが開いている場合のみ接続を許可する"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "物理ディスプレイが存在しないため、プライバシーモードは不要です。"),
|
||||
("Follow remote cursor", "リモートカーソルに追従"),
|
||||
("Follow remote window focus", "リモートウィンドウのフォーカスに追従"),
|
||||
("Follow remote cursor", "リモートカーソルに追従する"),
|
||||
("Follow remote window focus", "リモートウィンドウのフォーカスに追従する"),
|
||||
("default_proxy_tip", "既定のプロトコルとポートは Socks5 と 1080 です。"),
|
||||
("no_audio_input_device_tip", "オーディオ入力デバイスが見つかりません。"),
|
||||
("Incoming", "受信"),
|
||||
@@ -607,14 +607,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("confirm_clear_Wayland_screen_selection_tip", "本当に Wayland の画面選択をクリアしますか?"),
|
||||
("android_new_voice_call_tip", "新しい音声通話リクエストを受信しました。承認すると音声通話に切り替わります。"),
|
||||
("texture_render_tip", "テクスチャレンダリングを使用し、画像をより滑らかに描画します。レンダリングの問題が発生した場合は無効にしてみてください。"),
|
||||
("Use texture rendering", "テクスチャレンダリングを使用"),
|
||||
("Use texture rendering", "テクスチャレンダリングを使用する"),
|
||||
("Floating window", "フローティングウィンドウ"),
|
||||
("floating_window_tip", "RustDesk のバックグラウンドサービスを維持するために使用されます。"),
|
||||
("Keep screen on", "常に画面をオン"),
|
||||
("Never", "画面をオンにしない"),
|
||||
("During controlled", "操作中"),
|
||||
("During service is on", "サービスが動作中"),
|
||||
("Capture screen using DirectX", "DirectX を使用した画面キャプチャ"),
|
||||
("Capture screen using DirectX", "画面キャプチャに DirectX を使用する"),
|
||||
("Back", "戻る"),
|
||||
("Apps", "アプリ"),
|
||||
("Volume up", "音量を上げる"),
|
||||
@@ -628,15 +628,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("About RustDesk", "RustDesk について"),
|
||||
("Send clipboard keystrokes", "クリップボードの内容をキー入力として送信する"),
|
||||
("network_error_tip", "ネットワーク接続を確認し、再度お試しください。"),
|
||||
("Unlock with PIN", "PIN でロックを解除"),
|
||||
("Unlock with PIN", "PIN でロックを解除する"),
|
||||
("Requires at least {} characters", "最低でも {} 文字が必要です"),
|
||||
("Wrong PIN", "PIN が間違っています"),
|
||||
("Set PIN", "PIN を設定"),
|
||||
("Enable trusted devices", "承認済みデバイスを有効化"),
|
||||
("Enable trusted devices", "承認済みデバイスを有効化する"),
|
||||
("Manage trusted devices", "承認済みデバイスの管理"),
|
||||
("Platform", "プラットフォーム"),
|
||||
("Days remaining", "残り日数"),
|
||||
("enable-trusted-devices-tip", "承認済デバイスで 2FA チェックをスキップします。"),
|
||||
("enable-trusted-devices-tip", "承認済みのデバイスで 2FA の確認をスキップします。"),
|
||||
("Parent directory", "親ディレクトリ"),
|
||||
("Resume", "再開"),
|
||||
("Invalid file name", "無効なファイル名"),
|
||||
@@ -659,14 +659,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("printer-os-requirement-tip", "プリンター送信機能は Windows 10 以降が必要です。"),
|
||||
("printer-requires-installed-{}-client-tip", "リモート印刷を使用するには、このデバイスに {} がインストールされている必要があります。"),
|
||||
("printer-{}-not-installed-tip", "{} のプリンターがインストールされていません。"),
|
||||
("printer-{}-ready-tip", "{} のプリンターがインストールされ、使用できる状態になりました。"),
|
||||
("printer-{}-ready-tip", "{} のプリンターがインストールされ、使用可能になっています。"),
|
||||
("Install {} Printer", " {} のプリンターをインストール"),
|
||||
("Outgoing Print Jobs", "送信印刷ジョブ"),
|
||||
("Incoming Print Jobs", "受信印刷ジョブ"),
|
||||
("Incoming Print Job", "受信印刷ジョブ"),
|
||||
("use-the-default-printer-tip", "既定のプリンターを使用します。"),
|
||||
("use-the-selected-printer-tip", "選択したプリンターを使用します。"),
|
||||
("auto-print-tip", "選択したプリンターを使用して自動的に印刷します。"),
|
||||
("use-the-default-printer-tip", "既定のプリンターを使用する"),
|
||||
("use-the-selected-printer-tip", "選択したプリンターを使用する"),
|
||||
("auto-print-tip", "選択したプリンターを使用して自動的に印刷する"),
|
||||
("print-incoming-job-confirm-tip", "リモートから印刷ジョブを受信しました。こちらで実行しますか?"),
|
||||
("remote-printing-disallowed-tile-tip", "リモート印刷は許可されていません"),
|
||||
("remote-printing-disallowed-text-tip", "コントロールされる側の権限の設定により、リモート印刷が拒否されました。"),
|
||||
@@ -678,26 +678,26 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("screenshot-action-tip", "スクリーンショットを続行する方法を選択してください。"),
|
||||
("Save as", "保存先"),
|
||||
("Copy to clipboard", "クリップボードにコピー"),
|
||||
("Enable remote printer", "リモートプリンターを有効化"),
|
||||
("Enable remote printer", "リモートプリンターを有効化する"),
|
||||
("Downloading {}", "{} をダウンロード中"),
|
||||
("{} Update", "{} を更新"),
|
||||
("{}-to-update-tip", "{} を終了して新しいバージョンがインストールされます。"),
|
||||
("download-new-version-failed-tip", "ダウンロードに失敗しました。もう一度お試しいただくか、「ダウンロード」ボタンをクリックしてリリースページからダウンロードし、手動でアップグレードしてください。"),
|
||||
("Auto update", "自動更新"),
|
||||
("Auto update", "ソフトウェアの自動更新を行う"),
|
||||
("update-failed-check-msi-tip", "インストール方法の確認に失敗しました。「ダウンロード」ボタンをクリックしてリリースページからダウンロードし、手動でアップグレードしてください。"),
|
||||
("websocket_tip", "WebSocket を使用する場合、リレー接続のみがサポートされます。"),
|
||||
("Use WebSocket", "WebSocket を使用する"),
|
||||
("Trackpad speed", "トラックパッドの速度"),
|
||||
("Default trackpad speed", "既定のトラックパッドの速度"),
|
||||
("Numeric one-time password", "数字のワンタイムパスワード"),
|
||||
("Enable IPv6 P2P connection", "IPv6 P2P 接続を有効化"),
|
||||
("Enable UDP hole punching", "UDP ホールパンチを有効化"),
|
||||
("Enable IPv6 P2P connection", "IPv6 P2P 接続を有効化する"),
|
||||
("Enable UDP hole punching", "UDP ホールパンチを有効化する"),
|
||||
("View camera", "カメラを表示"),
|
||||
("Enable camera", "カメラを有効化"),
|
||||
("Enable camera", "カメラを有効化する"),
|
||||
("No cameras", "カメラなし"),
|
||||
("view_camera_unsupported_tip", "リモートデバイスはカメラの表示をサポートしていません。"),
|
||||
("Terminal", "ターミナル"),
|
||||
("Enable terminal", "ターミナルを有効化"),
|
||||
("Enable terminal", "ターミナルを有効化する"),
|
||||
("New tab", "新しいタブ"),
|
||||
("Keep terminal sessions on disconnect", "切断時にターミナルセッションを維持する"),
|
||||
("Terminal (Run as administrator)", "管理者として実行"),
|
||||
@@ -709,6 +709,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Supported only in the installed version.", "インストールされたバージョンでのみサポートされます。"),
|
||||
("elevation_username_tip", "ユーザー名またはドメインのユーザー名を入力してください。"),
|
||||
("Preparing for installation ...", "インストールの準備中です..."),
|
||||
("Show my cursor", ""),
|
||||
("Show my cursor", "自分のカーソルを表示する"),
|
||||
("Scale custom", "カスタムスケーリング"),
|
||||
("Custom scale slider", "カスタムスケールのスライダー"),
|
||||
("Decrease", "縮小"),
|
||||
("Increase", "拡大"),
|
||||
("Show virtual mouse", "仮想マウスを表示する"),
|
||||
("Virtual mouse size", "仮想マウスのサイズ"),
|
||||
("Small", "小"),
|
||||
("Large", "中"),
|
||||
("Show virtual joystick", "仮想ジョイスティックを表示する"),
|
||||
("Edit note", "メモを編集"),
|
||||
("Alias", "エイリアス"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "사용자 이름 또는 도메인\\사용자 이름 입력"),
|
||||
("Preparing for installation ...", "설치 준비 중 ..."),
|
||||
("Show my cursor", "내 커서 표시"),
|
||||
("Scale custom", "사용자 지정 크기 조정"),
|
||||
("Custom scale slider", "사용자 지정 크기 조정 슬라이더"),
|
||||
("Decrease", "축소"),
|
||||
("Increase", "확대"),
|
||||
("Show virtual mouse", "가상 마우스 표시"),
|
||||
("Virtual mouse size", "가상 마우스 크기"),
|
||||
("Small", "작게"),
|
||||
("Large", "크게"),
|
||||
("Show virtual joystick", "가상 조이스틱 표시"),
|
||||
("Edit note", "노트 편집"),
|
||||
("Alias", "별명"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Ievadiet lietotājvārdu vai domēnu\\lietotājvārdu"),
|
||||
("Preparing for installation ...", "Gatavošanās instalēšanai..."),
|
||||
("Show my cursor", "Rādīt manu kursoru"),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", ""),
|
||||
("Preparing for installation ...", ""),
|
||||
("Show my cursor", ""),
|
||||
("Scale custom", ""),
|
||||
("Custom scale slider", ""),
|
||||
("Decrease", ""),
|
||||
("Increase", ""),
|
||||
("Show virtual mouse", ""),
|
||||
("Virtual mouse size", ""),
|
||||
("Small", ""),
|
||||
("Large", ""),
|
||||
("Show virtual joystick", ""),
|
||||
("Edit note", ""),
|
||||
("Alias", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -710,5 +710,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevation_username_tip", "Voer je gebruikersnaam of domeinnaam in"),
|
||||
("Preparing for installation ...", "Installatie voorbereiden ..."),
|
||||
("Show my cursor", "Toon mijn cursor"),
|
||||
("Scale custom", "Aangepaste schaal"),
|
||||
("Custom scale slider", "Aangepaste schuifregelaar voor schaal"),
|
||||
("Decrease", "Verlagen"),
|
||||
("Increase", "Verhogen"),
|
||||
("Show virtual mouse", "Virtuele muis weergeven"),
|
||||
("Virtual mouse size", "Virtuele muis grootte"),
|
||||
("Small", "Klein"),
|
||||
("Large", "Groot"),
|
||||
("Show virtual joystick", "Virtuele joystick weergeven"),
|
||||
("Edit note", "Opmerking bewerken"),
|
||||
("Alias", "Alias"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -709,6 +709,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Supported only in the installed version.", "Wspierane tylko dla zainstalowanej aplikacji."),
|
||||
("elevation_username_tip", "Podaj nazwę użytkownika lub domena\\użytkownik"),
|
||||
("Preparing for installation ...", "Przygotowywanie do instalacji ..."),
|
||||
("Show my cursor", ""),
|
||||
("Show my cursor", "Pokaż mój kursor"),
|
||||
("Scale custom", "Skala użytkownika"),
|
||||
("Custom scale slider", "Suwak skali użytkownika"),
|
||||
("Decrease", "Zmniejsz"),
|
||||
("Increase", "Zwiększ"),
|
||||
("Show virtual mouse", "Pokaż wirtualną mysz"),
|
||||
("Virtual mouse size", "Wielkość wirtualnego kursora myszy"),
|
||||
("Small", "Mały"),
|
||||
("Large", "Duży"),
|
||||
("Show virtual joystick", "Pokaz wirtualny joystick"),
|
||||
("Edit note", "Edytuj notatkę"),
|
||||
("Alias", "Alias"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user