mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-28 00:33:06 +08:00
Compare commits
17
Commits
latest
..
lan-direct
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25fb6ee051 | ||
|
|
20ede65038 | ||
|
|
bfb2e4a84e | ||
|
|
99bb63f641 | ||
|
|
4506c27e7c | ||
|
|
b07f457e76 | ||
|
|
b4acf0b597 | ||
|
|
9e7848b9dd | ||
|
|
ea3a2e1faa | ||
|
|
d1e4a31efb | ||
|
|
a6658452c2 | ||
|
|
e123d5f2cc | ||
|
|
1687816bde | ||
|
|
a9e3178042 | ||
|
|
0e052e6d7a | ||
|
|
922cd41f70 | ||
|
|
b15925f857 |
@@ -134,14 +134,8 @@ jobs:
|
|||||||
actions-cache-folder: .xmake-toolchain-cache
|
actions-cache-folder: .xmake-toolchain-cache
|
||||||
actions-cache-key: ${{ runner.os }}-${{ runner.arch }}-ubuntu24.04-android-xmake${{ env.XMAKE_MIN_VERSION }}
|
actions-cache-key: ${{ runner.os }}-${{ runner.arch }}-ubuntu24.04-android-xmake${{ env.XMAKE_MIN_VERSION }}
|
||||||
|
|
||||||
- name: Cache Xmake package repository
|
# Repository selection follows desktop Xmake behavior. Keep installed
|
||||||
uses: actions/cache@v5
|
# packages isolated; never reuse another platform's build/config.
|
||||||
with:
|
|
||||||
path: apps/android/.native/global/.xmake/repositories/xmake-repo
|
|
||||||
key: ${{ runner.os }}-${{ runner.arch }}-android-xmake-repo-v1-${{ hashFiles('apps/android/scripts/build_native.py') }}
|
|
||||||
|
|
||||||
# Cache installed packages only. build_native.py checks Xmake >= 3.1.1 and pins
|
|
||||||
# its own package repository; never reuse another platform's build/config.
|
|
||||||
- name: Cache native dependencies
|
- name: Cache native dependencies
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ jobs:
|
|||||||
EXPECTED_IOS_SDK: "26.5"
|
EXPECTED_IOS_SDK: "26.5"
|
||||||
# Exercise the minimum supported version; local builds may use newer Xmake.
|
# Exercise the minimum supported version; local builds may use newer Xmake.
|
||||||
XMAKE_MIN_VERSION: "3.1.1"
|
XMAKE_MIN_VERSION: "3.1.1"
|
||||||
XMAKE_REPO_REVISION: "121109352da272cd1a3d4ed52f2dcda34f839955"
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Extract version number
|
- name: Extract version number
|
||||||
@@ -160,15 +159,22 @@ jobs:
|
|||||||
uses: actions/cache@v5
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: apps/ios/.xmake/packages
|
path: apps/ios/.xmake/packages
|
||||||
key: "${{ runner.os }}-xmake${{ env.XMAKE_MIN_VERSION }}-deps-ios-arm64-source-v1-xcode26.6-xrepo1211093-min16-${{ github.run_id }}"
|
key: "${{ runner.os }}-xmake${{ env.XMAKE_MIN_VERSION }}-deps-ios-arm64-source-v2-xcode26.6-min16-${{ hashFiles('apps/ios/scripts/build_minirtc_ios.sh', 'xmake.lua', 'deps/submodules/minirtc/xmake.lua', 'deps/submodules/minirtc/thirdparty/**') }}-${{ github.run_id }}"
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-xmake${{ env.XMAKE_MIN_VERSION }}-deps-ios-arm64-source-v1-xcode26.6-xrepo1211093-min16-
|
${{ runner.os }}-xmake${{ env.XMAKE_MIN_VERSION }}-deps-ios-arm64-source-v2-xcode26.6-min16-${{ hashFiles('apps/ios/scripts/build_minirtc_ios.sh', 'xmake.lua', 'deps/submodules/minirtc/xmake.lua', 'deps/submodules/minirtc/thirdparty/**') }}-
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
|
|
||||||
|
- name: Install Python build tools
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Xmake only accepts the host Python when all three modules exist;
|
||||||
|
# otherwise it falls back to compiling another Python from source.
|
||||||
|
python3 -m pip install --upgrade pip setuptools wheel
|
||||||
|
|
||||||
- name: Install xmake
|
- name: Install xmake
|
||||||
uses: xmake-io/github-action-setup-xmake@v1
|
uses: xmake-io/github-action-setup-xmake@v1
|
||||||
with:
|
with:
|
||||||
@@ -181,16 +187,37 @@ jobs:
|
|||||||
python3 -B apps/ios/scripts/verify_native_build.py \
|
python3 -B apps/ios/scripts/verify_native_build.py \
|
||||||
--xmake-version "${RUNNER_TEMP}/xmake-version.txt"
|
--xmake-version "${RUNNER_TEMP}/xmake-version.txt"
|
||||||
|
|
||||||
- name: Pin xmake package repository
|
# Xcode buffers Run Script output until the phase finishes. Build the
|
||||||
|
# native archives here so dependency downloads and compiler commands are
|
||||||
|
# streamed to Actions; the Xcode phase then reuses the same build products.
|
||||||
|
- name: Build iOS native dependencies
|
||||||
|
timeout-minutes: 60
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
CONFIGURATION: Release
|
||||||
|
PLATFORM_NAME: iphoneos
|
||||||
|
CURRENT_ARCH: arm64
|
||||||
|
CROSSDESK_NATIVE_VERBOSE: "1"
|
||||||
run: |
|
run: |
|
||||||
xmake repo -u
|
set -o pipefail
|
||||||
XMAKE_REPO_DIR="${HOME}/.xmake/repositories/xmake-repo"
|
(
|
||||||
git -C "${XMAKE_REPO_DIR}" fetch --depth=1 origin "${XMAKE_REPO_REVISION}"
|
trap 'kill "${SLEEP_PID:-}" 2>/dev/null || true; exit 0' TERM INT
|
||||||
git -C "${XMAKE_REPO_DIR}" checkout --detach "${XMAKE_REPO_REVISION}"
|
while true; do
|
||||||
test "$(git -C "${XMAKE_REPO_DIR}" rev-parse HEAD)" = "${XMAKE_REPO_REVISION}"
|
sleep 60 &
|
||||||
|
SLEEP_PID=$!
|
||||||
|
wait "${SLEEP_PID}" || exit 0
|
||||||
|
echo "iOS native build still running at $(date -u +%FT%TZ)"
|
||||||
|
ps -axo pid,ppid,state,etime,pcpu,comm | \
|
||||||
|
awk 'NR == 1 || /xmake|clang|cmake|ninja|meson|python|git|curl|make|zsh/'
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
MONITOR_PID=$!
|
||||||
|
trap 'kill "${MONITOR_PID}" 2>/dev/null || true; wait "${MONITOR_PID}" 2>/dev/null || true' EXIT
|
||||||
|
apps/ios/scripts/build_minirtc_ios.sh 2>&1 | \
|
||||||
|
tee "${RUNNER_TEMP}/ios-native-build.log"
|
||||||
|
|
||||||
- name: Build CrossDesk Mobile
|
- name: Build CrossDesk Mobile
|
||||||
|
timeout-minutes: 30
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
DERIVED_DATA="${RUNNER_TEMP}/CrossDeskMobileDerivedData"
|
DERIVED_DATA="${RUNNER_TEMP}/CrossDeskMobileDerivedData"
|
||||||
@@ -206,7 +233,20 @@ jobs:
|
|||||||
COMPILER_INDEX_STORE_ENABLE=NO \
|
COMPILER_INDEX_STORE_ENABLE=NO \
|
||||||
MARKETING_VERSION="${IOS_VERSION_BASE}" \
|
MARKETING_VERSION="${IOS_VERSION_BASE}" \
|
||||||
IPHONEOS_DEPLOYMENT_TARGET="${IOS_DEPLOYMENT_TARGET}" \
|
IPHONEOS_DEPLOYMENT_TARGET="${IOS_DEPLOYMENT_TARGET}" \
|
||||||
build
|
build 2>&1 | tee "${RUNNER_TEMP}/ios-app-build.log"
|
||||||
|
|
||||||
|
- name: Upload iOS build diagnostics
|
||||||
|
if: ${{ failure() || cancelled() }}
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: ios-build-diagnostics-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
${{ runner.temp }}/ios-native-build.log
|
||||||
|
${{ runner.temp }}/ios-app-build.log
|
||||||
|
~/.xmake/cache/packages/**/logs/*.txt
|
||||||
|
~/.xmake/cache/packages/**/meson-logs/meson-log.txt
|
||||||
|
include-hidden-files: true
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
- name: Verify CrossDesk Mobile
|
- name: Verify CrossDesk Mobile
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ CrossDesk 让你从电脑、浏览器或手机连接远端电脑,也能通过
|
|||||||
|
|
||||||
### Android
|
### Android
|
||||||
|
|
||||||
原生控制端支持触控与外接键鼠、多屏切换、文件收发、文本剪贴板和会话画面设置。H.264 / AV1 优先使用硬件解码,不支持或运行失败时回退到软件解码;切换应用时优先通过画中画保持会话,画中画不可用时尝试前台服务保活。当前不提供手机被控或隐私屏功能。
|
原生控制端支持触控与外接键鼠、多屏切换、文件收发、文本剪贴板和会话画面设置。H.264 / AV1 优先使用硬件解码,不支持或运行失败时回退到软件解码;切换应用时优先通过画中画保持会话,画中画不可用时尝试前台服务保活。当前不提供手机被控功能。悬浮菜单中的“隐私屏”按钮可直接开启或关闭电脑本地遮罩,按钮按电脑确认的状态显示“隐私屏已开启”或“隐私屏已关闭”,开启与关闭使用相同配色,正常切换不显示额外文字,仅在异常时显示提示;开启时与桌面端一致屏蔽电脑本地键鼠。
|
||||||
|
|
||||||
[使用说明](docs/USER_GUIDE.md#android) · [源码构建](apps/android/README.md)
|
[使用说明](docs/USER_GUIDE.md#android) · [源码构建](apps/android/README.md)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -138,7 +138,7 @@ The native controller supports touch and a virtual mouse, multiple monitors, rem
|
|||||||
|
|
||||||
### Android
|
### Android
|
||||||
|
|
||||||
The native controller supports touch and external keyboards/mice, monitor switching, file transfers, text clipboard, and video settings. H.264 / AV1 use hardware decoding when available, with software fallback when unsupported or when decoding fails. Active sessions use Picture in Picture when switching apps, with a foreground service as a fallback when Picture in Picture is unavailable. Phone-as-host and privacy screens are not currently supported.
|
The native controller supports touch and external keyboards/mice, monitor switching, file transfers, text clipboard, and video settings. H.264 / AV1 use hardware decoding when available, with software fallback when unsupported or when decoding fails. Active sessions use Picture in Picture when switching apps, with a foreground service as a fallback when Picture in Picture is unavailable. Phone-as-host is not currently supported. The floating menu’s **Privacy screen** button toggles the computer’s local cover directly, labels the host-confirmed on/off state without changing colors, and shows inline feedback only for errors or unavailable status. Enabling privacy also blocks local keyboard/mouse input, matching the desktop controller.
|
||||||
|
|
||||||
[User guide](docs/USER_GUIDE_EN.md#android) · [Build from source](apps/android/README.md) (Chinese)
|
[User guide](docs/USER_GUIDE_EN.md#android) · [Build from source](apps/android/README.md) (Chinese)
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,12 @@
|
|||||||
- 48 kHz 单声道 PCM 播放,支持静音,音频队列有容量限制。
|
- 48 kHz 单声道 PCM 播放,支持静音,音频队列有容量限制。
|
||||||
- 相对鼠标(触控板)和绝对位置模式;单指点击、绝对模式按住拖动、长按右键,以及外接键盘、鼠标。画面手势与 iOS 一致:双指可在整个触控区域(包括画面外黑边)捏合,以触点为中心缩放(1–10 倍),放大后双指拖动画面,原始比例下双指上下滑动控制滚轮。相对鼠标移到可见画面的上、下边缘后继续向外移动,会反向上下平移画面,不触发左右平移;未放大时和按住虚拟鼠标键拖动时同样生效,停止移动即停止平移,抬手保留位置。移动到极限时,画面可见部分至少占当前显示区域高度的 20%,与缩放倍数无关;整幅画面高度不足该比例时完整保留,范围计入上下黑边;缩放结束及缩回 1 倍时保留平移位置,仅在超过可见边界时限制位置;切换显示器恢复居中。
|
- 相对鼠标(触控板)和绝对位置模式;单指点击、绝对模式按住拖动、长按右键,以及外接键盘、鼠标。画面手势与 iOS 一致:双指可在整个触控区域(包括画面外黑边)捏合,以触点为中心缩放(1–10 倍),放大后双指拖动画面,原始比例下双指上下滑动控制滚轮。相对鼠标移到可见画面的上、下边缘后继续向外移动,会反向上下平移画面,不触发左右平移;未放大时和按住虚拟鼠标键拖动时同样生效,停止移动即停止平移,抬手保留位置。移动到极限时,画面可见部分至少占当前显示区域高度的 20%,与缩放倍数无关;整幅画面高度不足该比例时完整保留,范围计入上下黑边;缩放结束及缩回 1 倍时保留平移位置,仅在超过可见边界时限制位置;切换显示器恢复居中。
|
||||||
- 横竖屏等比例显示、远端多显示器切换。与 iOS 一致的画面质量(低/中/高)、采集帧率(30/60 fps)、画面偏好;支持能力检测、电脑端确认、失败/5 秒超时回退及连续操作的应答排序。
|
- 横竖屏等比例显示、远端多显示器切换。与 iOS 一致的画面质量(低/中/高)、采集帧率(30/60 fps)、画面偏好;支持能力检测、电脑端确认、失败/5 秒超时回退及连续操作的应答排序。
|
||||||
- “设置 → 屏幕方向 → 连接后自动横屏”默认开启并保存在本机。手机进入会话后先保持横屏,检测到手机实际横过来后才恢复跟随设备方向;竖持或平放不会立即切回竖屏。关闭后沿用自动旋转,平板与多窗口不受此选项影响。退出会话恢复首页方向,返回同一后台会话不重复锁定已解锁的方向。
|
- “设置 → 屏幕方向 → 连接建立后锁定横屏”默认关闭并保存在本机。开启后,手机连接时进入并保持横屏;关闭时跟随设备方向。平板和多窗口不受此选项影响。退出会话恢复首页方向,返回后台会话沿用该会话的方向设置。已移除检测手机姿态后解除初始横屏的逻辑。
|
||||||
- 根据常见移动设备解码能力和屏幕尺寸设置起始参数:显示长边不超过 1280 像素时默认低画质(最高 720p)/30 fps,其余默认中画质(最高 1080p)/30 fps。用户保存的画面偏好不变,仍可手动选择高画质或 60 fps。
|
- 根据常见移动设备解码能力和屏幕尺寸设置起始参数:显示长边不超过 1280 像素时默认低画质(最高 720p)/30 fps,其余默认中画质(最高 1080p)/30 fps。用户保存的画面偏好不变,仍可手动选择高画质或 60 fps。
|
||||||
- 网络状态按视频、音频、数据、合计展示收发速率和丢包率,并显示实际提交帧率、分辨率、画面延时、平滑 RTT、P2P/TURN 和实际 SRTP 状态。每秒刷新,过期或无效测量显示“—”。
|
- 网络状态按视频、音频、数据、合计展示收发速率和丢包率,并显示实际提交帧率、分辨率、画面延时、平滑 RTT、P2P/TURN 和实际 SRTP 状态。每秒刷新,过期或无效测量显示“—”。
|
||||||
- 可拖动的悬浮控制球、三列控制菜单;默认使用系统输入法,保留候选词、纠错和听写,确认文字后直接输入远端。电脑键盘固定底部,分“字母数字 / 符号功能”两页,共用末两行布局,按系统键盘高度适配横竖屏;统一切换、收起按钮和 Android 键盘触感反馈。
|
- 可拖动的悬浮控制球、三列控制菜单;默认使用系统输入法,保留候选词、纠错和听写,确认文字后直接输入远端。电脑键盘固定底部,分“字母数字 / 符号功能”两页,共用末两行布局,按系统键盘高度适配横竖屏;统一切换、收起按钮和 Android 键盘触感反馈。
|
||||||
- 悬浮菜单中的“虚拟鼠标”展开 140 × 48 dp 横向左键/滚轮/右键条,跟随悬浮球移动,可随时收起,不压缩远程画面。按住左键或右键后,同一根手指拖动即可拖拽,滑出按键范围仍继续,松开结束;也支持另一指在画面上移动光标。采用无文字的扁平分体按键,中间滚轮外观为 18 × 30 dp,三键均保留 44 × 44 dp 触控范围,通过留白分隔。无阴影、凹凸或按压位移,按压仅变色。等色横条随手指上下循环移动,松手停止;上下滑动可超出按键范围,在画面放大后仍控制远端滚动。切换模式、显示器、进入后台或取消触摸会释放按键。状态菜单按内容高度完整展开,只有安全区域空间不足时才滚动。
|
- 悬浮菜单中的“虚拟鼠标”展开 140 × 48 dp 横向左键/滚轮/右键条,跟随悬浮球移动,可随时收起,不压缩远程画面。按住左键或右键后,同一根手指拖动即可拖拽,滑出按键范围仍继续,松开结束;也支持另一指在画面上移动光标。采用无文字的扁平分体按键,中间滚轮外观为 18 × 30 dp,三键均保留 44 × 44 dp 触控范围,通过留白分隔。无阴影、凹凸或按压位移,按压仅变色。等色横条随手指上下循环移动,松手停止;上下滑动可超出按键范围,在画面放大后仍控制远端滚动。切换模式、显示器、进入后台或取消触摸会释放按键。状态菜单按内容高度完整展开,只有安全区域空间不足时才滚动。
|
||||||
|
- 悬浮菜单提供“隐私屏”直接开关,无需进入详情页;按钮按电脑确认的状态显示“隐私屏已开启”或“隐私屏已关闭”,开启与关闭使用相同配色,正常切换不显示额外文字,仅在异常时显示提示。开启时与桌面端一致屏蔽电脑本地键鼠;声明控制能力后可使用电脑的连接时自动开启设置。状态超过 5 秒未更新时停止显示成功,指令 15 秒未确认时提示超时;后台保活与画中画恢复沿用同一会话状态。
|
||||||
- 悬浮控制栏支持选择并发送文件、接收电脑端文件、查看确认进度及保存收到的文件;Ctrl+Alt+Del 使用远端 Windows 系统服务,未启用服务时显示提示。
|
- 悬浮控制栏支持选择并发送文件、接收电脑端文件、查看确认进度及保存收到的文件;Ctrl+Alt+Del 使用远端 Windows 系统服务,未启用服务时显示提示。
|
||||||
- 设备身份与选择保存的连接密码使用 Android Keystore / AES-GCM 加密;首次联网需同意。远程会话在切换应用时自动使用系统画中画,不可用时自动使用通知保活。
|
- 设备身份与选择保存的连接密码使用 Android Keystore / AES-GCM 加密;首次联网需同意。远程会话在切换应用时自动使用系统画中画,不可用时自动使用通知保活。
|
||||||
- 与 iOS 一致的首页卡片、顶部状态/公告/设置入口、密码弹层及分组设置页;最近连接按服务器隔离,长按可删除记录及密码。
|
- 与 iOS 一致的首页卡片、顶部状态/公告/设置入口、密码弹层及分组设置页;最近连接按服务器隔离,长按可删除记录及密码。
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
- 可选保存远程画面预览:默认关闭,经确认开启后,从下一次连接起保存一张 640×360 预览,用于最近连接卡片。支持清除预览,关闭选项或撤回授权会清除已有图片。
|
- 可选保存远程画面预览:默认关闭,经确认开启后,从下一次连接起保存一张 640×360 预览,用于最近连接卡片。支持清除预览,关闭选项或撤回授权会清除已有图片。
|
||||||
- “关于 CrossDesk”对齐 iOS 的版本、软件许可、开源组件及源码与构建说明页面,包含 OpenFEC 告知、组件详情和可离线选择复制的完整许可;版本号读取当前 APK,构建时记录应用与 MiniRTC 的基础版本。
|
- “关于 CrossDesk”对齐 iOS 的版本、软件许可、开源组件及源码与构建说明页面,包含 OpenFEC 告知、组件详情和可离线选择复制的完整许可;版本号读取当前 APK,构建时记录应用与 MiniRTC 的基础版本。
|
||||||
|
|
||||||
当前不包含安卓被控端、隐私屏或 iOS 全部附加功能。硬件能力取决于设备和编码格式,软件回退时高分辨率或高帧率可能增加功耗;可通过悬浮菜单的画面设置降低画质或采集帧率。暂只构建 `arm64-v8a`,不支持 x86 模拟器或 32 位设备。
|
当前不包含安卓被控端或 iOS 全部附加功能。硬件能力取决于设备和编码格式,软件回退时高分辨率或高帧率可能增加功耗;可通过悬浮菜单的画面设置降低画质或采集帧率。暂只构建 `arm64-v8a`,不支持 x86 模拟器或 32 位设备。
|
||||||
|
|
||||||
## 本机更新提示
|
## 本机更新提示
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ Release 构建使用 `./gradlew :app:assembleRelease`,默认输出未签名 AP
|
|||||||
首次构建会下载并从源码编译依赖,耗时取决于网络。后续编译复用缓存。Android 直接编译 `deps/submodules/minirtc` 中的源码;构建产物和依赖缓存位于 `.native/`,Xmake 工程配置位于 `native/.xmake/`,与桌面和 iOS 分开保存:
|
首次构建会下载并从源码编译依赖,耗时取决于网络。后续编译复用缓存。Android 直接编译 `deps/submodules/minirtc` 中的源码;构建产物和依赖缓存位于 `.native/`,Xmake 工程配置位于 `native/.xmake/`,与桌面和 iOS 分开保存:
|
||||||
|
|
||||||
- Gradle 9.3.1、AGP 9.1.0、NDK r28c 固定在工程中。
|
- Gradle 9.3.1、AGP 9.1.0、NDK r28c 固定在工程中。
|
||||||
- Xmake 包仓库固定到 `eda39de3fb99b420c168f1ab9ab2d1791e11b662`。
|
- Xmake 依赖仓库沿用 PC 端策略:使用默认仓库或调用环境指定的仓库,不固定或校验仓库提交。Android 编译产物和安装包缓存仍独立保存。
|
||||||
- `scripts/build_native.py` 调用独立的 `native/xmake.lua`,直接包含 MiniRTC 子模块的构建定义。构建过程不复制 MiniRTC 源码,也不应用补丁。
|
- `scripts/build_native.py` 调用独立的 `native/xmake.lua`,直接包含 MiniRTC 子模块的构建定义。构建过程不复制 MiniRTC 源码,也不应用补丁。
|
||||||
- MiniRTC 源码内的 Android 分支启用 OpenH264、SVT-AV1 和 dav1d 软件编解码及依赖配方,兼容 OpenFEC 的 Android 头文件,并关闭 UPnP。SVT-AV1 使用内置 ARM CPU 能力检测;当前应用作为控制端只接收视频,库同时提供 AV1 编码能力。TLS 校验位于 MiniRTC 内部。STUN、TURN UDP/TCP 与直接连接仍由 MiniRTC 提供。
|
- MiniRTC 源码内的 Android 分支启用 OpenH264、SVT-AV1 和 dav1d 软件编解码及依赖配方,兼容 OpenFEC 的 Android 头文件,并关闭 UPnP。SVT-AV1 使用内置 ARM CPU 能力检测;当前应用作为控制端只接收视频,库同时提供 AV1 编码能力。TLS 校验位于 MiniRTC 内部。STUN、TURN UDP/TCP 与直接连接仍由 MiniRTC 提供。
|
||||||
- MiniRTC 的 `thirdparty/openssl/xmake.lua` 统一维护 iOS 和 Android 配方;Android 显式使用 NDK 的归档索引工具,避免 macOS `ranlib` 损坏 ELF 静态库。
|
- MiniRTC 的 `thirdparty/openssl/xmake.lua` 统一维护 iOS 和 Android 配方;Android 显式使用 NDK 的归档索引工具,避免 macOS `ranlib` 损坏 ELF 静态库。
|
||||||
@@ -97,7 +98,7 @@ git push origin android-v1.6.1-20261010
|
|||||||
|
|
||||||
已有 Release 需要补传服务器或刷新下载信息时,可手动运行 `Update version.json from Release` 并填写已有 `source_tag`,无需重新构建。
|
已有 Release 需要补传服务器或刷新下载信息时,可手动运行 `Update version.json from Release` 并填写已有 `source_tag`,无需重新构建。
|
||||||
|
|
||||||
工作流使用 Ubuntu 24.04、JDK 17、Python 3.13、SDK 36、Build Tools 36.0.0、NDK r28c,并使用最低支持的 Xmake 3.1.1 验证构建;本地允许使用更新的 Xmake。Gradle 使用工程内的 Wrapper,原生构建器固定 Xmake 包仓库版本。SDK/NDK 使用独立目录缓存,按宿主系统、架构和工具链版本区分;命中缓存时跳过 SDK 安装,未命中时在安装完成后立即保存,即使后续 APK 构建失败,下次也可复用。Xmake 可执行环境、固定版本的包仓库和 pip 下载也有各自的缓存;Ubuntu 系统工具只安装缺失项。
|
工作流使用 Ubuntu 24.04、JDK 17、Python 3.13、SDK 36、Build Tools 36.0.0、NDK r28c,并使用最低支持的 Xmake 3.1.1 验证构建;本地允许使用更新的 Xmake。Gradle 使用工程内的 Wrapper,原生构建器沿用 PC 端的 Xmake 仓库选择方式。SDK/NDK 使用独立目录缓存,按宿主系统、架构和工具链版本区分;命中缓存时跳过 SDK 安装,未命中时在安装完成后立即保存,即使后续 APK 构建失败,下次也可复用。Xmake 可执行环境和 pip 下载也有各自的缓存;Ubuntu 系统工具只安装缺失项。
|
||||||
|
|
||||||
Gradle 缓存与 Android 原生依赖缓存分别管理;原生缓存按宿主架构、构建脚本及 MiniRTC 配方区分,不复用桌面或 iOS 的配置和目标文件。GitHub 托管 runner 每次启动仍是新环境;第一次运行或缓存失效时需要安装。不同分支和标签可读取默认分支的缓存,因此建议先在默认分支运行一次 `Build Android` 预热,再创建发布标签。
|
Gradle 缓存与 Android 原生依赖缓存分别管理;原生缓存按宿主架构、构建脚本及 MiniRTC 配方区分,不复用桌面或 iOS 的配置和目标文件。GitHub 托管 runner 每次启动仍是新环境;第一次运行或缓存失效时需要安装。不同分支和标签可读取默认分支的缓存,因此建议先在默认分支运行一次 `Build Android` 预热,再创建发布标签。
|
||||||
|
|
||||||
@@ -152,7 +153,9 @@ gh secret set ANDROID_KEY_PASSWORD --repo kunkundi/crossdesk
|
|||||||
|
|
||||||
身份按主机名和端口分开保存,备份与设备迁移不包含应用数据。密码不写入连接记录,仅在明确开启“保存密码”后写入独立加密存储;未开启时仅用于当前会话。连接成功通知只处理一次,重复的传输就绪回调不会再次保存密码或重建会话页面;密码存储拒绝空值覆盖。旧版本已被空值覆盖的密码无法恢复,遇到此记录时会提示重新输入一次,并保留“保存密码”开启状态。由于 MiniRTC 的诊断日志包含登录身份,Android 桥接层关闭其会话日志。身份存储损坏会显示错误,不会静默重新注册。
|
身份按主机名和端口分开保存,备份与设备迁移不包含应用数据。密码不写入连接记录,仅在明确开启“保存密码”后写入独立加密存储;未开启时仅用于当前会话。连接成功通知只处理一次,重复的传输就绪回调不会再次保存密码或重建会话页面;密码存储拒绝空值覆盖。旧版本已被空值覆盖的密码无法恢复,遇到此记录时会提示重新输入一次,并保留“保存密码”开启状态。由于 MiniRTC 的诊断日志包含登录身份,Android 桥接层关闭其会话日志。身份存储损坏会显示错误,不会静默重新注册。
|
||||||
|
|
||||||
会话中的“剪贴板”发送用户输入的文本;收到的远端文本先留在会话内,点击“复制远端文本”才写入手机剪贴板。退出会话后清除会话内容。每次发送最多 128 KB。电脑键盘的 Ctrl / Win / Alt / Shift 可锁定组合键;macOS 的 Command 对应 Win。系统键盘通过 `supports_text_input` 检测远端能力,支持 UTF-8 的 Windows 和 macOS 被控端可直接输入中文、表情和听写文本;其他端或旧版使用 ASCII 按键回退,不支持的文本会提示使用剪贴板,不会先删除远端内容。Windows 通过当前已解锁用户桌面的 Unicode 键盘事件输入文本,无需切换电脑输入法或覆盖剪贴板;锁屏和 UAC 安全桌面不接受此文本通道。Windows 被控端也必须更新到包含文本输入支持的版本,仅更新 Android 不会启用该能力。输入法尚未确认的组字不会发送,自动纠错仅替换已发送文本的变化后缀;点击远端、收起、切换键盘或进入后台会清除本地输入上下文。
|
会话中的“剪贴板”发送用户输入的文本;收到的远端文本先留在会话内,点击“复制远端文本”才写入手机剪贴板。退出会话后清除会话内容。每次发送最多 128 KB。电脑键盘的 Ctrl / Win / Alt / Shift 可锁定组合键;macOS 的 Command 对应 Win。系统键盘通过 `supports_text_input` 检测远端能力,支持 UTF-8 的 Windows 和 macOS 被控端可直接输入中文、表情和听写文本;其他端或旧版使用 ASCII 按键回退,不支持的文本会提示使用剪贴板,不会先删除远端内容。输入法尚未确认的组字不会发送,自动纠错仅替换已发送文本的变化后缀;点击远端、收起、切换键盘或进入后台会清除本地输入上下文。
|
||||||
|
|
||||||
|
Windows 文本输入使用 Unicode 键盘事件,无需切换电脑输入法或覆盖剪贴板。本次开发版将完整 UTF-8 文本通过 Windows 服务转发到锁屏、登录与 UAC 安全桌面,保留数字、大小写字母、符号、空格及中文等字符,不将密码转换成数字或 ASCII 按键。该通道需要同时更新 Windows 客户端、CrossDesk Service 和会话辅助程序;仅更新 Android,或仅确认旧服务正在运行,均不会启用修复。旧版可能已声明 `supports_text_input`,但只支持普通桌面文本输入,因此“电脑键盘可输入、搜狗等系统输入法不可输入”仍可能出现。锁屏实际输入效果需在更新后的 Windows 环境验证。
|
||||||
|
|
||||||
“发送文件”调用系统文件选择器,后台读取所选文件后使用桌面端相同的 `file` / `file_feedback` 协议传输,支持空文件和中文文件名。每次发送一个文件,以 64 KiB 分块、最多 1 MiB 未确认数据限制发送队列;进度来自接收端确认,30 秒没有有效进展则报告失败。接收时校验大小并追踪完整字节范围,允许跨线程回调乱序,未完成的文件不会提供保存入口。同名文件分开存储,不覆盖已有文件;可通过“保存收到的文件”导出最近收到的文件,连续接收多个文件时应逐个保存。
|
“发送文件”调用系统文件选择器,后台读取所选文件后使用桌面端相同的 `file` / `file_feedback` 协议传输,支持空文件和中文文件名。每次发送一个文件,以 64 KiB 分块、最多 1 MiB 未确认数据限制发送队列;进度来自接收端确认,30 秒没有有效进展则报告失败。接收时校验大小并追踪完整字节范围,允许跨线程回调乱序,未完成的文件不会提供保存入口。同名文件分开存储,不覆盖已有文件;可通过“保存收到的文件”导出最近收到的文件,连续接收多个文件时应逐个保存。
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,12 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<application android:theme="@style/AppTheme" android:label="CrossDesk"
|
<application android:name=".CrossDeskApplication" android:localeConfig="@xml/locales_config"
|
||||||
|
android:theme="@style/AppTheme" android:label="CrossDesk"
|
||||||
android:icon="@mipmap/ic_launcher" android:allowBackup="false"
|
android:icon="@mipmap/ic_launcher" android:allowBackup="false"
|
||||||
android:fullBackupContent="false"
|
android:fullBackupContent="false"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:supportsRtl="true" android:usesCleartextTraffic="false">
|
android:supportsRtl="false" android:usesCleartextTraffic="false">
|
||||||
<service android:name=".RemoteSessionService" android:exported="false"
|
<service android:name=".RemoteSessionService" android:exported="false"
|
||||||
android:foregroundServiceType="specialUse">
|
android:foregroundServiceType="specialUse">
|
||||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||||
|
|||||||
@@ -26,18 +26,18 @@ final class AboutPages {
|
|||||||
LicenseCatalog loaded;try{loaded=new LicenseCatalog(activity);}catch(Exception error){loaded=null;}catalog=loaded;
|
LicenseCatalog loaded;try{loaded=new LicenseCatalog(activity);}catch(Exception error){loaded=null;}catalog=loaded;
|
||||||
}
|
}
|
||||||
void show(){
|
void show(){
|
||||||
LinearLayout content=activity.formPage("about","关于 CrossDesk",settings);
|
LinearLayout content=activity.formPage("about",L10n.tr("关于 CrossDesk"),settings);
|
||||||
LinearLayout version=activity.section(content,"");value(version,"版本",applicationVersion());
|
LinearLayout version=activity.section(content,"");value(version,L10n.tr("版本"),applicationVersion());
|
||||||
updateRow=ui.row();updateRow.setPadding(ui.dp(16),0,ui.dp(16),0);updateRow.addView(activity.updateDot(),ui.size(8,8));
|
updateRow=ui.row();updateRow.setPadding(ui.dp(16),0,ui.dp(16),0);updateRow.addView(activity.updateDot(),ui.size(8,8));
|
||||||
updateLink=ui.action("",BLUE,()->open("https://crossdesk.cn"));updateLink.setGravity(Gravity.START|Gravity.CENTER_VERTICAL);updateLink.setPadding(ui.dp(8),ui.dp(12),0,ui.dp(12));updateRow.addView(updateLink,new LinearLayout.LayoutParams(0,-2,1));version.addView(updateRow);
|
updateLink=ui.action("",BLUE,()->open("https://crossdesk.cn"));updateLink.setGravity(Gravity.START|Gravity.CENTER_VERTICAL);updateLink.setPadding(ui.dp(8),ui.dp(12),0,ui.dp(12));updateRow.addView(updateLink,new LinearLayout.LayoutParams(0,-2,1));version.addView(updateRow);
|
||||||
updateStatus=ui.text("",13,false);updateStatus.setPadding(ui.dp(16),ui.dp(8),ui.dp(16),ui.dp(8));version.addView(updateStatus);
|
updateStatus=ui.text("",13,false);updateStatus.setPadding(ui.dp(16),ui.dp(8),ui.dp(16),ui.dp(8));version.addView(updateStatus);
|
||||||
checkButton=ui.action("检查更新",BLUE,activity::checkAppUpdates);version.addView(checkButton);renderUpdates();ui.gap(content,28);
|
checkButton=ui.action(L10n.tr("检查更新"),BLUE,activity::checkAppUpdates);version.addView(checkButton);renderUpdates();ui.gap(content,28);
|
||||||
if(catalog==null){unavailable(content);return;}
|
if(catalog==null){unavailable(content);return;}
|
||||||
LinearLayout links=activity.section(content,"");
|
LinearLayout links=activity.section(content,"");
|
||||||
activity.link(links,"软件许可","",()->component(catalog.find("crossdesk"),this::show));ui.divider(links);
|
activity.link(links,L10n.tr("软件许可"),"",()->component(catalog.find("crossdesk"),this::show));ui.divider(links);
|
||||||
activity.link(links,"开源组件","",this::components);ui.divider(links);
|
activity.link(links,L10n.tr("开源组件"),"",this::components);ui.divider(links);
|
||||||
activity.link(links,"源码与构建说明","",this::source);
|
activity.link(links,L10n.tr("源码与构建说明"),"",this::source);
|
||||||
activity.footer(content,"许可与版权声明可离线查阅。");
|
activity.footer(content,L10n.tr("许可与版权声明可离线查阅。"));
|
||||||
}
|
}
|
||||||
private String applicationVersion(){
|
private String applicationVersion(){
|
||||||
return activity.applicationVersion();
|
return activity.applicationVersion();
|
||||||
@@ -45,11 +45,11 @@ final class AboutPages {
|
|||||||
void renderUpdates(){
|
void renderUpdates(){
|
||||||
if(updateStatus==null)return;
|
if(updateStatus==null)return;
|
||||||
updateRow.setVisibility(updates.updateAvailable()?View.VISIBLE:View.GONE);
|
updateRow.setVisibility(updates.updateAvailable()?View.VISIBLE:View.GONE);
|
||||||
updateLink.setText("新版本可用:v"+updates.availableVersion);
|
updateLink.setText(L10n.format("新版本可用:v{0}",updates.availableVersion));
|
||||||
String status=!activity.hasNetworkConsent()?"完成隐私授权后可检查更新":switch(updates.status){
|
String status=!activity.hasNetworkConsent()?L10n.tr("完成隐私授权后可检查更新"):switch(updates.status){
|
||||||
case CHECKING -> "正在检查更新…";
|
case CHECKING -> L10n.tr("正在检查更新…");
|
||||||
case UP_TO_DATE -> "当前已是最新版本";
|
case UP_TO_DATE -> L10n.tr("当前已是最新版本");
|
||||||
case FAILED -> "检查失败,请稍后重试";
|
case FAILED -> L10n.tr("检查失败,请稍后重试");
|
||||||
default -> "";
|
default -> "";
|
||||||
};
|
};
|
||||||
updateStatus.setText(status);updateStatus.setVisibility(status.isEmpty()?View.GONE:View.VISIBLE);
|
updateStatus.setText(status);updateStatus.setVisibility(status.isEmpty()?View.GONE:View.VISIBLE);
|
||||||
@@ -58,11 +58,11 @@ final class AboutPages {
|
|||||||
checkButton.setAlpha(checkButton.isEnabled()?1f:.5f);
|
checkButton.setAlpha(checkButton.isEnabled()?1f:.5f);
|
||||||
}
|
}
|
||||||
private void components(){
|
private void components(){
|
||||||
LinearLayout content=activity.formPage("open-source","开源组件",this::show);
|
LinearLayout content=activity.formPage("open-source",L10n.tr("开源组件"),this::show);
|
||||||
LinearLayout notice=activity.section(content,"OpenFEC 告知");
|
LinearLayout notice=activity.section(content,L10n.tr("OpenFEC 告知"));
|
||||||
paragraph(notice,"本应用使用 OpenFEC 提供前向纠错功能,遵循 CeCILL-C 1.0 许可。该许可规定了有限保证及责任限制,详情见许可全文。",SECONDARY);
|
paragraph(notice,L10n.tr("本应用使用 OpenFEC 提供前向纠错功能,遵循 CeCILL-C 1.0 许可。该许可规定了有限保证及责任限制,详情见许可全文。"),SECONDARY);
|
||||||
ui.divider(notice);paragraph(notice,"(c) Copyright 2009 - 2012 INRIA - All rights reserved",INK);ui.divider(notice);
|
ui.divider(notice);paragraph(notice,"(c) Copyright 2009 - 2012 INRIA - All rights reserved",INK);ui.divider(notice);
|
||||||
activity.link(notice,"OpenFEC 许可与源码","",()->component(catalog.find("openfec"),this::components));ui.gap(content,28);
|
activity.link(notice,L10n.tr("OpenFEC 许可与源码"),"",()->component(catalog.find("openfec"),this::components));ui.gap(content,28);
|
||||||
LinearLayout list=activity.section(content,"");boolean first=true;
|
LinearLayout list=activity.section(content,"");boolean first=true;
|
||||||
for(LicenseCatalog.Component item:catalog.components){
|
for(LicenseCatalog.Component item:catalog.components){
|
||||||
if(item.id.equals("crossdesk")||item.id.equals("openfec"))continue;
|
if(item.id.equals("crossdesk")||item.id.equals("openfec"))continue;
|
||||||
@@ -73,41 +73,41 @@ final class AboutPages {
|
|||||||
row.addView(labels,new LinearLayout.LayoutParams(0,-2,1));TextView chevron=ui.text(" ›",23,false);chevron.setTextColor(0xFFB7B7BD);row.addView(chevron);
|
row.addView(labels,new LinearLayout.LayoutParams(0,-2,1));TextView chevron=ui.text(" ›",23,false);chevron.setTextColor(0xFFB7B7BD);row.addView(chevron);
|
||||||
row.setContentDescription(item.name+","+item.license);row.setClickable(true);row.setFocusable(true);row.setOnClickListener(v->component(item,this::components));list.addView(row);
|
row.setContentDescription(item.name+","+item.license);row.setClickable(true);row.setFocusable(true);row.setOnClickListener(v->component(item,this::components));list.addView(row);
|
||||||
}
|
}
|
||||||
activity.footer(content,"各组件遵循所列开源许可证,其授予的权利不受本应用普通使用条款限制。查看源码需要网络连接。");
|
activity.footer(content,L10n.tr("各组件遵循所列开源许可证,其授予的权利不受本应用普通使用条款限制。查看源码需要网络连接。"));
|
||||||
}
|
}
|
||||||
private void component(LicenseCatalog.Component item,Runnable back){
|
private void component(LicenseCatalog.Component item,Runnable back){
|
||||||
LinearLayout content=activity.formPage("license:"+item.id,item.name,back);LinearLayout info=activity.section(content,"");
|
LinearLayout content=activity.formPage("license:"+item.id,item.name,back);LinearLayout info=activity.section(content,"");
|
||||||
String version=item.id.equals("crossdesk")?applicationVersion():item.version.length()==40?item.version.substring(0,12):item.version;
|
String version=item.id.equals("crossdesk")?applicationVersion():item.version.length()==40?item.version.substring(0,12):item.version;
|
||||||
value(info,"版本",version);ui.divider(info);value(info,"许可",item.license);ui.divider(info);
|
value(info,L10n.tr("版本"),version);ui.divider(info);value(info,L10n.tr("许可"),item.license);ui.divider(info);
|
||||||
external(info,"查看源码",item.sourceURL);
|
external(info,L10n.tr("查看源码"),item.sourceURL);
|
||||||
if(!item.buildDocuments.isEmpty()||!item.buildSourceURL.isEmpty()){
|
if(!item.buildDocuments.isEmpty()||!item.buildSourceURL.isEmpty()){
|
||||||
ui.divider(info);
|
ui.divider(info);
|
||||||
if(!item.buildDocuments.isEmpty())activity.link(info,"构建配方与项目补丁","",()->buildSources(item,back));
|
if(!item.buildDocuments.isEmpty())activity.link(info,L10n.tr("构建配方与项目补丁"),"",()->buildSources(item,back));
|
||||||
else external(info,"构建配方与项目补丁",item.buildSourceURL);
|
else external(info,L10n.tr("构建配方与项目补丁"),item.buildSourceURL);
|
||||||
}
|
}
|
||||||
ui.gap(content,16);LinearLayout docs=activity.section(content,"许可与版权声明");
|
ui.gap(content,16);LinearLayout docs=activity.section(content,L10n.tr("许可与版权声明"));
|
||||||
for(int i=0;i<item.documents.size();i++){
|
for(int i=0;i<item.documents.size();i++){
|
||||||
if(i>0)ui.divider(docs);LicenseCatalog.Document document=item.documents.get(i);
|
if(i>0)ui.divider(docs);LicenseCatalog.Document document=item.documents.get(i);
|
||||||
activity.link(docs,item.documentTitle(document),"",()->document(item.documentTitle(document),document,()->component(item,back)));
|
activity.link(docs,item.documentTitle(document),"",()->document(item.documentTitle(document),document,()->component(item,back)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void source(){
|
private void source(){
|
||||||
LinearLayout content=activity.formPage("source","源码与构建说明",this::show);
|
LinearLayout content=activity.formPage("source",L10n.tr("源码与构建说明"),this::show);
|
||||||
LinearLayout info=activity.section(content,"本版本源码与构建说明");
|
LinearLayout info=activity.section(content,L10n.tr("本版本源码与构建说明"));
|
||||||
if(catalog.source==null){paragraph(info,"本版本源码信息暂不可用。",SECONDARY);ui.divider(info);external(info,"项目主页","https://github.com/kunkundi/crossdesk");return;}
|
if(catalog.source==null){paragraph(info,L10n.tr("本版本源码信息暂不可用。"),SECONDARY);ui.divider(info);external(info,L10n.tr("项目主页"),"https://github.com/kunkundi/crossdesk");return;}
|
||||||
String revision=catalog.source.optString("revision","");String tag=catalog.source.isNull("tag")?"":catalog.source.optString("tag","");
|
String revision=catalog.source.optString("revision","");String tag=catalog.source.isNull("tag")?"":catalog.source.optString("tag","");
|
||||||
value(info,"源码版本",tag.isEmpty()?revision.substring(0,Math.min(12,revision.length())):tag);ui.divider(info);
|
value(info,L10n.tr("源码版本"),tag.isEmpty()?revision.substring(0,Math.min(12,revision.length())):tag);ui.divider(info);
|
||||||
if(catalog.source.optBoolean("isModified")){paragraph(info,"开发版本包含尚未发布的修改,以下链接为基础版本源码。",SECONDARY);ui.divider(info);}
|
if(catalog.source.optBoolean("isModified")){paragraph(info,L10n.tr("开发版本包含尚未发布的修改,以下链接为基础版本源码。"),SECONDARY);ui.divider(info);}
|
||||||
external(info,"查看源码",catalog.source.optString("sourceURL",catalog.find("crossdesk").sourceURL));ui.divider(info);
|
external(info,L10n.tr("查看源码"),catalog.source.optString("sourceURL",catalog.find("crossdesk").sourceURL));ui.divider(info);
|
||||||
try{
|
try{
|
||||||
List<LicenseCatalog.Document> documents=LicenseCatalog.documents(catalog.source.optJSONArray("buildDocuments"));
|
List<LicenseCatalog.Document> documents=LicenseCatalog.documents(catalog.source.optJSONArray("buildDocuments"));
|
||||||
if(!documents.isEmpty())actionLink(info,"构建说明",()->document("构建说明",documents.get(0),this::source));
|
if(!documents.isEmpty())actionLink(info,L10n.tr("构建说明"),()->document(L10n.tr("构建说明"),documents.get(0),this::source));
|
||||||
else external(info,"构建说明",catalog.source.optString("buildInstructionsURL","https://github.com/kunkundi/crossdesk"));
|
else external(info,L10n.tr("构建说明"),catalog.source.optString("buildInstructionsURL","https://github.com/kunkundi/crossdesk"));
|
||||||
}catch(org.json.JSONException error){paragraph(info,"本版本源码信息暂不可用。",SECONDARY);}
|
}catch(org.json.JSONException error){paragraph(info,L10n.tr("本版本源码信息暂不可用。"),SECONDARY);}
|
||||||
}
|
}
|
||||||
private void buildSources(LicenseCatalog.Component item,Runnable back){
|
private void buildSources(LicenseCatalog.Component item,Runnable back){
|
||||||
LinearLayout content=activity.formPage("build:"+item.id,"构建配方与项目补丁",()->component(item,back));
|
LinearLayout content=activity.formPage("build:"+item.id,L10n.tr("构建配方与项目补丁"),()->component(item,back));
|
||||||
if(!item.buildSourceURL.isEmpty()){LinearLayout upstream=activity.section(content,"");external(upstream,"查看源码",item.buildSourceURL);ui.gap(content,28);}
|
if(!item.buildSourceURL.isEmpty()){LinearLayout upstream=activity.section(content,"");external(upstream,L10n.tr("查看源码"),item.buildSourceURL);ui.gap(content,28);}
|
||||||
LinearLayout docs=activity.section(content,"");
|
LinearLayout docs=activity.section(content,"");
|
||||||
for(int i=0;i<item.buildDocuments.size();i++){
|
for(int i=0;i<item.buildDocuments.size();i++){
|
||||||
if(i>0)ui.divider(docs);LicenseCatalog.Document document=item.buildDocuments.get(i);
|
if(i>0)ui.divider(docs);LicenseCatalog.Document document=item.buildDocuments.get(i);
|
||||||
@@ -129,10 +129,10 @@ final class AboutPages {
|
|||||||
private void actionLink(LinearLayout parent,String title,Runnable action){TextView link=ui.action(title,BLUE,action);link.setTypeface(android.graphics.Typeface.DEFAULT);link.setGravity(Gravity.START|Gravity.CENTER_VERTICAL);link.setPadding(ui.dp(16),ui.dp(12),ui.dp(16),ui.dp(12));parent.addView(link);}
|
private void actionLink(LinearLayout parent,String title,Runnable action){TextView link=ui.action(title,BLUE,action);link.setTypeface(android.graphics.Typeface.DEFAULT);link.setGravity(Gravity.START|Gravity.CENTER_VERTICAL);link.setPadding(ui.dp(16),ui.dp(12),ui.dp(16),ui.dp(12));parent.addView(link);}
|
||||||
private void open(String url){
|
private void open(String url){
|
||||||
Uri uri=Uri.parse(url);if(!"https".equals(uri.getScheme())&&!"http".equals(uri.getScheme()))return;
|
Uri uri=Uri.parse(url);if(!"https".equals(uri.getScheme())&&!"http".equals(uri.getScheme()))return;
|
||||||
try{activity.startActivity(new Intent(Intent.ACTION_VIEW,uri).addCategory(Intent.CATEGORY_BROWSABLE));}catch(ActivityNotFoundException error){Toast.makeText(activity,"未找到可打开链接的浏览器",Toast.LENGTH_SHORT).show();}
|
try{activity.startActivity(new Intent(Intent.ACTION_VIEW,uri).addCategory(Intent.CATEGORY_BROWSABLE));}catch(ActivityNotFoundException error){Toast.makeText(activity,L10n.tr("未找到可打开链接的浏览器"),Toast.LENGTH_SHORT).show();}
|
||||||
}
|
}
|
||||||
private void unavailable(LinearLayout content){
|
private void unavailable(LinearLayout content){
|
||||||
content.setGravity(Gravity.CENTER);ui.gap(content,32);TextView icon=ui.text("▤",36,false);icon.setTextColor(SECONDARY);content.addView(icon);ui.gap(content,12);
|
content.setGravity(Gravity.CENTER);ui.gap(content,32);TextView icon=ui.text("▤",36,false);icon.setTextColor(SECONDARY);content.addView(icon);ui.gap(content,12);
|
||||||
content.addView(ui.text("无法读取开源许可",17,true));TextView message=ui.text("应用中的许可文件缺失或损坏,请重新安装应用。",14,false);message.setGravity(Gravity.CENTER);message.setTextColor(SECONDARY);content.addView(message);
|
content.addView(ui.text(L10n.tr("无法读取开源许可"),17,true));TextView message=ui.text(L10n.tr("应用中的许可文件缺失或损坏,请重新安装应用。"),14,false);message.setGravity(Gravity.CENTER);message.setTextColor(SECONDARY);content.addView(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,15 +51,15 @@ final class AnnouncementView extends ScrollView {
|
|||||||
TextView text=ui.text(value,14,false);text.setTextColor(color);text.setLineSpacing(ui.dp(3),1);return text;
|
TextView text=ui.text(value,14,false);text.setTextColor(color);text.setLineSpacing(ui.dp(3),1);return text;
|
||||||
}
|
}
|
||||||
private void renderList(){
|
private void renderList(){
|
||||||
if(inbox.deleteFailed){content.addView(message("无法删除这条公告,请重试。",RED));ui.gap(content,12);}
|
if(inbox.deleteFailed){content.addView(message(L10n.tr("无法删除这条公告,请重试。"),RED));ui.gap(content,12);}
|
||||||
if(!inbox.isConnected()){
|
if(!inbox.isConnected()){
|
||||||
content.addView(message("连接服务器后可获取最新公告。",SECONDARY));
|
content.addView(message(L10n.tr("连接服务器后可获取最新公告。"),SECONDARY));
|
||||||
content.addView(ui.action("连接服务器",BLUE,authorize));ui.gap(content,8);
|
content.addView(ui.action(L10n.tr("连接服务器"),BLUE,authorize));ui.gap(content,8);
|
||||||
}else if(inbox.failed){
|
}else if(inbox.failed){
|
||||||
LinearLayout error=ui.row();error.addView(message("公告加载失败,请重试。",SECONDARY),new LinearLayout.LayoutParams(0,-2,1));
|
LinearLayout error=ui.row();error.addView(message(L10n.tr("公告加载失败,请重试。"),SECONDARY),new LinearLayout.LayoutParams(0,-2,1));
|
||||||
error.addView(ui.action("重试",BLUE,inbox::refresh),ui.size(64,48));content.addView(error);ui.gap(content,8);
|
error.addView(ui.action(L10n.tr("重试"),BLUE,inbox::refresh),ui.size(64,48));content.addView(error);ui.gap(content,8);
|
||||||
}
|
}
|
||||||
countLabel=ui.text("共 "+inbox.total+" 条 · "+inbox.unread+" 条未读",13,false);
|
countLabel=ui.text(L10n.format("共 {0} 条 · {1} 条未读",inbox.total,inbox.unread),13,false);
|
||||||
countLabel.setTextColor(SECONDARY);countLabel.setPadding(ui.dp(16),ui.dp(8),0,ui.dp(8));content.addView(countLabel);
|
countLabel.setTextColor(SECONDARY);countLabel.setPadding(ui.dp(16),ui.dp(8),0,ui.dp(8));content.addView(countLabel);
|
||||||
LinearLayout card=ui.column();ui.card(card,12);content.addView(card);
|
LinearLayout card=ui.column();ui.card(card,12);content.addView(card);
|
||||||
for(AnnouncementInbox.Item item:inbox.items){
|
for(AnnouncementInbox.Item item:inbox.items){
|
||||||
@@ -69,11 +69,11 @@ final class AnnouncementView extends ScrollView {
|
|||||||
if(inbox.loading){
|
if(inbox.loading){
|
||||||
LinearLayout loading=ui.row();loading.setGravity(Gravity.CENTER);loading.setPadding(0,ui.dp(18),0,ui.dp(18));
|
LinearLayout loading=ui.row();loading.setGravity(Gravity.CENTER);loading.setPadding(0,ui.dp(18),0,ui.dp(18));
|
||||||
ProgressBar spinner=new ProgressBar(getContext());spinner.setIndeterminateTintList(ColorStateList.valueOf(BLUE));loading.addView(spinner,ui.size(20,20));
|
ProgressBar spinner=new ProgressBar(getContext());spinner.setIndeterminateTintList(ColorStateList.valueOf(BLUE));loading.addView(spinner,ui.size(20,20));
|
||||||
TextView label=message("加载中…",SECONDARY);label.setPadding(ui.dp(10),0,0,0);loading.addView(label);card.addView(loading);
|
TextView label=message(L10n.tr("加载中…"),SECONDARY);label.setPadding(ui.dp(10),0,0,0);loading.addView(label);card.addView(loading);
|
||||||
}else if(inbox.items.size()<inbox.total&&!inbox.failed){
|
}else if(inbox.items.size()<inbox.total&&!inbox.failed){
|
||||||
TextView more=ui.action("加载更多",BLUE,inbox::loadMore);more.setEnabled(inbox.isConnected());card.addView(more,ui.size(-1,48));
|
TextView more=ui.action(L10n.tr("加载更多"),BLUE,inbox::loadMore);more.setEnabled(inbox.isConnected());card.addView(more,ui.size(-1,48));
|
||||||
}else if(inbox.loaded&&inbox.total==0&&!inbox.failed&&inbox.isConnected()){
|
}else if(inbox.loaded&&inbox.total==0&&!inbox.failed&&inbox.isConnected()){
|
||||||
TextView empty=message("暂无公告",SECONDARY);empty.setGravity(Gravity.CENTER);card.addView(empty,ui.size(-1,120));
|
TextView empty=message(L10n.tr("暂无公告"),SECONDARY);empty.setGravity(Gravity.CENTER);card.addView(empty,ui.size(-1,120));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private View itemRow(AnnouncementInbox.Item item){
|
private View itemRow(AnnouncementInbox.Item item){
|
||||||
@@ -86,23 +86,23 @@ final class AnnouncementView extends ScrollView {
|
|||||||
words.addView(title,ui.size(-1,-2));ui.gap(words,7);TextView date=ui.text(date(item),12,false);date.setTextColor(SECONDARY);words.addView(date);
|
words.addView(title,ui.size(-1,-2));ui.gap(words,7);TextView date=ui.text(date(item),12,false);date.setTextColor(SECONDARY);words.addView(date);
|
||||||
top.addView(words,new LinearLayout.LayoutParams(0,-2,1));row.addView(top,new LinearLayout.LayoutParams(0,-2,1));
|
top.addView(words,new LinearLayout.LayoutParams(0,-2,1));row.addView(top,new LinearLayout.LayoutParams(0,-2,1));
|
||||||
TextView chevron=ui.text("›",23,false);chevron.setTextColor(0xFFB7B7BD);chevron.setPadding(ui.dp(10),0,0,0);row.addView(chevron);
|
TextView chevron=ui.text("›",23,false);chevron.setTextColor(0xFFB7B7BD);chevron.setPadding(ui.dp(10),0,0,0);row.addView(chevron);
|
||||||
row.setContentDescription(item.title+","+date(item)+","+(item.read?"已读":"未读"));row.setFocusable(true);
|
row.setContentDescription(item.title+","+date(item)+","+(item.read?L10n.tr("已读"):L10n.tr("未读")));row.setFocusable(true);
|
||||||
row.setOnClickListener(v->open.accept(item));row.setOnLongClickListener(v->{confirmDelete(item);return true;});
|
row.setOnClickListener(v->open.accept(item));row.setOnLongClickListener(v->{confirmDelete(item);return true;});
|
||||||
return new SwipeRow(row,()->confirmDelete(item));
|
return new SwipeRow(row,()->confirmDelete(item));
|
||||||
}
|
}
|
||||||
private void confirmDelete(AnnouncementInbox.Item item){
|
private void confirmDelete(AnnouncementInbox.Item item){
|
||||||
if(deleteDialog!=null)deleteDialog.dismiss();
|
if(deleteDialog!=null)deleteDialog.dismiss();
|
||||||
deleteDialog=new AlertDialog.Builder(getContext()).setTitle("删除公告").setMessage("删除这条公告?")
|
deleteDialog=new AlertDialog.Builder(getContext()).setTitle(L10n.tr("删除公告")).setMessage(L10n.tr("删除这条公告?"))
|
||||||
.setNegativeButton("取消",null).setPositiveButton("删除",(dialog,which)->inbox.dismiss(item)).show();
|
.setNegativeButton(L10n.tr("取消"),null).setPositiveButton(L10n.tr("删除"),(dialog,which)->inbox.dismiss(item)).show();
|
||||||
}
|
}
|
||||||
private void renderDetail(){
|
private void renderDetail(){
|
||||||
AnnouncementInbox.Item item=inbox.find(id,revision);
|
AnnouncementInbox.Item item=inbox.find(id,revision);
|
||||||
if(item==null){content.addView(message("这条公告已更新或删除,请返回公告列表查看。",SECONDARY));return;}
|
if(item==null){content.addView(message(L10n.tr("这条公告已更新或删除,请返回公告列表查看。"),SECONDARY));return;}
|
||||||
content.addView(ui.text(item.title,22,true));ui.gap(content,16);
|
content.addView(ui.text(item.title,22,true));ui.gap(content,16);
|
||||||
TextView date=ui.text(date(item)+" · "+(item.read?"已读":"未读"),12,false);date.setTextColor(SECONDARY);content.addView(date);ui.gap(content,16);
|
TextView date=ui.text(date(item)+" · "+(item.read?L10n.tr("已读"):L10n.tr("未读")),12,false);date.setTextColor(SECONDARY);content.addView(date);ui.gap(content,16);
|
||||||
if(inbox.readSaveFailed){
|
if(inbox.readSaveFailed){
|
||||||
content.addView(message("无法保存本地阅读状态,请重试。",RED));
|
content.addView(message(L10n.tr("无法保存本地阅读状态,请重试。"),RED));
|
||||||
content.addView(ui.action("重试保存阅读状态",BLUE,()->inbox.markRead(item)));ui.gap(content,16);
|
content.addView(ui.action(L10n.tr("重试保存阅读状态"),BLUE,()->inbox.markRead(item)));ui.gap(content,16);
|
||||||
}
|
}
|
||||||
TextView body=ui.text("",17,false);body.setText(AnnouncementText.format(item.body));body.setTextIsSelectable(true);
|
TextView body=ui.text("",17,false);body.setText(AnnouncementText.format(item.body));body.setTextIsSelectable(true);
|
||||||
body.setMovementMethod(LinkMovementMethod.getInstance());body.setLinksClickable(true);body.setLinkTextColor(BLUE);body.setLineSpacing(ui.dp(4),1);
|
body.setMovementMethod(LinkMovementMethod.getInstance());body.setLinksClickable(true);body.setLinkTextColor(BLUE);body.setLineSpacing(ui.dp(4),1);
|
||||||
@@ -127,7 +127,7 @@ final class AnnouncementView extends ScrollView {
|
|||||||
trackPull(event);
|
trackPull(event);
|
||||||
if(!pulling)return super.onTouchEvent(event);
|
if(!pulling)return super.onTouchEvent(event);
|
||||||
boolean reached=event.getY()-downY>=ui.dp(72);
|
boolean reached=event.getY()-downY>=ui.dp(72);
|
||||||
if(event.getActionMasked()==MotionEvent.ACTION_MOVE&&countLabel!=null)countLabel.setText(reached?"松开刷新":"下拉刷新");
|
if(event.getActionMasked()==MotionEvent.ACTION_MOVE&&countLabel!=null)countLabel.setText(reached?L10n.tr("松开刷新"):L10n.tr("下拉刷新"));
|
||||||
if(event.getActionMasked()==MotionEvent.ACTION_UP||event.getActionMasked()==MotionEvent.ACTION_CANCEL){
|
if(event.getActionMasked()==MotionEvent.ACTION_UP||event.getActionMasked()==MotionEvent.ACTION_CANCEL){
|
||||||
pulling=false;if(reached&&event.getActionMasked()==MotionEvent.ACTION_UP)inbox.refresh();else render();
|
pulling=false;if(reached&&event.getActionMasked()==MotionEvent.ACTION_UP)inbox.refresh();else render();
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ final class AnnouncementView extends ScrollView {
|
|||||||
private boolean dragging;
|
private boolean dragging;
|
||||||
SwipeRow(View front,Runnable delete){
|
SwipeRow(View front,Runnable delete){
|
||||||
super(ui.context);this.front=front;setClipChildren(true);
|
super(ui.context);this.front=front;setClipChildren(true);
|
||||||
remove=ui.action("删除",Color.WHITE,delete);remove.setBackgroundColor(RED);
|
remove=ui.action(L10n.tr("删除"),Color.WHITE,delete);remove.setBackgroundColor(RED);
|
||||||
remove.setVisibility(INVISIBLE);
|
remove.setVisibility(INVISIBLE);
|
||||||
remove.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);remove.setFocusable(false);
|
remove.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);remove.setFocusable(false);
|
||||||
addView(remove,new FrameLayout.LayoutParams(ui.dp(76),-1,Gravity.END));addView(front,new FrameLayout.LayoutParams(-1,-2));
|
addView(remove,new FrameLayout.LayoutParams(ui.dp(76),-1,Gravity.END));addView(front,new FrameLayout.LayoutParams(-1,-2));
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ final class BackgroundSession implements NativeSession.Listener {
|
|||||||
RemoteVideoSettings videoSettings;
|
RemoteVideoSettings videoSettings;
|
||||||
int signalState, selectedDisplay, width, height;
|
int signalState, selectedDisplay, width, height;
|
||||||
boolean muted, remoteServiceAvailable, background, ended;
|
boolean muted, remoteServiceAvailable, background, ended;
|
||||||
boolean waitingForLandscape;
|
boolean autoLandscape;
|
||||||
File receivedFile;
|
File receivedFile;
|
||||||
|
|
||||||
BackgroundSession(Context context, NativeSession session, String host, int port, String remote) {
|
BackgroundSession(Context context, NativeSession session, String host, int port, String remote) {
|
||||||
@@ -46,7 +46,7 @@ final class BackgroundSession implements NativeSession.Listener {
|
|||||||
if (listener != null) listener.ended(reason);
|
if (listener != null) listener.ended(reason);
|
||||||
}
|
}
|
||||||
@Override public void ended(String reason) { finish(reason); }
|
@Override public void ended(String reason) { finish(reason); }
|
||||||
@Override public void passwordRejected() { finish("远端访问密码已失效,请重新连接"); }
|
@Override public void passwordRejected() { finish(L10n.tr("远端访问密码已失效,请重新连接")); }
|
||||||
@Override public void connected() { /* Already connected before the service takes ownership. */ }
|
@Override public void connected() { /* Already connected before the service takes ownership. */ }
|
||||||
@Override public void status(String message) { if (view != null) view.status(message); }
|
@Override public void status(String message) { if (view != null) view.status(message); }
|
||||||
@Override public void signaling(int state, String id) {
|
@Override public void signaling(int state, String id) {
|
||||||
@@ -75,11 +75,12 @@ final class BackgroundSession implements NativeSession.Listener {
|
|||||||
@Override public void statistics(RemoteNetworkStatistics.Snapshot value) { if (view != null) view.statistics(value); }
|
@Override public void statistics(RemoteNetworkStatistics.Snapshot value) { if (view != null) view.statistics(value); }
|
||||||
@Override public void fileTransfer(String name, double progress, boolean sending, File received) {
|
@Override public void fileTransfer(String name, double progress, boolean sending, File received) {
|
||||||
if (received != null) receivedFile = received;
|
if (received != null) receivedFile = received;
|
||||||
fileStatus = progress < 0 ? name + " 传输失败" : (sending ? "发送 " : "接收 ") + name
|
fileStatus = progress < 0 ? L10n.format("{0} 传输失败", name) : (sending ? L10n.tr("发送 ") : L10n.tr("接收 ")) + name
|
||||||
+ (progress >= 1 ? " · 已完成" : " · " + (int)(progress * 100) + "%");
|
+ (progress >= 1 ? L10n.tr(" · 已完成") : " · " + (int)(progress * 100) + "%");
|
||||||
if (view != null) view.fileTransfer(name, progress, sending, received);
|
if (view != null) view.fileTransfer(name, progress, sending, received);
|
||||||
}
|
}
|
||||||
@Override public void controlFeedback(String text) { if (view != null) view.controlFeedback(text); }
|
@Override public void controlFeedback(String text) { if (view != null) view.controlFeedback(text); }
|
||||||
|
@Override public void privacyChanged() { if (view != null) view.privacyChanged(); }
|
||||||
@Override public void presence(JSONObject message) { if (view != null) view.presence(message); }
|
@Override public void presence(JSONObject message) { if (view != null) view.presence(message); }
|
||||||
@Override public void announcement(JSONObject message) { if (view != null) view.announcement(message); }
|
@Override public void announcement(JSONObject message) { if (view != null) view.announcement(message); }
|
||||||
@Override public void announcementFailed(String id) { if (view != null) view.announcementFailed(id); }
|
@Override public void announcementFailed(String id) { if (view != null) view.announcementFailed(id); }
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package cn.crossdesk.mobile;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
/** Initialize localization before any activity, background service or model. */
|
||||||
|
public final class CrossDeskApplication extends Application {
|
||||||
|
@Override public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
L10n.initialize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package cn.crossdesk.mobile;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/** Native resource lookup; never translates remote content, file names or protocol data. */
|
||||||
|
final class L10n {
|
||||||
|
private static volatile Context application;
|
||||||
|
static void initialize(Context context) { application = context.getApplicationContext(); }
|
||||||
|
static String tr(String key) {
|
||||||
|
Context context = application;
|
||||||
|
Integer id = LocalizationKeys.IDS.get(key);
|
||||||
|
return context == null || id == null ? key : context.getString(id);
|
||||||
|
}
|
||||||
|
static String format(String key, Object... args) {
|
||||||
|
java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("\\{(\\d+)\\}").matcher(tr(key));
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
while (matcher.find()) {
|
||||||
|
int index = Integer.parseInt(matcher.group(1));
|
||||||
|
String value = index < args.length ? String.valueOf(args[index]) : matcher.group();
|
||||||
|
matcher.appendReplacement(result, java.util.regex.Matcher.quoteReplacement(value));
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
static boolean isChinese() {
|
||||||
|
Context context = application;
|
||||||
|
Locale locale = context == null ? Locale.getDefault()
|
||||||
|
: context.getResources().getConfiguration().getLocales().get(0);
|
||||||
|
return locale.getLanguage().equals("zh");
|
||||||
|
}
|
||||||
|
private L10n() {}
|
||||||
|
}
|
||||||
@@ -28,9 +28,9 @@ final class LicenseCatalog {
|
|||||||
if(documents.isEmpty())throw new JSONException("Missing documents");
|
if(documents.isEmpty())throw new JSONException("Missing documents");
|
||||||
}
|
}
|
||||||
String documentTitle(Document document){
|
String documentTitle(Document document){
|
||||||
if(document.title.equals("Source copyright and license notices"))return "版权与许可声明";
|
if(document.title.equals("Source copyright and license notices"))return L10n.tr("版权与许可声明");
|
||||||
if(document.title.equals("apps/android/licenses/OPEN_SOURCE_NOTICE.txt"))return "开源软件权利说明";
|
if(document.title.equals("apps/android/licenses/OPEN_SOURCE_NOTICE.txt"))return L10n.tr("开源软件权利说明");
|
||||||
if(id.equals("crossdesk")&&document.title.equals("LICENSE"))return "GNU GPL 第 3 版";
|
if(id.equals("crossdesk")&&document.title.equals("LICENSE"))return L10n.tr("GNU GPL 第 3 版");
|
||||||
return document.title;
|
return document.title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,316 @@
|
|||||||
|
package cn.crossdesk.mobile;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** Generated resource keys; text and locale selection live in Android resources. */
|
||||||
|
final class LocalizationKeys {
|
||||||
|
static final Map<String, Integer> IDS = new HashMap<>();
|
||||||
|
static {
|
||||||
|
IDS.put(" · 已完成", R.string.text_58b6df1059088699);
|
||||||
|
IDS.put(" 传输失败", R.string.text_53af552fb3af7f6e);
|
||||||
|
IDS.put(" 台设备", R.string.text_23ebae95d26dfe5e);
|
||||||
|
IDS.put(" 条 · ", R.string.text_a7f4dfd234a43b16);
|
||||||
|
IDS.put(" 条未读", R.string.text_2c894057fc526576);
|
||||||
|
IDS.put("CrossDesk 远程连接", R.string.text_d0ac753c6681e578);
|
||||||
|
IDS.put("GNU GPL 第 3 版", R.string.text_0991c6fc1de4ffd6);
|
||||||
|
IDS.put("OpenFEC 告知", R.string.text_5ae7dc4079223967);
|
||||||
|
IDS.put("OpenFEC 许可与源码", R.string.text_69ad29820a259526);
|
||||||
|
IDS.put("P2P 直连", R.string.text_63ae4a0c7a448d21);
|
||||||
|
IDS.put("SRTP 已启用", R.string.text_70cae3c6aad8e152);
|
||||||
|
IDS.put("TURN 中继", R.string.text_ef813fec7d6d8a3b);
|
||||||
|
IDS.put("{0} 传输失败", R.string.text_ebede975edd52aee);
|
||||||
|
IDS.put("{0} 台设备", R.string.text_b3ffb6e982ecf5df);
|
||||||
|
IDS.put("{0} 条未读", R.string.text_fc66a78b9e27fecf);
|
||||||
|
IDS.put("{0},接收 {1},发送 {2},丢包率 {3}", R.string.text_dcb59cdcf66150a1);
|
||||||
|
IDS.put("● 已连接", R.string.text_01965b3249458d01);
|
||||||
|
IDS.put("下拉刷新", R.string.text_eb4236fe6a926a5a);
|
||||||
|
IDS.put("丢包率", R.string.text_37772187ab854191);
|
||||||
|
IDS.put("中", R.string.text_a567bdaa11367f26);
|
||||||
|
IDS.put("优先保持画面流畅,带宽或编码压力较大时会更积极地降低分辨率。", R.string.text_e6ff95a9fe6acc7f);
|
||||||
|
IDS.put("低", R.string.text_aa9e366f68d3d097);
|
||||||
|
IDS.put("使用自定义服务器", R.string.text_f03d03eb35e3330c);
|
||||||
|
IDS.put("保存密码", R.string.text_2e9f4819e064ce12);
|
||||||
|
IDS.put("保存收到的文件", R.string.text_10019cef593819ed);
|
||||||
|
IDS.put("保存远程画面预览", R.string.text_3bcdcd330bb3044c);
|
||||||
|
IDS.put("保存远程画面预览?", R.string.text_0f197074373e38fd);
|
||||||
|
IDS.put("信令连接中断,请重新连接", R.string.text_71ca53f95a8484da);
|
||||||
|
IDS.put("公告加载失败,请重试。", R.string.text_789f22a8b1b34797);
|
||||||
|
IDS.put("公告详情", R.string.text_00554b17c432937e);
|
||||||
|
IDS.put("共 ", R.string.text_2428420e407fe2e8);
|
||||||
|
IDS.put("共 {0} 条 · {1} 条未读", R.string.text_d977c7568cc5201e);
|
||||||
|
IDS.put("关于", R.string.text_52d25a9e30ba94f1);
|
||||||
|
IDS.put("关于 CrossDesk", R.string.text_459c1e980c1c8ce2);
|
||||||
|
IDS.put("关于,有新版本", R.string.text_390a95b65eb33052);
|
||||||
|
IDS.put("关闭", R.string.text_3fd47edce45b3603);
|
||||||
|
IDS.put("关闭控制栏", R.string.text_56a18c93e12fdc4e);
|
||||||
|
IDS.put("关闭隐私屏", R.string.text_4eb0565cc4658d7f);
|
||||||
|
IDS.put("兼顾清晰度和流畅度,只在持续压力下逐步降低分辨率。", R.string.text_7d466b69be42fc43);
|
||||||
|
IDS.put("分辨率", R.string.text_b92421822c213c2b);
|
||||||
|
IDS.put("删除", R.string.text_2f9daa828907b93f);
|
||||||
|
IDS.put("删除公告", R.string.text_0e46cdb3442edfba);
|
||||||
|
IDS.put("删除记录", R.string.text_f2cf9101bb2d8816);
|
||||||
|
IDS.put("删除这条公告?", R.string.text_d087671ca7df7f0f);
|
||||||
|
IDS.put("刷新公告", R.string.text_33e7584cd5c252d7);
|
||||||
|
IDS.put("剪贴板", R.string.text_8c10e2c1728ddf8e);
|
||||||
|
IDS.put("加载中…", R.string.text_4927a53bcc886afb);
|
||||||
|
IDS.put("加载更多", R.string.text_af90a08fec8ee28d);
|
||||||
|
IDS.put("升级提示", R.string.text_a739f24a9fd8a5d9);
|
||||||
|
IDS.put("原生连接初始化失败", R.string.text_861521c9e107c648);
|
||||||
|
IDS.put("发送", R.string.text_edecf0ae6e5144f9);
|
||||||
|
IDS.put("发送 ", R.string.text_43b7b60867ca51e7);
|
||||||
|
IDS.put("发送后,在远程键盘中点击“粘贴”。接收的远端文本可手动复制到本机。", R.string.text_da7df5a5543c4ba1);
|
||||||
|
IDS.put("发送文件", R.string.text_e7281fbca7af369c);
|
||||||
|
IDS.put("发送文本", R.string.text_ce6c02d19022e016);
|
||||||
|
IDS.put("取消", R.string.text_2cd0f3be8738a86c);
|
||||||
|
IDS.put("取消连接", R.string.text_3687f953cfe85fb8);
|
||||||
|
IDS.put("各组件遵循所列开源许可证,其授予的权利不受本应用普通使用条款限制。查看源码需要网络连接。", R.string.text_72814b15a6fa152c);
|
||||||
|
IDS.put("合计", R.string.text_e009c148d1890dac);
|
||||||
|
IDS.put("同意并继续", R.string.text_81c92013219dd115);
|
||||||
|
IDS.put("后台保持连接,点按返回远程桌面", R.string.text_409696774c9998ce);
|
||||||
|
IDS.put("后台保活未能启动,请保持应用在前台", R.string.text_dcb8d4e2e6973912);
|
||||||
|
IDS.put("后台保活未能启动,请保持应用在前台后重新连接", R.string.text_d96a21463557171d);
|
||||||
|
IDS.put("后台连接已结束,请重新连接", R.string.text_952f7300de0a7bd9);
|
||||||
|
IDS.put("后台连接服务已停止,请重新连接", R.string.text_da904f715e1cff87);
|
||||||
|
IDS.put("后续连接仍会保存新的预览图。", R.string.text_0812359e18082071);
|
||||||
|
IDS.put("在线", R.string.text_b9086662b1df1f20);
|
||||||
|
IDS.put("填写主机名或 IPv4 地址,不含协议和路径", R.string.text_39f2f444a042643e);
|
||||||
|
IDS.put("声音", R.string.text_473400815c0f687d);
|
||||||
|
IDS.put("复制远端文本", R.string.text_ab5ca1fab212f4ca);
|
||||||
|
IDS.put("始终保持较高分辨率,带宽不足时通过降低帧率来保证画面清晰度。", R.string.text_c92269b2039e7aa3);
|
||||||
|
IDS.put("媒体加密", R.string.text_7ff73de85ec16d15);
|
||||||
|
IDS.put("字母数字", R.string.text_22dc6217af77bc37);
|
||||||
|
IDS.put("安全存储或证书初始化失败,请重试或清除应用数据", R.string.text_9ca1cdaf831182e3);
|
||||||
|
IDS.put("完成", R.string.text_c0b3fbff51ccc40b);
|
||||||
|
IDS.put("完成隐私授权后可检查更新", R.string.text_678328f6f02881c7);
|
||||||
|
IDS.put("完成隐私授权后登记本机身份", R.string.text_c4361f9abdba4882);
|
||||||
|
IDS.put("密码保存失败,本次连接仍可使用", R.string.text_c29b888e357d727d);
|
||||||
|
IDS.put("密码将加密保存在本机 Android Keystore 中", R.string.text_3f31300a23afb390);
|
||||||
|
IDS.put("对端 ID", R.string.text_8d3b052c4c67acda);
|
||||||
|
IDS.put("对端 ID ", R.string.text_0512084e130984bc);
|
||||||
|
IDS.put("对端 ID {0}", R.string.text_c5147d65960196b0);
|
||||||
|
IDS.put("将删除本机保存的所有远程预览图,保留连接记录和密码。", R.string.text_882b705d31ac7e2a);
|
||||||
|
IDS.put("屏幕方向", R.string.text_60cc99cc0013972e);
|
||||||
|
IDS.put("展开远程控制菜单", R.string.text_9e7294d264afecb7);
|
||||||
|
IDS.put("已从通知断开远程连接", R.string.text_9343612ee473edd6);
|
||||||
|
IDS.put("已保存密码", R.string.text_9d32476fe83aef60);
|
||||||
|
IDS.put("已保存密码无法读取,请重新输入", R.string.text_1cbd391383283924);
|
||||||
|
IDS.put("已保存的密码未能保留,请重新输入一次", R.string.text_6792b9f341380c68);
|
||||||
|
IDS.put("已发送 Ctrl+Alt+Del", R.string.text_74d3f0927d465047);
|
||||||
|
IDS.put("已启用后台保活,切换应用可继续保持连接", R.string.text_c28d86acbedba3b6);
|
||||||
|
IDS.put("已启用后台保活,可从连接通知返回或断开", R.string.text_506409ec08cb2a79);
|
||||||
|
IDS.put("已授权", R.string.text_521e65ffc7d0ff2d);
|
||||||
|
IDS.put("已读", R.string.text_efdd1a47e4beb2f9);
|
||||||
|
IDS.put("已连接 ", R.string.text_1f672b9b18d18e87);
|
||||||
|
IDS.put("已连接服务器", R.string.text_9c05d9d7fa1b0abd);
|
||||||
|
IDS.put("帧率", R.string.text_f9404bf335bc14cf);
|
||||||
|
IDS.put("帧率优先", R.string.text_9ae0c69e26db1d6a);
|
||||||
|
IDS.put("平衡", R.string.text_df544e46aa4d5c17);
|
||||||
|
IDS.put("应用中的许可文件缺失或损坏,请重新安装应用。", R.string.text_3362e8fa7f65cc2f);
|
||||||
|
IDS.put("开发版本包含尚未发布的修改,以下链接为基础版本源码。", R.string.text_3f2904435942cc0f);
|
||||||
|
IDS.put("开启保存", R.string.text_4338afaad52ea445);
|
||||||
|
IDS.put("开启隐私屏", R.string.text_c8bb8255390df3b6);
|
||||||
|
IDS.put("开源组件", R.string.text_c6ecaca362ae1c80);
|
||||||
|
IDS.put("开源软件权利说明", R.string.text_6099db946bdb5ee4);
|
||||||
|
IDS.put("当前已是最新版本", R.string.text_6e86570183c3cdd0);
|
||||||
|
IDS.put("当前远端不支持直接输入此文本,请使用支持文本输入的被控端版本,或通过剪贴板发送", R.string.text_24a52788cf474bf6);
|
||||||
|
IDS.put("成功连接后,这里会显示最近的远程设备。", R.string.text_33f25b500697a7d9);
|
||||||
|
IDS.put("我已阅读并同意《隐私政策》", R.string.text_70341256d7317e7d);
|
||||||
|
IDS.put("所选文件(无法读取或空间不足)", R.string.text_85c4c8fda0461f35);
|
||||||
|
IDS.put("找不到该设备 ID", R.string.text_9b58c9ff5f6a856f);
|
||||||
|
IDS.put("指令发送失败,请重试", R.string.text_97b7b18df07b9787);
|
||||||
|
IDS.put("指令已发送,等待电脑确认…", R.string.text_cb782027ca5986c7);
|
||||||
|
IDS.put("授权状态", R.string.text_3ca40a586103f6b7);
|
||||||
|
IDS.put("接收", R.string.text_6e684586884ebee6);
|
||||||
|
IDS.put("接收 ", R.string.text_84ae92e54e3078aa);
|
||||||
|
IDS.put("控制端解码器", R.string.text_d16488b987723161);
|
||||||
|
IDS.put("撤回", R.string.text_6b58198955259066);
|
||||||
|
IDS.put("撤回后将停止远程连接、关闭画面预览保存并清除已有预览。连接记录和密码会保留,重新连接前需再次授权。", R.string.text_c52e42ee856825db);
|
||||||
|
IDS.put("撤回联网授权", R.string.text_61f4788032ec596b);
|
||||||
|
IDS.put("撤回联网授权?", R.string.text_e736e419008ac10a);
|
||||||
|
IDS.put("收起远程控制菜单", R.string.text_73431dcf0afb7a65);
|
||||||
|
IDS.put("收起键盘", R.string.text_d826c4ece4dd4031);
|
||||||
|
IDS.put("收起鼠标", R.string.text_6e9a457c0c3c34ed);
|
||||||
|
IDS.put("数据", R.string.text_5440f7424f2865bf);
|
||||||
|
IDS.put("文件保存失败,请重试", R.string.text_94244f41d0a41378);
|
||||||
|
IDS.put("文件已保存", R.string.text_2553a785fef2a405);
|
||||||
|
IDS.put("文件选择已超时,会话已结束", R.string.text_d573b3f9a810ea04);
|
||||||
|
IDS.put("文本超过 128 KB", R.string.text_985a5c380be49f4f);
|
||||||
|
IDS.put("断开", R.string.text_f33ac04eece6a6a6);
|
||||||
|
IDS.put("断开后将返回首页", R.string.text_09b1395f50589327);
|
||||||
|
IDS.put("断开远程连接?", R.string.text_a73c0680db0d2e06);
|
||||||
|
IDS.put("断开连接", R.string.text_a33f3804768e68ee);
|
||||||
|
IDS.put("新版本", R.string.text_47ceaf502c5a05d6);
|
||||||
|
IDS.put("新版本可用:v", R.string.text_78823e6cf1a8afe5);
|
||||||
|
IDS.put("新版本可用:v{0}", R.string.text_b7e58b67fa8f7e78);
|
||||||
|
IDS.put("无未读公告", R.string.text_ef23787ec32c5588);
|
||||||
|
IDS.put("无法保存本地阅读状态,请重试。", R.string.text_dca2c418ac967366);
|
||||||
|
IDS.put("无法删除这条公告,请重试。", R.string.text_5c92da76360056cc);
|
||||||
|
IDS.put("无法发起远程连接", R.string.text_615709dd3129313c);
|
||||||
|
IDS.put("无法读取开源许可", R.string.text_a5c4f4ad79a640ae);
|
||||||
|
IDS.put("显示器", R.string.text_1a13ea5910a0c5e6);
|
||||||
|
IDS.put("显示密码", R.string.text_ffe21a671190323e);
|
||||||
|
IDS.put("暂不同意", R.string.text_a94460e8a04f04c5);
|
||||||
|
IDS.put("暂无公告", R.string.text_6d9a7758610a2432);
|
||||||
|
IDS.put("最近连接", R.string.text_0b290c6600d92f43);
|
||||||
|
IDS.put("服务器", R.string.text_2388cbc6a486af9f);
|
||||||
|
IDS.put("服务器 TLS 证书校验失败", R.string.text_dfe408e686175095);
|
||||||
|
IDS.put("服务器地址", R.string.text_d3716cc5a2f5a810);
|
||||||
|
IDS.put("服务器登录失败,请检查服务器地址和网络", R.string.text_2a9608d4465d0f70);
|
||||||
|
IDS.put("服务器端口", R.string.text_d270868960e68629);
|
||||||
|
IDS.put("服务器连接超时,请检查服务器地址和网络", R.string.text_7a4e4a6a1b479a52);
|
||||||
|
IDS.put("未启用", R.string.text_f95ea7f4c063eea7);
|
||||||
|
IDS.put("未找到可打开链接的浏览器", R.string.text_c8f7ed6f2186fb98);
|
||||||
|
IDS.put("未授权", R.string.text_94bc3d40defef3e9);
|
||||||
|
IDS.put("未读", R.string.text_4d0680f9efaef147);
|
||||||
|
IDS.put("未连接服务器 · 点击重试", R.string.text_d1fcb66a323a16f5);
|
||||||
|
IDS.put("本应用使用 OpenFEC 提供前向纠错功能,遵循 CeCILL-C 1.0 许可。该许可规定了有限保证及责任限制,详情见许可全文。", R.string.text_7eea7966c0b746c5);
|
||||||
|
IDS.put("本机 ID ", R.string.text_c611b4e50b0dbc08);
|
||||||
|
IDS.put("本机 ID {0}", R.string.text_97d10220f08dd7fc);
|
||||||
|
IDS.put("本版本源码与构建说明", R.string.text_91f15d505770d7a0);
|
||||||
|
IDS.put("本版本源码信息暂不可用。", R.string.text_5b2c8ca2c5a87241);
|
||||||
|
IDS.put("松开刷新", R.string.text_f0e274aa130e08a2);
|
||||||
|
IDS.put("构建说明", R.string.text_47077144715b7074);
|
||||||
|
IDS.put("构建配方与项目补丁", R.string.text_837ab2d0b2f4cb98);
|
||||||
|
IDS.put("查看源码", R.string.text_921c3b62f1f7d6f4);
|
||||||
|
IDS.put("检查失败,请稍后重试", R.string.text_fdd1059f10c4b979);
|
||||||
|
IDS.put("检查更新", R.string.text_7f68ebad19ba6bcd);
|
||||||
|
IDS.put("正在关闭隐私屏…", R.string.text_4f4d1e704e38c161);
|
||||||
|
IDS.put("正在建立安全连接…", R.string.text_2a05f6fbd3f4436e);
|
||||||
|
IDS.put("正在开启隐私屏…", R.string.text_0da575e3c92de73a);
|
||||||
|
IDS.put("正在检查更新…", R.string.text_91834aebecabcbc3);
|
||||||
|
IDS.put("正在清除…", R.string.text_0571a5f5f8ec4c83);
|
||||||
|
IDS.put("正在获取本机 ID…", R.string.text_0e6336c8fe918730);
|
||||||
|
IDS.put("正在读取所选文件…", R.string.text_faebd20c2801fd0f);
|
||||||
|
IDS.put("正在连接", R.string.text_694186911ae3e4da);
|
||||||
|
IDS.put("正在连接信令服务器…", R.string.text_c3ccc7f7d37533e3);
|
||||||
|
IDS.put("正在连接服务器…", R.string.text_4024d3c0f467a4f6);
|
||||||
|
IDS.put("正在重连服务器…", R.string.text_46fe1ef52ec3a10f);
|
||||||
|
IDS.put("此设备没有可用的文件选择器", R.string.text_4e4fce649fc00238);
|
||||||
|
IDS.put("清除", R.string.text_bce2377283c2455a);
|
||||||
|
IDS.put("清除预览图", R.string.text_ee0e76b8507212de);
|
||||||
|
IDS.put("清除预览图?", R.string.text_ecc809e44637de18);
|
||||||
|
IDS.put("源码与构建说明", R.string.text_2782e4485ad23d34);
|
||||||
|
IDS.put("源码版本", R.string.text_1f566fe8f8b718f3);
|
||||||
|
IDS.put("版本", R.string.text_5f76b2bf82dd2c5d);
|
||||||
|
IDS.put("版权与许可声明", R.string.text_16410ee9663fad10);
|
||||||
|
IDS.put("电脑键盘", R.string.text_11de77e1e41e680f);
|
||||||
|
IDS.put("画质优先", R.string.text_20f57900234debd7);
|
||||||
|
IDS.put("画面偏好", R.string.text_43b04aba53a54cf5);
|
||||||
|
IDS.put("画面延时", R.string.text_82a5600c470179fa);
|
||||||
|
IDS.put("画面设置", R.string.text_33b458faaf967e15);
|
||||||
|
IDS.put("画面质量", R.string.text_afb5acdf8f07fadb);
|
||||||
|
IDS.put("画面采集帧率", R.string.text_12b41b7a3fbcf8ca);
|
||||||
|
IDS.put("相对位置", R.string.text_66062971234f422d);
|
||||||
|
IDS.put("相对鼠标", R.string.text_c421dc051bea3f11);
|
||||||
|
IDS.put("知道了", R.string.text_de32e20193ad1042);
|
||||||
|
IDS.put("确定", R.string.text_fac2a67ad87807c4);
|
||||||
|
IDS.put("确认", R.string.text_36f33adaf0942634);
|
||||||
|
IDS.put("确认连接", R.string.text_a5b7fba4dbd8f622);
|
||||||
|
IDS.put("离线", R.string.text_be1b4f3c6c1cda1e);
|
||||||
|
IDS.put("空格", R.string.text_bfa4a6e0fe42ac28);
|
||||||
|
IDS.put("端口范围为 1–65535", R.string.text_471a579e8d87faac);
|
||||||
|
IDS.put("符号功能", R.string.text_c7d0c77862e32b52);
|
||||||
|
IDS.put("等待电脑返回隐私屏状态;旧版电脑端可能不支持", R.string.text_a562f07bd38c1a49);
|
||||||
|
IDS.put("等待远端屏幕信息", R.string.text_5ee880e2524a983a);
|
||||||
|
IDS.put("等待远端画面…", R.string.text_674c723a85ee7f57);
|
||||||
|
IDS.put("等待隐私授权", R.string.text_119f0483da4756c4);
|
||||||
|
IDS.put("系统键盘", R.string.text_7916071c1a8ee458);
|
||||||
|
IDS.put("绝对位置", R.string.text_02e9ba6fda719e2e);
|
||||||
|
IDS.put("绝对鼠标", R.string.text_8527c8c6d1ccc1ca);
|
||||||
|
IDS.put("网络状态", R.string.text_872428a7210d5e0c);
|
||||||
|
IDS.put("自定义", R.string.text_4eafa9e925b30bcd);
|
||||||
|
IDS.put("虚拟鼠标", R.string.text_dffd0c12b8f27247);
|
||||||
|
IDS.put("被控端已通过组合键解除隐私屏。点击“锁定”将重新开启隐私屏,并禁止本机使用组合键解锁。", R.string.text_62c199f37bf95869);
|
||||||
|
IDS.put("被控端版本过低,请升级到最新版本。", R.string.text_a569ce729c1d6e19);
|
||||||
|
IDS.put("被控端编码器", R.string.text_d814476826827b78);
|
||||||
|
IDS.put("要发送到远端的文本", R.string.text_488402883e27f6f0);
|
||||||
|
IDS.put("视频", R.string.text_c20f7618d330a854);
|
||||||
|
IDS.put("许可", R.string.text_310c625425f014d8);
|
||||||
|
IDS.put("许可与版权声明", R.string.text_5c34f7d0e2124dca);
|
||||||
|
IDS.put("许可与版权声明可离线查阅。", R.string.text_e24134b643a17975);
|
||||||
|
IDS.put("设备身份保存失败,请检查安全存储", R.string.text_5e7628f043ce71d0);
|
||||||
|
IDS.put("设备身份及选择保存的访问密码使用 Android Keystore 加密。连接记录仅保存在本机,不纳入设备备份。剪贴板内容在结束会话时清除。离开应用后通过画中画或连接通知保持会话,可在应用或通知中断开。", R.string.text_5d35ee0d7945fcbc);
|
||||||
|
IDS.put("设置", R.string.text_df3d58c7d84b85f2);
|
||||||
|
IDS.put("设置,有新版本", R.string.text_4f8771952222a011);
|
||||||
|
IDS.put("访问密码", R.string.text_f4f712d5be91721a);
|
||||||
|
IDS.put("访问密码错误,请重新输入", R.string.text_dd6c465847af1b60);
|
||||||
|
IDS.put("证书校验失败 · 点击重试", R.string.text_defba1d2989bfab4);
|
||||||
|
IDS.put("请先完成联网授权", R.string.text_44f3d4d52347095a);
|
||||||
|
IDS.put("请在远端启用 CrossDesk 系统服务后重试", R.string.text_2a07ca002bc5093d);
|
||||||
|
IDS.put("请求超时,尚未确认电脑的隐私屏状态", R.string.text_990fd23c62de5ccf);
|
||||||
|
IDS.put("请等待当前文件发送完成", R.string.text_e5c8fa6dcf7b2a8b);
|
||||||
|
IDS.put("请输入 1–65535 范围内的端口", R.string.text_9305d9d72b06110d);
|
||||||
|
IDS.put("请输入 9 位设备 ID", R.string.text_3c7223dfb8dde5cf);
|
||||||
|
IDS.put("请输入有效访问密码", R.string.text_285a0714894d2899);
|
||||||
|
IDS.put("调整失败,请重试。", R.string.text_c808bb77ffd2b4cd);
|
||||||
|
IDS.put("软件许可", R.string.text_a762e7e650e285fa);
|
||||||
|
IDS.put("返回", R.string.text_572cf45ba43634b3);
|
||||||
|
IDS.put("返回控制栏", R.string.text_7565f94031582c7c);
|
||||||
|
IDS.put("还没有连接记录", R.string.text_55113b6925c362fd);
|
||||||
|
IDS.put("这条公告已更新或删除,请返回公告列表查看。", R.string.text_3aa527b2106cc987);
|
||||||
|
IDS.put("远程任务已关闭", R.string.text_e357aa9a0fb28c16);
|
||||||
|
IDS.put("远程桌面", R.string.text_c255841f68096d3b);
|
||||||
|
IDS.put("远程桌面触控区域", R.string.text_133f4c7dabd88b97);
|
||||||
|
IDS.put("远程桌面:轻触点击,拖动移动,长按右键,双指捏合缩放,放大后双指拖动画面,原始比例下双指上下滑动滚动", R.string.text_5931aeb3778f28f1);
|
||||||
|
IDS.put("远程电脑", R.string.text_3535087994d050de);
|
||||||
|
IDS.put("远程窗口已关闭,或后台保活未能启动,会话已结束", R.string.text_ed2e03072ce131de);
|
||||||
|
IDS.put("远程连接", R.string.text_678b783fb578172b);
|
||||||
|
IDS.put("远端密码错误", R.string.text_deccbd3eb9ba45f2);
|
||||||
|
IDS.put("远端已关闭连接", R.string.text_a2ce499295e358ae);
|
||||||
|
IDS.put("远端当前不可用", R.string.text_176e117df3ace734);
|
||||||
|
IDS.put("远端访问密码已失效,请重新连接", R.string.text_d2c0694243f93249);
|
||||||
|
IDS.put("连接", R.string.text_a5574109f0208e89);
|
||||||
|
IDS.put("连接 ", R.string.text_1d54bfbae5f8ac08);
|
||||||
|
IDS.put("连接失败", R.string.text_89c4766afc85125f);
|
||||||
|
IDS.put("连接失败 · 点击重试", R.string.text_04bf1a61ba1369f6);
|
||||||
|
IDS.put("连接已断开", R.string.text_2ab7d89557e779ab);
|
||||||
|
IDS.put("连接已结束", R.string.text_c1d20bf4c3e471c1);
|
||||||
|
IDS.put("连接延时(RTT)", R.string.text_e57d2cbc0e9908e6);
|
||||||
|
IDS.put("连接建立后可调整,需被控端支持。", R.string.text_db4d4f0fab78498a);
|
||||||
|
IDS.put("连接建立后锁定横屏", R.string.text_2123a9ed718f2c40);
|
||||||
|
IDS.put("连接提示", R.string.text_f741e0536210eafa);
|
||||||
|
IDS.put("连接方式", R.string.text_485a26050ce57431);
|
||||||
|
IDS.put("连接服务器", R.string.text_5a048351a77fe88d);
|
||||||
|
IDS.put("连接服务器后可获取最新公告。", R.string.text_5854c4add7ab0f7a);
|
||||||
|
IDS.put("连接超时,请检查设备 ID、服务器及远端在线状态", R.string.text_32cd27b5a72515f3);
|
||||||
|
IDS.put("连接远程桌面", R.string.text_6edc4b54cc1a9838);
|
||||||
|
IDS.put("退格", R.string.text_d5407b4f3c666681);
|
||||||
|
IDS.put("通知公告", R.string.text_e02926784fcedb91);
|
||||||
|
IDS.put("部分预览图未能清除,请重试。", R.string.text_465a092680d132b3);
|
||||||
|
IDS.put("重试", R.string.text_b8784c8dd5636ff2);
|
||||||
|
IDS.put("重试保存阅读状态", R.string.text_a504fb6f162bdb20);
|
||||||
|
IDS.put("锁定", R.string.text_ff9872a023e3a8aa);
|
||||||
|
IDS.put("键盘", R.string.text_864200bbb241e067);
|
||||||
|
IDS.put("阅读并授权", R.string.text_1f0f014d4177a0cc);
|
||||||
|
IDS.put("隐私", R.string.text_86651d17a401c55b);
|
||||||
|
IDS.put("隐私与授权", R.string.text_26c6e011e343708c);
|
||||||
|
IDS.put("隐私屏", R.string.text_6cad42fd3da85585);
|
||||||
|
IDS.put("隐私屏已关闭", R.string.text_e157fafb76aad954);
|
||||||
|
IDS.put("隐私屏已开启", R.string.text_bbef0eac0c04f214);
|
||||||
|
IDS.put("隐私屏已被解锁", R.string.text_0c7535f7c5e43b6f);
|
||||||
|
IDS.put("隐私屏异常", R.string.text_b62ac3dfb606c00b);
|
||||||
|
IDS.put("隐私屏暂不可用", R.string.text_598a2747dbd21dd5);
|
||||||
|
IDS.put("隐私屏状态已过期,等待电脑确认", R.string.text_4d59be3c75189032);
|
||||||
|
IDS.put("隐私屏状态待确认", R.string.text_1674532c70ed1db2);
|
||||||
|
IDS.put("隐私政策", R.string.text_e464b41fd5c45ba5);
|
||||||
|
IDS.put("隐私政策暂时无法读取,请拒绝联网并重新安装应用。", R.string.text_554e957e8e3c00af);
|
||||||
|
IDS.put("静音", R.string.text_7db2e189ecb01065);
|
||||||
|
IDS.put("音频", R.string.text_296c632ec857a0ba);
|
||||||
|
IDS.put("项目主页", R.string.text_0cadb49096a382e0);
|
||||||
|
IDS.put("预览可能包含远程电脑上的个人或工作信息。开启后,每次连接会自动将一张画面保存在本机并显示在最近连接中。你可以随时关闭并清除。", R.string.text_3beca60bd0c654f7);
|
||||||
|
IDS.put("高", R.string.text_b1c27820fec23edb);
|
||||||
|
IDS.put("默认", R.string.text_844b8cc8dff7c1d8);
|
||||||
|
IDS.put("默认关闭。开启后,手机连接时进入并保持横屏;关闭时跟随设备方向。平板和多窗口不受影响。", R.string.text_4db8737054f3b0cc);
|
||||||
|
IDS.put("鼠标右键,按住并拖动,松开结束拖拽", R.string.text_1184a97edd8e1646);
|
||||||
|
IDS.put("鼠标左键,按住并拖动,松开结束拖拽", R.string.text_926eaa3af0b5e48e);
|
||||||
|
IDS.put("鼠标控制", R.string.text_269f39a07b4a4e01);
|
||||||
|
IDS.put("鼠标滚轮,上下滑动,可滑出按键范围", R.string.text_cba62aeeb4a63a23);
|
||||||
|
IDS.put(",丢包率 ", R.string.text_9aa238fc6121dede);
|
||||||
|
IDS.put(",发送 ", R.string.text_0b5d16c21095fe10);
|
||||||
|
IDS.put(",已保存密码", R.string.text_ba9a792d063a8c86);
|
||||||
|
IDS.put(",接收 ", R.string.text_f4c1f9d7896760a7);
|
||||||
|
IDS.put(",点击重试", R.string.text_42237b5ec70c26f3);
|
||||||
|
}
|
||||||
|
private LocalizationKeys() {}
|
||||||
|
}
|
||||||
@@ -79,6 +79,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
private AlertDialog connectionDialog;
|
private AlertDialog connectionDialog;
|
||||||
private String pendingConnectionMessage="";
|
private String pendingConnectionMessage="";
|
||||||
private AlertDialog remoteUpdateDialog;
|
private AlertDialog remoteUpdateDialog;
|
||||||
|
private AlertDialog privacyUnlockDialog;
|
||||||
private RemoteVersionCheck remoteVersionCheck;
|
private RemoteVersionCheck remoteVersionCheck;
|
||||||
private String host, connectedRemote="", hostPlatform="", remoteClipboard="", pendingPassword="", page="home";
|
private String host, connectedRemote="", hostPlatform="", remoteClipboard="", pendingPassword="", page="home";
|
||||||
private int port,selectedDisplay;
|
private int port,selectedDisplay;
|
||||||
@@ -88,7 +89,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
private static final int PICK_FILE=41,SAVE_FILE=42;
|
private static final int PICK_FILE=41,SAVE_FILE=42;
|
||||||
private NativeSession documentSession;
|
private NativeSession documentSession;
|
||||||
private java.io.File receivedFile,documentToSave;
|
private java.io.File receivedFile,documentToSave;
|
||||||
private final Runnable documentTimeout=()->{if(documentSession!=null&&!foreground)end("文件选择已超时,会话已结束");};
|
private final Runnable documentTimeout=()->{if(documentSession!=null&&!foreground)end(L10n.tr("文件选择已超时,会话已结束"));};
|
||||||
private RemoteVideoSettings videoSettings=new RemoteVideoSettings(1);
|
private RemoteVideoSettings videoSettings=new RemoteVideoSettings(1);
|
||||||
private final android.os.Handler mainHandler=new android.os.Handler(android.os.Looper.getMainLooper());
|
private final android.os.Handler mainHandler=new android.os.Handler(android.os.Looper.getMainLooper());
|
||||||
private Runnable videoSettingsTimeout;
|
private Runnable videoSettingsTimeout;
|
||||||
@@ -157,7 +158,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
}
|
}
|
||||||
private LinearLayout navigation(String title,Runnable back){
|
private LinearLayout navigation(String title,Runnable back){
|
||||||
LinearLayout bar=row();bar.setPadding(dp(12),0,dp(12),0);root.addView(bar,ui.size(-1,56));
|
LinearLayout bar=row();bar.setPadding(dp(12),0,dp(12),0);root.addView(bar,ui.size(-1,56));
|
||||||
bar.addView(ui.iconButton("back","返回",this::navigateBack),ui.size(44,44));
|
bar.addView(ui.iconButton("back",L10n.tr("返回"),this::navigateBack),ui.size(44,44));
|
||||||
TextView label=text(title,17,true);label.setGravity(Gravity.CENTER);label.setSingleLine();label.setEllipsize(TextUtils.TruncateAt.END);bar.addView(label,new LinearLayout.LayoutParams(0,-1,1));
|
TextView label=text(title,17,true);label.setGravity(Gravity.CENTER);label.setSingleLine();label.setEllipsize(TextUtils.TruncateAt.END);bar.addView(label,new LinearLayout.LayoutParams(0,-1,1));
|
||||||
bar.addView(new View(this),ui.size(44,44));backAction=back;return bar;
|
bar.addView(new View(this),ui.size(44,44));backAction=back;return bar;
|
||||||
}
|
}
|
||||||
@@ -199,34 +200,34 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
signalBadge=text("",12,true);signalBadge.setGravity(Gravity.CENTER);signalBadge.setPadding(dp(11),0,dp(11),0);signalBadge.setSingleLine(true);
|
signalBadge=text("",12,true);signalBadge.setGravity(Gravity.CENTER);signalBadge.setPadding(dp(11),0,dp(11),0);signalBadge.setSingleLine(true);
|
||||||
signalBadge.setOnClickListener(v->{
|
signalBadge.setOnClickListener(v->{
|
||||||
if(!preferences.getBoolean("networkConsent",false))consent(this::renderSignaling);
|
if(!preferences.getBoolean("networkConsent",false))consent(this::renderSignaling);
|
||||||
else if(signalState==1)toast("已连接 "+serverKey());
|
else if(signalState==1)toast(L10n.tr("已连接 ")+serverKey());
|
||||||
else {stopSignaling();ensureSignaling();}
|
else {stopSignaling();ensureSignaling();}
|
||||||
});renderSignaling();toolbar.addView(signalBadge,ui.size(-2,34));toolbar.addView(new View(this),new LinearLayout.LayoutParams(0,1,1));
|
});renderSignaling();toolbar.addView(signalBadge,ui.size(-2,34));toolbar.addView(new View(this),new LinearLayout.LayoutParams(0,1,1));
|
||||||
FrameLayout announcementButton=(FrameLayout)ui.iconButton("envelope","通知公告",this::announcements);announcementButton.setClipChildren(false);toolbar.setClipChildren(false);toolbar.setClipToPadding(false);
|
FrameLayout announcementButton=(FrameLayout)ui.iconButton("envelope",L10n.tr("通知公告"),this::announcements);announcementButton.setClipChildren(false);toolbar.setClipChildren(false);toolbar.setClipToPadding(false);
|
||||||
announcementBadge=text("",10,true);announcementBadge.setTextColor(Color.WHITE);announcementBadge.setGravity(Gravity.CENTER);announcementBadge.setMinWidth(dp(16));announcementBadge.setPadding(dp(4),0,dp(4),0);announcementBadge.setBackground(ui.background(RED,12));announcementBadge.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
announcementBadge=text("",10,true);announcementBadge.setTextColor(Color.WHITE);announcementBadge.setGravity(Gravity.CENTER);announcementBadge.setMinWidth(dp(16));announcementBadge.setPadding(dp(4),0,dp(4),0);announcementBadge.setBackground(ui.background(RED,12));announcementBadge.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||||
FrameLayout.LayoutParams badgePosition=new FrameLayout.LayoutParams(-2,dp(16),Gravity.TOP|Gravity.END);badgePosition.topMargin=-dp(3);badgePosition.setMarginEnd(-dp(5));announcementButton.addView(announcementBadge,badgePosition);renderAnnouncements();
|
FrameLayout.LayoutParams badgePosition=new FrameLayout.LayoutParams(-2,dp(16),Gravity.TOP|Gravity.END);badgePosition.topMargin=-dp(3);badgePosition.setMarginEnd(-dp(5));announcementButton.addView(announcementBadge,badgePosition);renderAnnouncements();
|
||||||
toolbar.addView(announcementButton,ui.size(40,40));View spacer=new View(this);toolbar.addView(spacer,ui.size(12,1));
|
toolbar.addView(announcementButton,ui.size(40,40));View spacer=new View(this);toolbar.addView(spacer,ui.size(12,1));
|
||||||
FrameLayout settingsButton=(FrameLayout)ui.iconButton("settings","设置",this::settings);settingsButton.setClipChildren(false);settingsUpdateButton=settingsButton;
|
FrameLayout settingsButton=(FrameLayout)ui.iconButton("settings",L10n.tr("设置"),this::settings);settingsButton.setClipChildren(false);settingsUpdateButton=settingsButton;
|
||||||
TextView updateBadge=text("!",10,true);updateBadge.setTextColor(Color.WHITE);updateBadge.setGravity(Gravity.CENTER);updateBadge.setIncludeFontPadding(false);updateBadge.setBackground(ui.background(RED,8));updateBadge.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);settingsUpdateDot=updateBadge;
|
TextView updateBadge=text("!",10,true);updateBadge.setTextColor(Color.WHITE);updateBadge.setGravity(Gravity.CENTER);updateBadge.setIncludeFontPadding(false);updateBadge.setBackground(ui.background(RED,8));updateBadge.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);settingsUpdateDot=updateBadge;
|
||||||
FrameLayout.LayoutParams updatePosition=new FrameLayout.LayoutParams(dp(16),dp(16),Gravity.TOP|Gravity.END);updatePosition.topMargin=-dp(3);updatePosition.setMarginEnd(-dp(5));settingsButton.addView(settingsUpdateDot,updatePosition);
|
FrameLayout.LayoutParams updatePosition=new FrameLayout.LayoutParams(dp(16),dp(16),Gravity.TOP|Gravity.END);updatePosition.topMargin=-dp(3);updatePosition.setMarginEnd(-dp(5));settingsButton.addView(settingsUpdateDot,updatePosition);
|
||||||
toolbar.addView(settingsButton,ui.size(40,40));renderAppUpdates();
|
toolbar.addView(settingsButton,ui.size(40,40));renderAppUpdates();
|
||||||
HomeLayout layout=new HomeLayout(ui);root.addView(layout,new LinearLayout.LayoutParams(-1,0,1));LinearLayout content=layout.panels;
|
HomeLayout layout=new HomeLayout(ui);root.addView(layout,new LinearLayout.LayoutParams(-1,0,1));LinearLayout content=layout.panels;
|
||||||
LinearLayout connection=column();connection.setPadding(dp(18),dp(18),dp(18),dp(18));content.addView(ui.shadowCard(connection,20));
|
LinearLayout connection=column();connection.setPadding(dp(18),dp(18),dp(18),dp(18));content.addView(ui.shadowCard(connection,20));
|
||||||
connection.addView(text("远程桌面",20,true));ui.gap(connection,16);
|
connection.addView(text(L10n.tr("远程桌面"),20,true));ui.gap(connection,16);
|
||||||
LinearLayout entry=row();connection.addView(entry,ui.size(-1,54));
|
LinearLayout entry=row();connection.addView(entry,ui.size(-1,54));
|
||||||
remote=field("对端 ID",InputType.TYPE_CLASS_NUMBER);remote.setTextSize(20);remote.setTypeface(Typeface.create("sans-serif-medium",Typeface.NORMAL));
|
remote=field(L10n.tr("对端 ID"),InputType.TYPE_CLASS_NUMBER);remote.setTextSize(20);remote.setTypeface(Typeface.create("sans-serif-medium",Typeface.NORMAL));
|
||||||
remote.setOnFocusChangeListener((view,focused)->{GradientDrawable shape=ui.background(BACKGROUND,12);shape.setStroke(dp(focused?2:1),focused?BLUE:LINE);remote.setBackground(shape);});
|
remote.setOnFocusChangeListener((view,focused)->{GradientDrawable shape=ui.background(BACKGROUND,12);shape.setStroke(dp(focused?2:1),focused?BLUE:LINE);remote.setBackground(shape);});
|
||||||
remote.setFilters(new InputFilter[]{new InputFilter.LengthFilter(11)});remote.setText(formatId(preferences.getString("lastRemote","")));
|
remote.setFilters(new InputFilter[]{new InputFilter.LengthFilter(11)});remote.setText(formatId(preferences.getString("lastRemote","")));
|
||||||
entry.addView(remote,new LinearLayout.LayoutParams(0,-1,1));
|
entry.addView(remote,new LinearLayout.LayoutParams(0,-1,1));
|
||||||
TextView connect=ui.primary("连接 →",()->promptConnection(id()));connect.setContentDescription("连接");LinearLayout.LayoutParams buttonParams=ui.size(96,54);buttonParams.leftMargin=dp(10);entry.addView(connect,buttonParams);
|
TextView connect=ui.primary(L10n.tr("连接"),()->promptConnection(id()));connect.setContentDescription(L10n.tr("连接"));LinearLayout.LayoutParams buttonParams=ui.size(96,54);buttonParams.leftMargin=dp(10);entry.addView(connect,buttonParams);
|
||||||
Runnable update=()->{connect.setEnabled(!id().isEmpty());connect.setAlpha(id().isEmpty()?.45f:1);};update.run();
|
Runnable update=()->{connect.setEnabled(!id().isEmpty());connect.setAlpha(id().isEmpty()?.45f:1);};update.run();
|
||||||
remote.addTextChangedListener(new TextWatcher(){boolean editing;public void beforeTextChanged(CharSequence s,int start,int count,int after){}public void onTextChanged(CharSequence s,int start,int before,int count){update.run();}public void afterTextChanged(Editable value){if(editing)return;String formatted=formatId(value.toString());if(!formatted.equals(value.toString())){editing=true;int cursor=remote.getSelectionStart(),digits=value.subSequence(0,Math.max(0,cursor)).toString().replaceAll("[^0-9]","").length();remote.setText(formatted);remote.setSelection(Math.min(formatted.length(),digits+Math.max(0,digits-1)/3));editing=false;}}});
|
remote.addTextChangedListener(new TextWatcher(){boolean editing;public void beforeTextChanged(CharSequence s,int start,int count,int after){}public void onTextChanged(CharSequence s,int start,int before,int count){update.run();}public void afterTextChanged(Editable value){if(editing)return;String formatted=formatId(value.toString());if(!formatted.equals(value.toString())){editing=true;int cursor=remote.getSelectionStart(),digits=value.subSequence(0,Math.max(0,cursor)).toString().replaceAll("[^0-9]","").length();remote.setText(formatted);remote.setSelection(Math.min(formatted.length(),digits+Math.max(0,digits-1)/3));editing=false;}}});
|
||||||
LinearLayout recent=column();recent.setPadding(dp(16),dp(16),dp(16),dp(16));ui.card(recent,18);LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(-1,0,1);p.topMargin=dp(20);content.addView(recent,p);
|
LinearLayout recent=column();recent.setPadding(dp(16),dp(16),dp(16),dp(16));ui.card(recent,18);LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(-1,0,1);p.topMargin=dp(20);content.addView(recent,p);
|
||||||
JSONArray records=history.list(serverKey());recentPresence.watch(records,android.os.SystemClock.elapsedRealtime());LinearLayout heading=row();heading.addView(text("最近连接",20,true),new LinearLayout.LayoutParams(0,-2,1));
|
JSONArray records=history.list(serverKey());recentPresence.watch(records,android.os.SystemClock.elapsedRealtime());LinearLayout heading=row();heading.addView(text(L10n.tr("最近连接"),20,true),new LinearLayout.LayoutParams(0,-2,1));
|
||||||
if(records.length()>0){TextView count=text(records.length()+" 台设备",12,false);count.setTextColor(SECONDARY);heading.addView(count);}recent.addView(heading);
|
if(records.length()>0){TextView count=text(L10n.format("{0} 台设备",records.length()),12,false);count.setTextColor(SECONDARY);heading.addView(count);}recent.addView(heading);
|
||||||
if(records.length()==0){
|
if(records.length()==0){
|
||||||
LinearLayout empty=column();empty.setGravity(Gravity.CENTER);recent.addView(empty,new LinearLayout.LayoutParams(-1,0,1));empty.addView(ui.icon("history",0xFFB6B6BB),ui.size(34,34));ui.gap(empty,12);
|
LinearLayout empty=column();empty.setGravity(Gravity.CENTER);recent.addView(empty,new LinearLayout.LayoutParams(-1,0,1));empty.addView(ui.icon("history",0xFFB6B6BB),ui.size(34,34));ui.gap(empty,12);
|
||||||
TextView emptyTitle=text("还没有连接记录",17,true);emptyTitle.setGravity(Gravity.CENTER);empty.addView(emptyTitle);ui.gap(empty,8);TextView hint=text("成功连接后,这里会显示最近的远程设备。",13,false);hint.setTextColor(SECONDARY);hint.setGravity(Gravity.CENTER);empty.addView(hint);
|
TextView emptyTitle=text(L10n.tr("还没有连接记录"),17,true);emptyTitle.setGravity(Gravity.CENTER);empty.addView(emptyTitle);ui.gap(empty,8);TextView hint=text(L10n.tr("成功连接后,这里会显示最近的远程设备。"),13,false);hint.setTextColor(SECONDARY);hint.setGravity(Gravity.CENTER);empty.addView(hint);
|
||||||
}else{
|
}else{
|
||||||
ui.gap(recent,14);ScrollView scroll=new ScrollView(this);recent.addView(scroll,new LinearLayout.LayoutParams(-1,0,1));recentGrid=column();scroll.addView(recentGrid);
|
ui.gap(recent,14);ScrollView scroll=new ScrollView(this);recent.addView(scroll,new LinearLayout.LayoutParams(-1,0,1));recentGrid=column();scroll.addView(recentGrid);
|
||||||
recentGrid.addOnLayoutChangeListener((view,left,top,right,bottom,oldLeft,oldTop,oldRight,oldBottom)->{
|
recentGrid.addOnLayoutChangeListener((view,left,top,right,bottom,oldLeft,oldTop,oldRight,oldBottom)->{
|
||||||
@@ -242,7 +243,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
private void showConnectionMessage(){
|
private void showConnectionMessage(){
|
||||||
if(pendingConnectionMessage.isEmpty()||!foreground||isFinishing()||isDestroyed())return;
|
if(pendingConnectionMessage.isEmpty()||!foreground||isFinishing()||isDestroyed())return;
|
||||||
if(connectionDialog!=null)connectionDialog.dismiss();
|
if(connectionDialog!=null)connectionDialog.dismiss();
|
||||||
connectionDialog=new AlertDialog.Builder(this).setTitle("连接提示").setMessage(pendingConnectionMessage).setPositiveButton("确定",null).create();
|
connectionDialog=new AlertDialog.Builder(this).setTitle(L10n.tr("连接提示")).setMessage(pendingConnectionMessage).setPositiveButton(L10n.tr("确定"),null).create();
|
||||||
connectionDialog.setOnDismissListener(dialog->connectionDialog=null);
|
connectionDialog.setOnDismissListener(dialog->connectionDialog=null);
|
||||||
pendingConnectionMessage="";connectionDialog.show();
|
pendingConnectionMessage="";connectionDialog.show();
|
||||||
}
|
}
|
||||||
@@ -258,27 +259,27 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
if(item==null)return new View(this);String id=item.optString("id"),platform=item.optString("platform");
|
if(item==null)return new View(this);String id=item.optString("id"),platform=item.optString("platform");
|
||||||
LinearLayout card=column();ui.card(card,14);FrameLayout preview=new FrameLayout(this){@Override protected void onMeasure(int w,int h){super.onMeasure(w,MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(w)*9/16,MeasureSpec.EXACTLY));}};
|
LinearLayout card=column();ui.card(card,14);FrameLayout preview=new FrameLayout(this){@Override protected void onMeasure(int w,int h){super.onMeasure(w,MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(w)*9/16,MeasureSpec.EXACTLY));}};
|
||||||
int color=platform.equals("windows")?0xFF316AB7:platform.equals("macos")?0xFF8057B9:platform.equals("linux")?0xFFAE642B:0xFF677587;preview.setBackgroundColor(color);
|
int color=platform.equals("windows")?0xFF316AB7:platform.equals("macos")?0xFF8057B9:platform.equals("linux")?0xFFAE642B:0xFF677587;preview.setBackgroundColor(color);
|
||||||
TextView label=text(platform.equals("windows")?"Windows":platform.equals("macos")?"macOS":platform.equals("linux")?"Linux":"远程电脑",19,true);label.setGravity(Gravity.CENTER);label.setTextColor(Color.WHITE);preview.addView(label,new FrameLayout.LayoutParams(-1,-1));card.addView(preview,ui.size(-1,80));
|
TextView label=text(platform.equals("windows")?"Windows":platform.equals("macos")?"macOS":platform.equals("linux")?"Linux":L10n.tr("远程电脑"),19,true);label.setGravity(Gravity.CENTER);label.setTextColor(Color.WHITE);preview.addView(label,new FrameLayout.LayoutParams(-1,-1));card.addView(preview,ui.size(-1,80));
|
||||||
ImageView image=new ImageView(this);image.setScaleType(ImageView.ScaleType.CENTER_CROP);image.setVisibility(View.GONE);image.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);preview.addView(image,new FrameLayout.LayoutParams(-1,-1));
|
ImageView image=new ImageView(this);image.setScaleType(ImageView.ScaleType.CENTER_CROP);image.setVisibility(View.GONE);image.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);preview.addView(image,new FrameLayout.LayoutParams(-1,-1));
|
||||||
String server=serverKey();Runnable load=()->{if(!image.isAttachedToWindow())return;Object binding=new Object();image.setTag(binding);image.setImageDrawable(null);image.setVisibility(View.GONE);label.setVisibility(View.VISIBLE);
|
String server=serverKey();Runnable load=()->{if(!image.isAttachedToWindow())return;Object binding=new Object();image.setTag(binding);image.setImageDrawable(null);image.setVisibility(View.GONE);label.setVisibility(View.VISIBLE);
|
||||||
previews.load(server,id,bitmap->{if(image.getTag()!=binding||!image.isAttachedToWindow()){if(bitmap!=null)bitmap.recycle();return;}if(bitmap!=null){image.setImageBitmap(bitmap);image.setVisibility(View.VISIBLE);label.setVisibility(View.GONE);}});};previewCards.add(load);image.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener(){public void onViewAttachedToWindow(View view){load.run();}public void onViewDetachedFromWindow(View view){image.setTag(null);}});if(image.isAttachedToWindow())load.run();
|
previews.load(server,id,bitmap->{if(image.getTag()!=binding||!image.isAttachedToWindow()){if(bitmap!=null)bitmap.recycle();return;}if(bitmap!=null){image.setImageBitmap(bitmap);image.setVisibility(View.VISIBLE);label.setVisibility(View.GONE);}});};previewCards.add(load);image.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener(){public void onViewAttachedToWindow(View view){load.run();}public void onViewDetachedFromWindow(View view){image.setTag(null);}});if(image.isAttachedToWindow())load.run();
|
||||||
LinearLayout caption=column();caption.setPadding(dp(9),dp(9),dp(9),dp(9));TextView name=text(item.optString("name",id),12,true);name.setSingleLine();name.setEllipsize(TextUtils.TruncateAt.END);caption.addView(name);ui.gap(caption,4);
|
LinearLayout caption=column();caption.setPadding(dp(9),dp(9),dp(9),dp(9));TextView name=text(item.optString("name",id),12,true);name.setSingleLine();name.setEllipsize(TextUtils.TruncateAt.END);caption.addView(name);ui.gap(caption,4);
|
||||||
LinearLayout details=row();TextView detail=text("ID "+id,10,false);detail.setTypeface(Typeface.create("sans-serif",Typeface.NORMAL));detail.setTextColor(SECONDARY);detail.setSingleLine();detail.setEllipsize(TextUtils.TruncateAt.END);details.addView(detail,new LinearLayout.LayoutParams(0,-2,1));
|
LinearLayout details=row();TextView detail=text("ID "+id,10,false);detail.setTypeface(Typeface.create("sans-serif",Typeface.NORMAL));detail.setTextColor(SECONDARY);detail.setSingleLine();detail.setEllipsize(TextUtils.TruncateAt.END);details.addView(detail,new LinearLayout.LayoutParams(0,-2,1));
|
||||||
if(item.optBoolean("remember")){View key=ui.icon("key",SECONDARY);key.setContentDescription("已保存密码");LinearLayout.LayoutParams keySize=ui.size(10,10);keySize.leftMargin=dp(4);details.addView(key,keySize);}
|
if(item.optBoolean("remember")){View key=ui.icon("key",SECONDARY);key.setContentDescription(L10n.tr("已保存密码"));LinearLayout.LayoutParams keySize=ui.size(10,10);keySize.leftMargin=dp(4);details.addView(key,keySize);}
|
||||||
boolean online=recentPresence.isOnline(id,android.os.SystemClock.elapsedRealtime());String availability=online?"在线":"离线";TextView presence=text(availability,10,true);presence.setSingleLine();presence.setTextColor(online?GREEN:SECONDARY);LinearLayout.LayoutParams presenceSize=ui.size(-2,-2);presenceSize.leftMargin=dp(5);details.addView(presence,presenceSize);caption.addView(details);card.addView(caption);
|
boolean online=recentPresence.isOnline(id,android.os.SystemClock.elapsedRealtime());String availability=online?L10n.tr("在线"):L10n.tr("离线");TextView presence=text(availability,10,true);presence.setSingleLine();presence.setTextColor(online?GREEN:SECONDARY);LinearLayout.LayoutParams presenceSize=ui.size(-2,-2);presenceSize.leftMargin=dp(5);details.addView(presence,presenceSize);caption.addView(details);card.addView(caption);
|
||||||
card.setContentDescription("连接 "+item.optString("name",id)+",ID "+id+(item.optBoolean("remember")?",已保存密码":"")+","+availability);card.setClickable(true);card.setFocusable(true);card.setOnClickListener(v->{remote.setText(formatId(id));promptConnection(id);});
|
card.setContentDescription(L10n.tr("连接 ")+item.optString("name",id)+",ID "+id+(item.optBoolean("remember")?L10n.tr(",已保存密码"):"")+","+availability);card.setClickable(true);card.setFocusable(true);card.setOnClickListener(v->{remote.setText(formatId(id));promptConnection(id);});
|
||||||
card.setOnLongClickListener(v->{new AlertDialog.Builder(this).setTitle(item.optString("name",id)).setItems(new String[]{"连接","删除记录"},(d,w)->{if(w==0)promptConnection(id);else{history.delete(serverKey(),id);home("");}}).show();return true;});return card;
|
card.setOnLongClickListener(v->{new AlertDialog.Builder(this).setTitle(item.optString("name",id)).setItems(new String[]{L10n.tr("连接"),L10n.tr("删除记录")},(d,w)->{if(w==0)promptConnection(id);else{history.delete(serverKey(),id);home("");}}).show();return true;});return card;
|
||||||
}
|
}
|
||||||
private void promptConnection(String id){
|
private void promptConnection(String id){
|
||||||
hideKeyboard();if(!id.matches("[0-9]{9}")){remote.setError("请输入 9 位设备 ID");remote.requestFocus();return;}
|
hideKeyboard();if(!id.matches("[0-9]{9}")){remote.setError(L10n.tr("请输入 9 位设备 ID"));remote.requestFocus();return;}
|
||||||
if(!preferences.getBoolean("networkConsent",false)){consent(()->connectOrPrompt(id));return;}connectOrPrompt(id);
|
if(!preferences.getBoolean("networkConsent",false)){consent(()->connectOrPrompt(id));return;}connectOrPrompt(id);
|
||||||
}
|
}
|
||||||
private void connectOrPrompt(String id){
|
private void connectOrPrompt(String id){
|
||||||
String stored;
|
String stored;
|
||||||
try{stored=history.password(serverKey(),id);}catch(Exception error){passwordSheet(id,"已保存密码无法读取,请重新输入",true);return;}
|
try{stored=history.password(serverKey(),id);}catch(Exception error){passwordSheet(id,L10n.tr("已保存密码无法读取,请重新输入"),true);return;}
|
||||||
if(!stored.isEmpty()){rememberPassword=true;begin(id,stored);return;}
|
if(!stored.isEmpty()){rememberPassword=true;begin(id,stored);return;}
|
||||||
boolean remember=history.remembersPassword(serverKey(),id);
|
boolean remember=history.remembersPassword(serverKey(),id);
|
||||||
passwordSheet(id,remember?"已保存的密码未能保留,请重新输入一次":"",remember);
|
passwordSheet(id,remember?L10n.tr("已保存的密码未能保留,请重新输入一次"):"",remember);
|
||||||
}
|
}
|
||||||
private void consent(Runnable accepted){
|
private void consent(Runnable accepted){
|
||||||
if(privacyDialog!=null&&privacyDialog.isShowing())return;
|
if(privacyDialog!=null&&privacyDialog.isShowing())return;
|
||||||
@@ -290,15 +291,15 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
private void passwordSheet(String id,String error,boolean rememberDefault){
|
private void passwordSheet(String id,String error,boolean rememberDefault){
|
||||||
Dialog dialog=new Dialog(this);passwordDialog=dialog;LinearLayout sheet=column();sheet.setPadding(dp(22),dp(10),dp(22),dp(22));sheet.setBackground(ui.background(BACKGROUND,24));
|
Dialog dialog=new Dialog(this);passwordDialog=dialog;LinearLayout sheet=column();sheet.setPadding(dp(22),dp(10),dp(22),dp(22));sheet.setBackground(ui.background(BACKGROUND,24));
|
||||||
View handle=new View(this);handle.setBackground(ui.background(0xFFC6C6CB,3));LinearLayout.LayoutParams hp=ui.size(36,5);hp.gravity=Gravity.CENTER;sheet.addView(handle,hp);ui.gap(sheet,20);
|
View handle=new View(this);handle.setBackground(ui.background(0xFFC6C6CB,3));LinearLayout.LayoutParams hp=ui.size(36,5);hp.gravity=Gravity.CENTER;sheet.addView(handle,hp);ui.gap(sheet,20);
|
||||||
LinearLayout header=row(),titles=column();titles.addView(text("连接远程桌面",22,true));ui.gap(titles,5);TextView subtitle=text("对端 ID "+id,15,false);subtitle.setTextColor(SECONDARY);titles.addView(subtitle);header.addView(titles,new LinearLayout.LayoutParams(0,-2,1));header.addView(ui.iconButton("close","关闭",dialog::dismiss),ui.size(36,36));sheet.addView(header);ui.gap(sheet,18);
|
LinearLayout header=row(),titles=column();titles.addView(text(L10n.tr("连接远程桌面"),22,true));ui.gap(titles,5);TextView subtitle=text(L10n.format("对端 ID {0}",id),15,false);subtitle.setTextColor(SECONDARY);titles.addView(subtitle);header.addView(titles,new LinearLayout.LayoutParams(0,-2,1));header.addView(ui.iconButton("close",L10n.tr("关闭"),dialog::dismiss),ui.size(36,36));sheet.addView(header);ui.gap(sheet,18);
|
||||||
LinearLayout passwordRow=row();ui.card(passwordRow,10);EditText password=field("访问密码",InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_PASSWORD);password.setBackgroundColor(Color.TRANSPARENT);passwordRow.addView(password,new LinearLayout.LayoutParams(0,dp(50),1));
|
LinearLayout passwordRow=row();ui.card(passwordRow,10);EditText password=field(L10n.tr("访问密码"),InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_PASSWORD);password.setBackgroundColor(Color.TRANSPARENT);passwordRow.addView(password,new LinearLayout.LayoutParams(0,dp(50),1));
|
||||||
View eye=ui.iconButton("eye","显示密码",()->{boolean hidden=password.getTransformationMethod()!=null;password.setTransformationMethod(hidden?null:PasswordTransformationMethod.getInstance());password.setSelection(password.length());});passwordRow.addView(eye,ui.size(44,44));sheet.addView(passwordRow);ui.gap(sheet,18);
|
View eye=ui.iconButton("eye",L10n.tr("显示密码"),()->{boolean hidden=password.getTransformationMethod()!=null;password.setTransformationMethod(hidden?null:PasswordTransformationMethod.getInstance());password.setSelection(password.length());});passwordRow.addView(eye,ui.size(44,44));sheet.addView(passwordRow);ui.gap(sheet,18);
|
||||||
LinearLayout save=row(),saveText=column();saveText.addView(text("保存密码",15,true));ui.gap(saveText,3);TextView security=text("密码将加密保存在本机 Android Keystore 中",11,false);security.setTextColor(SECONDARY);saveText.addView(security);save.addView(saveText,new LinearLayout.LayoutParams(0,-2,1));Switch remember=new Switch(this);remember.setContentDescription("保存密码");save.addView(remember);sheet.addView(save);
|
LinearLayout save=row(),saveText=column();saveText.addView(text(L10n.tr("保存密码"),15,true));ui.gap(saveText,3);TextView security=text(L10n.tr("密码将加密保存在本机 Android Keystore 中"),11,false);security.setTextColor(SECONDARY);saveText.addView(security);save.addView(saveText,new LinearLayout.LayoutParams(0,-2,1));Switch remember=new Switch(this);remember.setContentDescription(L10n.tr("保存密码"));save.addView(remember);sheet.addView(save);
|
||||||
remember.setChecked(rememberDefault);
|
remember.setChecked(rememberDefault);
|
||||||
ui.gap(sheet,18);TextView connect=ui.primary("连接 →",()->{
|
ui.gap(sheet,18);TextView connect=ui.primary(L10n.tr("连接"),()->{
|
||||||
String secret=password.getText().toString();if(secret.isEmpty()||secret.length()>128||secret.contains("@")){password.setError("请输入有效访问密码");return;}
|
String secret=password.getText().toString();if(secret.isEmpty()||secret.length()>128||secret.contains("@")){password.setError(L10n.tr("请输入有效访问密码"));return;}
|
||||||
rememberPassword=remember.isChecked();password.setText("");dialog.dismiss();begin(id,secret);
|
rememberPassword=remember.isChecked();password.setText("");dialog.dismiss();begin(id,secret);
|
||||||
});connect.setContentDescription("确认连接");sheet.addView(connect,ui.size(-1,48));
|
});connect.setContentDescription(L10n.tr("确认连接"));sheet.addView(connect,ui.size(-1,48));
|
||||||
ScrollView scroll=new ScrollView(this);scroll.addView(sheet);
|
ScrollView scroll=new ScrollView(this);scroll.addView(sheet);
|
||||||
dialog.setContentView(scroll);Window window=dialog.getWindow();if(window!=null){window.setBackgroundDrawableResource(android.R.color.transparent);window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);window.setDimAmount(.22f);}dialog.show();sizePasswordDialog();password.requestFocus();if(!error.isEmpty())password.setError(error);
|
dialog.setContentView(scroll);Window window=dialog.getWindow();if(window!=null){window.setBackgroundDrawableResource(android.R.color.transparent);window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);window.setDimAmount(.22f);}dialog.show();sizePasswordDialog();password.requestFocus();if(!error.isEmpty())password.setError(error);
|
||||||
dialog.setOnDismissListener(d->{password.setText("");passwordDialog=null;});
|
dialog.setOnDismissListener(d->{password.setText("");passwordDialog=null;});
|
||||||
@@ -318,27 +319,29 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
previewCapture=previews.begin(serverKey(),id);previewCopying=false;previewAttempts=0;
|
previewCapture=previews.begin(serverKey(),id);previewCopying=false;previewAttempts=0;
|
||||||
connectedRemote=id;pendingPassword=rememberPassword?secret:"";ready=false;muted=false;selectedDisplay=0;hostPlatform="";remoteClipboard="";displays=new JSONArray();preferences.edit().putString("lastRemote",id).apply();
|
connectedRemote=id;pendingPassword=rememberPassword?secret:"";ready=false;muted=false;selectedDisplay=0;hostPlatform="";remoteClipboard="";displays=new JSONArray();preferences.edit().putString("lastRemote",id).apply();
|
||||||
ensureSignaling();
|
ensureSignaling();
|
||||||
if(signaling==null){pendingPassword="";toast("请先完成联网授权");return;}
|
if(signaling==null){pendingPassword="";toast(L10n.tr("请先完成联网授权"));return;}
|
||||||
session=signaling;showProgress();session.connect(id,secret);
|
session=signaling;showProgress();session.connect(id,secret);
|
||||||
}
|
}
|
||||||
private void showProgress(){
|
private void showProgress(){
|
||||||
Dialog dialog=new Dialog(this);progressDialog=dialog;LinearLayout card=column();ui.card(card,24);card.setGravity(Gravity.CENTER_HORIZONTAL);LinearLayout body=column();body.setGravity(Gravity.CENTER);body.setPadding(dp(26),dp(24),dp(26),dp(20));
|
Dialog dialog=new Dialog(this);progressDialog=dialog;LinearLayout card=column();ui.card(card,24);card.setGravity(Gravity.CENTER_HORIZONTAL);LinearLayout body=column();body.setGravity(Gravity.CENTER);body.setPadding(dp(26),dp(24),dp(26),dp(20));
|
||||||
ProgressBar spinner=new ProgressBar(this);spinner.setIndeterminateTintList(ColorStateList.valueOf(BLUE));body.addView(spinner,ui.size(34,34));ui.gap(body,14);TextView title=text("正在连接",20,true);title.setGravity(Gravity.CENTER);body.addView(title);ui.gap(body,6);status=text("正在连接信令服务器…",14,false);status.setGravity(Gravity.CENTER);status.setTextColor(SECONDARY);status.setMinHeight(dp(36));body.addView(status);card.addView(body,ui.size(-1,-2));ui.divider(card);card.addView(ui.action("取消连接",RED,()->end("")),ui.size(-1,46));
|
ProgressBar spinner=new ProgressBar(this);spinner.setIndeterminateTintList(ColorStateList.valueOf(BLUE));body.addView(spinner,ui.size(34,34));ui.gap(body,14);TextView title=text(L10n.tr("正在连接"),20,true);title.setGravity(Gravity.CENTER);body.addView(title);ui.gap(body,6);status=text(L10n.tr("正在连接信令服务器…"),14,false);status.setGravity(Gravity.CENTER);status.setTextColor(SECONDARY);status.setMinHeight(dp(36));body.addView(status);card.addView(body,ui.size(-1,-2));ui.divider(card);card.addView(ui.action(L10n.tr("取消连接"),RED,()->end("")),ui.size(-1,46));
|
||||||
dialog.setContentView(card);dialog.setCanceledOnTouchOutside(false);dialog.setOnCancelListener(d->end(""));Window w=dialog.getWindow();if(w!=null){w.setBackgroundDrawableResource(android.R.color.transparent);w.setDimAmount(.08f);}dialog.show();if(w!=null)w.setLayout(dp(292),-2);
|
dialog.setContentView(card);dialog.setCanceledOnTouchOutside(false);dialog.setOnCancelListener(d->end(""));Window w=dialog.getWindow();if(w!=null){w.setBackgroundDrawableResource(android.R.color.transparent);w.setDimAmount(.08f);}dialog.show();if(w!=null)w.setLayout(dp(292),-2);
|
||||||
}
|
}
|
||||||
private void sessionScreen(boolean waitForLandscape){
|
private void sessionScreen(boolean autoLandscape){
|
||||||
installRoot("session",true);remoteOrientation.begin(waitForLandscape&&!isTablet()&&!isInMultiWindowMode());video=new RemoteVideoView(this,session);video.setRelative(preferences.getBoolean("relativeMouse",true));canvas.addView(video,0,new FrameLayout.LayoutParams(-1,-1));
|
installRoot("session",true);remoteOrientation.begin(autoLandscape,!isTablet()&&!isInMultiWindowMode());video=new RemoteVideoView(this,session);video.setRelative(preferences.getBoolean("relativeMouse",true));canvas.addView(video,0,new FrameLayout.LayoutParams(-1,-1));
|
||||||
controls=new SessionControls(this,ui,new SessionControls.Actions(){
|
controls=new SessionControls(this,ui,new SessionControls.Actions(){
|
||||||
public void disconnect(){confirmDisconnect();}public void display(){selectDisplay();}public void clipboard(){MainActivity.this.clipboard();}
|
public void disconnect(){confirmDisconnect();}public void display(){selectDisplay();}public void clipboard(){MainActivity.this.clipboard();}
|
||||||
public void sendFile(){chooseFile();}public void saveFile(){saveReceivedFile();}public void secureAttention(){MainActivity.this.secureAttention();}
|
public void sendFile(){chooseFile();}public void saveFile(){saveReceivedFile();}public void secureAttention(){MainActivity.this.secureAttention();}
|
||||||
|
public void togglePrivacy(){if(ready&&session!=null)session.togglePrivacy();}
|
||||||
public void key(int code){tapKey(code);}
|
public void key(int code){tapKey(code);}
|
||||||
public boolean textEdit(int count,String value){return sendTextEdit(count,value);}
|
public boolean textEdit(int count,String value){return sendTextEdit(count,value);}
|
||||||
public void chord(int code,int[] modifiers){if(session==null)return;for(int modifier:modifiers)session.key(modifier,true);tapKey(code);for(int i=modifiers.length-1;i>=0;i--)session.key(modifiers[i],false);}public void shortcut(int code){MainActivity.this.shortcut(code);}public void text(String value){sendText(value);}
|
public void chord(int code,int[] modifiers){if(session==null)return;for(int modifier:modifiers)session.key(modifier,true);tapKey(code);for(int i=modifiers.length-1;i>=0;i--)session.key(modifiers[i],false);}public void shortcut(int code){MainActivity.this.shortcut(code);}public void text(String value){sendText(value);}
|
||||||
public void mute(){muted=!muted;if(session!=null)session.control(2,muted?0:1);controls.setMuted(muted);}
|
public void mute(){muted=!muted;if(session!=null)session.control(2,muted?0:1);controls.setMuted(muted);}
|
||||||
public void mouse(){boolean relative=!preferences.getBoolean("relativeMouse",true);preferences.edit().putBoolean("relativeMouse",relative).apply();video.setRelative(relative);controls.setRelative(relative);}
|
public void mouse(){boolean relative=!preferences.getBoolean("relativeMouse",true);preferences.edit().putBoolean("relativeMouse",relative).apply();video.setRelative(relative);controls.setRelative(relative);}
|
||||||
public void videoSettings(int field,int value){RemoteVideoSettings.Values old=MainActivity.this.videoSettings.selection;updateVideoSettings(new RemoteVideoSettings.Values(field==0?value:old.quality,field==1?value:old.frameRate,field==2?value:old.preference));}
|
public void videoSettings(int field,int value){RemoteVideoSettings.Values old=MainActivity.this.videoSettings.selection;updateVideoSettings(new RemoteVideoSettings.Values(field==0?value:old.quality,field==1?value:old.frameRate,field==2?value:old.preference));}
|
||||||
});controls.setRelative(preferences.getBoolean("relativeMouse",true));controls.setMuted(muted);controls.videoSettings(videoSettings);root.addView(controls,new LinearLayout.LayoutParams(-1,-1));getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
});controls.setHostPlatform(hostPlatform);controls.setRelative(preferences.getBoolean("relativeMouse",true));controls.setMuted(muted);controls.videoSettings(videoSettings);root.addView(controls,new LinearLayout.LayoutParams(-1,-1));getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
controls.attachMouseBar(canvas,new VirtualMouseBar(ui,video.mouseInput));
|
controls.attachMouseBar(canvas,new VirtualMouseBar(ui,video.mouseInput));
|
||||||
|
privacyChanged();
|
||||||
video.remoteFocusChanged=()->{if(controls!=null)controls.resetKeyboardContext();};
|
video.remoteFocusChanged=()->{if(controls!=null)controls.resetKeyboardContext();};
|
||||||
controls.requestApplyInsets();
|
controls.requestApplyInsets();
|
||||||
video.addOnLayoutChangeListener((view,left,top,right,bottom,oldLeft,oldTop,oldRight,oldBottom)->{
|
video.addOnLayoutChangeListener((view,left,top,right,bottom,oldLeft,oldTop,oldRight,oldBottom)->{
|
||||||
@@ -346,8 +349,9 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
});
|
});
|
||||||
pictureInPicture.connected();updatePictureInPicture();
|
pictureInPicture.connected();updatePictureInPicture();
|
||||||
}
|
}
|
||||||
private void confirmDisconnect(){disconnectDialog=new AlertDialog.Builder(this).setTitle("断开远程连接?").setMessage("断开后将返回首页").setNegativeButton("取消",null).setPositiveButton("断开连接",(d,w)->end("")).show();}
|
private void confirmDisconnect(){disconnectDialog=new AlertDialog.Builder(this).setTitle(L10n.tr("断开远程连接?")).setMessage(L10n.tr("断开后将返回首页")).setNegativeButton(L10n.tr("取消"),null).setPositiveButton(L10n.tr("断开连接"),(d,w)->end("")).show();}
|
||||||
private void end(String reason){
|
private void end(String reason){
|
||||||
|
dismissPrivacyUnlockDialog();
|
||||||
remoteOrientation.end();
|
remoteOrientation.end();
|
||||||
if(backgroundSession!=null){BackgroundSession retained=backgroundSession;backgroundSession=null;retained.detach(this);retained.finish(reason);}
|
if(backgroundSession!=null){BackgroundSession retained=backgroundSession;backgroundSession=null;retained.detach(this);retained.finish(reason);}
|
||||||
boolean closeWindow=isInPictureInPictureMode();
|
boolean closeWindow=isInPictureInPictureMode();
|
||||||
@@ -363,48 +367,48 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
appUpdates.checkNow();
|
appUpdates.checkNow();
|
||||||
}
|
}
|
||||||
private void settings(){
|
private void settings(){
|
||||||
LinearLayout content=formPage("settings","设置",()->home(""));
|
LinearLayout content=formPage("settings",L10n.tr("设置"),()->home(""));
|
||||||
LinearLayout mouse=section(content,"鼠标控制",MobileUi.SETTINGS_CARD_RADIUS);mouse.setPadding(dp(12),dp(12),dp(12),dp(12));boolean relative=preferences.getBoolean("relativeMouse",true);
|
LinearLayout mouse=section(content,L10n.tr("鼠标控制"),MobileUi.SETTINGS_CARD_RADIUS);mouse.setPadding(dp(12),dp(12),dp(12),dp(12));boolean relative=preferences.getBoolean("relativeMouse",true);
|
||||||
mouse.addView(segments(new String[]{"相对位置","绝对位置"},relative?0:1,true,index->preferences.edit().putBoolean("relativeMouse",index==0).apply()));
|
mouse.addView(segments(new String[]{L10n.tr("相对位置"),L10n.tr("绝对位置")},relative?0:1,true,index->preferences.edit().putBoolean("relativeMouse",index==0).apply()));
|
||||||
LinearLayout picture=section(content,"画面偏好",MobileUi.SETTINGS_CARD_RADIUS);picture.setPadding(dp(12),dp(12),dp(12),dp(12));int preference=Math.max(0,Math.min(2,preferences.getInt("videoPreference",1)));
|
LinearLayout picture=section(content,L10n.tr("画面偏好"),MobileUi.SETTINGS_CARD_RADIUS);picture.setPadding(dp(12),dp(12),dp(12),dp(12));int preference=Math.max(0,Math.min(2,preferences.getInt("videoPreference",1)));
|
||||||
picture.addView(segments(RemoteVideoSettings.PREFERENCES,preference,true,index->preferences.edit().putInt("videoPreference",index).apply()));ui.gap(content,28);
|
picture.addView(segments(RemoteVideoSettings.preferences(),preference,true,index->preferences.edit().putInt("videoPreference",index).apply()));ui.gap(content,28);
|
||||||
LinearLayout direction=section(content,"屏幕方向",MobileUi.SETTINGS_CARD_RADIUS);LinearLayout directionRow=row();directionRow.setPadding(dp(16),dp(10),dp(16),dp(10));directionRow.addView(text("连接后自动横屏",16,false),new LinearLayout.LayoutParams(0,-2,1));
|
LinearLayout direction=section(content,L10n.tr("屏幕方向"),MobileUi.SETTINGS_CARD_RADIUS);LinearLayout directionRow=row();directionRow.setPadding(dp(16),dp(10),dp(16),dp(10));directionRow.addView(text(L10n.tr("连接建立后锁定横屏"),16,false),new LinearLayout.LayoutParams(0,-2,1));
|
||||||
Switch autoLandscape=new Switch(this);autoLandscape.setContentDescription("连接后自动横屏");autoLandscape.setChecked(preferences.getBoolean("autoLandscape",true));autoLandscape.setOnCheckedChangeListener((button,checked)->preferences.edit().putBoolean("autoLandscape",checked).apply());directionRow.addView(autoLandscape);direction.addView(directionRow);
|
Switch autoLandscape=new Switch(this);autoLandscape.setContentDescription(L10n.tr("连接建立后锁定横屏"));autoLandscape.setChecked(preferences.getBoolean("autoLandscape",false));autoLandscape.setOnCheckedChangeListener((button,checked)->preferences.edit().putBoolean("autoLandscape",checked).apply());directionRow.addView(autoLandscape);direction.addView(directionRow);
|
||||||
footer(content,"手机连接后先进入横屏,将手机横过来后恢复跟随设备方向。关闭后始终跟随设备方向。平板和多窗口不受影响。");
|
footer(content,L10n.tr("默认关闭。开启后,手机连接时进入并保持横屏;关闭时跟随设备方向。平板和多窗口不受影响。"));
|
||||||
LinearLayout server=section(content,"",MobileUi.SETTINGS_CARD_RADIUS);LinearLayout fields=column();fields.setVisibility(View.GONE);boolean custom=!host.equals("api.crossdesk.cn")||port!=9099;
|
LinearLayout server=section(content,"",MobileUi.SETTINGS_CARD_RADIUS);LinearLayout fields=column();fields.setVisibility(View.GONE);boolean custom=!host.equals("api.crossdesk.cn")||port!=9099;
|
||||||
link(server,"服务器",custom?"自定义":"默认",()->fields.setVisibility(fields.getVisibility()==View.VISIBLE?View.GONE:View.VISIBLE));server.addView(fields);
|
link(server,L10n.tr("服务器"),custom?L10n.tr("自定义"):L10n.tr("默认"),()->fields.setVisibility(fields.getVisibility()==View.VISIBLE?View.GONE:View.VISIBLE));server.addView(fields);
|
||||||
LinearLayout toggleRow=row();toggleRow.setPadding(dp(16),dp(8),dp(16),dp(8));toggleRow.addView(text("使用自定义服务器",16,false),new LinearLayout.LayoutParams(0,-2,1));Switch toggle=new Switch(this);toggle.setContentDescription("使用自定义服务器");toggle.setChecked(custom);toggleRow.addView(toggle);fields.addView(toggleRow);
|
LinearLayout toggleRow=row();toggleRow.setPadding(dp(16),dp(8),dp(16),dp(8));toggleRow.addView(text(L10n.tr("使用自定义服务器"),16,false),new LinearLayout.LayoutParams(0,-2,1));Switch toggle=new Switch(this);toggle.setContentDescription(L10n.tr("使用自定义服务器"));toggle.setChecked(custom);toggleRow.addView(toggle);fields.addView(toggleRow);
|
||||||
LinearLayout inputs=column();inputs.setPadding(dp(12),0,dp(12),dp(12));EditText address=field("服务器地址",InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_URI),number=field("服务器端口",InputType.TYPE_CLASS_NUMBER);address.setText(custom?host:"");number.setText(custom?String.valueOf(port):"");inputs.addView(address,ui.size(-1,48));ui.gap(inputs,8);inputs.addView(number,ui.size(-1,48));ui.gap(inputs,8);
|
LinearLayout inputs=column();inputs.setPadding(dp(12),0,dp(12),dp(12));EditText address=field(L10n.tr("服务器地址"),InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_URI),number=field(L10n.tr("服务器端口"),InputType.TYPE_CLASS_NUMBER);address.setText(custom?host:"");number.setText(custom?String.valueOf(port):"");inputs.addView(address,ui.size(-1,48));ui.gap(inputs,8);inputs.addView(number,ui.size(-1,48));ui.gap(inputs,8);
|
||||||
inputs.addView(ui.action("完成",BLUE,()->{if(saveServer(address,number)){hideKeyboard();settings();}}));fields.addView(inputs);inputs.setVisibility(custom?View.VISIBLE:View.GONE);
|
inputs.addView(ui.action(L10n.tr("完成"),BLUE,()->{if(saveServer(address,number)){hideKeyboard();settings();}}));fields.addView(inputs);inputs.setVisibility(custom?View.VISIBLE:View.GONE);
|
||||||
toggle.setOnCheckedChangeListener((button,checked)->{inputs.setVisibility(checked?View.VISIBLE:View.GONE);if(!checked){host="api.crossdesk.cn";port=9099;preferences.edit().putString("host",host).putInt("port",port).apply();ensureSignaling();} });
|
toggle.setOnCheckedChangeListener((button,checked)->{inputs.setVisibility(checked?View.VISIBLE:View.GONE);if(!checked){host="api.crossdesk.cn";port=9099;preferences.edit().putString("host",host).putInt("port",port).apply();ensureSignaling();} });
|
||||||
backAction=()->{if(toggle.isChecked()&&!saveServer(address,number))return;hideKeyboard();home("");};
|
backAction=()->{if(toggle.isChecked()&&!saveServer(address,number))return;hideKeyboard();home("");};
|
||||||
identitySummary=text("",12,false);identitySummary.setTextColor(SECONDARY);identitySummary.setGravity(Gravity.CENTER);identitySummary.setPadding(dp(16),dp(12),dp(16),dp(16));identitySummary.setTextIsSelectable(true);content.addView(identitySummary);renderSignaling();
|
identitySummary=text("",12,false);identitySummary.setTextColor(SECONDARY);identitySummary.setGravity(Gravity.CENTER);identitySummary.setPadding(dp(16),dp(12),dp(16),dp(16));identitySummary.setTextIsSelectable(true);content.addView(identitySummary);renderSignaling();
|
||||||
LinearLayout privacy=section(content,"隐私",MobileUi.SETTINGS_CARD_RADIUS);link(privacy,"隐私与授权",preferences.getBoolean("networkConsent",false)?"已授权":"未授权",this::privacyPage);ui.divider(privacy);
|
LinearLayout privacy=section(content,L10n.tr("隐私"),MobileUi.SETTINGS_CARD_RADIUS);link(privacy,L10n.tr("隐私与授权"),preferences.getBoolean("networkConsent",false)?L10n.tr("已授权"):L10n.tr("未授权"),this::privacyPage);ui.divider(privacy);
|
||||||
LinearLayout preview=row();preview.setPadding(dp(16),dp(10),dp(16),dp(10));preview.addView(text("保存远程画面预览",16,false),new LinearLayout.LayoutParams(0,-2,1));previewSwitch=new Switch(this);previewSwitch.setContentDescription("保存远程画面预览");preview.addView(previewSwitch);privacy.addView(preview);
|
LinearLayout preview=row();preview.setPadding(dp(16),dp(10),dp(16),dp(10));preview.addView(text(L10n.tr("保存远程画面预览"),16,false),new LinearLayout.LayoutParams(0,-2,1));previewSwitch=new Switch(this);previewSwitch.setContentDescription(L10n.tr("保存远程画面预览"));preview.addView(previewSwitch);privacy.addView(preview);
|
||||||
previewSwitch.setOnCheckedChangeListener((button,checked)->{if(updatingPreviewSwitch)return;if(checked){renderPreviews();confirmPreviewSaving();}else previews.setEnabled(false);});
|
previewSwitch.setOnCheckedChangeListener((button,checked)->{if(updatingPreviewSwitch)return;if(checked){renderPreviews();confirmPreviewSaving();}else previews.setEnabled(false);});
|
||||||
ui.divider(privacy);clearPreviewsButton=ui.action("清除预览图",RED,this::confirmClearPreviews);clearPreviewsButton.setGravity(Gravity.START|Gravity.CENTER_VERTICAL);clearPreviewsButton.setTypeface(Typeface.DEFAULT);clearPreviewsButton.setPadding(dp(16),0,dp(16),0);privacy.addView(clearPreviewsButton,ui.size(-1,48));
|
ui.divider(privacy);clearPreviewsButton=ui.action(L10n.tr("清除预览图"),RED,this::confirmClearPreviews);clearPreviewsButton.setGravity(Gravity.START|Gravity.CENTER_VERTICAL);clearPreviewsButton.setTypeface(Typeface.DEFAULT);clearPreviewsButton.setPadding(dp(16),0,dp(16),0);privacy.addView(clearPreviewsButton,ui.size(-1,48));
|
||||||
previewCleanupError=text("",12,false);previewCleanupError.setTextColor(RED);previewCleanupError.setPadding(dp(16),dp(8),dp(16),dp(12));privacy.addView(previewCleanupError);renderPreviews();
|
previewCleanupError=text("",12,false);previewCleanupError.setTextColor(RED);previewCleanupError.setPadding(dp(16),dp(8),dp(16),dp(12));privacy.addView(previewCleanupError);renderPreviews();
|
||||||
ui.gap(content,28);LinearLayout about=section(content,"",MobileUi.SETTINGS_CARD_RADIUS);LinearLayout aboutLink=link(about,"关于","新版本",this::about);aboutUpdateRow=aboutLink;
|
ui.gap(content,28);LinearLayout about=section(content,"",MobileUi.SETTINGS_CARD_RADIUS);LinearLayout aboutLink=link(about,L10n.tr("关于"),L10n.tr("新版本"),this::about);aboutUpdateRow=aboutLink;
|
||||||
aboutUpdateLabel=(TextView)aboutLink.getChildAt(1);aboutUpdateLabel.setPadding(0,0,dp(8),0);
|
aboutUpdateLabel=(TextView)aboutLink.getChildAt(1);aboutUpdateLabel.setPadding(0,0,dp(8),0);
|
||||||
aboutUpdateDot=updateDot();LinearLayout.LayoutParams dotPosition=new LinearLayout.LayoutParams(dp(8),dp(8));dotPosition.setMarginEnd(dp(8));aboutLink.addView(aboutUpdateDot,2,dotPosition);renderAppUpdates();
|
aboutUpdateDot=updateDot();LinearLayout.LayoutParams dotPosition=new LinearLayout.LayoutParams(dp(8),dp(8));dotPosition.setMarginEnd(dp(8));aboutLink.addView(aboutUpdateDot,2,dotPosition);renderAppUpdates();
|
||||||
}
|
}
|
||||||
private void confirmPreviewSaving(){
|
private void confirmPreviewSaving(){
|
||||||
previewDialog=new AlertDialog.Builder(this).setTitle("保存远程画面预览?")
|
previewDialog=new AlertDialog.Builder(this).setTitle(L10n.tr("保存远程画面预览?"))
|
||||||
.setMessage("预览可能包含远程电脑上的个人或工作信息。开启后,每次连接会自动将一张画面保存在本机并显示在最近连接中。你可以随时关闭并清除。")
|
.setMessage(L10n.tr("预览可能包含远程电脑上的个人或工作信息。开启后,每次连接会自动将一张画面保存在本机并显示在最近连接中。你可以随时关闭并清除。"))
|
||||||
.setNegativeButton("取消",null).setPositiveButton("开启保存",(d,w)->previews.setEnabled(true)).show();
|
.setNegativeButton(L10n.tr("取消"),null).setPositiveButton(L10n.tr("开启保存"),(d,w)->previews.setEnabled(true)).show();
|
||||||
previewDialog.setOnDismissListener(d->{previewDialog=null;renderPreviews();});
|
previewDialog.setOnDismissListener(d->{previewDialog=null;renderPreviews();});
|
||||||
}
|
}
|
||||||
private void confirmClearPreviews(){
|
private void confirmClearPreviews(){
|
||||||
previewDialog=new AlertDialog.Builder(this).setTitle("清除预览图?")
|
previewDialog=new AlertDialog.Builder(this).setTitle(L10n.tr("清除预览图?"))
|
||||||
.setMessage("将删除本机保存的所有远程预览图,保留连接记录和密码。"+(previews.enabled()?"后续连接仍会保存新的预览图。":""))
|
.setMessage(L10n.tr("将删除本机保存的所有远程预览图,保留连接记录和密码。")+(previews.enabled()?L10n.tr("后续连接仍会保存新的预览图。"):""))
|
||||||
.setNegativeButton("取消",null).setPositiveButton("清除",(d,w)->previews.clear()).show();
|
.setNegativeButton(L10n.tr("取消"),null).setPositiveButton(L10n.tr("清除"),(d,w)->previews.clear()).show();
|
||||||
previewDialog.setOnDismissListener(d->previewDialog=null);
|
previewDialog.setOnDismissListener(d->previewDialog=null);
|
||||||
}
|
}
|
||||||
private void renderPreviews(){
|
private void renderPreviews(){
|
||||||
if(previews==null)return;
|
if(previews==null)return;
|
||||||
if(!previews.enabled()||previews.cleaning)previewCapture=null;
|
if(!previews.enabled()||previews.cleaning)previewCapture=null;
|
||||||
if(previewSwitch!=null){updatingPreviewSwitch=true;previewSwitch.setChecked(previews.enabled());previewSwitch.setEnabled(preferences.getBoolean("networkConsent",false)&&!previews.cleaning);updatingPreviewSwitch=false;}
|
if(previewSwitch!=null){updatingPreviewSwitch=true;previewSwitch.setChecked(previews.enabled());previewSwitch.setEnabled(preferences.getBoolean("networkConsent",false)&&!previews.cleaning);updatingPreviewSwitch=false;}
|
||||||
if(clearPreviewsButton!=null){clearPreviewsButton.setText(previews.cleaning?"正在清除…":"清除预览图");clearPreviewsButton.setEnabled(!previews.cleaning);}
|
if(clearPreviewsButton!=null){clearPreviewsButton.setText(previews.cleaning?L10n.tr("正在清除…"):L10n.tr("清除预览图"));clearPreviewsButton.setEnabled(!previews.cleaning);}
|
||||||
if(previewCleanupError!=null){previewCleanupError.setText(previews.cleanupError);previewCleanupError.setVisibility(previews.cleanupError.isEmpty()?View.GONE:View.VISIBLE);}
|
if(previewCleanupError!=null){previewCleanupError.setText(previews.cleanupError);previewCleanupError.setVisibility(previews.cleanupError.isEmpty()?View.GONE:View.VISIBLE);}
|
||||||
for(Runnable card:previewCards)card.run();
|
for(Runnable card:previewCards)card.run();
|
||||||
}
|
}
|
||||||
@@ -423,17 +427,17 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
return new SegmentedControl(ui,titles,selected,enabled,changed);
|
return new SegmentedControl(ui,titles,selected,enabled,changed);
|
||||||
}
|
}
|
||||||
private boolean saveServer(EditText address,EditText number){
|
private boolean saveServer(EditText address,EditText number){
|
||||||
String name=address.getText().toString().trim();int value;try{value=Integer.parseInt(number.getText().toString());}catch(NumberFormatException e){number.setError("请输入 1–65535 范围内的端口");return false;}
|
String name=address.getText().toString().trim();int value;try{value=Integer.parseInt(number.getText().toString());}catch(NumberFormatException e){number.setError(L10n.tr("请输入 1–65535 范围内的端口"));return false;}
|
||||||
if(name.length()>253||!name.matches("[A-Za-z0-9.-]+")||name.startsWith(".")||name.endsWith(".")){address.setError("填写主机名或 IPv4 地址,不含协议和路径");return false;}if(value<1||value>65535){number.setError("端口范围为 1–65535");return false;}host=name;port=value;preferences.edit().putString("host",host).putInt("port",port).apply();ensureSignaling();return true;
|
if(name.length()>253||!name.matches("[A-Za-z0-9.-]+")||name.startsWith(".")||name.endsWith(".")){address.setError(L10n.tr("填写主机名或 IPv4 地址,不含协议和路径"));return false;}if(value<1||value>65535){number.setError(L10n.tr("端口范围为 1–65535"));return false;}host=name;port=value;preferences.edit().putString("host",host).putInt("port",port).apply();ensureSignaling();return true;
|
||||||
}
|
}
|
||||||
private void announcements(){
|
private void announcements(){
|
||||||
installRoot("announcements",false);LinearLayout bar=navigation("通知公告",()->home(""));
|
installRoot("announcements",false);LinearLayout bar=navigation(L10n.tr("通知公告"),()->home(""));
|
||||||
bar.removeViewAt(bar.getChildCount()-1);announcementRefresh=ui.iconButton("refresh","刷新公告",announcementInbox::refresh);bar.addView(announcementRefresh,ui.size(44,44));
|
bar.removeViewAt(bar.getChildCount()-1);announcementRefresh=ui.iconButton("refresh",L10n.tr("刷新公告"),announcementInbox::refresh);bar.addView(announcementRefresh,ui.size(44,44));
|
||||||
announcementView=new AnnouncementView(ui,announcementInbox,null,this::announcementDetail,this::connectAnnouncements);
|
announcementView=new AnnouncementView(ui,announcementInbox,null,this::announcementDetail,this::connectAnnouncements);
|
||||||
root.addView(announcementView,new LinearLayout.LayoutParams(-1,0,1));announcementInbox.refresh();renderAnnouncements();
|
root.addView(announcementView,new LinearLayout.LayoutParams(-1,0,1));announcementInbox.refresh();renderAnnouncements();
|
||||||
}
|
}
|
||||||
private void announcementDetail(AnnouncementInbox.Item item){
|
private void announcementDetail(AnnouncementInbox.Item item){
|
||||||
installRoot("announcement-detail",false);navigation("公告详情",this::announcements);
|
installRoot("announcement-detail",false);navigation(L10n.tr("公告详情"),this::announcements);
|
||||||
announcementView=new AnnouncementView(ui,announcementInbox,item,this::announcementDetail,this::connectAnnouncements);
|
announcementView=new AnnouncementView(ui,announcementInbox,item,this::announcementDetail,this::connectAnnouncements);
|
||||||
root.addView(announcementView,new LinearLayout.LayoutParams(-1,0,1));announcementInbox.markRead(item);
|
root.addView(announcementView,new LinearLayout.LayoutParams(-1,0,1));announcementInbox.markRead(item);
|
||||||
}
|
}
|
||||||
@@ -443,7 +447,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
}
|
}
|
||||||
private void renderAnnouncements(){
|
private void renderAnnouncements(){
|
||||||
if(announcementBadge!=null){int unread=announcementInbox.unread;announcementBadge.setText(unread>99?"99+":String.valueOf(unread));announcementBadge.setVisibility(unread>0?View.VISIBLE:View.GONE);
|
if(announcementBadge!=null){int unread=announcementInbox.unread;announcementBadge.setText(unread>99?"99+":String.valueOf(unread));announcementBadge.setVisibility(unread>0?View.VISIBLE:View.GONE);
|
||||||
if(android.os.Build.VERSION.SDK_INT>=30)((View)announcementBadge.getParent()).setStateDescription(unread>0?unread+" 条未读":"无未读公告");}
|
if(android.os.Build.VERSION.SDK_INT>=30)((View)announcementBadge.getParent()).setStateDescription(unread>0?L10n.format("{0} 条未读",unread):L10n.tr("无未读公告"));}
|
||||||
if(announcementRefresh!=null){boolean enabled=announcementInbox.isConnected()&&!announcementInbox.loading;announcementRefresh.setEnabled(enabled);announcementRefresh.setAlpha(enabled?1:.4f);}
|
if(announcementRefresh!=null){boolean enabled=announcementInbox.isConnected()&&!announcementInbox.loading;announcementRefresh.setEnabled(enabled);announcementRefresh.setAlpha(enabled?1:.4f);}
|
||||||
if(announcementView!=null)announcementView.render();
|
if(announcementView!=null)announcementView.render();
|
||||||
}
|
}
|
||||||
@@ -453,12 +457,12 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
}
|
}
|
||||||
@Override public void announcementFailed(String requestId){announcementInbox.failed(requestId);}
|
@Override public void announcementFailed(String requestId){announcementInbox.failed(requestId);}
|
||||||
private void privacyPage(){
|
private void privacyPage(){
|
||||||
LinearLayout content=formPage("privacy","隐私与授权",this::settings);LinearLayout card=section(content,"");boolean consent=preferences.getBoolean("networkConsent",false);link(card,"授权状态",consent?"已授权":"未授权",()->{});ui.divider(card);link(card,"隐私政策","",this::privacyPolicyPage);
|
LinearLayout content=formPage("privacy",L10n.tr("隐私与授权"),this::settings);LinearLayout card=section(content,"");boolean consent=preferences.getBoolean("networkConsent",false);link(card,L10n.tr("授权状态"),consent?L10n.tr("已授权"):L10n.tr("未授权"),()->{});ui.divider(card);link(card,L10n.tr("隐私政策"),"",this::privacyPolicyPage);
|
||||||
footer(content,"设备身份及选择保存的访问密码使用 Android Keystore 加密。连接记录仅保存在本机,不纳入设备备份。剪贴板内容在结束会话时清除。离开应用后通过画中画或连接通知保持会话,可在应用或通知中断开。");
|
footer(content,L10n.tr("设备身份及选择保存的访问密码使用 Android Keystore 加密。连接记录仅保存在本机,不纳入设备备份。剪贴板内容在结束会话时清除。离开应用后通过画中画或连接通知保持会话,可在应用或通知中断开。"));
|
||||||
if(consent)content.addView(ui.action("撤回联网授权",RED,()->new AlertDialog.Builder(this).setTitle("撤回联网授权?").setMessage("撤回后将停止远程连接、关闭画面预览保存并清除已有预览。连接记录和密码会保留,重新连接前需再次授权。").setNegativeButton("取消",null).setPositiveButton("撤回",(d,w)->{preferences.edit().putBoolean("networkConsent",false).apply();appUpdates.setEnabled(false);previews.setEnabled(false);stopSignaling();announcementInbox.reset();privacyPage();}).show()));else content.addView(ui.primary("阅读并授权",()->consent(this::privacyPage)));
|
if(consent)content.addView(ui.action(L10n.tr("撤回联网授权"),RED,()->new AlertDialog.Builder(this).setTitle(L10n.tr("撤回联网授权?")).setMessage(L10n.tr("撤回后将停止远程连接、关闭画面预览保存并清除已有预览。连接记录和密码会保留,重新连接前需再次授权。")).setNegativeButton(L10n.tr("取消"),null).setPositiveButton(L10n.tr("撤回"),(d,w)->{preferences.edit().putBoolean("networkConsent",false).apply();appUpdates.setEnabled(false);previews.setEnabled(false);stopSignaling();announcementInbox.reset();privacyPage();}).show()));else content.addView(ui.primary(L10n.tr("阅读并授权"),()->consent(this::privacyPage)));
|
||||||
}
|
}
|
||||||
private void privacyPolicyPage(){
|
private void privacyPolicyPage(){
|
||||||
installRoot("privacy-policy",false);navigation("隐私政策",this::privacyPage);
|
installRoot("privacy-policy",false);navigation(L10n.tr("隐私政策"),this::privacyPage);
|
||||||
root.addView(new PrivacyPolicyDocument(this).view(ui),new LinearLayout.LayoutParams(-1,0,1));
|
root.addView(new PrivacyPolicyDocument(this).view(ui),new LinearLayout.LayoutParams(-1,0,1));
|
||||||
}
|
}
|
||||||
String applicationVersion(){
|
String applicationVersion(){
|
||||||
@@ -475,8 +479,8 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
if(settingsUpdateDot!=null)settingsUpdateDot.setVisibility(available?View.VISIBLE:View.GONE);
|
if(settingsUpdateDot!=null)settingsUpdateDot.setVisibility(available?View.VISIBLE:View.GONE);
|
||||||
if(aboutUpdateDot!=null)aboutUpdateDot.setVisibility(available?View.VISIBLE:View.GONE);
|
if(aboutUpdateDot!=null)aboutUpdateDot.setVisibility(available?View.VISIBLE:View.GONE);
|
||||||
if(aboutUpdateLabel!=null)aboutUpdateLabel.setVisibility(available?View.VISIBLE:View.GONE);
|
if(aboutUpdateLabel!=null)aboutUpdateLabel.setVisibility(available?View.VISIBLE:View.GONE);
|
||||||
if(settingsUpdateButton!=null)settingsUpdateButton.setContentDescription(available?"设置,有新版本":"设置");
|
if(settingsUpdateButton!=null)settingsUpdateButton.setContentDescription(available?L10n.tr("设置,有新版本"):L10n.tr("设置"));
|
||||||
if(aboutUpdateRow!=null)aboutUpdateRow.setContentDescription(available?"关于,有新版本":"关于");
|
if(aboutUpdateRow!=null)aboutUpdateRow.setContentDescription(available?L10n.tr("关于,有新版本"):L10n.tr("关于"));
|
||||||
if(aboutPages!=null&&page.equals("about"))aboutPages.renderUpdates();
|
if(aboutPages!=null&&page.equals("about"))aboutPages.renderUpdates();
|
||||||
}
|
}
|
||||||
private void about(){
|
private void about(){
|
||||||
@@ -486,16 +490,16 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
private boolean sendTextEdit(int count,String value){
|
private boolean sendTextEdit(int count,String value){
|
||||||
if(session==null)return false;
|
if(session==null)return false;
|
||||||
if(session.textEdit(count,value))return true;
|
if(session.textEdit(count,value))return true;
|
||||||
toast("当前远端不支持直接输入此文本,请使用支持文本输入的被控端版本,或通过剪贴板发送");return false;
|
toast(L10n.tr("当前远端不支持直接输入此文本,请使用支持文本输入的被控端版本,或通过剪贴板发送"));return false;
|
||||||
}
|
}
|
||||||
private void tapKey(int code){if(session!=null){session.key(code,true);session.key(code,false);}}
|
private void tapKey(int code){if(session!=null){session.key(code,true);session.key(code,false);}}
|
||||||
private void shortcut(int code){if(session!=null){int modifier=hostPlatform.equals("macos")?0x5B:0x11;session.key(modifier,true);tapKey(code);session.key(modifier,false);}}
|
private void shortcut(int code){if(session!=null){int modifier=hostPlatform.equals("macos")?0x5B:0x11;session.key(modifier,true);tapKey(code);session.key(modifier,false);}}
|
||||||
private void selectDisplay(){
|
private void selectDisplay(){
|
||||||
if(!ready)return;int count=Math.min(displays.length(),8);if(count==0){toast("等待远端屏幕信息");return;}String[] labels=new String[count];for(int i=0;i<count;i++){JSONObject display=displays.optJSONObject(i);labels[i]=(i+1)+" · "+(display==null?"显示器":display.optString("name","显示器"));}new AlertDialog.Builder(this).setTitle("显示器").setItems(labels,(d,w)->{if(session!=null){if(w!=selectedDisplay&&video!=null)video.resetViewport();selectedDisplay=w;session.control(4,w);if(controls!=null)controls.resetVideoStatistics();}}).show();
|
if(!ready)return;int count=Math.min(displays.length(),8);if(count==0){toast(L10n.tr("等待远端屏幕信息"));return;}String[] labels=new String[count];for(int i=0;i<count;i++){JSONObject display=displays.optJSONObject(i);labels[i]=(i+1)+" · "+(display==null?L10n.tr("显示器"):display.optString("name",L10n.tr("显示器")));}new AlertDialog.Builder(this).setTitle(L10n.tr("显示器")).setItems(labels,(d,w)->{if(session!=null){if(w!=selectedDisplay&&video!=null)video.resetViewport();selectedDisplay=w;session.control(4,w);if(controls!=null)controls.resetVideoStatistics();}}).show();
|
||||||
}
|
}
|
||||||
private void secureAttention(){
|
private void secureAttention(){
|
||||||
if(!ready||session==null)return;
|
if(!ready||session==null||!hostPlatform.equals("windows"))return;
|
||||||
if(!remoteServiceAvailable){controlFeedback(hostPlatform.equals("windows")?"请在远端启用 CrossDesk 系统服务后重试":"Ctrl+Alt+Del 需要远端 Windows 系统服务支持");return;}
|
if(!remoteServiceAvailable){controlFeedback(L10n.tr("请在远端启用 CrossDesk 系统服务后重试"));return;}
|
||||||
session.secureAttention();
|
session.secureAttention();
|
||||||
}
|
}
|
||||||
private void chooseFile(){
|
private void chooseFile(){
|
||||||
@@ -512,7 +516,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
documentSession=session;releaseKeys();
|
documentSession=session;releaseKeys();
|
||||||
pictureInPicture.choosingDocument(true);updatePictureInPicture();
|
pictureInPicture.choosingDocument(true);updatePictureInPicture();
|
||||||
try{startActivityForResult(intent,request);mainHandler.postDelayed(documentTimeout,5*60*1000);}
|
try{startActivityForResult(intent,request);mainHandler.postDelayed(documentTimeout,5*60*1000);}
|
||||||
catch(android.content.ActivityNotFoundException error){documentSession=null;documentToSave=null;pictureInPicture.choosingDocument(false);updatePictureInPicture();controlFeedback("此设备没有可用的文件选择器");}
|
catch(android.content.ActivityNotFoundException error){documentSession=null;documentToSave=null;pictureInPicture.choosingDocument(false);updatePictureInPicture();controlFeedback(L10n.tr("此设备没有可用的文件选择器"));}
|
||||||
}
|
}
|
||||||
@Override protected void onActivityResult(int request,int result,Intent data){
|
@Override protected void onActivityResult(int request,int result,Intent data){
|
||||||
super.onActivityResult(request,result,data);
|
super.onActivityResult(request,result,data);
|
||||||
@@ -526,7 +530,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
@Override public void fileTransfer(String name,double progress,boolean sending,java.io.File received){
|
@Override public void fileTransfer(String name,double progress,boolean sending,java.io.File received){
|
||||||
if(!ready)return;
|
if(!ready)return;
|
||||||
if(received!=null)receivedFile=received;
|
if(received!=null)receivedFile=received;
|
||||||
String fileStatus=progress<0?name+" 传输失败":(sending?"发送 ":"接收 ")+name+(progress>=1?" · 已完成":" · "+(int)(progress*100)+"%");
|
String fileStatus=progress<0?L10n.format("{0} 传输失败",name):(sending?L10n.tr("发送 "):L10n.tr("接收 "))+name+(progress>=1?L10n.tr(" · 已完成"):" · "+(int)(progress*100)+"%");
|
||||||
if(controls!=null)controls.fileStatus(fileStatus,receivedFile!=null);
|
if(controls!=null)controls.fileStatus(fileStatus,receivedFile!=null);
|
||||||
if(progress<0||progress>=1)toast(fileStatus);
|
if(progress<0||progress>=1)toast(fileStatus);
|
||||||
}
|
}
|
||||||
@@ -535,14 +539,14 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
}
|
}
|
||||||
private void clipboard(){
|
private void clipboard(){
|
||||||
if(!ready)return;
|
if(!ready)return;
|
||||||
EditText input=field("要发送到远端的文本",InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_FLAG_MULTI_LINE);input.setSingleLine(false);input.setMinLines(3);
|
EditText input=field(L10n.tr("要发送到远端的文本"),InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_FLAG_MULTI_LINE);input.setSingleLine(false);input.setMinLines(3);
|
||||||
new AlertDialog.Builder(this).setTitle("剪贴板").setView(input)
|
new AlertDialog.Builder(this).setTitle(L10n.tr("剪贴板")).setView(input)
|
||||||
.setMessage("发送后,在远程键盘中点击“粘贴”。接收的远端文本可手动复制到本机。")
|
.setMessage(L10n.tr("发送后,在远程键盘中点击“粘贴”。接收的远端文本可手动复制到本机。"))
|
||||||
.setNegativeButton("关闭",null).setNeutralButton("复制远端文本",(d,w)->{
|
.setNegativeButton(L10n.tr("关闭"),null).setNeutralButton(L10n.tr("复制远端文本"),(d,w)->{
|
||||||
if(!remoteClipboard.isEmpty())((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("CrossDesk",remoteClipboard));
|
if(!remoteClipboard.isEmpty())((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("CrossDesk",remoteClipboard));
|
||||||
}).setPositiveButton("发送文本",(d,w)->{
|
}).setPositiveButton(L10n.tr("发送文本"),(d,w)->{
|
||||||
String value=input.getText().toString();
|
String value=input.getText().toString();
|
||||||
if(value.getBytes(java.nio.charset.StandardCharsets.UTF_8).length>128*1024){Toast.makeText(this,"文本超过 128 KB",Toast.LENGTH_SHORT).show();return;}
|
if(value.getBytes(java.nio.charset.StandardCharsets.UTF_8).length>128*1024){Toast.makeText(this,L10n.tr("文本超过 128 KB"),Toast.LENGTH_SHORT).show();return;}
|
||||||
if(session!=null)session.clipboard(value);
|
if(session!=null)session.clipboard(value);
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
@@ -577,17 +581,17 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
boolean consent=preferences.getBoolean("networkConsent",false);
|
boolean consent=preferences.getBoolean("networkConsent",false);
|
||||||
boolean connected=consent&&signalError.isEmpty()&&signalState==1;
|
boolean connected=consent&&signalError.isEmpty()&&signalState==1;
|
||||||
String label;int tint,background;
|
String label;int tint,background;
|
||||||
if(!consent){label="等待隐私授权";tint=0xFFB76A00;background=0xFFFFEBD2;}
|
if(!consent){label=L10n.tr("等待隐私授权");tint=0xFFB76A00;background=0xFFFFEBD2;}
|
||||||
else if(!signalError.isEmpty()){label=signalError.contains("TLS")?"证书校验失败 · 点击重试":"连接失败 · 点击重试";tint=RED;background=0xFFFFE8E6;}
|
else if(!signalError.isEmpty()){label=signalError.contains("TLS")?L10n.tr("证书校验失败 · 点击重试"):L10n.tr("连接失败 · 点击重试");tint=RED;background=0xFFFFE8E6;}
|
||||||
else if(connected){label="已连接服务器";tint=GREEN;background=0xFFE5F4E8;}
|
else if(connected){label=L10n.tr("已连接服务器");tint=GREEN;background=0xFFE5F4E8;}
|
||||||
else{label=signalState==0?"正在连接服务器…":signalState==4?"正在重连服务器…":"未连接服务器 · 点击重试";tint=0xFFB76A00;background=0xFFFFEBD2;}
|
else{label=signalState==0?L10n.tr("正在连接服务器…"):signalState==4?L10n.tr("正在重连服务器…"):L10n.tr("未连接服务器 · 点击重试");tint=0xFFB76A00;background=0xFFFFEBD2;}
|
||||||
if(signalBadge!=null){
|
if(signalBadge!=null){
|
||||||
android.graphics.drawable.Drawable icon=connected?getDrawable(R.drawable.ic_check_circle):null;
|
android.graphics.drawable.Drawable icon=connected?getDrawable(R.drawable.ic_check_circle):null;
|
||||||
if(icon!=null){int size=dp(14);icon.setBounds(0,0,size,size);}
|
if(icon!=null){int size=dp(14);icon.setBounds(0,0,size,size);}
|
||||||
signalBadge.setCompoundDrawablesRelative(icon,null,null,null);signalBadge.setCompoundDrawablePadding(dp(6));
|
signalBadge.setCompoundDrawablesRelative(icon,null,null,null);signalBadge.setCompoundDrawablePadding(dp(6));
|
||||||
signalBadge.setText(label);signalBadge.setTextColor(tint);signalBadge.setBackground(ui.background(background,20));signalBadge.setContentDescription(signalError.isEmpty()?label:signalError+",点击重试");
|
signalBadge.setText(label);signalBadge.setTextColor(tint);signalBadge.setBackground(ui.background(background,20));signalBadge.setContentDescription(signalError.isEmpty()?label:signalError+L10n.tr(",点击重试"));
|
||||||
}
|
}
|
||||||
if(identitySummary!=null)identitySummary.setText(!consent?"完成隐私授权后登记本机身份":!localIdentity.isEmpty()?"本机 ID "+localIdentity:!signalError.isEmpty()?signalError:"正在获取本机 ID…");
|
if(identitySummary!=null)identitySummary.setText(!consent?L10n.tr("完成隐私授权后登记本机身份"):!localIdentity.isEmpty()?L10n.format("本机 ID {0}",localIdentity):!signalError.isEmpty()?signalError:L10n.tr("正在获取本机 ID…"));
|
||||||
}
|
}
|
||||||
@Override public void signaling(int state,String deviceId){
|
@Override public void signaling(int state,String deviceId){
|
||||||
signalState=state;localIdentity=deviceId;signalError="";renderSignaling();
|
signalState=state;localIdentity=deviceId;signalError="";renderSignaling();
|
||||||
@@ -602,7 +606,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
// Transport readiness can be reported again after ICE changes its selected path.
|
// Transport readiness can be reported again after ICE changes its selected path.
|
||||||
// The first success consumes pendingPassword; a duplicate must not save it again.
|
// The first success consumes pendingPassword; a duplicate must not save it again.
|
||||||
if(session==null||ready)return;ready=true;if(progressDialog!=null){progressDialog.dismiss();progressDialog=null;}
|
if(session==null||ready)return;ready=true;if(progressDialog!=null){progressDialog.dismiss();progressDialog=null;}
|
||||||
try{history.save(serverKey(),connectedRemote,connectedRemote,hostPlatform,rememberPassword,pendingPassword);}catch(Exception error){toast("密码保存失败,本次连接仍可使用");}pendingPassword="";sessionScreen(preferences.getBoolean("autoLandscape",true));
|
try{history.save(serverKey(),connectedRemote,connectedRemote,hostPlatform,rememberPassword,pendingPassword);}catch(Exception error){toast(L10n.tr("密码保存失败,本次连接仍可使用"));}pendingPassword="";sessionScreen(preferences.getBoolean("autoLandscape",false));
|
||||||
if(!canUsePictureInPicture())prepareBackgroundSession(true);
|
if(!canUsePictureInPicture())prepareBackgroundSession(true);
|
||||||
if(videoSettings.supported&&videoSettings.requestId==0)updateVideoSettings(videoSettings.selection);
|
if(videoSettings.supported&&videoSettings.requestId==0)updateVideoSettings(videoSettings.selection);
|
||||||
if(foreground&&preferences.getBoolean("networkConsent",false))remoteVersionCheck.connected();
|
if(foreground&&preferences.getBoolean("networkConsent",false))remoteVersionCheck.connected();
|
||||||
@@ -616,12 +620,12 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
String id=connectedRemote;boolean remember=rememberPassword;
|
String id=connectedRemote;boolean remember=rememberPassword;
|
||||||
end("");
|
end("");
|
||||||
// Let the user replace an expired credential; never retry it automatically.
|
// Let the user replace an expired credential; never retry it automatically.
|
||||||
if(foreground)passwordSheet(id,"访问密码错误,请重新输入",remember);
|
if(foreground)passwordSheet(id,L10n.tr("访问密码错误,请重新输入"),remember);
|
||||||
}
|
}
|
||||||
private void cancelVideoSettingsTimeout(){if(videoSettingsTimeout!=null)mainHandler.removeCallbacks(videoSettingsTimeout);videoSettingsTimeout=null;}
|
private void cancelVideoSettingsTimeout(){if(videoSettingsTimeout!=null)mainHandler.removeCallbacks(videoSettingsTimeout);videoSettingsTimeout=null;}
|
||||||
private void showRemoteUpdate(){
|
private void showRemoteUpdate(){
|
||||||
if(!ready||session==null||!foreground||pictureInPictureUi||isInPictureInPictureMode()||isFinishing()||!preferences.getBoolean("networkConsent",false))return;
|
if(!ready||session==null||!foreground||pictureInPictureUi||isInPictureInPictureMode()||isFinishing()||!preferences.getBoolean("networkConsent",false))return;
|
||||||
remoteUpdateDialog=new AlertDialog.Builder(this).setTitle("升级提示").setMessage(RemoteVersionCheck.NOTICE).setPositiveButton("知道了",null).create();
|
remoteUpdateDialog=new AlertDialog.Builder(this).setTitle(L10n.tr("升级提示")).setMessage(L10n.tr(RemoteVersionCheck.NOTICE)).setPositiveButton(L10n.tr("知道了"),null).create();
|
||||||
remoteUpdateDialog.setOnDismissListener(dialog->remoteUpdateDialog=null);
|
remoteUpdateDialog.setOnDismissListener(dialog->remoteUpdateDialog=null);
|
||||||
remoteUpdateDialog.setCanceledOnTouchOutside(false);
|
remoteUpdateDialog.setCanceledOnTouchOutside(false);
|
||||||
remoteUpdateDialog.show();
|
remoteUpdateDialog.show();
|
||||||
@@ -648,6 +652,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
JSONObject info=message.optJSONObject("host_info");
|
JSONObject info=message.optJSONObject("host_info");
|
||||||
if(info!=null){
|
if(info!=null){
|
||||||
hostPlatform=info.optString("platform","");JSONArray list=info.optJSONArray("displays");if(list!=null)displays=list;history.metadata(serverKey(),connectedRemote,info.optString("host_name",connectedRemote),hostPlatform);
|
hostPlatform=info.optString("platform","");JSONArray list=info.optJSONArray("displays");if(list!=null)displays=list;history.metadata(serverKey(),connectedRemote,info.optString("host_name",connectedRemote),hostPlatform);
|
||||||
|
if(controls!=null)controls.setHostPlatform(hostPlatform);
|
||||||
boolean wasSupported=videoSettings.supported;videoSettings.setSupported(info.optBoolean("supports_video_settings",false));
|
boolean wasSupported=videoSettings.supported;videoSettings.setSupported(info.optBoolean("supports_video_settings",false));
|
||||||
if(!videoSettings.supported)cancelVideoSettingsTimeout();
|
if(!videoSettings.supported)cancelVideoSettingsTimeout();
|
||||||
if(controls!=null)controls.videoSettings(videoSettings);
|
if(controls!=null)controls.videoSettings(videoSettings);
|
||||||
@@ -655,10 +660,42 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
if(foreground&&preferences.getBoolean("networkConsent",false))remoteVersionCheck.hostInfo(info);
|
if(foreground&&preferences.getBoolean("networkConsent",false))remoteVersionCheck.hostInfo(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void dismissPrivacyUnlockDialog() {
|
||||||
|
if (privacyUnlockDialog != null) { privacyUnlockDialog.dismiss(); privacyUnlockDialog = null; }
|
||||||
|
}
|
||||||
|
@Override public void privacyChanged() {
|
||||||
|
if (controls != null && session != null) controls.privacyScreen(session.privacyScreen);
|
||||||
|
if (!ready || session == null || !session.privacyScreen.unlockNotice) {
|
||||||
|
dismissPrivacyUnlockDialog(); return;
|
||||||
|
}
|
||||||
|
if (!foreground || pictureInPictureUi || isInPictureInPictureMode() || isFinishing()) return;
|
||||||
|
if (privacyUnlockDialog == null) {
|
||||||
|
releaseKeys();
|
||||||
|
if (controls != null) { controls.closeKeyboard(); controls.cancelMouseInput(); }
|
||||||
|
if (video != null) video.releaseMouse();
|
||||||
|
privacyUnlockDialog = new AlertDialog.Builder(this).setTitle(L10n.tr("隐私屏已被解锁"))
|
||||||
|
.setMessage(L10n.tr("被控端已通过组合键解除隐私屏。点击“锁定”将重新开启隐私屏,并禁止本机使用组合键解锁。"))
|
||||||
|
.setNegativeButton(L10n.tr("确认"), (dialog, which) -> {
|
||||||
|
if (session != null) session.privacyScreen.unlockNotice = false;
|
||||||
|
dismissPrivacyUnlockDialog();
|
||||||
|
}).setPositiveButton(L10n.tr("锁定"), null).create();
|
||||||
|
privacyUnlockDialog.setCancelable(false);
|
||||||
|
privacyUnlockDialog.show();
|
||||||
|
privacyUnlockDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(view -> {
|
||||||
|
if (session != null) session.lockPrivacy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
String feedback = session.privacyScreen.feedback(android.os.SystemClock.elapsedRealtime());
|
||||||
|
privacyUnlockDialog.setMessage(L10n.tr("被控端已通过组合键解除隐私屏。点击“锁定”将重新开启隐私屏,并禁止本机使用组合键解锁。")
|
||||||
|
+ (feedback.isEmpty() ? "" : "\n\n" + feedback));
|
||||||
|
privacyUnlockDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(
|
||||||
|
session.privacyScreen.canLock(android.os.SystemClock.elapsedRealtime()));
|
||||||
|
}
|
||||||
@Override public void videoSize(int width,int height){videoWidth=width;videoHeight=height;if(video!=null)video.videoSize(width,height);if(controls!=null)controls.videoSize(width,height);pictureInPicture.videoSize(width,height);updatePictureInPicture();}
|
@Override public void videoSize(int width,int height){videoWidth=width;videoHeight=height;if(video!=null)video.videoSize(width,height);if(controls!=null)controls.videoSize(width,height);pictureInPicture.videoSize(width,height);updatePictureInPicture();}
|
||||||
@Override public void clipboard(String value){remoteClipboard=value;}
|
@Override public void clipboard(String value){remoteClipboard=value;}
|
||||||
@Override public void statistics(RemoteNetworkStatistics.Snapshot value){if(controls!=null)controls.statistics(value);if(value.fps>0)capturePreviewIfNeeded();}
|
@Override public void statistics(RemoteNetworkStatistics.Snapshot value){if(controls!=null)controls.statistics(value);if(value.fps>0)capturePreviewIfNeeded();}
|
||||||
@Override public boolean dispatchKeyEvent(KeyEvent event){
|
@Override public boolean dispatchKeyEvent(KeyEvent event){
|
||||||
|
if (privacyUnlockDialog != null) return super.dispatchKeyEvent(event);
|
||||||
int code=KeyMap.windowsCode(event.getKeyCode());View focus=getCurrentFocus();
|
int code=KeyMap.windowsCode(event.getKeyCode());View focus=getCurrentFocus();
|
||||||
boolean remoteEditor=focus instanceof RemoteKeyboardInput;
|
boolean remoteEditor=focus instanceof RemoteKeyboardInput;
|
||||||
boolean desktopKey=event.isCtrlPressed()||event.isAltPressed()||event.isMetaPressed()||pressed.contains(code)
|
boolean desktopKey=event.isCtrlPressed()||event.isAltPressed()||event.isMetaPressed()||pressed.contains(code)
|
||||||
@@ -679,7 +716,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
super.onConfigurationChanged(config);
|
super.onConfigurationChanged(config);
|
||||||
releaseKeys();
|
releaseKeys();
|
||||||
if(!page.equals("session"))updateHomeOrientation();
|
if(!page.equals("session"))updateHomeOrientation();
|
||||||
else if(!isInPictureInPictureMode()&&(isTablet()||isInMultiWindowMode()))remoteOrientation.followDevice();
|
else if(!isInPictureInPictureMode())remoteOrientation.update(!isTablet()&&!isInMultiWindowMode());
|
||||||
sizePasswordDialog();
|
sizePasswordDialog();
|
||||||
if(privacyDialog instanceof PrivacyConsentDialog)((PrivacyConsentDialog)privacyDialog).updateSize();
|
if(privacyDialog instanceof PrivacyConsentDialog)((PrivacyConsentDialog)privacyDialog).updateSize();
|
||||||
if(controls!=null&&!pictureInPictureUi)controls.post(controls::constrainPanels);
|
if(controls!=null&&!pictureInPictureUi)controls.post(controls::constrainPanels);
|
||||||
@@ -698,7 +735,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
retained.muted=muted;retained.selectedDisplay=selectedDisplay;retained.remoteServiceAvailable=remoteServiceAvailable;
|
retained.muted=muted;retained.selectedDisplay=selectedDisplay;retained.remoteServiceAvailable=remoteServiceAvailable;
|
||||||
retained.clipboard=remoteClipboard;retained.videoSettings=videoSettings;retained.receivedFile=receivedFile;
|
retained.clipboard=remoteClipboard;retained.videoSettings=videoSettings;retained.receivedFile=receivedFile;
|
||||||
retained.width=videoWidth;retained.height=videoHeight;
|
retained.width=videoWidth;retained.height=videoHeight;
|
||||||
retained.waitingForLandscape=remoteOrientation.waitingForLandscape();
|
retained.autoLandscape=remoteOrientation.autoLandscape();
|
||||||
}
|
}
|
||||||
private boolean prepareBackgroundSession(boolean inform){
|
private boolean prepareBackgroundSession(boolean inform){
|
||||||
if(!ready||session==null||session.isClosed()||isFinishing()||!hasNetworkConsent())return false;
|
if(!ready||session==null||session.isClosed()||isFinishing()||!hasNetworkConsent())return false;
|
||||||
@@ -707,10 +744,10 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
backgroundSession=retained;captureBackgroundSession();retained.attach(this);session.setListener(retained);
|
backgroundSession=retained;captureBackgroundSession();retained.attach(this);session.setListener(retained);
|
||||||
if(!RemoteSessionService.retain(this,retained)){
|
if(!RemoteSessionService.retain(this,retained)){
|
||||||
session.setListener(this);retained.detach(this);backgroundSession=null;
|
session.setListener(this);retained.detach(this);backgroundSession=null;
|
||||||
if(inform)toast("后台保活未能启动,请保持应用在前台");return false;
|
if(inform)toast(L10n.tr("后台保活未能启动,请保持应用在前台"));return false;
|
||||||
}
|
}
|
||||||
if(inform){
|
if(inform){
|
||||||
toast("已启用后台保活,可从连接通知返回或断开");
|
toast(L10n.tr("已启用后台保活,可从连接通知返回或断开"));
|
||||||
requestBackgroundNotifications();
|
requestBackgroundNotifications();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -725,7 +762,7 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
BackgroundSession retained=backgroundSession!=null?backgroundSession:RemoteSessionService.current();
|
BackgroundSession retained=backgroundSession!=null?backgroundSession:RemoteSessionService.current();
|
||||||
if(retained==null)return false;
|
if(retained==null)return false;
|
||||||
if(retained.ended||retained.session.isClosed()||!hasNetworkConsent()||!host.equals(retained.host)||port!=retained.port){
|
if(retained.ended||retained.session.isClosed()||!hasNetworkConsent()||!host.equals(retained.host)||port!=retained.port){
|
||||||
retained.detach(this);retained.finish("后台连接已结束,请重新连接");backgroundSession=null;
|
retained.detach(this);retained.finish(L10n.tr("后台连接已结束,请重新连接"));backgroundSession=null;
|
||||||
if(session==retained.session)end(retained.endReason);return false;
|
if(session==retained.session)end(retained.endReason);return false;
|
||||||
}
|
}
|
||||||
boolean rebuild=session!=retained.session||video==null||!page.equals("session");
|
boolean rebuild=session!=retained.session||video==null||!page.equals("session");
|
||||||
@@ -734,9 +771,9 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
hostPlatform=retained.platform;displays=retained.displays;muted=retained.muted;selectedDisplay=retained.selectedDisplay;
|
hostPlatform=retained.platform;displays=retained.displays;muted=retained.muted;selectedDisplay=retained.selectedDisplay;
|
||||||
remoteServiceAvailable=retained.remoteServiceAvailable;remoteClipboard=retained.clipboard;receivedFile=retained.receivedFile;
|
remoteServiceAvailable=retained.remoteServiceAvailable;remoteClipboard=retained.clipboard;receivedFile=retained.receivedFile;
|
||||||
videoSettings=retained.videoSettings;ready=true;retained.attach(this);
|
videoSettings=retained.videoSettings;ready=true;retained.attach(this);
|
||||||
if(rebuild)sessionScreen(retained.waitingForLandscape);
|
if(rebuild)sessionScreen(retained.autoLandscape);
|
||||||
if(retained.width>0&&retained.height>0)videoSize(retained.width,retained.height);
|
if(retained.width>0&&retained.height>0)videoSize(retained.width,retained.height);
|
||||||
if(controls!=null){controls.setMuted(muted);controls.videoSettings(videoSettings);controls.fileStatus(retained.fileStatus,receivedFile!=null);}
|
if(controls!=null){controls.setHostPlatform(hostPlatform);controls.setMuted(muted);controls.videoSettings(videoSettings);controls.fileStatus(retained.fileStatus,receivedFile!=null);}
|
||||||
retained.setBackground(false);signaling(signalState,localIdentity);
|
retained.setBackground(false);signaling(signalState,localIdentity);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -782,11 +819,11 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
super.onPictureInPictureUiStateChanged(state);
|
super.onPictureInPictureUiStateChanged(state);
|
||||||
if(android.os.Build.VERSION.SDK_INT>=35&&state.isTransitioningToPip())setPictureInPictureUi(true);
|
if(android.os.Build.VERSION.SDK_INT>=35&&state.isTransitioningToPip())setPictureInPictureUi(true);
|
||||||
}
|
}
|
||||||
@Override protected void onResume(){super.onResume();remoteOrientation.resume();if(!isInPictureInPictureMode())enteredPictureInPicture=false;setPictureInPictureUi(isInPictureInPictureMode());updatePictureInPicture();if(ready&&!canUsePictureInPicture())prepareBackgroundSession(true);if(backgroundSession!=null)requestBackgroundNotifications();}
|
@Override protected void onResume(){super.onResume();privacyChanged();if(!isInPictureInPictureMode())enteredPictureInPicture=false;setPictureInPictureUi(isInPictureInPictureMode());updatePictureInPicture();if(ready&&!canUsePictureInPicture())prepareBackgroundSession(true);if(backgroundSession!=null)requestBackgroundNotifications();}
|
||||||
// onStart marks a new visit; focus/resume callbacks must not reopen a refused notice.
|
// onStart marks a new visit; focus/resume callbacks must not reopen a refused notice.
|
||||||
@Override protected void onPause(){remoteOrientation.pause();releaseKeys();if(controls!=null)controls.cancelMouseInput();if(video!=null)video.releaseMouse();super.onPause();}
|
@Override protected void onPause(){releaseKeys();if(controls!=null)controls.cancelMouseInput();if(video!=null)video.releaseMouse();super.onPause();}
|
||||||
@Override protected void onStart(){super.onStart();foreground=true;restoreBackgroundSession();appUpdates.setEnabled(preferences.getBoolean("networkConsent",false));if(!preferences.getBoolean("networkConsent",false))consent(this::renderSignaling);ensureSignaling();showConnectionMessage();}
|
@Override protected void onStart(){super.onStart();foreground=true;restoreBackgroundSession();appUpdates.setEnabled(preferences.getBoolean("networkConsent",false));if(!preferences.getBoolean("networkConsent",false))consent(this::renderSignaling);ensureSignaling();showConnectionMessage();}
|
||||||
@Override protected void onStop(){if(controls!=null)controls.closeKeyboard();foreground=false;appUpdates.setEnabled(false);if(privacyDialog!=null)privacyDialog.dismiss();pageHost.finish();
|
@Override protected void onStop(){dismissPrivacyUnlockDialog();if(controls!=null)controls.closeKeyboard();foreground=false;appUpdates.setEnabled(false);if(privacyDialog!=null)privacyDialog.dismiss();pageHost.finish();
|
||||||
if(connectionDialog!=null)connectionDialog.dismiss();
|
if(connectionDialog!=null)connectionDialog.dismiss();
|
||||||
// Closing native PiP is an explicit end, not a request for invisible
|
// Closing native PiP is an explicit end, not a request for invisible
|
||||||
// fallback. An unavailable/failed PiP can hand its live session off.
|
// fallback. An unavailable/failed PiP can hand its live session off.
|
||||||
@@ -795,9 +832,9 @@ public final class MainActivity extends Activity implements NativeSession.Listen
|
|||||||
mainHandler.removeCallbacks(presenceTick);recentPresence.setConnected(false,android.os.SystemClock.elapsedRealtime());announcementInbox.setConnected(false);
|
mainHandler.removeCallbacks(presenceTick);recentPresence.setConnected(false,android.os.SystemClock.elapsedRealtime());announcementInbox.setConnected(false);
|
||||||
cancelVideoSettingsTimeout();if(videoSettings.pending)videoSettings.expire(videoSettings.requestId);
|
cancelVideoSettingsTimeout();if(videoSettings.pending)videoSettings.expire(videoSettings.requestId);
|
||||||
resetRemoteVersionCheck();if(disconnectDialog!=null)disconnectDialog.dismiss();
|
resetRemoteVersionCheck();if(disconnectDialog!=null)disconnectDialog.dismiss();
|
||||||
}else if(documentSession==null||documentSession!=session||isFinishing()){if(session!=null)end(isFinishing()?"":"远程窗口已关闭,或后台保活未能启动,会话已结束");stopSignaling();}
|
}else if(documentSession==null||documentSession!=session||isFinishing()){if(session!=null)end(isFinishing()?"":L10n.tr("远程窗口已关闭,或后台保活未能启动,会话已结束"));stopSignaling();}
|
||||||
super.onStop();}
|
super.onStop();}
|
||||||
@Override protected void onDestroy(){mainHandler.removeCallbacks(documentTimeout);documentSession=null;documentToSave=null;appUpdates.setEnabled(false);pageHost.finish();resetRemoteVersionCheck();cancelVideoSettingsTimeout();previews.removeListener(previewChanged);if(privacyDialog!=null)privacyDialog.dismiss();if(previewDialog!=null)previewDialog.dismiss();announcementInbox.close();if(disconnectDialog!=null)disconnectDialog.dismiss();if(passwordDialog!=null)passwordDialog.dismiss();if(progressDialog!=null)progressDialog.dismiss();
|
@Override protected void onDestroy(){dismissPrivacyUnlockDialog();mainHandler.removeCallbacks(documentTimeout);documentSession=null;documentToSave=null;appUpdates.setEnabled(false);pageHost.finish();resetRemoteVersionCheck();cancelVideoSettingsTimeout();previews.removeListener(previewChanged);if(privacyDialog!=null)privacyDialog.dismiss();if(previewDialog!=null)previewDialog.dismiss();announcementInbox.close();if(disconnectDialog!=null)disconnectDialog.dismiss();if(passwordDialog!=null)passwordDialog.dismiss();if(progressDialog!=null)progressDialog.dismiss();
|
||||||
if(connectionDialog!=null)connectionDialog.dismiss();
|
if(connectionDialog!=null)connectionDialog.dismiss();
|
||||||
boolean retained=!isFinishing()&&backgroundSession!=null&&!backgroundSession.ended&&RemoteSessionService.current()==backgroundSession;
|
boolean retained=!isFinishing()&&backgroundSession!=null&&!backgroundSession.ended&&RemoteSessionService.current()==backgroundSession;
|
||||||
if(backgroundSession!=null)backgroundSession.detach(this);
|
if(backgroundSession!=null)backgroundSession.detach(this);
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ final class MobileUi {
|
|||||||
}
|
}
|
||||||
TextView primary(String title,Runnable click) {
|
TextView primary(String title,Runnable click) {
|
||||||
TextView v=action(title,Color.WHITE,click);
|
TextView v=action(title,Color.WHITE,click);
|
||||||
|
v.setMaxLines(2);v.setAutoSizeTextTypeUniformWithConfiguration(11,16,1,android.util.TypedValue.COMPLEX_UNIT_SP);
|
||||||
GradientDrawable gradient=new GradientDrawable(GradientDrawable.Orientation.TL_BR,new int[]{BLUE,0xFF1F61C7});gradient.setCornerRadius(dp(12));v.setBackground(gradient);return v;
|
GradientDrawable gradient=new GradientDrawable(GradientDrawable.Orientation.TL_BR,new int[]{BLUE,0xFF1F61C7});gradient.setCornerRadius(dp(12));v.setBackground(gradient);return v;
|
||||||
}
|
}
|
||||||
View iconButton(String icon,String label,Runnable click) {
|
View iconButton(String icon,String label,Runnable click) {
|
||||||
@@ -59,7 +60,7 @@ final class MobileUi {
|
|||||||
Icon glyph=new Icon(context,icon,INK);FrameLayout.LayoutParams p=new FrameLayout.LayoutParams(dp(22),dp(22),Gravity.CENTER);box.addView(glyph,p);
|
Icon glyph=new Icon(context,icon,INK);FrameLayout.LayoutParams p=new FrameLayout.LayoutParams(dp(22),dp(22),Gravity.CENTER);box.addView(glyph,p);
|
||||||
box.setContentDescription(label);box.setClickable(true);box.setFocusable(true);box.setOnClickListener(v->click.run());return box;
|
box.setContentDescription(label);box.setClickable(true);box.setFocusable(true);box.setOnClickListener(v->click.run());return box;
|
||||||
}
|
}
|
||||||
View icon(String name,int color) { return new Icon(context,name,color); }
|
Icon icon(String name,int color) { return new Icon(context,name,color); }
|
||||||
void gap(LinearLayout parent,int height){parent.addView(new View(context),new LinearLayout.LayoutParams(1,dp(height)));}
|
void gap(LinearLayout parent,int height){parent.addView(new View(context),new LinearLayout.LayoutParams(1,dp(height)));}
|
||||||
void divider(LinearLayout parent){View line=new View(context);line.setBackgroundColor(LINE);parent.addView(line,new LinearLayout.LayoutParams(-1,dp(.5f)));}
|
void divider(LinearLayout parent){View line=new View(context);line.setBackgroundColor(LINE);parent.addView(line,new LinearLayout.LayoutParams(-1,dp(.5f)));}
|
||||||
LinearLayout.LayoutParams size(int width,int height){return new LinearLayout.LayoutParams(width<0?width:dp(width),height<0?height:dp(height));}
|
LinearLayout.LayoutParams size(int width,int height){return new LinearLayout.LayoutParams(width<0?width:dp(width),height<0?height:dp(height));}
|
||||||
@@ -93,8 +94,9 @@ final class MobileUi {
|
|||||||
}
|
}
|
||||||
@android.annotation.SuppressLint("ViewConstructor") // Programmatic icon primitive, not inflated from XML.
|
@android.annotation.SuppressLint("ViewConstructor") // Programmatic icon primitive, not inflated from XML.
|
||||||
static final class Icon extends View {
|
static final class Icon extends View {
|
||||||
private final Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG); private final String name;
|
private final Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG); private String name;
|
||||||
Icon(Context context,String name,int color){super(context);this.name=name;paint.setColor(color);setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);}
|
Icon(Context context,String name,int color){super(context);this.name=name;paint.setColor(color);setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);}
|
||||||
|
void setName(String value){if(name.equals(value))return;name=value;invalidate();}
|
||||||
@Override protected void onDraw(Canvas c){
|
@Override protected void onDraw(Canvas c){
|
||||||
c.save();c.scale(getWidth()/24f,getHeight()/24f);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(1.7f);paint.setStrokeCap(Paint.Cap.ROUND);paint.setStrokeJoin(Paint.Join.ROUND);
|
c.save();c.scale(getWidth()/24f,getHeight()/24f);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(1.7f);paint.setStrokeCap(Paint.Cap.ROUND);paint.setStrokeJoin(Paint.Join.ROUND);
|
||||||
switch(name){
|
switch(name){
|
||||||
@@ -136,6 +138,14 @@ final class MobileUi {
|
|||||||
case "unlock":c.drawRoundRect(5,10,19,22,2,2,paint);c.drawArc(8,2,16,15,180,150,false,paint);c.drawPoint(12,16,paint);break;
|
case "unlock":c.drawRoundRect(5,10,19,22,2,2,paint);c.drawArc(8,2,16,15,180,150,false,paint);c.drawPoint(12,16,paint);break;
|
||||||
case "clipboard":c.drawRoundRect(4,4,20,22,2,2,paint);c.drawRoundRect(8,2,16,7,2,2,paint);break;
|
case "clipboard":c.drawRoundRect(4,4,20,22,2,2,paint);c.drawRoundRect(8,2,16,7,2,2,paint);break;
|
||||||
case "eye":path(c,2,12,6,7,12,5,18,7,22,12,18,17,12,19,6,17,2,12);c.drawCircle(12,12,3,paint);break;
|
case "eye":path(c,2,12,6,7,12,5,18,7,22,12,18,17,12,19,6,17,2,12);c.drawCircle(12,12,3,paint);break;
|
||||||
|
case "eye_off":
|
||||||
|
Path hiddenEye=new Path();
|
||||||
|
hiddenEye.moveTo(10,5);hiddenEye.cubicTo(15,4,19,8,22,12);
|
||||||
|
hiddenEye.quadTo(20.4f,14.7f,18,17);
|
||||||
|
hiddenEye.moveTo(6,6);hiddenEye.quadTo(3.5f,8.5f,2,12);
|
||||||
|
hiddenEye.cubicTo(7,20,13,21,18,17);
|
||||||
|
hiddenEye.moveTo(10,10);hiddenEye.cubicTo(8,13,11,16,14,14);
|
||||||
|
c.drawPath(hiddenEye,paint);path(c,3,3,21,21);break;
|
||||||
default:c.drawCircle(12,12,9,paint);path(c,12,10,12,17);c.drawPoint(12,6,paint);
|
default:c.drawCircle(12,12,9,paint);path(c,12,10,12,17);c.drawPoint(12,6,paint);
|
||||||
}c.restore();
|
}c.restore();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,12 @@ final class NativeSession implements AutoCloseable {
|
|||||||
void statistics(RemoteNetworkStatistics.Snapshot value);
|
void statistics(RemoteNetworkStatistics.Snapshot value);
|
||||||
default void fileTransfer(String name,double progress,boolean sending,File received) { }
|
default void fileTransfer(String name,double progress,boolean sending,File received) { }
|
||||||
default void controlFeedback(String message) { }
|
default void controlFeedback(String message) { }
|
||||||
|
default void privacyChanged() { }
|
||||||
default void signaling(int state, String deviceId) { }
|
default void signaling(int state, String deviceId) { }
|
||||||
default void presence(JSONObject message) { }
|
default void presence(JSONObject message) { }
|
||||||
default void announcement(JSONObject message) { }
|
default void announcement(JSONObject message) { }
|
||||||
default void announcementFailed(String requestId) { }
|
default void announcementFailed(String requestId) { }
|
||||||
default void passwordRejected() { ended("远端密码错误"); }
|
default void passwordRejected() { ended(L10n.tr("远端密码错误")); }
|
||||||
}
|
}
|
||||||
// Serializes all peer creation/API calls/destruction, including across activities.
|
// Serializes all peer creation/API calls/destruction, including across activities.
|
||||||
private static final ExecutorService RTC = Executors.newSingleThreadExecutor(r -> new Thread(r, "CrossDesk RTC"));
|
private static final ExecutorService RTC = Executors.newSingleThreadExecutor(r -> new Thread(r, "CrossDesk RTC"));
|
||||||
@@ -49,6 +50,7 @@ final class NativeSession implements AutoCloseable {
|
|||||||
private long handle;
|
private long handle;
|
||||||
private boolean signalReady, joining, started;
|
private boolean signalReady, joining, started;
|
||||||
private volatile boolean connected, supportsTextInput;
|
private volatile boolean connected, supportsTextInput;
|
||||||
|
final RemotePrivacyScreen privacyScreen = new RemotePrivacyScreen();
|
||||||
private int signalState;
|
private int signalState;
|
||||||
private volatile long signalGeneration;
|
private volatile long signalGeneration;
|
||||||
private boolean presenceRequested;
|
private boolean presenceRequested;
|
||||||
@@ -63,12 +65,14 @@ final class NativeSession implements AutoCloseable {
|
|||||||
private final RemoteNetworkStatistics networkStatistics=new RemoteNetworkStatistics();
|
private final RemoteNetworkStatistics networkStatistics=new RemoteNetworkStatistics();
|
||||||
private final Runnable statisticsTick=new Runnable(){public void run(){
|
private final Runnable statisticsTick=new Runnable(){public void run(){
|
||||||
if(closed)return;
|
if(closed)return;
|
||||||
|
privacyScreen.tick(android.os.SystemClock.elapsedRealtime());
|
||||||
|
listener.privacyChanged();
|
||||||
listener.statistics(networkStatistics.snapshot(RemoteNetworkStatistics.now()));
|
listener.statistics(networkStatistics.snapshot(RemoteNetworkStatistics.now()));
|
||||||
if(!closed)main.postDelayed(this,1000);
|
if(!closed)main.postDelayed(this,1000);
|
||||||
}};
|
}};
|
||||||
private final Runnable timeout = () -> fail(remote.isEmpty()
|
private final Runnable timeout = () -> fail(remote.isEmpty()
|
||||||
? "服务器连接超时,请检查服务器地址和网络"
|
? L10n.tr("服务器连接超时,请检查服务器地址和网络")
|
||||||
: "连接超时,请检查设备 ID、服务器及远端在线状态");
|
: L10n.tr("连接超时,请检查设备 ID、服务器及远端在线状态"));
|
||||||
|
|
||||||
NativeSession(Context context, String host, int port, String remote, String password, Listener listener) {
|
NativeSession(Context context, String host, int port, String remote, String password, Listener listener) {
|
||||||
this.context = context.getApplicationContext(); this.listener = listener;
|
this.context = context.getApplicationContext(); this.listener = listener;
|
||||||
@@ -88,10 +92,10 @@ final class NativeSession implements AutoCloseable {
|
|||||||
File logs = new File(context.getNoBackupFilesDir(), "logs");
|
File logs = new File(context.getNoBackupFilesDir(), "logs");
|
||||||
if (!logs.isDirectory() && !logs.mkdirs()) throw new IllegalStateException("Log directory unavailable");
|
if (!logs.isDirectory() && !logs.mkdirs()) throw new IllegalStateException("Log directory unavailable");
|
||||||
handle = nCreate(this, host, port, identity, logs.getAbsolutePath(), certificates.getAbsolutePath());
|
handle = nCreate(this, host, port, identity, logs.getAbsolutePath(), certificates.getAbsolutePath());
|
||||||
if (handle == 0) { fail("原生连接初始化失败"); return; }
|
if (handle == 0) { fail(L10n.tr("原生连接初始化失败")); return; }
|
||||||
nFileDirectory(handle,new File(documents.directory,"received").getAbsolutePath());
|
nFileDirectory(handle,new File(documents.directory,"received").getAbsolutePath());
|
||||||
if (surface != null && surface.isValid()) nSurface(handle, surface);
|
if (surface != null && surface.isValid()) nSurface(handle, surface);
|
||||||
} catch (Exception error) { fail("安全存储或证书初始化失败,请重试或清除应用数据"); }
|
} catch (Exception error) { fail(L10n.tr("安全存储或证书初始化失败,请重试或清除应用数据")); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/** Promote the already registered identity peer to an outgoing desktop session. */
|
/** Promote the already registered identity peer to an outgoing desktop session. */
|
||||||
@@ -143,7 +147,7 @@ final class NativeSession implements AutoCloseable {
|
|||||||
if (handle == 0 || joining || remote.isEmpty() || !signalReady || identity.isEmpty()) return;
|
if (handle == 0 || joining || remote.isEmpty() || !signalReady || identity.isEmpty()) return;
|
||||||
joining = true;
|
joining = true;
|
||||||
String base = identity.split("@", 2)[0];
|
String base = identity.split("@", 2)[0];
|
||||||
if (!nConnect(handle, base, remote, password)) fail("无法发起远程连接");
|
if (!nConnect(handle, base, remote, password)) fail(L10n.tr("无法发起远程连接"));
|
||||||
password = "";
|
password = "";
|
||||||
}
|
}
|
||||||
// Called on arbitrary native callback threads; borrowed payloads were copied by JNI.
|
// Called on arbitrary native callback threads; borrowed payloads were copied by JNI.
|
||||||
@@ -171,10 +175,10 @@ final class NativeSession implements AutoCloseable {
|
|||||||
publishSignaling();
|
publishSignaling();
|
||||||
tryJoin();
|
tryJoin();
|
||||||
}
|
}
|
||||||
if (status == 6) fail("服务器 TLS 证书校验失败");
|
if (status == 6) fail(L10n.tr("服务器 TLS 证书校验失败"));
|
||||||
else if (status == 2) fail("服务器登录失败,请检查服务器地址和网络");
|
else if (status == 2) fail(L10n.tr("服务器登录失败,请检查服务器地址和网络"));
|
||||||
else if (status >= 3 && connected) fail("信令连接中断,请重新连接");
|
else if (status >= 3 && connected) fail(L10n.tr("信令连接中断,请重新连接"));
|
||||||
else if (status == 0 || status == 4) ui(() -> listener.status("正在连接信令服务器…"));
|
else if (status == 0 || status == 4) ui(() -> listener.status(L10n.tr("正在连接信令服务器…")));
|
||||||
} else if (type == 2) {
|
} else if (type == 2) {
|
||||||
int status = event.getInt("status");
|
int status = event.getInt("status");
|
||||||
if (status == 1) {
|
if (status == 1) {
|
||||||
@@ -183,17 +187,31 @@ final class NativeSession implements AutoCloseable {
|
|||||||
audio = new AudioPlayer();
|
audio = new AudioPlayer();
|
||||||
nControl(handle, 2, 1);
|
nControl(handle, 2, 1);
|
||||||
ui(() -> { listener.connected(); if(!closed){statisticsTick.run();fileTick.run();} });
|
ui(() -> { listener.connected(); if(!closed){statisticsTick.run();fileTick.run();} });
|
||||||
} else if (status == 0 || status == 2) ui(() -> listener.status("正在建立安全连接…"));
|
} else if (status == 0 || status == 2) ui(() -> listener.status(L10n.tr("正在建立安全连接…")));
|
||||||
else if (status == 6) ui(() -> { listener.passwordRejected(); close(); });
|
else if (status == 6) ui(() -> { listener.passwordRejected(); close(); });
|
||||||
else {
|
else {
|
||||||
String[] reasons = {"", "", "", "连接已断开", "连接失败", "远端已关闭连接",
|
String[] reasons = {"", "", "", L10n.tr("连接已断开"), L10n.tr("连接失败"), L10n.tr("远端已关闭连接"),
|
||||||
"远端密码错误", "找不到该设备 ID", "远端当前不可用"};
|
L10n.tr("远端密码错误"), L10n.tr("找不到该设备 ID"), L10n.tr("远端当前不可用")};
|
||||||
fail(status >= 0 && status < reasons.length ? reasons[status] : "连接已结束");
|
fail(status >= 0 && status < reasons.length ? reasons[status] : L10n.tr("连接已结束"));
|
||||||
}
|
}
|
||||||
} else if (type == 4) {
|
} else if (type == 4) {
|
||||||
|
if(event.optInt("type",-1)==14){
|
||||||
|
networkStatistics.receiveRemoteEncoder(event.getJSONObject("video_codec").getString("encoder"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
JSONObject info=event.optJSONObject("host_info");
|
JSONObject info=event.optJSONObject("host_info");
|
||||||
if(info!=null)supportsTextInput=info.optBoolean("supports_text_input",false);
|
if(info!=null)supportsTextInput=info.optBoolean("supports_text_input",false);
|
||||||
ui(() -> listener.data(event));
|
ui(() -> {
|
||||||
|
if(event.optInt("type",-1)==10){
|
||||||
|
privacyScreen.receive(event,android.os.SystemClock.elapsedRealtime());
|
||||||
|
listener.privacyChanged();
|
||||||
|
} else if(event.optInt("type",-1)==5){
|
||||||
|
JSONObject service=event.optJSONObject("service_status");
|
||||||
|
privacyScreen.setInteractiveStage(service==null?"":service.optString("interactive_stage",""));
|
||||||
|
listener.privacyChanged();
|
||||||
|
}
|
||||||
|
listener.data(event);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else if (type == 5) {
|
else if (type == 5) {
|
||||||
int width = event.getInt("width"), height = event.getInt("height");
|
int width = event.getInt("width"), height = event.getInt("height");
|
||||||
@@ -210,7 +228,7 @@ final class NativeSession implements AutoCloseable {
|
|||||||
listener.fileTransfer(name,progress,sending,path.isEmpty()?null:new File(path));});
|
listener.fileTransfer(name,progress,sending,path.isEmpty()?null:new File(path));});
|
||||||
}
|
}
|
||||||
} catch (Exception error) {
|
} catch (Exception error) {
|
||||||
if (type == 3) fail("设备身份保存失败,请检查安全存储");
|
if (type == 3) fail(L10n.tr("设备身份保存失败,请检查安全存储"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -266,6 +284,18 @@ final class NativeSession implements AutoCloseable {
|
|||||||
if (type == 2 && player != null) player.setEnabled(value != 0);
|
if (type == 2 && player != null) player.setEnabled(value != 0);
|
||||||
execute(() -> { if (connected){nControl(handle, type, value);if(type==4)networkStatistics.resetVideo();} });
|
execute(() -> { if (connected){nControl(handle, type, value);if(type==4)networkStatistics.resetVideo();} });
|
||||||
}
|
}
|
||||||
|
void lockPrivacy() { sendPrivacy(true); }
|
||||||
|
void togglePrivacy() { sendPrivacy(false); }
|
||||||
|
private void sendPrivacy(boolean lock) {
|
||||||
|
if(closed||!connected)return;
|
||||||
|
boolean enable=!privacyScreen.active();
|
||||||
|
long request=privacyScreen.begin(android.os.SystemClock.elapsedRealtime(),lock);
|
||||||
|
if(request<0)return;
|
||||||
|
listener.privacyChanged();
|
||||||
|
execute(()->{if(connected&&!nPrivacy(handle,lock?3:enable?1:2,true))ui(()->{
|
||||||
|
privacyScreen.failed(request);listener.privacyChanged();
|
||||||
|
});});
|
||||||
|
}
|
||||||
void clipboard(String value) {
|
void clipboard(String value) {
|
||||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||||
if (bytes.length == 0 || bytes.length > 128 * 1024) return;
|
if (bytes.length == 0 || bytes.length > 128 * 1024) return;
|
||||||
@@ -273,24 +303,24 @@ final class NativeSession implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
void secureAttention(){execute(()->{
|
void secureAttention(){execute(()->{
|
||||||
boolean sent=connected&&nSecureAttention(handle);
|
boolean sent=connected&&nSecureAttention(handle);
|
||||||
ui(()->listener.controlFeedback(sent?"已发送 Ctrl+Alt+Del":"指令发送失败,请重试"));
|
ui(()->listener.controlFeedback(sent?L10n.tr("已发送 Ctrl+Alt+Del"):L10n.tr("指令发送失败,请重试")));
|
||||||
});}
|
});}
|
||||||
void sendFile(android.net.Uri uri){
|
void sendFile(android.net.Uri uri){
|
||||||
if(closed)return;
|
if(closed)return;
|
||||||
if(sendingFile){listener.controlFeedback("请等待当前文件发送完成");return;}
|
if(sendingFile){listener.controlFeedback(L10n.tr("请等待当前文件发送完成"));return;}
|
||||||
sendingFile=true;listener.fileTransfer("正在读取所选文件…",0,true,null);
|
sendingFile=true;listener.fileTransfer(L10n.tr("正在读取所选文件…"),0,true,null);
|
||||||
documents.prepare(uri,new SessionDocuments.Prepared(){
|
documents.prepare(uri,new SessionDocuments.Prepared(){
|
||||||
public void ready(File file,String name){execute(()->{
|
public void ready(File file,String name){execute(()->{
|
||||||
if(!connected||!nSendFile(handle,file.getAbsolutePath(),name.getBytes(StandardCharsets.UTF_8))){
|
if(!connected||!nSendFile(handle,file.getAbsolutePath(),name.getBytes(StandardCharsets.UTF_8))){
|
||||||
file.delete();ui(()->{sendingFile=false;listener.fileTransfer(name,-1,true,null);});
|
file.delete();ui(()->{sendingFile=false;listener.fileTransfer(name,-1,true,null);});
|
||||||
}
|
}
|
||||||
});}
|
});}
|
||||||
public void failed(){ui(()->{sendingFile=false;listener.fileTransfer("所选文件(无法读取或空间不足)",-1,true,null);});}
|
public void failed(){ui(()->{sendingFile=false;listener.fileTransfer(L10n.tr("所选文件(无法读取或空间不足)"),-1,true,null);});}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void saveFile(File file,android.net.Uri destination){
|
void saveFile(File file,android.net.Uri destination){
|
||||||
if(closed)return;
|
if(closed)return;
|
||||||
documents.save(file,destination,success->ui(()->listener.controlFeedback(success?"文件已保存":"文件保存失败,请重试")));
|
documents.save(file,destination,success->ui(()->listener.controlFeedback(success?L10n.tr("文件已保存"):L10n.tr("文件保存失败,请重试"))));
|
||||||
}
|
}
|
||||||
@Override public void close() {
|
@Override public void close() {
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
@@ -319,6 +349,7 @@ final class NativeSession implements AutoCloseable {
|
|||||||
private static native boolean nPresenceRequest(long handle,byte[] data);
|
private static native boolean nPresenceRequest(long handle,byte[] data);
|
||||||
private static native void nClipboard(long handle, byte[] data);
|
private static native void nClipboard(long handle, byte[] data);
|
||||||
private static native boolean nSecureAttention(long handle);
|
private static native boolean nSecureAttention(long handle);
|
||||||
|
private static native boolean nPrivacy(long handle,int flag,boolean blockLocalInput);
|
||||||
private static native void nFileDirectory(long handle,String directory);
|
private static native void nFileDirectory(long handle,String directory);
|
||||||
private static native boolean nSendFile(long handle,String path,byte[] name);
|
private static native boolean nSendFile(long handle,String path,byte[] name);
|
||||||
private static native boolean nPumpFiles(long handle);
|
private static native boolean nPumpFiles(long handle);
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ final class PrivacyConsentDialog extends Dialog {
|
|||||||
PrivacyConsentDialog(Activity activity,MobileUi ui,PrivacyPolicyDocument policy,Runnable accepted){
|
PrivacyConsentDialog(Activity activity,MobileUi ui,PrivacyPolicyDocument policy,Runnable accepted){
|
||||||
super(activity);this.activity=activity;this.ui=ui;requestWindowFeature(Window.FEATURE_NO_TITLE);setCancelable(false);setCanceledOnTouchOutside(false);
|
super(activity);this.activity=activity;this.ui=ui;requestWindowFeature(Window.FEATURE_NO_TITLE);setCancelable(false);setCanceledOnTouchOutside(false);
|
||||||
LinearLayout sheet=ui.column();sheet.setBackground(ui.background(android.graphics.Color.WHITE,20));sheet.setClipToOutline(true);
|
LinearLayout sheet=ui.column();sheet.setBackground(ui.background(android.graphics.Color.WHITE,20));sheet.setClipToOutline(true);
|
||||||
TextView title=ui.text("隐私政策",17,true);title.setGravity(Gravity.CENTER);sheet.addView(title,ui.size(-1,56));ui.divider(sheet);
|
TextView title=ui.text(L10n.tr("隐私政策"),17,true);title.setGravity(Gravity.CENTER);sheet.addView(title,ui.size(-1,56));ui.divider(sheet);
|
||||||
sheet.addView(policy.view(ui),new LinearLayout.LayoutParams(-1,0,1));ui.divider(sheet);
|
sheet.addView(policy.view(ui),new LinearLayout.LayoutParams(-1,0,1));ui.divider(sheet);
|
||||||
LinearLayout actions=ui.column();actions.setPadding(ui.dp(20),ui.dp(12),ui.dp(20),ui.dp(12));sheet.addView(actions);
|
LinearLayout actions=ui.column();actions.setPadding(ui.dp(20),ui.dp(12),ui.dp(20),ui.dp(12));sheet.addView(actions);
|
||||||
CheckBox hasReadPolicy=new CheckBox(activity);hasReadPolicy.setText("我已阅读并同意《隐私政策》");hasReadPolicy.setTextColor(INK);hasReadPolicy.setTextSize(14);hasReadPolicy.setMinHeight(ui.dp(44));actions.addView(hasReadPolicy,ui.size(-1,-2));ui.gap(actions,12);
|
CheckBox hasReadPolicy=new CheckBox(activity);hasReadPolicy.setText(L10n.tr("我已阅读并同意《隐私政策》"));hasReadPolicy.setTextColor(INK);hasReadPolicy.setTextSize(14);hasReadPolicy.setMinHeight(ui.dp(44));actions.addView(hasReadPolicy,ui.size(-1,-2));ui.gap(actions,12);
|
||||||
TextView agree=ui.primary("同意并继续",()->{
|
TextView agree=ui.primary(L10n.tr("同意并继续"),()->{
|
||||||
if(!hasReadPolicy.isChecked()||!policy.available)return;
|
if(!hasReadPolicy.isChecked()||!policy.available)return;
|
||||||
dismiss();accepted.run();
|
dismiss();accepted.run();
|
||||||
});agree.setEnabled(false);agree.setAlpha(.45f);actions.addView(agree,ui.size(-1,48));
|
});agree.setEnabled(false);agree.setAlpha(.45f);actions.addView(agree,ui.size(-1,48));
|
||||||
hasReadPolicy.setOnCheckedChangeListener((button,checked)->{boolean enabled=checked&&policy.available;agree.setEnabled(enabled);agree.setAlpha(enabled?1:.45f);});
|
hasReadPolicy.setOnCheckedChangeListener((button,checked)->{boolean enabled=checked&&policy.available;agree.setEnabled(enabled);agree.setAlpha(enabled?1:.45f);});
|
||||||
ui.gap(actions,12);actions.addView(ui.action("暂不同意",SECONDARY,this::dismiss),ui.size(-1,44));
|
ui.gap(actions,12);actions.addView(ui.action(L10n.tr("暂不同意"),SECONDARY,this::dismiss),ui.size(-1,44));
|
||||||
setContentView(sheet);Window window=getWindow();if(window!=null){window.setBackgroundDrawableResource(android.R.color.transparent);window.setDimAmount(.22f);}
|
setContentView(sheet);Window window=getWindow();if(window!=null){window.setBackgroundDrawableResource(android.R.color.transparent);window.setDimAmount(.22f);}
|
||||||
}
|
}
|
||||||
@Override public void show(){
|
@Override public void show(){
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ final class PrivacyPolicyDocument {
|
|||||||
final boolean available;
|
final boolean available;
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
PrivacyPolicyDocument(Context context){this(load(context));}
|
PrivacyPolicyDocument(Context context){this(load(context),L10n.isChinese());}
|
||||||
PrivacyPolicyDocument(String document){
|
PrivacyPolicyDocument(String document){this(document,true);}
|
||||||
|
private PrivacyPolicyDocument(String document,boolean chinese){
|
||||||
available=document!=null&&!document.trim().isEmpty();
|
available=document!=null&&!document.trim().isEmpty();
|
||||||
String value=available?document:"隐私政策暂时无法读取,请拒绝联网并重新安装应用。";
|
String value=available?document:L10n.tr("隐私政策暂时无法读取,请拒绝联网并重新安装应用。");
|
||||||
int start=value.indexOf("## 中文\n"),end=start<0?-1:value.indexOf("## English",start);
|
int start=value.indexOf("## 中文\n"),end=start<0?-1:value.indexOf("## English",start);
|
||||||
text=start>=0&&end>start?value.substring(start+"## 中文\n".length(),end).trim():value;
|
text=chinese&&start>=0&&end>start?value.substring(start+"## 中文\n".length(),end).trim():
|
||||||
|
!chinese&&end>=0?value.substring(end+"## English".length()).trim():value;
|
||||||
}
|
}
|
||||||
private static String load(Context context){
|
private static String load(Context context){
|
||||||
try{return LicenseCatalog.read(context,"PRIVACY.md");}catch(IOException error){return null;}
|
try{return LicenseCatalog.read(context,"PRIVACY.md");}catch(IOException error){return null;}
|
||||||
@@ -28,7 +30,7 @@ final class PrivacyPolicyDocument {
|
|||||||
LinearLayout content=ui.column();content.setPadding(ui.dp(20),ui.dp(20),ui.dp(20),ui.dp(20));scroll.addView(content);
|
LinearLayout content=ui.column();content.setPadding(ui.dp(20),ui.dp(20),ui.dp(20),ui.dp(20));scroll.addView(content);
|
||||||
for(String paragraph:text.split("\n\n")){
|
for(String paragraph:text.split("\n\n")){
|
||||||
paragraph=paragraph.trim();if(paragraph.isEmpty()||paragraph.equals("---"))continue;
|
paragraph=paragraph.trim();if(paragraph.isEmpty()||paragraph.equals("---"))continue;
|
||||||
boolean heading=paragraph.startsWith("#"),date=paragraph.startsWith("更新日期:");
|
boolean heading=paragraph.startsWith("#"),date=paragraph.startsWith("更新日期:")||paragraph.startsWith("Last updated:");
|
||||||
String value=heading?paragraph.replaceFirst("^#+\\s*",""):paragraph.replace("**","");
|
String value=heading?paragraph.replaceFirst("^#+\\s*",""):paragraph.replace("**","");
|
||||||
TextView label=ui.text(value,date?12:16,heading);label.setTextIsSelectable(true);label.setLineSpacing(ui.dp(4),1);
|
TextView label=ui.text(value,date?12:16,heading);label.setTextIsSelectable(true);label.setLineSpacing(ui.dp(4),1);
|
||||||
if(date)label.setTextColor(SECONDARY);
|
if(date)label.setTextColor(SECONDARY);
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ final class RemoteKeyboard extends LinearLayout {
|
|||||||
header.addView(editor,new FrameLayout.LayoutParams(1,1,Gravity.BOTTOM));
|
header.addView(editor,new FrameLayout.LayoutParams(1,1,Gravity.BOTTOM));
|
||||||
toolbar=ui.row();toolbar.setGravity(Gravity.CENTER_VERTICAL);toolbar.setPadding(ui.dp(6),0,ui.dp(6),0);
|
toolbar=ui.row();toolbar.setGravity(Gravity.CENTER_VERTICAL);toolbar.setPadding(ui.dp(6),0,ui.dp(6),0);
|
||||||
header.addView(toolbar,new FrameLayout.LayoutParams(-1,-1));
|
header.addView(toolbar,new FrameLayout.LayoutParams(-1,-1));
|
||||||
switchButton=button("电脑键盘",this::switchKeyboard);switchButton.setPadding(ui.dp(8),0,ui.dp(8),0);
|
switchButton=button(L10n.tr("电脑键盘"),this::switchKeyboard);switchButton.setPadding(ui.dp(8),0,ui.dp(8),0);
|
||||||
toolbar.addView(switchButton,new LayoutParams(-2,-1));
|
toolbar.addView(switchButton,new LayoutParams(-2,-1));
|
||||||
LinearLayout pages=ui.row();pages.setGravity(Gravity.CENTER);toolbar.addView(pages,new LayoutParams(0,-1,1));
|
LinearLayout pages=ui.row();pages.setGravity(Gravity.CENTER);toolbar.addView(pages,new LayoutParams(0,-1,1));
|
||||||
lettersButton=button("字母数字",()->page(false));functionsButton=button("符号功能",()->page(true));
|
lettersButton=button(L10n.tr("字母数字"),()->page(false));functionsButton=button(L10n.tr("符号功能"),()->page(true));
|
||||||
pages.addView(lettersButton,new LayoutParams(-2,ui.dp(32)));pages.addView(functionsButton,new LayoutParams(-2,ui.dp(32)));
|
pages.addView(lettersButton,new LayoutParams(-2,ui.dp(32)));pages.addView(functionsButton,new LayoutParams(-2,ui.dp(32)));
|
||||||
FrameLayout hide=new FrameLayout(context);hide.setContentDescription("收起键盘");hide.setClickable(true);hide.setFocusable(true);
|
FrameLayout hide=new FrameLayout(context);hide.setContentDescription(L10n.tr("收起键盘"));hide.setClickable(true);hide.setFocusable(true);
|
||||||
hide.addView(ui.icon("keyboard_hide",foreground()),new FrameLayout.LayoutParams(ui.dp(24),ui.dp(24),Gravity.CENTER));
|
hide.addView(ui.icon("keyboard_hide",foreground()),new FrameLayout.LayoutParams(ui.dp(24),ui.dp(24),Gravity.CENTER));
|
||||||
hide.setOnClickListener(v->{feedback(v);close();});toolbar.addView(hide,new LayoutParams(ui.dp(44),ui.dp(44)));
|
hide.setOnClickListener(v->{feedback(v);close();});toolbar.addView(hide,new LayoutParams(ui.dp(44),ui.dp(44)));
|
||||||
keys=ui.column();keys.setPadding(ui.dp(3),ui.dp(4),ui.dp(3),ui.dp(4));addView(keys,new LayoutParams(-1,0,1));
|
keys=ui.column();keys.setPadding(ui.dp(3),ui.dp(4),ui.dp(3),ui.dp(4));addView(keys,new LayoutParams(-1,0,1));
|
||||||
@@ -157,7 +157,7 @@ final class RemoteKeyboard extends LinearLayout {
|
|||||||
}
|
}
|
||||||
private void render(){
|
private void render(){
|
||||||
setBackgroundColor(dark()?0xFF202124:0xFFE8EAED);
|
setBackgroundColor(dark()?0xFF202124:0xFFE8EAED);
|
||||||
switchButton.setText(computer?"系统键盘":"电脑键盘");switchButton.setTextColor(foreground());
|
switchButton.setText(computer?L10n.tr("系统键盘"):L10n.tr("电脑键盘"));switchButton.setTextColor(foreground());
|
||||||
lettersButton.setVisibility(computer?VISIBLE:GONE);functionsButton.setVisibility(computer?VISIBLE:GONE);
|
lettersButton.setVisibility(computer?VISIBLE:GONE);functionsButton.setVisibility(computer?VISIBLE:GONE);
|
||||||
for(TextView view:new TextView[]{lettersButton,functionsButton}){
|
for(TextView view:new TextView[]{lettersButton,functionsButton}){
|
||||||
boolean selected=(view==functionsButton)==functions;view.setSelected(selected);
|
boolean selected=(view==functionsButton)==functions;view.setSelected(selected);
|
||||||
@@ -185,12 +185,12 @@ final class RemoteKeyboard extends LinearLayout {
|
|||||||
LinearLayout line=ui.row();line.setGravity(Gravity.CENTER);
|
LinearLayout line=ui.row();line.setGravity(Gravity.CENTER);
|
||||||
for(String value:values){
|
for(String value:values){
|
||||||
boolean shift=modifiers.contains(16);String shown=shift&&value.length()==1?shifted(value):value;
|
boolean shift=modifiers.contains(16);String shown=shift&&value.length()==1?shifted(value):value;
|
||||||
TextView key=button(shown,()->key(value));key.setPadding(0,0,0,0);
|
TextView key=button(L10n.tr(shown),()->key(value));key.setPadding(0,0,0,0);
|
||||||
key.setTextSize(value.length()>1?12:18);key.setSingleLine(true);
|
key.setTextSize(value.length()>1?12:18);key.setSingleLine(true);
|
||||||
int modifier=modifier(value);boolean selected=modifier!=0&&modifiers.contains(modifier);key.setSelected(selected);
|
int modifier=modifier(value);boolean selected=modifier!=0&&modifiers.contains(modifier);key.setSelected(selected);
|
||||||
int color=selected?(dark()?0xFF596D8C:0xFFA8C7FA):value.length()>1||value.equals("⇧")||value.equals("⌫")?(dark()?0xFF30343B:0xFFD0D5DC):(dark()?0xFF3C4043:Color.WHITE);
|
int color=selected?(dark()?0xFF596D8C:0xFFA8C7FA):value.length()>1||value.equals("⇧")||value.equals("⌫")?(dark()?0xFF30343B:0xFFD0D5DC):(dark()?0xFF3C4043:Color.WHITE);
|
||||||
StateListDrawable background=new StateListDrawable();background.addState(new int[]{android.R.attr.state_pressed},ui.background(dark()?0xFF68717D:0xFFBDC9D8,7));background.addState(new int[]{},ui.background(color,7));key.setBackground(background);
|
StateListDrawable background=new StateListDrawable();background.addState(new int[]{android.R.attr.state_pressed},ui.background(dark()?0xFF68717D:0xFFBDC9D8,7));background.addState(new int[]{},ui.background(color,7));key.setBackground(background);
|
||||||
if(value.equals("⇧"))key.setContentDescription("Shift");else if(value.equals("⌫"))key.setContentDescription("退格");
|
if(value.equals("⇧"))key.setContentDescription("Shift");else if(value.equals("⌫"))key.setContentDescription(L10n.tr("退格"));
|
||||||
// Both penultimate rows allocate exactly 1/8 to Shift and Backspace.
|
// Both penultimate rows allocate exactly 1/8 to Shift and Backspace.
|
||||||
boolean penultimate=values[0].equals("⇧");float weight=value.equals("空格")?4:1;
|
boolean penultimate=values[0].equals("⇧");float weight=value.equals("空格")?4:1;
|
||||||
if(penultimate)weight=value.equals("⇧")||value.equals("⌫")?1:(6f/(values.length-2));
|
if(penultimate)weight=value.equals("⇧")||value.equals("⌫")?1:(6f/(values.length-2));
|
||||||
|
|||||||
@@ -12,31 +12,32 @@ final class RemoteNetworkStatistics {
|
|||||||
}
|
}
|
||||||
static final class Report {
|
static final class Report {
|
||||||
final Traffic[] traffic=new Traffic[4];final int mode;final boolean srtp;final double rtt;
|
final Traffic[] traffic=new Traffic[4];final int mode;final boolean srtp;final double rtt;
|
||||||
|
final String decoder;
|
||||||
Report(JSONObject json){
|
Report(JSONObject json){
|
||||||
String[] keys={"video","audio","data","total"};
|
String[] keys={"video","audio","data","total"};
|
||||||
for(int i=0;i<keys.length;i++){JSONObject item=json.optJSONObject(keys[i]);traffic[i]=item==null?null:new Traffic(item);}
|
for(int i=0;i<keys.length;i++){JSONObject item=json.optJSONObject(keys[i]);traffic[i]=item==null?null:new Traffic(item);}
|
||||||
mode=json.optInt("mode",2);srtp=json.optBoolean("srtp");rtt=json.optDouble("rtt",Double.NaN);
|
mode=json.optInt("mode",2);srtp=json.optBoolean("srtp");rtt=json.optDouble("rtt",Double.NaN);
|
||||||
|
decoder=json.optString("decoder","");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static final class Snapshot {
|
static final class Snapshot {
|
||||||
final Report report;final int fps,width,height;final double latency,rtt;
|
final Report report;final int fps,width,height;final double latency,rtt;
|
||||||
Snapshot(Report report,int fps,int width,int height,double latency,double rtt){this.report=report;this.fps=fps;this.width=width;this.height=height;this.latency=latency;this.rtt=rtt;}
|
final String remoteEncoder,localDecoder;
|
||||||
String mode(){return report==null?"—":report.mode==0?"P2P 直连":report.mode==1?"TURN 中继":"—";}
|
Snapshot(Report report,int fps,int width,int height,double latency,double rtt,String remoteEncoder,String localDecoder){this.report=report;this.fps=fps;this.width=width;this.height=height;this.latency=latency;this.rtt=rtt;this.remoteEncoder=remoteEncoder;this.localDecoder=localDecoder;}
|
||||||
|
String mode(){return report==null?"—":report.mode==0?L10n.tr("P2P 直连"):report.mode==1?L10n.tr("TURN 中继"):"—";}
|
||||||
String resolution(){return width>0&&height>0?width+" × "+height:"—";}
|
String resolution(){return width>0&&height>0?width+" × "+height:"—";}
|
||||||
String summary(){
|
|
||||||
String value=mode()+(width>0&&height>0?" · "+width+"×"+height:"")+" · "+bitrate(report==null||report.traffic[3]==null?-1:report.traffic[3].inbound);
|
|
||||||
if(report!=null&&report.traffic[0]!=null&&report.traffic[0].loss>0)value+=" · 丢包 "+loss(report.traffic[0].loss);return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
private static final class Frame {
|
private static final class Frame {
|
||||||
final double time,latency;Frame(double time,double latency){this.time=time;this.latency=latency;}
|
final double time,latency;Frame(double time,double latency){this.time=time;this.latency=latency;}
|
||||||
}
|
}
|
||||||
private Report report;
|
private Report report;
|
||||||
|
private String remoteEncoder="";
|
||||||
private double reportTime,rttTime,averageRtt=Double.NaN;
|
private double reportTime,rttTime,averageRtt=Double.NaN;
|
||||||
private long lastFrameId;
|
private long lastFrameId;
|
||||||
private int width,height;
|
private int width,height;
|
||||||
private final ArrayDeque<Frame> frames=new ArrayDeque<>();
|
private final ArrayDeque<Frame> frames=new ArrayDeque<>();
|
||||||
static double now(){return System.nanoTime()/1_000_000_000.0;}
|
static double now(){return System.nanoTime()/1_000_000_000.0;}
|
||||||
|
synchronized void receiveRemoteEncoder(String encoder){remoteEncoder=encoder;}
|
||||||
synchronized void receive(Report value,double now){
|
synchronized void receive(Report value,double now){
|
||||||
report=value;reportTime=now;
|
report=value;reportTime=now;
|
||||||
if(!Double.isFinite(value.rtt)||value.rtt<0||value.rtt>2000)return;
|
if(!Double.isFinite(value.rtt)||value.rtt<0||value.rtt>2000)return;
|
||||||
@@ -53,7 +54,7 @@ final class RemoteNetworkStatistics {
|
|||||||
synchronized Snapshot snapshot(double now){
|
synchronized Snapshot snapshot(double now){
|
||||||
prune(now);double total=0;int count=0;
|
prune(now);double total=0;int count=0;
|
||||||
for(Frame frame:frames)if(Double.isFinite(frame.latency)){total+=frame.latency;count++;}
|
for(Frame frame:frames)if(Double.isFinite(frame.latency)){total+=frame.latency;count++;}
|
||||||
return new Snapshot(now-reportTime<3?report:null,frames.size(),width,height,count==0?Double.NaN:total/count,now-rttTime<3?averageRtt:Double.NaN);
|
return new Snapshot(now-reportTime<3?report:null,frames.size(),width,height,count==0?Double.NaN:total/count,now-rttTime<3?averageRtt:Double.NaN,remoteEncoder,report==null?"":report.decoder);
|
||||||
}
|
}
|
||||||
private void prune(double now){while(!frames.isEmpty()&&now-frames.peekFirst().time>=1)frames.removeFirst();}
|
private void prune(double now){while(!frames.isEmpty()&&now-frames.peekFirst().time>=1)frames.removeFirst();}
|
||||||
static String bitrate(long value){return value<0?"—":value>=1_000_000?String.format(Locale.ROOT,"%.1f Mbps",value/1_000_000.0):value>=1000?String.format(Locale.ROOT,"%.0f Kbps",value/1000.0):value+" bps";}
|
static String bitrate(long value){return value<0?"—":value>=1_000_000?String.format(Locale.ROOT,"%.1f Mbps",value/1_000_000.0):value>=1000?String.format(Locale.ROOT,"%.0f Kbps",value/1000.0):value+" bps";}
|
||||||
|
|||||||
@@ -2,54 +2,27 @@ package cn.crossdesk.mobile;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.view.OrientationEventListener;
|
|
||||||
|
|
||||||
/** Holds the initial landscape until the phone itself has been turned sideways. */
|
/** Applies the selected orientation policy for the whole remote session. */
|
||||||
final class RemoteOrientation {
|
final class RemoteOrientation {
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
private final OrientationEventListener sensor;
|
private boolean autoLandscape;
|
||||||
private boolean waitingForLandscape, resumed;
|
|
||||||
|
|
||||||
RemoteOrientation(Activity activity) {
|
RemoteOrientation(Activity activity) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
sensor = new OrientationEventListener(activity) {
|
|
||||||
@Override public void onOrientationChanged(int degrees) {
|
|
||||||
if (!resumed || !waitingForLandscape || activity.isInPictureInPictureMode()) return;
|
|
||||||
// Use physical orientation, never the window rotated by our own request.
|
|
||||||
// Ignore flat/unknown poses and keep a margin away from the diagonal.
|
|
||||||
if ((degrees >= 60 && degrees <= 120) || (degrees >= 240 && degrees <= 300)) {
|
|
||||||
followDevice();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void begin(boolean autoLandscape) {
|
void begin(boolean autoLandscape, boolean fullScreenPhone) {
|
||||||
sensor.disable();
|
this.autoLandscape = autoLandscape;
|
||||||
waitingForLandscape = autoLandscape && sensor.canDetectOrientation();
|
update(fullScreenPhone);
|
||||||
apply();
|
|
||||||
if (resumed && waitingForLandscape) sensor.enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean waitingForLandscape() { return waitingForLandscape; }
|
boolean autoLandscape() { return autoLandscape; }
|
||||||
|
|
||||||
void resume() {
|
void end() { autoLandscape = false; }
|
||||||
resumed = true;
|
|
||||||
if (waitingForLandscape && !activity.isInPictureInPictureMode()) sensor.enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void pause() { resumed = false; sensor.disable(); }
|
void update(boolean fullScreenPhone) {
|
||||||
|
int orientation = autoLandscape && fullScreenPhone
|
||||||
void end() { waitingForLandscape = false; sensor.disable(); }
|
|
||||||
|
|
||||||
void followDevice() {
|
|
||||||
waitingForLandscape = false;
|
|
||||||
sensor.disable();
|
|
||||||
apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void apply() {
|
|
||||||
int orientation = waitingForLandscape
|
|
||||||
? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||||
: ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
|
: ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
|
||||||
if (activity.getRequestedOrientation() != orientation) activity.setRequestedOrientation(orientation);
|
if (activity.getRequestedOrientation() != orientation) activity.setRequestedOrientation(orientation);
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ final class RemotePreviewStore {
|
|||||||
new AtomicFile(new File(directory,key+".jpg")).delete();
|
new AtomicFile(new File(directory,key+".jpg")).delete();
|
||||||
synchronized(this){
|
synchronized(this){
|
||||||
boolean saved=index.edit().putStringSet("files",new HashSet<>(files)).commit();
|
boolean saved=index.edit().putStringSet("files",new HashSet<>(files)).commit();
|
||||||
if(!saved||new File(directory,key+".jpg").exists())cleanupError="部分预览图未能清除,请重试。";
|
if(!saved||new File(directory,key+".jpg").exists())cleanupError=L10n.tr("部分预览图未能清除,请重试。");
|
||||||
}
|
}
|
||||||
changed();
|
changed();
|
||||||
});changed();
|
});changed();
|
||||||
@@ -139,7 +139,7 @@ final class RemotePreviewStore {
|
|||||||
try{deleteTree(directory);}catch(IOException error){success=false;}
|
try{deleteTree(directory);}catch(IOException error){success=false;}
|
||||||
synchronized(this){
|
synchronized(this){
|
||||||
if(epoch!=generation)return;
|
if(epoch!=generation)return;
|
||||||
cleaning=false;cleanupError=success?"":"部分预览图未能清除,请重试。";
|
cleaning=false;cleanupError=success?"":L10n.tr("部分预览图未能清除,请重试。");
|
||||||
}
|
}
|
||||||
changed();
|
changed();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package cn.crossdesk.mobile;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/** Host-confirmed privacy state; all access belongs to the main thread. */
|
||||||
|
final class RemotePrivacyScreen {
|
||||||
|
boolean desktopAvailable = true;
|
||||||
|
boolean received, supported, inputBlockSupported, overlay, inputBlocked, remotePaused, pending;
|
||||||
|
boolean unlockNotice;
|
||||||
|
long localUnlockRevision;
|
||||||
|
int state;
|
||||||
|
long revision, requestRevision, requestId, receivedAt, requestedAt;
|
||||||
|
String reason = "", failure = "";
|
||||||
|
|
||||||
|
void setInteractiveStage(String stage) {
|
||||||
|
desktopAvailable = !"lock-screen".equals(stage) &&
|
||||||
|
!"credential-ui".equals(stage) && !"secure-desktop".equals(stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive(JSONObject message, long now) {
|
||||||
|
try {
|
||||||
|
if (message.getInt("type") != 10) return;
|
||||||
|
JSONObject value = message.getJSONObject("privacy_status");
|
||||||
|
int next = value.getInt("state");
|
||||||
|
long rev = value.getLong("revision");
|
||||||
|
if (next < 0 || next > 5 || rev < 0 || rev > 0xffffffffL ||
|
||||||
|
received && (int)(rev - revision) < 0) return;
|
||||||
|
boolean support = value.getBoolean("supported"), cover = value.getBoolean("overlay_active");
|
||||||
|
boolean paused = value.getBoolean("remote_paused");
|
||||||
|
if (next == 3 && (!support || !cover || paused)) return;
|
||||||
|
boolean blockSupport = value.getBoolean("input_block_supported"), blocked = value.getBoolean("input_blocked");
|
||||||
|
String detail = value.getString("reason");
|
||||||
|
long unlock = value.has("local_unlock_revision") ? value.getLong("local_unlock_revision") : 0;
|
||||||
|
if (unlock < 0 || unlock > 0xffffffffL) return;
|
||||||
|
if (received && (int)(unlock - localUnlockRevision) > 0) unlockNotice = true;
|
||||||
|
if (next == 3) unlockNotice = false;
|
||||||
|
localUnlockRevision = unlock;
|
||||||
|
state = next; revision = rev; supported = support; overlay = cover; remotePaused = paused;
|
||||||
|
inputBlockSupported = blockSupport; inputBlocked = blocked; reason = detail;
|
||||||
|
received = true; receivedAt = now; failure = "";
|
||||||
|
if (pending && (int)(revision - requestRevision) > 0 && state != 2 && state != 4) pending = false;
|
||||||
|
} catch (org.json.JSONException ignored) { }
|
||||||
|
}
|
||||||
|
boolean active() { return overlay || remotePaused || state == 2 || state == 3 || state == 4; }
|
||||||
|
boolean fresh(long now) { return received && now - receivedAt <= 5000; }
|
||||||
|
boolean enabled(long now) { return fresh(now) && state == 3 && overlay && !remotePaused; }
|
||||||
|
String buttonTitle(long now) {
|
||||||
|
if (enabled(now)) return L10n.tr("隐私屏已开启");
|
||||||
|
if (fresh(now) && supported && state == 1 && !active()) return L10n.tr("隐私屏已关闭");
|
||||||
|
return L10n.tr("隐私屏");
|
||||||
|
}
|
||||||
|
String feedback(long now) {
|
||||||
|
if (!desktopAvailable) return "";
|
||||||
|
if (failure.isEmpty() && (pending || fresh(now) && (state == 2 || state == 4))) return "";
|
||||||
|
if (failure.isEmpty() && !pending && fresh(now) && supported &&
|
||||||
|
(enabled(now) || state == 1 && !active())) return "";
|
||||||
|
String detail = detail(now);
|
||||||
|
return status(now) + (detail.isEmpty() ? "" : ":" + detail);
|
||||||
|
}
|
||||||
|
boolean canToggle(long now) {
|
||||||
|
return desktopAvailable && !pending && fresh(now) && state != 4 && (active() || supported && state == 1);
|
||||||
|
}
|
||||||
|
boolean canLock(long now) {
|
||||||
|
return desktopAvailable && !pending && fresh(now) && supported;
|
||||||
|
}
|
||||||
|
long begin(long now) { return begin(now, false); }
|
||||||
|
long begin(long now, boolean lock) {
|
||||||
|
tick(now);
|
||||||
|
if (!(lock ? canLock(now) : canToggle(now))) return -1;
|
||||||
|
pending = true; requestedAt = now; requestRevision = revision; failure = "";
|
||||||
|
return ++requestId;
|
||||||
|
}
|
||||||
|
void failed(long id) {
|
||||||
|
if (pending && requestId == id) { pending = false; failure = L10n.tr("指令发送失败,请重试"); }
|
||||||
|
}
|
||||||
|
void tick(long now) {
|
||||||
|
if (pending && now - requestedAt >= 15000) {
|
||||||
|
pending = false; failure = L10n.tr("请求超时,尚未确认电脑的隐私屏状态");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String status(long now) {
|
||||||
|
if (!failure.isEmpty()) return failure;
|
||||||
|
if (!received) return L10n.tr("等待电脑返回隐私屏状态;旧版电脑端可能不支持");
|
||||||
|
if (!fresh(now)) return L10n.tr("隐私屏状态已过期,等待电脑确认");
|
||||||
|
if (state == 2) return L10n.tr("正在开启隐私屏…");
|
||||||
|
if (state == 4) return L10n.tr("正在关闭隐私屏…");
|
||||||
|
if (pending) return L10n.tr("指令已发送,等待电脑确认…");
|
||||||
|
if (state == 5) return L10n.tr("隐私屏异常");
|
||||||
|
if (!supported || state == 0) return L10n.tr("隐私屏暂不可用");
|
||||||
|
if (state == 3 && overlay && !remotePaused)
|
||||||
|
return L10n.tr("隐私屏已开启");
|
||||||
|
return state == 1 && !active() ? L10n.tr("隐私屏已关闭") : L10n.tr("隐私屏状态待确认");
|
||||||
|
}
|
||||||
|
String detail(long now) {
|
||||||
|
return fresh(now) && (state == 0 || state == 5 || !supported) ? reason : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,7 +51,7 @@ public final class RemoteSessionService extends Service {
|
|||||||
@Override public void onCreate() {
|
@Override public void onCreate() {
|
||||||
super.onCreate(); running = this;
|
super.onCreate(); running = this;
|
||||||
getSystemService(NotificationManager.class).createNotificationChannel(
|
getSystemService(NotificationManager.class).createNotificationChannel(
|
||||||
new NotificationChannel(CHANNEL, "远程连接", NotificationManager.IMPORTANCE_LOW));
|
new NotificationChannel(CHANNEL, L10n.tr("远程连接"), NotificationManager.IMPORTANCE_LOW));
|
||||||
wakeLock = getSystemService(PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CrossDesk:RemoteSession");
|
wakeLock = getSystemService(PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CrossDesk:RemoteSession");
|
||||||
wakeLock.setReferenceCounted(false);
|
wakeLock.setReferenceCounted(false);
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ public final class RemoteSessionService extends Service {
|
|||||||
if (value == null || value.ended || value.session.isClosed()) { stopSelf(startId); return START_NOT_STICKY; }
|
if (value == null || value.ended || value.session.isClosed()) { stopSelf(startId); return START_NOT_STICKY; }
|
||||||
if (intent == null || !value.token.equals(intent.getStringExtra("session"))) return START_NOT_STICKY;
|
if (intent == null || !value.token.equals(intent.getStringExtra("session"))) return START_NOT_STICKY;
|
||||||
owned = value;
|
owned = value;
|
||||||
if (STOP.equals(intent.getAction())) { value.finish("已从通知断开远程连接"); return START_NOT_STICKY; }
|
if (STOP.equals(intent.getAction())) { value.finish(L10n.tr("已从通知断开远程连接")); return START_NOT_STICKY; }
|
||||||
try {
|
try {
|
||||||
Notification notification = notification();
|
Notification notification = notification();
|
||||||
if (Build.VERSION.SDK_INT >= 34) startForeground(NOTIFICATION, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
if (Build.VERSION.SDK_INT >= 34) startForeground(NOTIFICATION, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||||
@@ -68,7 +68,7 @@ public final class RemoteSessionService extends Service {
|
|||||||
update();
|
update();
|
||||||
} catch (RuntimeException error) {
|
} catch (RuntimeException error) {
|
||||||
Log.w("CrossDesk", "Background session foreground promotion failed", error);
|
Log.w("CrossDesk", "Background session foreground promotion failed", error);
|
||||||
value.finish("后台保活未能启动,请保持应用在前台后重新连接");
|
value.finish(L10n.tr("后台保活未能启动,请保持应用在前台后重新连接"));
|
||||||
}
|
}
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
@@ -80,12 +80,12 @@ public final class RemoteSessionService extends Service {
|
|||||||
.setData(android.net.Uri.parse("crossdesk-session:" + owned.token)).putExtra("session", owned.token);
|
.setData(android.net.Uri.parse("crossdesk-session:" + owned.token)).putExtra("session", owned.token);
|
||||||
PendingIntent stop = PendingIntent.getService(this, 1, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
PendingIntent stop = PendingIntent.getService(this, 1, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
return new Notification.Builder(this, CHANNEL).setSmallIcon(R.drawable.ic_remote_session)
|
return new Notification.Builder(this, CHANNEL).setSmallIcon(R.drawable.ic_remote_session)
|
||||||
.setContentTitle("CrossDesk 远程连接")
|
.setContentTitle(L10n.tr("CrossDesk 远程连接"))
|
||||||
.setContentText(owned.background ? "后台保持连接,点按返回远程桌面" : "已启用后台保活,切换应用可继续保持连接")
|
.setContentText(owned.background ? L10n.tr("后台保持连接,点按返回远程桌面") : L10n.tr("已启用后台保活,切换应用可继续保持连接"))
|
||||||
.setContentIntent(open).setOngoing(true).setOnlyAlertOnce(true).setShowWhen(false)
|
.setContentIntent(open).setOngoing(true).setOnlyAlertOnce(true).setShowWhen(false)
|
||||||
.setCategory(Notification.CATEGORY_SERVICE).setVisibility(Notification.VISIBILITY_PRIVATE)
|
.setCategory(Notification.CATEGORY_SERVICE).setVisibility(Notification.VISIBILITY_PRIVATE)
|
||||||
.addAction(new Notification.Action.Builder(null, "返回", open).build())
|
.addAction(new Notification.Action.Builder(null, L10n.tr("返回"), open).build())
|
||||||
.addAction(new Notification.Action.Builder(null, "断开", stop).build()).build();
|
.addAction(new Notification.Action.Builder(null, L10n.tr("断开"), stop).build()).build();
|
||||||
}
|
}
|
||||||
@android.annotation.SuppressLint("WakelockTimeout") // Released on foreground return, disconnect, task removal and service destruction.
|
@android.annotation.SuppressLint("WakelockTimeout") // Released on foreground return, disconnect, task removal and service destruction.
|
||||||
private void update() {
|
private void update() {
|
||||||
@@ -96,14 +96,14 @@ public final class RemoteSessionService extends Service {
|
|||||||
}
|
}
|
||||||
private void releaseWakeLock() { if (wakeLock != null && wakeLock.isHeld()) wakeLock.release(); }
|
private void releaseWakeLock() { if (wakeLock != null && wakeLock.isHeld()) wakeLock.release(); }
|
||||||
@Override public void onTaskRemoved(Intent rootIntent) {
|
@Override public void onTaskRemoved(Intent rootIntent) {
|
||||||
if (current == owned && owned != null) owned.finish("远程任务已关闭");
|
if (current == owned && owned != null) owned.finish(L10n.tr("远程任务已关闭"));
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
@Override public void onDestroy() {
|
@Override public void onDestroy() {
|
||||||
if (running == this) running = null;
|
if (running == this) running = null;
|
||||||
releaseWakeLock(); stopForeground(STOP_FOREGROUND_REMOVE);
|
releaseWakeLock(); stopForeground(STOP_FOREGROUND_REMOVE);
|
||||||
BackgroundSession value = owned;
|
BackgroundSession value = owned;
|
||||||
if (current == value && value != null) { current = null; value.finish("后台连接服务已停止,请重新连接"); }
|
if (current == value && value != null) { current = null; value.finish(L10n.tr("后台连接服务已停止,请重新连接")); }
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import org.json.JSONObject;
|
|||||||
|
|
||||||
/** Same request/acknowledgement and rollback rules as the iOS controller. */
|
/** Same request/acknowledgement and rollback rules as the iOS controller. */
|
||||||
final class RemoteVideoSettings {
|
final class RemoteVideoSettings {
|
||||||
static final String[] PREFERENCES={"帧率优先","画质优先","平衡"};
|
static String[] preferences(){return new String[]{L10n.tr("帧率优先"),L10n.tr("画质优先"),L10n.tr("平衡")};}
|
||||||
static final String[] DETAILS={
|
static String[] details(){return new String[]{
|
||||||
"优先保持画面流畅,带宽或编码压力较大时会更积极地降低分辨率。",
|
L10n.tr("优先保持画面流畅,带宽或编码压力较大时会更积极地降低分辨率。"),
|
||||||
"始终保持较高分辨率,带宽不足时通过降低帧率来保证画面清晰度。",
|
L10n.tr("始终保持较高分辨率,带宽不足时通过降低帧率来保证画面清晰度。"),
|
||||||
"兼顾清晰度和流畅度,只在持续压力下逐步降低分辨率。"};
|
L10n.tr("兼顾清晰度和流畅度,只在持续压力下逐步降低分辨率。")};}
|
||||||
static final class Values {
|
static final class Values {
|
||||||
final int quality,frameRate,preference;
|
final int quality,frameRate,preference;
|
||||||
Values(int quality,int frameRate,int preference){this.quality=quality;this.frameRate=frameRate;this.preference=preference;}
|
Values(int quality,int frameRate,int preference){this.quality=quality;this.frameRate=frameRate;this.preference=preference;}
|
||||||
@@ -54,5 +54,5 @@ final class RemoteVideoSettings {
|
|||||||
}catch(org.json.JSONException ignored){ }
|
}catch(org.json.JSONException ignored){ }
|
||||||
}
|
}
|
||||||
void expire(long expiredId){if(pending&&expiredId==requestId){selection=applied;pending=false;failed=true;}}
|
void expire(long expiredId){if(pending&&expiredId==requestId){selection=applied;pending=false;failed=true;}}
|
||||||
String feedback(){return !supported?"连接建立后可调整,需被控端支持。":failed?"调整失败,请重试。":"";}
|
String feedback(){return !supported?L10n.tr("连接建立后可调整,需被控端支持。"):failed?L10n.tr("调整失败,请重试。"):"";}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ final class RemoteVideoView extends FrameLayout implements SurfaceHolder.Callbac
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
setContentDescription("远程桌面:轻触点击,拖动移动,长按右键,双指捏合缩放,放大后双指拖动画面,原始比例下双指上下滑动滚动");
|
setContentDescription(L10n.tr("远程桌面:轻触点击,拖动移动,长按右键,双指捏合缩放,放大后双指拖动画面,原始比例下双指上下滑动滚动"));
|
||||||
}
|
}
|
||||||
void videoSize(int width, int height) {
|
void videoSize(int width, int height) {
|
||||||
if (width <= 0 || height <= 0) return;
|
if (width <= 0 || height <= 0) return;
|
||||||
@@ -244,7 +244,7 @@ final class RemoteVideoView extends FrameLayout implements SurfaceHolder.Callbac
|
|||||||
private final Path arrow = new Path();
|
private final Path arrow = new Path();
|
||||||
private final float arrowWidth = 12, arrowHeight = 15;
|
private final float arrowWidth = 12, arrowHeight = 15;
|
||||||
Cursor(Context context) {
|
Cursor(Context context) {
|
||||||
super(context); setClickable(true); setContentDescription("远程桌面触控区域");
|
super(context); setClickable(true); setContentDescription(L10n.tr("远程桌面触控区域"));
|
||||||
// Match the iOS RemoteCursorArrowShape in density-independent units.
|
// Match the iOS RemoteCursorArrowShape in density-independent units.
|
||||||
arrow.moveTo(arrowWidth*.08f,arrowHeight*.04f);
|
arrow.moveTo(arrowWidth*.08f,arrowHeight*.04f);
|
||||||
arrow.lineTo(arrowWidth*.08f,arrowHeight*.78f);
|
arrow.lineTo(arrowWidth*.08f,arrowHeight*.78f);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ final class SegmentedControl extends LinearLayout {
|
|||||||
setGravity(Gravity.CENTER_VERTICAL);setPadding(inset,inset,inset,inset);setBackground(ui.background(0xFFE9E9EF,MobileUi.SEGMENT_HEIGHT/2));paint.setColor(Color.WHITE);
|
setGravity(Gravity.CENTER_VERTICAL);setPadding(inset,inset,inset,inset);setBackground(ui.background(0xFFE9E9EF,MobileUi.SEGMENT_HEIGHT/2));paint.setColor(Color.WHITE);
|
||||||
for(int i=0;i<titles.length;i++){
|
for(int i=0;i<titles.length;i++){
|
||||||
final int index=i;TextView choice=ui.action(titles[i],MobileUi.INK,()->select(index));
|
final int index=i;TextView choice=ui.action(titles[i],MobileUi.INK,()->select(index));
|
||||||
choice.setTextSize(13);choice.setMinHeight(height);choice.setSelected(i==selected);choice.setEnabled(enabled);choice.setAlpha(enabled?1:.45f);
|
choice.setTextSize(13);choice.setSingleLine();choice.setAutoSizeTextTypeUniformWithConfiguration(9,13,1,android.util.TypedValue.COMPLEX_UNIT_SP);choice.setMinHeight(height);choice.setSelected(i==selected);choice.setEnabled(enabled);choice.setAlpha(enabled?1:.45f);
|
||||||
addView(choice,new LayoutParams(0,height,1));
|
addView(choice,new LayoutParams(0,height,1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ final class SessionControls extends FrameLayout {
|
|||||||
default boolean textEdit(int deletions,String value){return false;}
|
default boolean textEdit(int deletions,String value){return false;}
|
||||||
void videoSettings(int field,int value);
|
void videoSettings(int field,int value);
|
||||||
void sendFile(); void saveFile(); void secureAttention();
|
void sendFile(); void saveFile(); void secureAttention();
|
||||||
|
default void togglePrivacy() { }
|
||||||
}
|
}
|
||||||
private final MobileUi ui;
|
private final MobileUi ui;
|
||||||
private final Actions actions;
|
private final Actions actions;
|
||||||
@@ -35,7 +36,11 @@ final class SessionControls extends FrameLayout {
|
|||||||
int height=Math.max(0,getRootView().getHeight()-visible.bottom);
|
int height=Math.max(0,getRootView().getHeight()-visible.bottom);
|
||||||
keyboardInsets(height,height>ui.dp(80),0);
|
keyboardInsets(height,height>ui.dp(80),0);
|
||||||
}
|
}
|
||||||
private TextView networkLabel,videoFeedback;
|
private TextView videoFeedback;
|
||||||
|
private RemotePrivacyScreen privacy=new RemotePrivacyScreen();
|
||||||
|
private View privacyButton;
|
||||||
|
private MobileUi.Icon privacyIcon;
|
||||||
|
private TextView privacyFeedback,privacyCaption;
|
||||||
private TextView transferLabel,saveFileButton;
|
private TextView transferLabel,saveFileButton;
|
||||||
private String transferStatus="";
|
private String transferStatus="";
|
||||||
private boolean hasReceivedFile;
|
private boolean hasReceivedFile;
|
||||||
@@ -47,21 +52,27 @@ final class SessionControls extends FrameLayout {
|
|||||||
private LinearLayout[] trafficRows;
|
private LinearLayout[] trafficRows;
|
||||||
private TextView[] networkDetails;
|
private TextView[] networkDetails;
|
||||||
private View encryptionLock,encryptionOpen;
|
private View encryptionLock,encryptionOpen;
|
||||||
private boolean muted,relative=true,orbPositioned;
|
private boolean muted,relative=true,orbPositioned,secureAttentionVisible;
|
||||||
SessionControls(Context context,MobileUi ui,Actions actions){
|
SessionControls(Context context,MobileUi ui,Actions actions){
|
||||||
super(context);this.ui=ui;this.actions=actions;
|
super(context);this.ui=ui;this.actions=actions;
|
||||||
keyboard=new RemoteKeyboard(context,ui,actions,()->{requestLayout();positionMouse();});
|
keyboard=new RemoteKeyboard(context,ui,actions,()->{requestLayout();positionMouse();});
|
||||||
addView(keyboard,new LayoutParams(-1,ui.dp(44),Gravity.BOTTOM));
|
addView(keyboard,new LayoutParams(-1,ui.dp(44),Gravity.BOTTOM));
|
||||||
// This container must remain transparent to remote touch outside its controls.
|
// This container must remain transparent to remote touch outside its controls.
|
||||||
waiting=ui.column();waiting.setMinimumWidth(ui.dp(200));waiting.setGravity(Gravity.CENTER);waiting.setPadding(ui.dp(20),ui.dp(20),ui.dp(20),ui.dp(20));waiting.setBackground(ui.background(0xB0202024,14));
|
waiting=ui.column();waiting.setMinimumWidth(ui.dp(200));waiting.setGravity(Gravity.CENTER);waiting.setPadding(ui.dp(20),ui.dp(20),ui.dp(20),ui.dp(20));waiting.setBackground(ui.background(0xB0202024,14));
|
||||||
ProgressBar progress=new ProgressBar(context);progress.setIndeterminateTintList(android.content.res.ColorStateList.valueOf(Color.WHITE));waiting.addView(progress,ui.size(28,28));ui.gap(waiting,12);TextView message=ui.text("等待远端画面…",14,false);message.setTextColor(Color.WHITE);message.setGravity(Gravity.CENTER);waiting.addView(message);addView(waiting,new LayoutParams(-2,-2,Gravity.CENTER));
|
ProgressBar progress=new ProgressBar(context);progress.setIndeterminateTintList(android.content.res.ColorStateList.valueOf(Color.WHITE));waiting.addView(progress,ui.size(28,28));ui.gap(waiting,12);TextView message=ui.text(L10n.tr("等待远端画面…"),14,false);message.setTextColor(Color.WHITE);message.setGravity(Gravity.CENTER);waiting.addView(message);addView(waiting,new LayoutParams(-2,-2,Gravity.CENTER));
|
||||||
orb=ui.iconButton("display","展开远程控制菜单",()->{if(menu==null)openMenu("controls");else closeMenu();});
|
orb=ui.iconButton("display",L10n.tr("展开远程控制菜单"),()->{if(menu==null)openMenu("controls");else closeMenu();});
|
||||||
ImageView brand=new ImageView(context);brand.setImageResource(R.drawable.mobile_brand);brand.setScaleType(ImageView.ScaleType.CENTER_CROP);brand.setBackground(ui.background(Color.WHITE,23));brand.setClipToOutline(true);brand.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
ImageView brand=new ImageView(context);brand.setImageResource(R.drawable.mobile_brand);brand.setScaleType(ImageView.ScaleType.CENTER_CROP);brand.setBackground(ui.background(Color.WHITE,23));brand.setClipToOutline(true);brand.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||||
((FrameLayout)orb).removeAllViews();((FrameLayout)orb).addView(brand,new LayoutParams(ui.dp(46),ui.dp(46),Gravity.CENTER));
|
((FrameLayout)orb).removeAllViews();((FrameLayout)orb).addView(brand,new LayoutParams(ui.dp(46),ui.dp(46),Gravity.CENTER));
|
||||||
orb.setBackground(ui.background(0xFFF5F5F7,27));orb.setElevation(ui.dp(7));addView(orb,new LayoutParams(ui.dp(54),ui.dp(54)));
|
orb.setBackground(ui.background(0xFFF5F5F7,27));orb.setElevation(ui.dp(7));addView(orb,new LayoutParams(ui.dp(54),ui.dp(54)));
|
||||||
drag(orb,orb,()->{if(menu==null)openMenu("controls");else closeMenu();});
|
drag(orb,orb,()->{if(menu==null)openMenu("controls");else closeMenu();});
|
||||||
}
|
}
|
||||||
void setRelative(boolean value){relative=value;if(menu!=null&&menuPage.equals("controls"))openMenu("controls");}
|
void setRelative(boolean value){relative=value;if(menu!=null&&menuPage.equals("controls"))openMenu("controls");}
|
||||||
|
void setHostPlatform(String platform){
|
||||||
|
boolean visible="windows".equals(platform);
|
||||||
|
if(secureAttentionVisible==visible)return;
|
||||||
|
secureAttentionVisible=visible;
|
||||||
|
if(menu!=null&&menuPage.equals("controls"))openMenu("controls");
|
||||||
|
}
|
||||||
void attachMouseBar(FrameLayout host, VirtualMouseBar bar) {
|
void attachMouseBar(FrameLayout host, VirtualMouseBar bar) {
|
||||||
mouseHost=host;mouseBar=bar;
|
mouseHost=host;mouseBar=bar;
|
||||||
// A direct, tightly bounded sibling of the video is essential: otherwise
|
// A direct, tightly bounded sibling of the video is essential: otherwise
|
||||||
@@ -97,23 +108,24 @@ final class SessionControls extends FrameLayout {
|
|||||||
}
|
}
|
||||||
void setMuted(boolean value){muted=value;if(menu!=null&&menuPage.equals("controls"))openMenu("controls");}
|
void setMuted(boolean value){muted=value;if(menu!=null&&menuPage.equals("controls"))openMenu("controls");}
|
||||||
void videoSize(int width,int height){waiting.setVisibility(GONE);positionMouse();}
|
void videoSize(int width,int height){waiting.setVisibility(GONE);positionMouse();}
|
||||||
void resetVideoStatistics(){statistics(new RemoteNetworkStatistics.Snapshot(network.report,0,0,0,Double.NaN,network.rtt));}
|
void resetVideoStatistics(){statistics(new RemoteNetworkStatistics.Snapshot(network.report,0,0,0,Double.NaN,network.rtt,network.remoteEncoder,network.localDecoder));}
|
||||||
void statistics(RemoteNetworkStatistics.Snapshot value){network=value;renderNetwork();}
|
void statistics(RemoteNetworkStatistics.Snapshot value){network=value;renderNetwork();}
|
||||||
void videoSettings(RemoteVideoSettings value){settings=value;renderVideoSettings();}
|
void videoSettings(RemoteVideoSettings value){settings=value;renderVideoSettings();}
|
||||||
|
void privacyScreen(RemotePrivacyScreen value){privacy=value;renderPrivacy();}
|
||||||
void fileStatus(String status,boolean received){transferStatus=status;hasReceivedFile=received;renderFileStatus();}
|
void fileStatus(String status,boolean received){transferStatus=status;hasReceivedFile=received;renderFileStatus();}
|
||||||
private void renderFileStatus(){
|
private void renderFileStatus(){
|
||||||
if(transferLabel!=null){transferLabel.setText(transferStatus);transferLabel.setVisibility(transferStatus.isEmpty()?GONE:VISIBLE);}
|
if(transferLabel!=null){transferLabel.setText(transferStatus);transferLabel.setVisibility(transferStatus.isEmpty()?GONE:VISIBLE);}
|
||||||
if(saveFileButton!=null)saveFileButton.setVisibility(hasReceivedFile?VISIBLE:GONE);
|
if(saveFileButton!=null)saveFileButton.setVisibility(hasReceivedFile?VISIBLE:GONE);
|
||||||
}
|
}
|
||||||
private void closeMenu(){if(menu!=null){removeView(menu);menu=null;menuScroll=null;}if(shield!=null){removeView(shield);shield=null;}networkLabel=videoFeedback=transferLabel=saveFileButton=null;trafficCells=null;trafficRows=null;networkDetails=null;encryptionLock=encryptionOpen=null;settingRows.clear();orb.setContentDescription("展开远程控制菜单");positionMouse();}
|
private void closeMenu(){if(menu!=null){removeView(menu);menu=null;menuScroll=null;}if(shield!=null){removeView(shield);shield=null;}videoFeedback=transferLabel=saveFileButton=null;privacyFeedback=privacyCaption=null;privacyButton=null;privacyIcon=null;trafficCells=null;trafficRows=null;networkDetails=null;encryptionLock=encryptionOpen=null;settingRows.clear();orb.setContentDescription(L10n.tr("展开远程控制菜单"));positionMouse();}
|
||||||
private void openMenu(String page){
|
private void openMenu(String page){
|
||||||
cancelMouseInput();
|
cancelMouseInput();
|
||||||
closeMenu();menuPage=page;shield=new View(getContext());shield.setOnClickListener(v->closeMenu());addView(shield,new LayoutParams(-1,-1));
|
closeMenu();menuPage=page;shield=new View(getContext());shield.setOnClickListener(v->closeMenu());addView(shield,new LayoutParams(-1,-1));
|
||||||
menu=ui.column();menu.setPadding(ui.dp(12),ui.dp(12),ui.dp(12),ui.dp(12));ui.card(menu,18);menu.setElevation(ui.dp(12));
|
menu=ui.column();menu.setPadding(ui.dp(12),ui.dp(12),ui.dp(12),ui.dp(12));ui.card(menu,18);menu.setElevation(ui.dp(12));
|
||||||
LinearLayout header=ui.row();
|
LinearLayout header=ui.row();
|
||||||
if(!page.equals("controls"))header.addView(ui.iconButton("back","返回控制栏",()->openMenu("controls")),ui.size(28,32));
|
if(!page.equals("controls"))header.addView(ui.iconButton("back",L10n.tr("返回控制栏"),()->openMenu("controls")),ui.size(28,32));
|
||||||
LinearLayout titles=ui.column();TextView title=ui.text(page.equals("controls")?"● 已连接":page.equals("video")?"画面设置":"网络状态",14,true);titles.addView(title);
|
TextView title=ui.text(page.equals("controls")?L10n.tr("● 已连接"):page.equals("video")?L10n.tr("画面设置"):L10n.tr("网络状态"),14,true);
|
||||||
if(page.equals("controls")){ui.gap(titles,3);networkLabel=ui.text(network.summary(),10,false);networkLabel.setFontFeatureSettings("tnum");networkLabel.setTextColor(SECONDARY);titles.addView(networkLabel);}header.addView(titles,new LinearLayout.LayoutParams(0,-2,1));header.addView(ui.iconButton("close","关闭控制栏",this::closeMenu),ui.size(30,32));menu.addView(header);ui.gap(menu,9);
|
header.addView(title,new LinearLayout.LayoutParams(0,-2,1));header.addView(ui.iconButton("close",L10n.tr("关闭控制栏"),this::closeMenu),ui.size(30,32));menu.addView(header);ui.gap(menu,9);
|
||||||
if(page.equals("video")){videoFeedback=ui.text("",12,false);videoFeedback.setTextColor(SECONDARY);videoFeedback.setPadding(0,0,0,ui.dp(9));menu.addView(videoFeedback);}
|
if(page.equals("video")){videoFeedback=ui.text("",12,false);videoFeedback.setTextColor(SECONDARY);videoFeedback.setPadding(0,0,0,ui.dp(9));menu.addView(videoFeedback);}
|
||||||
ScrollView scroll=new ScrollView(getContext());menuScroll=scroll;menu.addView(scroll,new LinearLayout.LayoutParams(-1,0,1));LinearLayout body=ui.column();scroll.addView(body);
|
ScrollView scroll=new ScrollView(getContext());menuScroll=scroll;menu.addView(scroll,new LinearLayout.LayoutParams(-1,0,1));LinearLayout body=ui.column();scroll.addView(body);
|
||||||
if(page.equals("network")){
|
if(page.equals("network")){
|
||||||
@@ -121,26 +133,48 @@ final class SessionControls extends FrameLayout {
|
|||||||
scroll.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);body.setPadding(0,0,ui.dp(6),0);
|
scroll.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);body.setPadding(0,0,ui.dp(6),0);
|
||||||
}
|
}
|
||||||
if(page.equals("controls")){
|
if(page.equals("controls")){
|
||||||
String[] labels={"键盘",mouseVisible?"收起鼠标":"虚拟鼠标",relative?"相对鼠标":"绝对鼠标","显示器","画面设置",muted?"静音":"声音","发送文件","网络状态","Ctrl+Alt+Del","剪贴板"};
|
String[] labels={L10n.tr("键盘"),mouseVisible?L10n.tr("收起鼠标"):L10n.tr("虚拟鼠标"),relative?L10n.tr("相对鼠标"):L10n.tr("绝对鼠标"),L10n.tr("显示器"),L10n.tr("画面设置"),muted?L10n.tr("静音"):L10n.tr("声音"),L10n.tr("发送文件"),L10n.tr("网络状态"),"Ctrl+Alt+Del",L10n.tr("剪贴板"),L10n.tr("隐私屏")};
|
||||||
String[] icons={"keyboard","mouse","mouse","display","sliders","audio","folder","chart","lock","clipboard"};
|
String[] icons={"keyboard","mouse","mouse","display","sliders","audio","folder","chart","lock","clipboard","eye"};
|
||||||
Runnable[] clicks={()->{closeMenu();toggleKeyboard();},this::toggleMouse,actions::mouse,actions::display,()->openMenu("video"),actions::mute,actions::sendFile,()->openMenu("network"),actions::secureAttention,actions::clipboard};
|
Runnable[] clicks={()->{closeMenu();toggleKeyboard();},this::toggleMouse,actions::mouse,actions::display,()->openMenu("video"),actions::mute,actions::sendFile,()->openMenu("network"),actions::secureAttention,actions::clipboard,actions::togglePrivacy};
|
||||||
menuColumns=controlColumns();
|
menuColumns=controlColumns();
|
||||||
for(int row=0;row<(labels.length+menuColumns-1)/menuColumns;row++){LinearLayout line=ui.row();for(int col=0;col<menuColumns;col++){int index=row*menuColumns+col;if(index>=labels.length){View spacer=new View(getContext());LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(0,ui.dp(52),1);if(col>0)p.leftMargin=ui.dp(7);line.addView(spacer,p);continue;}LinearLayout control=ui.column();control.setGravity(Gravity.CENTER);control.setBackground(ui.background(0xFFF0F0F2,10));control.addView(ui.icon(icons[index],INK),ui.size(19,19));ui.gap(control,4);TextView caption=ui.text(labels[index],10,false);caption.setSingleLine();caption.setGravity(Gravity.CENTER);control.addView(caption);control.setContentDescription(labels[index]);control.setClickable(true);control.setFocusable(true);control.setOnClickListener(v->clicks[index].run());
|
java.util.List<Integer> visibleControls=new java.util.ArrayList<>();
|
||||||
|
for(int index=0;index<labels.length;index++){
|
||||||
|
if(index==8&&!secureAttentionVisible||index==10&&!privacy.desktopAvailable)continue;
|
||||||
|
visibleControls.add(index);
|
||||||
|
}
|
||||||
|
int controlCount=visibleControls.size();
|
||||||
|
for(int row=0;row<(controlCount+menuColumns-1)/menuColumns;row++){LinearLayout line=ui.row();for(int col=0;col<menuColumns;col++){int position=row*menuColumns+col;if(position>=controlCount){View spacer=new View(getContext());LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(0,ui.dp(52),1);if(col>0)p.leftMargin=ui.dp(7);line.addView(spacer,p);continue;}int index=visibleControls.get(position);LinearLayout control=ui.column();control.setGravity(Gravity.CENTER);control.setBackground(ui.background(0xFFF0F0F2,10));MobileUi.Icon icon=ui.icon(icons[index],INK);control.addView(icon,ui.size(19,19));ui.gap(control,4);TextView caption=ui.text(labels[index],10,false);caption.setMaxLines(2);caption.setAutoSizeTextTypeUniformWithConfiguration(8,10,1,android.util.TypedValue.COMPLEX_UNIT_SP);caption.setGravity(Gravity.CENTER);control.addView(caption);control.setContentDescription(labels[index]);control.setClickable(true);control.setFocusable(true);control.setOnClickListener(v->clicks[index].run());
|
||||||
|
if(index==10){privacyButton=control;privacyIcon=icon;privacyCaption=caption;}
|
||||||
LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(0,ui.dp(52),1);if(col>0)p.leftMargin=ui.dp(7);line.addView(control,p);
|
LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(0,ui.dp(52),1);if(col>0)p.leftMargin=ui.dp(7);line.addView(control,p);
|
||||||
}body.addView(line);if((row+1)*menuColumns<labels.length)ui.gap(body,7);}
|
}body.addView(line);if((row+1)*menuColumns<controlCount)ui.gap(body,7);}
|
||||||
|
privacyFeedback=ui.text("",11,false);privacyFeedback.setTextColor(SECONDARY);privacyFeedback.setPadding(0,ui.dp(7),0,0);privacyFeedback.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);body.addView(privacyFeedback);renderPrivacy();
|
||||||
ui.gap(body,7);transferLabel=ui.text(transferStatus,11,false);transferLabel.setTextColor(SECONDARY);transferLabel.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);body.addView(transferLabel);
|
ui.gap(body,7);transferLabel=ui.text(transferStatus,11,false);transferLabel.setTextColor(SECONDARY);transferLabel.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);body.addView(transferLabel);
|
||||||
saveFileButton=ui.action("保存收到的文件",BLUE,actions::saveFile);body.addView(saveFileButton);renderFileStatus();
|
saveFileButton=ui.action(L10n.tr("保存收到的文件"),BLUE,actions::saveFile);body.addView(saveFileButton);renderFileStatus();
|
||||||
}else if(page.equals("network")){
|
}else if(page.equals("network")){
|
||||||
networkPage(body);
|
networkPage(body);
|
||||||
}else{
|
}else{
|
||||||
settingRows.add(new SettingRow(body,0,"画面质量",new String[]{"低","中","高"},new int[]{0,1,2}));ui.gap(body,12);
|
settingRows.add(new SettingRow(body,0,L10n.tr("画面质量"),new String[]{L10n.tr("低"),L10n.tr("中"),L10n.tr("高")},new int[]{0,1,2}));ui.gap(body,12);
|
||||||
settingRows.add(new SettingRow(body,1,"画面采集帧率",new String[]{"30 fps","60 fps"},new int[]{30,60}));ui.gap(body,12);
|
settingRows.add(new SettingRow(body,1,L10n.tr("画面采集帧率"),new String[]{"30 fps","60 fps"},new int[]{30,60}));ui.gap(body,12);
|
||||||
settingRows.add(new SettingRow(body,2,"画面偏好",RemoteVideoSettings.PREFERENCES,new int[]{0,1,2}));renderVideoSettings();
|
settingRows.add(new SettingRow(body,2,L10n.tr("画面偏好"),RemoteVideoSettings.preferences(),new int[]{0,1,2}));renderVideoSettings();
|
||||||
}
|
}
|
||||||
ui.gap(menu,9);TextView disconnect=ui.action("断开连接",Color.WHITE,()->{closeMenu();closeKeyboard();actions.disconnect();});disconnect.setMinHeight(ui.dp(34));disconnect.setTextSize(14);disconnect.setBackground(ui.background(RED,8));menu.addView(disconnect,ui.size(-1,34));
|
ui.gap(menu,9);TextView disconnect=ui.action(L10n.tr("断开连接"),Color.WHITE,()->{closeMenu();closeKeyboard();actions.disconnect();});disconnect.setMinHeight(ui.dp(34));disconnect.setTextSize(14);disconnect.setBackground(ui.background(RED,8));menu.addView(disconnect,ui.size(-1,34));
|
||||||
addView(menu,new LayoutParams(panelWidth(getWidth()),panelHeight(getHeight())));orb.bringToFront();orb.setContentDescription("收起远程控制菜单");
|
addView(menu,new LayoutParams(panelWidth(getWidth()),panelHeight(getHeight())));orb.bringToFront();orb.setContentDescription(L10n.tr("收起远程控制菜单"));
|
||||||
positionMouse();
|
positionMouse();
|
||||||
}
|
}
|
||||||
|
private void renderPrivacy(){
|
||||||
|
if(menu!=null&&menuPage.equals("controls")&&(privacyButton!=null)!=privacy.desktopAvailable){
|
||||||
|
openMenu("controls");return;
|
||||||
|
}
|
||||||
|
long now=android.os.SystemClock.elapsedRealtime();privacy.tick(now);
|
||||||
|
if(privacyFeedback!=null){String feedback=privacy.feedback(now);privacyFeedback.setText(feedback);privacyFeedback.setVisibility(feedback.isEmpty()?GONE:VISIBLE);}
|
||||||
|
if(privacyButton==null)return;
|
||||||
|
boolean enabled=privacy.enabled(now);String action=privacy.active()?L10n.tr("关闭隐私屏"):L10n.tr("开启隐私屏");
|
||||||
|
privacyIcon.setName(enabled?"eye_off":"eye");
|
||||||
|
privacyCaption.setText(privacy.buttonTitle(now));
|
||||||
|
privacyButton.setSelected(enabled);
|
||||||
|
privacyButton.setContentDescription(action+","+privacy.status(now));
|
||||||
|
privacyButton.setEnabled(privacy.canToggle(now));privacyButton.setAlpha(privacyButton.isEnabled()?1f:.45f);
|
||||||
|
}
|
||||||
private final class SettingRow {
|
private final class SettingRow {
|
||||||
final int field;final int[] values;final SegmentedControl group;
|
final int field;final int[] values;final SegmentedControl group;
|
||||||
SettingRow(LinearLayout body,int field,String title,String[] labels,int[] values){
|
SettingRow(LinearLayout body,int field,String title,String[] labels,int[] values){
|
||||||
@@ -149,7 +183,7 @@ final class SessionControls extends FrameLayout {
|
|||||||
group=new SegmentedControl(ui,labels,selectionIndex(),settings.supported,index->actions.videoSettings(field,values[index]));
|
group=new SegmentedControl(ui,labels,selectionIndex(),settings.supported,index->actions.videoSettings(field,values[index]));
|
||||||
group.setPadding(ui.dp(2),ui.dp(2),ui.dp(2),ui.dp(2));body.addView(group);
|
group.setPadding(ui.dp(2),ui.dp(2),ui.dp(2),ui.dp(2));body.addView(group);
|
||||||
for(int i=0;i<labels.length;i++){
|
for(int i=0;i<labels.length;i++){
|
||||||
TextView button=(TextView)group.getChildAt(i);button.setTextSize(12);button.setMinHeight(ui.dp(28));button.setSingleLine();button.setContentDescription(title+","+labels[i]);
|
TextView button=(TextView)group.getChildAt(i);button.setTextSize(12);button.setMinHeight(ui.dp(28));button.setSingleLine();button.setAutoSizeTextTypeUniformWithConfiguration(9,12,1,android.util.TypedValue.COMPLEX_UNIT_SP);button.setContentDescription(title+","+labels[i]);
|
||||||
button.setLayoutParams(new LinearLayout.LayoutParams(0,ui.dp(28),1));
|
button.setLayoutParams(new LinearLayout.LayoutParams(0,ui.dp(28),1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,15 +197,17 @@ final class SessionControls extends FrameLayout {
|
|||||||
for(SettingRow row:settingRows)row.render();
|
for(SettingRow row:settingRows)row.render();
|
||||||
}
|
}
|
||||||
private void networkPage(LinearLayout body){
|
private void networkPage(LinearLayout body){
|
||||||
trafficCells=new TextView[4][3];trafficRows=new LinearLayout[4];String[] names={"视频","音频","数据","合计"};
|
trafficCells=new TextView[4][3];trafficRows=new LinearLayout[4];String[] names={L10n.tr("视频"),L10n.tr("音频"),L10n.tr("数据"),L10n.tr("合计")};
|
||||||
LinearLayout heading=trafficRow(body,"",new TextView[3],false);((TextView)heading.getChildAt(1)).setText("接收");((TextView)heading.getChildAt(2)).setText("发送");((TextView)heading.getChildAt(3)).setText("丢包率");
|
LinearLayout heading=trafficRow(body,"",new TextView[3],false);((TextView)heading.getChildAt(1)).setText(L10n.tr("接收"));((TextView)heading.getChildAt(2)).setText(L10n.tr("发送"));((TextView)heading.getChildAt(3)).setText(L10n.tr("丢包率"));
|
||||||
for(int i=0;i<heading.getChildCount();i++)((TextView)heading.getChildAt(i)).setTextColor(SECONDARY);
|
for(int i=0;i<heading.getChildCount();i++)((TextView)heading.getChildAt(i)).setTextColor(SECONDARY);
|
||||||
for(int i=0;i<4;i++)trafficRows[i]=trafficRow(body,names[i],trafficCells[i],i==3);
|
for(int i=0;i<4;i++)trafficRows[i]=trafficRow(body,names[i],trafficCells[i],i==3);
|
||||||
ui.gap(body,10);ui.divider(body);ui.gap(body,10);
|
ui.gap(body,10);ui.divider(body);ui.gap(body,10);
|
||||||
String[] titles={"帧率","分辨率","画面延时","连接延时(RTT)","连接方式","媒体加密"};networkDetails=new TextView[titles.length];
|
String[] titles={L10n.tr("帧率"),L10n.tr("分辨率"),L10n.tr("画面延时"),L10n.tr("连接延时(RTT)"),L10n.tr("连接方式"),L10n.tr("媒体加密"),L10n.tr("被控端编码器"),L10n.tr("控制端解码器")};networkDetails=new TextView[titles.length];
|
||||||
for(int i=0;i<titles.length;i++){LinearLayout row=ui.row();TextView title=ui.text(titles[i],12,false);title.setTextColor(SECONDARY);row.addView(title,new LinearLayout.LayoutParams(0,-2,1));
|
for(int i=0;i<titles.length;i++){LinearLayout row=ui.row();TextView title=ui.text(titles[i],12,false);title.setTextColor(SECONDARY);row.addView(title,i>=6?new LinearLayout.LayoutParams(-2,-2):new LinearLayout.LayoutParams(0,-2,1));
|
||||||
if(i==5){encryptionLock=ui.icon("lock",GREEN);encryptionOpen=ui.icon("unlock",SECONDARY);for(View icon:new View[]{encryptionLock,encryptionOpen}){LinearLayout.LayoutParams p=ui.size(14,14);p.rightMargin=ui.dp(4);row.addView(icon,p);}}
|
if(i==5){encryptionLock=ui.icon("lock",GREEN);encryptionOpen=ui.icon("unlock",SECONDARY);for(View icon:new View[]{encryptionLock,encryptionOpen}){LinearLayout.LayoutParams p=ui.size(14,14);p.rightMargin=ui.dp(4);row.addView(icon,p);}}
|
||||||
TextView value=ui.text("—",12,false);value.setFontFeatureSettings("tnum");row.addView(value);networkDetails[i]=value;body.addView(row);if(i<titles.length-1)ui.gap(body,7);}
|
TextView value=ui.text("—",12,false);value.setFontFeatureSettings("tnum");
|
||||||
|
if(i>=6){value.setGravity(Gravity.END);value.setSingleLine();value.setEllipsize(android.text.TextUtils.TruncateAt.MARQUEE);value.setMarqueeRepeatLimit(-1);value.setSelected(true);LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(0,-2,1);p.leftMargin=ui.dp(8);row.addView(value,p);}else row.addView(value);
|
||||||
|
networkDetails[i]=value;body.addView(row);if(i<titles.length-1)ui.gap(body,7);}
|
||||||
renderNetwork();
|
renderNetwork();
|
||||||
}
|
}
|
||||||
private LinearLayout trafficRow(LinearLayout body,String title,TextView[] cells,boolean total){
|
private LinearLayout trafficRow(LinearLayout body,String title,TextView[] cells,boolean total){
|
||||||
@@ -182,16 +218,16 @@ final class SessionControls extends FrameLayout {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
private void renderNetwork(){
|
private void renderNetwork(){
|
||||||
if(networkLabel!=null)networkLabel.setText(network.summary());
|
|
||||||
if(trafficCells==null)return;
|
if(trafficCells==null)return;
|
||||||
String[] names={"视频","音频","数据","合计"};
|
String[] names={L10n.tr("视频"),L10n.tr("音频"),L10n.tr("数据"),L10n.tr("合计")};
|
||||||
for(int i=0;i<4;i++){
|
for(int i=0;i<4;i++){
|
||||||
RemoteNetworkStatistics.Traffic traffic=network.report==null?null:network.report.traffic[i];
|
RemoteNetworkStatistics.Traffic traffic=network.report==null?null:network.report.traffic[i];
|
||||||
String in=RemoteNetworkStatistics.bitrate(traffic==null?-1:traffic.inbound),out=RemoteNetworkStatistics.bitrate(traffic==null?-1:traffic.outbound),loss=RemoteNetworkStatistics.loss(traffic==null?Double.NaN:traffic.loss);
|
String in=RemoteNetworkStatistics.bitrate(traffic==null?-1:traffic.inbound),out=RemoteNetworkStatistics.bitrate(traffic==null?-1:traffic.outbound),loss=RemoteNetworkStatistics.loss(traffic==null?Double.NaN:traffic.loss);
|
||||||
trafficCells[i][0].setText(in);trafficCells[i][1].setText(out);trafficCells[i][2].setText(loss);trafficRows[i].setContentDescription(names[i]+",接收 "+in+",发送 "+out+",丢包率 "+loss);
|
trafficCells[i][0].setText(in);trafficCells[i][1].setText(out);trafficCells[i][2].setText(loss);trafficRows[i].setContentDescription(L10n.format("{0},接收 {1},发送 {2},丢包率 {3}",names[i],in,out,loss));
|
||||||
}
|
}
|
||||||
String[] values={network.fps+" FPS",network.resolution(),RemoteNetworkStatistics.latency(network.latency),RemoteNetworkStatistics.latency(network.rtt),network.mode(),network.report==null?"—":network.report.srtp?"SRTP 已启用":"未启用"};
|
String[] values={network.fps+" FPS",network.resolution(),RemoteNetworkStatistics.latency(network.latency),RemoteNetworkStatistics.latency(network.rtt),network.mode(),network.report==null?"—":network.report.srtp?L10n.tr("SRTP 已启用"):L10n.tr("未启用"),network.remoteEncoder.isEmpty()?"—":network.remoteEncoder,network.localDecoder.isEmpty()?"—":network.localDecoder};
|
||||||
for(int i=0;i<values.length;i++)networkDetails[i].setText(values[i]);networkDetails[5].setTextColor(network.report!=null&&network.report.srtp?GREEN:SECONDARY);
|
// Reassigning identical text every statistics tick restarts the marquee.
|
||||||
|
for(int i=0;i<values.length;i++)if(!android.text.TextUtils.equals(networkDetails[i].getText(),values[i]))networkDetails[i].setText(values[i]);networkDetails[5].setTextColor(network.report!=null&&network.report.srtp?GREEN:SECONDARY);
|
||||||
encryptionLock.setVisibility(network.report!=null&&network.report.srtp?VISIBLE:GONE);encryptionOpen.setVisibility(network.report!=null&&!network.report.srtp?VISIBLE:GONE);
|
encryptionLock.setVisibility(network.report!=null&&network.report.srtp?VISIBLE:GONE);encryptionOpen.setVisibility(network.report!=null&&!network.report.srtp?VISIBLE:GONE);
|
||||||
}
|
}
|
||||||
private int controlColumns(){return getMeasuredHeight()<ui.dp(400)&&getMeasuredWidth()>=ui.dp(400)?4:3;}
|
private int controlColumns(){return getMeasuredHeight()<ui.dp(400)&&getMeasuredWidth()>=ui.dp(400)?4:3;}
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ final class VirtualMouseBar extends LinearLayout {
|
|||||||
shell.setStroke(ui.dp(.5f), 0xFFD4DAE2);
|
shell.setStroke(ui.dp(.5f), 0xFFD4DAE2);
|
||||||
setBackground(shell);
|
setBackground(shell);
|
||||||
setMotionEventSplittingEnabled(true);
|
setMotionEventSplittingEnabled(true);
|
||||||
keys[0] = key("鼠标左键,按住并拖动,松开结束拖拽", 0);
|
keys[0] = key(L10n.tr("鼠标左键,按住并拖动,松开结束拖拽"), 0);
|
||||||
keys[1] = key("鼠标滚轮,上下滑动,可滑出按键范围", 1);
|
keys[1] = key(L10n.tr("鼠标滚轮,上下滑动,可滑出按键范围"), 1);
|
||||||
keys[2] = key("鼠标右键,按住并拖动,松开结束拖拽", 2);
|
keys[2] = key(L10n.tr("鼠标右键,按住并拖动,松开结束拖拽"), 2);
|
||||||
input.didCancel = this::resetTouches;
|
input.didCancel = this::resetTouches;
|
||||||
setContentDescription("虚拟鼠标");
|
setContentDescription(L10n.tr("虚拟鼠标"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility") // Touch sends real down/up; performClick serves accessibility only.
|
@SuppressLint("ClickableViewAccessibility") // Touch sends real down/up; performClick serves accessibility only.
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" · 已完成"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" 传输失败"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" 台设备"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" 条 · "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" 条未读"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"CrossDesk 远程连接"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL 第 3 版"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"OpenFEC 告知"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"OpenFEC 许可与源码"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"P2P 直连"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRTP 已启用"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"TURN 中继"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"{0} 传输失败"</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"{0} 台设备"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"{0} 条未读"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0},接收 {1},发送 {2},丢包率 {3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● 已连接"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"下拉刷新"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"丢包率"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"中"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"优先保持画面流畅,带宽或编码压力较大时会更积极地降低分辨率。"</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"低"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"使用自定义服务器"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"保存密码"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"保存收到的文件"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"保存远程画面预览"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"保存远程画面预览?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"信令连接中断,请重新连接"</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"公告加载失败,请重试。"</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"公告详情"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"共 "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"共 {0} 条 · {1} 条未读"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"关于"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"关于 CrossDesk"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"关于,有新版本"</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"关闭"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"关闭控制栏"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"关闭隐私屏"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"兼顾清晰度和流畅度,只在持续压力下逐步降低分辨率。"</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"分辨率"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"删除"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"删除公告"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"删除记录"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"删除这条公告?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"刷新公告"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"剪贴板"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"加载中…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"加载更多"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"升级提示"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"原生连接初始化失败"</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"发送"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"发送 "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"发送后,在远程键盘中点击“粘贴”。接收的远端文本可手动复制到本机。"</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"发送文件"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"发送文本"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"取消"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"取消连接"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"各组件遵循所列开源许可证,其授予的权利不受本应用普通使用条款限制。查看源码需要网络连接。"</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"合计"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"同意并继续"</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"后台保持连接,点按返回远程桌面"</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"后台保活未能启动,请保持应用在前台"</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"后台保活未能启动,请保持应用在前台后重新连接"</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"后台连接已结束,请重新连接"</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"后台连接服务已停止,请重新连接"</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"后续连接仍会保存新的预览图。"</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"在线"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"填写主机名或 IPv4 地址,不含协议和路径"</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"声音"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"复制远端文本"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"始终保持较高分辨率,带宽不足时通过降低帧率来保证画面清晰度。"</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"媒体加密"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"字母数字"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"安全存储或证书初始化失败,请重试或清除应用数据"</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"完成"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"完成隐私授权后可检查更新"</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"完成隐私授权后登记本机身份"</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"密码保存失败,本次连接仍可使用"</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"密码将加密保存在本机 Android Keystore 中"</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"对端 ID"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"对端 ID "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"对端 ID {0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"将删除本机保存的所有远程预览图,保留连接记录和密码。"</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"屏幕方向"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"展开远程控制菜单"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"已从通知断开远程连接"</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"已保存密码"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"已保存密码无法读取,请重新输入"</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"已保存的密码未能保留,请重新输入一次"</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"已发送 Ctrl+Alt+Del"</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"已启用后台保活,切换应用可继续保持连接"</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"已启用后台保活,可从连接通知返回或断开"</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"已授权"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"已读"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"已连接 "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"已连接服务器"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"帧率"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"帧率优先"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"平衡"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"应用中的许可文件缺失或损坏,请重新安装应用。"</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"开发版本包含尚未发布的修改,以下链接为基础版本源码。"</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"开启保存"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"开启隐私屏"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"开源组件"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"开源软件权利说明"</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"当前已是最新版本"</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"当前远端不支持直接输入此文本,请使用支持文本输入的被控端版本,或通过剪贴板发送"</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"成功连接后,这里会显示最近的远程设备。"</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"我已阅读并同意《隐私政策》"</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"所选文件(无法读取或空间不足)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"找不到该设备 ID"</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"指令发送失败,请重试"</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"指令已发送,等待电脑确认…"</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"授权状态"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"接收"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"接收 "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"控制端解码器"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"撤回"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"撤回后将停止远程连接、关闭画面预览保存并清除已有预览。连接记录和密码会保留,重新连接前需再次授权。"</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"撤回联网授权"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"撤回联网授权?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"收起远程控制菜单"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"收起键盘"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"收起鼠标"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"数据"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"文件保存失败,请重试"</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"文件已保存"</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"文件选择已超时,会话已结束"</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"文本超过 128 KB"</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"断开"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"断开后将返回首页"</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"断开远程连接?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"断开连接"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"新版本"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"新版本可用:v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"新版本可用:v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"无未读公告"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"无法保存本地阅读状态,请重试。"</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"无法删除这条公告,请重试。"</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"无法发起远程连接"</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"无法读取开源许可"</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"显示器"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"显示密码"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"暂不同意"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"暂无公告"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"最近连接"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"服务器"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"服务器 TLS 证书校验失败"</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"服务器地址"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"服务器登录失败,请检查服务器地址和网络"</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"服务器端口"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"服务器连接超时,请检查服务器地址和网络"</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"未启用"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"未找到可打开链接的浏览器"</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"未授权"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"未读"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"未连接服务器 · 点击重试"</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"本应用使用 OpenFEC 提供前向纠错功能,遵循 CeCILL-C 1.0 许可。该许可规定了有限保证及责任限制,详情见许可全文。"</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"本机 ID "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"本机 ID {0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"本版本源码与构建说明"</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"本版本源码信息暂不可用。"</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"松开刷新"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"构建说明"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"构建配方与项目补丁"</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"查看源码"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"检查失败,请稍后重试"</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"检查更新"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"正在关闭隐私屏…"</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"正在建立安全连接…"</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"正在开启隐私屏…"</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"正在检查更新…"</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"正在清除…"</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"正在获取本机 ID…"</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"正在读取所选文件…"</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"正在连接"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"正在连接信令服务器…"</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"正在连接服务器…"</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"正在重连服务器…"</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"此设备没有可用的文件选择器"</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"清除"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"清除预览图"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"清除预览图?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"源码与构建说明"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"源码版本"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"版本"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"版权与许可声明"</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"电脑键盘"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"画质优先"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"画面偏好"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"画面延时"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"画面设置"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"画面质量"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"画面采集帧率"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"相对位置"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"相对鼠标"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"知道了"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"确定"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"确认"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"确认连接"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"离线"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"空格"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"端口范围为 1–65535"</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"符号功能"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"等待电脑返回隐私屏状态;旧版电脑端可能不支持"</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"等待远端屏幕信息"</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"等待远端画面…"</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"等待隐私授权"</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"系统键盘"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"绝对位置"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"绝对鼠标"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"网络状态"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"自定义"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"虚拟鼠标"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"被控端已通过组合键解除隐私屏。点击“锁定”将重新开启隐私屏,并禁止本机使用组合键解锁。"</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"被控端版本过低,请升级到最新版本。"</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"被控端编码器"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"要发送到远端的文本"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"视频"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"许可"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"许可与版权声明"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"许可与版权声明可离线查阅。"</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"设备身份保存失败,请检查安全存储"</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"设备身份及选择保存的访问密码使用 Android Keystore 加密。连接记录仅保存在本机,不纳入设备备份。剪贴板内容在结束会话时清除。离开应用后通过画中画或连接通知保持会话,可在应用或通知中断开。"</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"设置"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"设置,有新版本"</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"访问密码"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"访问密码错误,请重新输入"</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"证书校验失败 · 点击重试"</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"请先完成联网授权"</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"请在远端启用 CrossDesk 系统服务后重试"</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"请求超时,尚未确认电脑的隐私屏状态"</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"请等待当前文件发送完成"</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"请输入 1–65535 范围内的端口"</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"请输入 9 位设备 ID"</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"请输入有效访问密码"</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"调整失败,请重试。"</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"软件许可"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"返回"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"返回控制栏"</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"还没有连接记录"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"这条公告已更新或删除,请返回公告列表查看。"</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"远程任务已关闭"</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"远程桌面"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"远程桌面触控区域"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"远程桌面:轻触点击,拖动移动,长按右键,双指捏合缩放,放大后双指拖动画面,原始比例下双指上下滑动滚动"</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"远程电脑"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"远程窗口已关闭,或后台保活未能启动,会话已结束"</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"远程连接"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"远端密码错误"</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"远端已关闭连接"</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"远端当前不可用"</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"远端访问密码已失效,请重新连接"</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"连接"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"连接 "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"连接失败"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"连接失败 · 点击重试"</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"连接已断开"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"连接已结束"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"连接延时(RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"连接建立后可调整,需被控端支持。"</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"连接建立后锁定横屏"</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"连接提示"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"连接方式"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"连接服务器"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"连接服务器后可获取最新公告。"</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"连接超时,请检查设备 ID、服务器及远端在线状态"</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"连接远程桌面"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"退格"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"通知公告"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"部分预览图未能清除,请重试。"</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"重试"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"重试保存阅读状态"</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"锁定"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"键盘"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"阅读并授权"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"隐私"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"隐私与授权"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"隐私屏"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"隐私屏已关闭"</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"隐私屏已开启"</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"隐私屏已被解锁"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"隐私屏异常"</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"隐私屏暂不可用"</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"隐私屏状态已过期,等待电脑确认"</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"隐私屏状态待确认"</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"隐私政策"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"隐私政策暂时无法读取,请拒绝联网并重新安装应用。"</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"静音"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"音频"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"项目主页"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"预览可能包含远程电脑上的个人或工作信息。开启后,每次连接会自动将一张画面保存在本机并显示在最近连接中。你可以随时关闭并清除。"</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"高"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"默认"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"默认关闭。开启后,手机连接时进入并保持横屏;关闭时跟随设备方向。平板和多窗口不受影响。"</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"鼠标右键,按住并拖动,松开结束拖拽"</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"鼠标左键,按住并拖动,松开结束拖拽"</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"鼠标控制"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"鼠标滚轮,上下滑动,可滑出按键范围"</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">",丢包率 "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">",发送 "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">",已保存密码"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">",接收 "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">",点击重试"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" · 已完成"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" 傳輸失敗"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" 臺裝置"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" 條 · "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" 條未讀"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"CrossDesk 遠端連線"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL 第 3 版"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"OpenFEC 告知"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"OpenFEC 許可與原始碼"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"P2P 直連"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRTP 已啟用"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"TURN 中繼"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"{0} 傳輸失敗"</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"{0} 臺裝置"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"{0} 條未讀"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0},接收 {1},傳送 {2},丟包率 {3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● 已連線"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"下拉重新整理"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"丟包率"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"中"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"優先保持畫面流暢,頻寬或編碼壓力較大時會更積極地降低解析度。"</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"低"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"使用自定義伺服器"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"儲存密碼"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"儲存收到的檔案"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"儲存遠端畫面預覽"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"儲存遠端畫面預覽?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"信令連線中斷,請重新連線"</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"公告載入失敗,請重試。"</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"公告詳情"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"共 "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"共 {0} 條 · {1} 條未讀"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"關於"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"關於 CrossDesk"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"關於,有新版本"</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"關閉"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"關閉控制欄"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"關閉隱私屏"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"兼顧清晰度和流暢度,只在持續壓力下逐步降低解析度。"</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"解析度"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"刪除"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"刪除公告"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"刪除記錄"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"刪除這條公告?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"重新整理公告"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"剪貼簿"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"載入中…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"載入更多"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"更新提示"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"原生連線初始化失敗"</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"傳送"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"傳送 "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"傳送後,在遠端鍵盤中點選“貼上”。接收的遠端文字可手動複製到本機。"</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"傳送檔案"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"傳送文字"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"取消"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"取消連線"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"各元件遵循所列開源許可證,其授予的權利不受本應用普通使用條款限制。檢視原始碼需要網路連線。"</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"合計"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"同意並繼續"</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"後臺保持連線,點按返回遠端桌面"</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"後臺保活未能啟動,請保持應用在前臺"</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"後臺保活未能啟動,請保持應用在前臺後重新連線"</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"後臺連線已結束,請重新連線"</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"後臺連線服務已停止,請重新連線"</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"後續連線仍會儲存新的預覽圖。"</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"線上"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"填寫主機名或 IPv4 地址,不含協議和路徑"</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"聲音"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"複製遠端文字"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"始終保持較高解析度,頻寬不足時透過降低幀率來保證畫面清晰度。"</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"媒體加密"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"字母數字"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"安全儲存或證書初始化失敗,請重試或清除應用資料"</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"完成"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"完成隱私授權後可檢查更新"</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"完成隱私授權後登記本機身份"</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"密碼儲存失敗,本次連線仍可使用"</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"密碼將加密儲存在本機 Android Keystore 中"</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"對端 ID"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"對端 ID "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"對端 ID {0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"將刪除本機儲存的所有遠端預覽圖,保留連線記錄和密碼。"</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"螢幕方向"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"展開遠端控制選單"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"已從通知斷開遠端連線"</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"已儲存密碼"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"已儲存密碼無法讀取,請重新輸入"</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"已儲存的密碼未能保留,請重新輸入一次"</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"已傳送 Ctrl+Alt+Del"</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"已啟用後臺保活,切換應用可繼續保持連線"</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"已啟用後臺保活,可從連線通知返回或斷開"</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"已授權"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"已讀"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"已連線 "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"已連線伺服器"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"幀率"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"影格率優先"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"平衡"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"應用中的許可檔案缺失或損壞,請重新安裝應用。"</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"開發版本包含尚未釋出的修改,以下連結為基礎版本原始碼。"</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"啟用儲存"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"開啟隱私屏"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"開源元件"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"開源軟體權利說明"</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"當前已是最新版本"</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"當前遠端不支援直接輸入此文字,請使用支援文字輸入的被控端版本,或透過剪貼簿傳送"</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"成功連線後,這裡會顯示最近的遠端裝置。"</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"我已閱讀並同意《隱私政策》"</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"所選檔案(無法讀取或空間不足)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"找不到該裝置 ID"</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"指令傳送失敗,請重試"</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"指令已傳送,等待電腦確認…"</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"授權狀態"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"接收"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"接收 "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"控制端解碼器"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"撤回"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"撤回後將停止遠端連線、關閉畫面預覽儲存並清除已有預覽。連線記錄和密碼會保留,重新連線前需再次授權。"</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"撤回聯網授權"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"撤回聯網授權?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"收起遠端控制選單"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"收起鍵盤"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"收起滑鼠"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"資料"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"檔案儲存失敗,請重試"</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"檔案已儲存"</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"檔案選擇已超時,會話已結束"</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"文字超過 128 KB"</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"斷開"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"斷開後將返回首頁"</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"斷開遠端連線?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"中斷連線"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"新版本"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"新版本可用:v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"新版本可用:v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"無未讀公告"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"無法儲存本地閱讀狀態,請重試。"</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"無法刪除這條公告,請重試。"</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"無法發起遠端連線"</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"無法讀取開源許可"</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"顯示器"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"顯示密碼"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"暫不同意"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"暫無公告"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"最近連線"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"伺服器"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"伺服器 TLS 證書校驗失敗"</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"伺服器地址"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"伺服器登入失敗,請檢查伺服器地址和網路"</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"伺服器埠"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"伺服器連線超時,請檢查伺服器地址和網路"</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"未啟用"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"未找到可開啟連結的瀏覽器"</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"未授權"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"未讀"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"未連線伺服器 · 點選重試"</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"本應用使用 OpenFEC 提供前向糾錯功能,遵循 CeCILL-C 1.0 許可。該許可規定了有限保證及責任限制,詳情見許可全文。"</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"本機 ID "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"本機 ID {0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"本版本原始碼與構建說明"</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"本版本原始碼資訊暫不可用。"</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"鬆開重新整理"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"構建說明"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"構建配方與專案補丁"</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"檢視原始碼"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"檢查失敗,請稍後重試"</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"檢查更新"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"正在關閉隱私屏…"</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"正在建立安全連線…"</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"正在開啟隱私屏…"</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"正在檢查更新…"</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"正在清除…"</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"正在獲取本機 ID…"</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"正在讀取所選檔案…"</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"正在連線"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"正在連線信令伺服器…"</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"正在連線伺服器…"</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"正在重連伺服器…"</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"此裝置沒有可用的檔案選擇器"</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"清除"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"清除預覽圖"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"清除預覽圖?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"原始碼與構建說明"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"原始碼版本"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"版本"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"版權與許可宣告"</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"電腦鍵盤"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"畫質優先"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"畫面偏好"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"畫面延遲"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"畫面設定"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"畫面質量"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"畫面採集幀率"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"相對位置"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"相對滑鼠"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"知道了"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"確定"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"確認"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"確認連線"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"離線"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"空白鍵"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"埠範圍為 1–65535"</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"符號功能"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"等待電腦返回隱私屏狀態;舊版電腦端可能不支援"</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"等待遠端螢幕資訊"</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"等待遠端畫面…"</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"等待隱私授權"</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"系統鍵盤"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"絕對位置"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"絕對滑鼠"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"網路狀態"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"自定義"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"虛擬滑鼠"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"被控端已透過組合鍵解除隱私屏。點選“鎖定”將重新開啟隱私屏,並禁止本機使用組合鍵解鎖。"</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"被控端版本過低,請升級到最新版本。"</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"被控端編碼器"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"要傳送到遠端的文字"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"影片"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"許可"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"許可與版權宣告"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"許可與版權宣告可離線查閱。"</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"裝置身份儲存失敗,請檢查安全儲存"</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"裝置身份及選擇儲存的訪問密碼使用 Android Keystore 加密。連線記錄僅儲存在本機,不納入裝置備份。剪貼簿內容在結束會話時清除。離開應用後透過畫中畫或連線通知保持會話,可在應用或通知中斷開。"</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"設定"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"設定,有新版本"</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"訪問密碼"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"訪問密碼錯誤,請重新輸入"</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"證書校驗失敗 · 點選重試"</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"請先完成聯網授權"</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"請在遠端啟用 CrossDesk 系統服務後重試"</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"請求超時,尚未確認電腦的隱私屏狀態"</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"請等待當前檔案傳送完成"</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"請輸入 1–65535 範圍內的埠"</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"請輸入 9 位裝置 ID"</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"請輸入有效訪問密碼"</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"調整失敗,請重試。"</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"軟體許可"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"返回"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"返回控制欄"</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"還沒有連線記錄"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"這條公告已更新或刪除,請返回公告列表檢視。"</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"遠端任務已關閉"</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"遠端桌面"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"遠端桌面觸控區域"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"遠端桌面:輕觸點選,拖動移動,長按右鍵,雙指捏合縮放,放大後雙指拖動畫面,原始比例下雙指上下滑動滾動"</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"遠端電腦"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"遠端視窗已關閉,或後臺保活未能啟動,會話已結束"</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"遠端連線"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"遠端密碼錯誤"</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"遠端已關閉連線"</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"遠端當前不可用"</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"遠端訪問密碼已失效,請重新連線"</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"連線"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"連線 "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"連線失敗"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"連線失敗 · 點選重試"</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"連線已斷開"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"連線已結束"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"連線延時(RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"連線建立後可調整,需被控端支援。"</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"連線建立後鎖定橫屏"</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"連線提示"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"連線方式"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"連線伺服器"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"連線伺服器後可獲取最新公告。"</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"連線超時,請檢查裝置 ID、伺服器及遠端線上狀態"</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"連線遠端桌面"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"退格"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"通知公告"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"部分預覽圖未能清除,請重試。"</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"重試"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"重試儲存閱讀狀態"</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"鎖定"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"鍵盤"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"閱讀並授權"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"隱私"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"隱私與授權"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"隱私屏"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"隱私屏已關閉"</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"隱私屏已開啟"</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"隱私屏已被解鎖"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"隱私屏異常"</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"隱私屏暫不可用"</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"隱私屏狀態已過期,等待電腦確認"</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"隱私屏狀態待確認"</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"隱私政策"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"隱私政策暫時無法讀取,請拒絕聯網並重新安裝應用。"</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"靜音"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"音訊"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"專案主頁"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"預覽可能包含遠端電腦上的個人或工作資訊。開啟後,每次連線會自動將一張畫面儲存在本機並顯示在最近連線中。你可以隨時關閉並清除。"</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"高"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"預設"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"預設關閉。開啟後,手機連線時進入並保持橫屏;關閉時跟隨裝置方向。平板和多視窗不受影響。"</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"滑鼠右鍵,按住並拖動,鬆開結束拖拽"</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"滑鼠左鍵,按住並拖動,鬆開結束拖拽"</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"滑鼠控制"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"滑鼠滾輪,上下滑動,可滑出按鍵範圍"</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">",丟包率 "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">",傳送 "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">",已儲存密碼"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">",接收 "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">",點選重試"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" · Abgeschlossen"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" Übertragung fehlgeschlagen"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" Geräte"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" Artikel · "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" ungelesene Artikel"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"CrossDesk Remote-Verbindung"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL Version 3"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"OpenFEC-Hinweis"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"OpenFEC Lizenz und Quelle"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"P2P-Direktverbindung"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRTP enabled"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"TURN-Relais"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"{0}-Übertragung fehlgeschlagen"</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"Geräte: {0}"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"{0} ungelesene Elemente"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0}, Empfangen von {1}, Senden von {2}, Paketverlustrate {3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● Verbunden"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"Zum Aktualisieren nach unten ziehen"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"Verlustrate"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"Mittel"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"Priorisiert die Aufrechterhaltung eines flüssigen Bildes und reduziert die Auflösung stärker, wenn die Bandbreite oder der Codierungsdruck hoch ist."</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"Niedrig"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"Verwendung eines benutzerdefinierten Servers"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"Passwort speichern"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"Empfangene Datei speichern"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"Remote-Bildschirmvorschauen speichern"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"Remote-Bildschirmvorschau speichern?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"Die Signalverbindung ist unterbrochen. Bitte stellen Sie die Verbindung wieder her."</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"Das Laden der Ankündigung ist fehlgeschlagen. Bitte versuchen Sie es erneut."</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"Einzelheiten zur Ankündigung"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"Insgesamt "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"Insgesamt {0} Artikel ·{1} ungelesene Artikel"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"Ungefähr"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"Über CrossDesk"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"Ungefähr, es gibt eine neue Version"</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"Schließen"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"Bedienbügel schließen"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"Privatsphäre-Bildschirm deaktivieren"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"Gleicht Klarheit und Glätte aus und verringert die Auflösung bei konstantem Druck nur allmählich."</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"Auflösung"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"Löschen"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"Ansage löschen"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"Datensatz löschen"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"Diese Ankündigung löschen?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"Ankündigung aktualisieren"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"Zwischenablage"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"Laden…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"Mehr laden"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"Update empfohlen"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"Die Initialisierung der nativen Verbindung ist fehlgeschlagen"</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"Senden"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"Senden "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"Klicken Sie nach dem Senden auf der Remote-Tastatur auf „Einfügen“. Der empfangene Remote-Text kann manuell auf den lokalen Computer kopiert werden."</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"Datei senden"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"Text senden"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"Abbrechen"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"Verbindung abbrechen"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"Jede Komponente unterliegt der aufgeführten Open-Source-Lizenz und die gewährten Rechte unterliegen nicht den allgemeinen Nutzungsbedingungen dieser Anwendung. Für die Anzeige des Quellcodes ist eine Internetverbindung erforderlich."</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"Insgesamt"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"Zustimmen und fortfahren"</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"Bleiben Sie im Hintergrund verbunden. Klicken Sie, um zum Remote-Desktop zurückzukehren"</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"Hintergrund-Keepalive konnte nicht gestartet werden. Bitte lassen Sie die Anwendung im Vordergrund"</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"Hintergrund-Keepalive konnte nicht gestartet werden. Bitte lassen Sie die Anwendung im Vordergrund und stellen Sie die Verbindung erneut her."</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"Die Hintergrundverbindung wurde beendet. Bitte stellen Sie die Verbindung wieder her."</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"Der Hintergrundverbindungsdienst wurde beendet. Bitte stellen Sie die Verbindung wieder her."</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"Neue Vorschauen werden bei nachfolgenden Verbindungen weiterhin gespeichert."</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"Online"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"Geben Sie den Hostnamen oder die IPv4-Adresse ein, ohne Protokoll und Pfad"</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"Ton"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"Remote-Text kopieren"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"Behalten Sie immer eine höhere Auflösung bei und reduzieren Sie die Bildrate, um die Bildschärfe zu gewährleisten, wenn die Bandbreite nicht ausreicht."</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"Medienverschlüsselung"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"Buchstaben & Zahlen"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"Der sichere Speicher oder die Initialisierung des Zertifikats ist fehlgeschlagen. Bitte versuchen Sie es erneut oder löschen Sie die Anwendungsdaten"</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"Fertig"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"Sie können nach Abschluss der Datenschutzautorisierung nach Updates suchen"</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"Registrieren Sie die lokale Identität, nachdem Sie die Datenschutzautorisierung abgeschlossen haben"</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"Das Speichern des Passworts ist fehlgeschlagen. Diese Verbindung kann weiterhin verwendet werden"</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"Passwörter werden verschlüsselt im nativen Android Keystore gespeichert"</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"Remote-ID"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"Peer-ID "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"Peer-ID {0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"Alle auf diesem Computer gespeicherten Remote-Vorschauen werden gelöscht und die Verbindungsdatensätze und Passwörter bleiben erhalten."</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"Bildschirmausrichtung"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"Fernbedienungsmenü erweitern"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"Remote-Verbindung wegen Benachrichtigung getrennt"</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"Passwort gespeichert"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"Das gespeicherte Passwort kann nicht gelesen werden, bitte geben Sie es erneut ein."</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"Das gespeicherte Passwort kann nicht beibehalten werden, bitte geben Sie es erneut ein."</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"Ctrl+Alt+Del gesendet"</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"Hintergrund-Keep-Alive ist aktiviert, wechselnde Anwendungen können die Verbindung weiterhin aufrechterhalten"</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"Hintergrund-Keepalive aktiviert, um Verbindungsbenachrichtigungen zurückzugeben oder zu trennen"</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"Zugegeben"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"Lesen"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"Verbunden "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"Mit Server verbunden"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"Bildrate"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"Flüssig"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"Balance"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"Die Lizenzdatei in der Anwendung fehlt oder ist beschädigt. Bitte installieren Sie die Anwendung neu."</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"Die Entwicklungsversion enthält Änderungen, die noch nicht veröffentlicht wurden. Der folgende Link ist der Quellcode der Basisversion."</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"Speichern aktivieren"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"Privatsphärenschutz aktivieren"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"Open-Source-Komponenten"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"Beschreibung der Open-Source-Softwarerechte"</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"Dies ist derzeit die neueste Version"</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"Derzeit unterstützt die Gegenstelle keine direkte Eingabe dieses Textes. Bitte verwenden Sie eine Version des gesteuerten Endes, die die Texteingabe unterstützt, oder senden Sie sie über die Zwischenablage."</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"Nach erfolgreicher Verbindung wird hier das nächstgelegene Remote-Gerät angezeigt."</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"Ich habe die Datenschutzerklärung gelesen und bin damit einverstanden"</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"Ausgewählte Datei (kann nicht gelesen werden oder nicht genügend Speicherplatz)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"Die Geräte-ID wurde nicht gefunden"</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"Das Senden des Befehls ist fehlgeschlagen. Bitte versuchen Sie es erneut"</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"Der Befehl wurde gesendet und wartet auf die Bestätigung des Computers ..."</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"Autorisierungsstatus"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"Empfangen"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"erhalten "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"Lokaler Decoder"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"Widerrufen"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"Nach dem Abheben wird die Fernverbindung beendet, die Bildschirmvorschau geschlossen und die bestehende Vorschau gelöscht. Der Verbindungsdatensatz und das Passwort bleiben erhalten und vor der erneuten Verbindung ist eine erneute Autorisierung erforderlich."</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"Netzwerkberechtigung entziehen"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"Netzwerkberechtigung entziehen?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"Fernbedienungsmenü einklappen"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"Tastatur ausblenden"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"Maus ausblenden"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"Daten"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"Das Speichern der Datei ist fehlgeschlagen. Bitte versuchen Sie es erneut"</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"Datei gespeichert"</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"Zeitüberschreitung bei der Dateiauswahl und Sitzung beendet"</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"Der Text ist größer als 128 KB"</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"Trennen"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"Kehrt nach dem Trennen der Verbindung zur Startseite zurück"</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"Remote-Verbindung trennen?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"Trennen"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"Neue Version"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"Neue Version verfügbar: v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"Neue Version verfügbar: v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"Keine ungelesenen Ankündigungen"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"Lesestatus kann nicht lokal gespeichert werden. Bitte versuchen Sie es erneut."</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"Diese Ankündigung kann nicht gelöscht werden. Bitte versuchen Sie es erneut."</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"Remote-Verbindung konnte nicht initiiert werden"</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"Open-Source-Lizenz kann nicht gelesen werden"</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"Bildschirm"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"Passwort anzeigen"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"Jetzt nicht"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"Keine Ankündigungen"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"Letzte Verbindungen"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"Server"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"Server TLS certificate-Überprüfung fehlgeschlagen"</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"Serveradresse"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"Die Serveranmeldung ist fehlgeschlagen. Bitte überprüfen Sie die Serveradresse und das Netzwerk"</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"Server-Port"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"Zeitüberschreitung bei der Serververbindung. Bitte überprüfen Sie die Serveradresse und das Netzwerk"</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"Nicht aktiviert"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"Es wurde kein Browser zum Öffnen des Links gefunden"</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"Nicht autorisiert"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"Ungelesen"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"Server nicht verbunden · Klicken Sie, um es erneut zu versuchen"</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"Diese Anwendung verwendet OpenFEC zur Vorwärtsfehlerkorrektur unterCeCILL-C1.0, was eingeschränkte Garantien und Haftungsbeschränkungen bietet. Einzelheiten finden Sie in der vollständigen Lizenz."</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"Lokale ID "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"Lokale ID {0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"Quellcode und Build-Anweisungen für diese Version"</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"Die Quellrevision ist für diesen Build nicht verfügbar."</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"Zum Aktualisieren loslassen"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"Bauanleitung"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"Erstellen Sie Rezepte und Projekt-Patches"</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"Quellcode anzeigen"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"Prüfung fehlgeschlagen. Bitte versuchen Sie es später noch einmal."</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"Suchen Sie nach Updates"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"Sichtschutz wird geschlossen…"</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"Sichere Verbindung wird hergestellt…"</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"Privatsphärenschutz einschalten..."</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"Suche nach Updates…"</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"Löschen..."</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"Lokale ID erhalten..."</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"Ausgewählte Datei wird gelesen..."</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"Verbinden"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"Verbindung zum Signalisierungsserver wird hergestellt..."</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"Verbindung zum Server herstellen..."</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"Verbindung zum Server wird wiederhergestellt..."</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"Für dieses Gerät ist keine Dateiauswahl verfügbar"</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"Klar"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"Vorschaubilder löschen"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"Vorschaubilder löschen?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"Quellcode und Build-Anweisungen"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"Quellcode-Version"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"Version"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"Urheberrechts- und Lizenzerklärung"</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"Computertastatur"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"Qualität"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"Videopräferenz"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"Videolatenz"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"Videoeinstellungen"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"Videoqualität"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"Bildrate der Videoaufnahme"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"Touchpad"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"Relative Maus"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"Verstanden"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"Okay"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"Bestätigen"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"Verbindung bestätigen"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"Offline"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"Leertaste"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"Der Portbereich liegt zwischen 1 und 65535"</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"Symbole & Tasten"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"Warten Sie, bis der Computer in den Datenschutzstatus zurückkehrt. Ältere Computer unterstützen dies möglicherweise nicht."</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"Warten auf Remote-Bildschirminformationen"</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"Warten auf den Remote-Bildschirm ..."</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"Datenschutzzustimmung ausstehend"</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"Systemtastatur"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"Direkte Berührung"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"Absolute Maus"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"Netzwerk"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"Benutzerdefiniert"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"Virtuelle Maus"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"Der Remote-Datenschutzbildschirm wurde über die lokale Verknüpfung entsperrt. Sperren Sie es erneut, um die Privatsphäre wiederherzustellen und das Entsperren lokaler Verknüpfungen zu deaktivieren."</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"Die kontrollierte Version ist zu niedrig, bitte aktualisieren Sie auf die neueste Version."</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"Remote-Encoder"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"Text, der an die Gegenstelle gesendet werden soll"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"Video"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"Lizenz"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"Lizenzen und Urheberrechtshinweise"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"Die Lizenz- und Urheberrechtshinweise sind offline verfügbar."</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"Die Geräteidentität konnte nicht gespeichert werden. Bitte überprüfen Sie den sicheren Speicher"</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"Geräteidentität und ausgewählte gespeicherte Zugangskennwörter werden mit Android Keystore verschlüsselt. Verbindungsdatensätze werden nur lokal gespeichert und sind nicht in der Gerätesicherung enthalten. Der Inhalt der Zwischenablage wird gelöscht, wenn die Sitzung beendet wird. Bleiben Sie mit Bild-in-Bild- oder verbundenen Benachrichtigungen in der Sitzung, nachdem Sie die App verlassen haben, und trennen Sie die Verbindung innerhalb der App oder der Benachrichtigungen."</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"Einstellungen"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"Einstellungen, es gibt eine neue Version"</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"Zugangspasswort"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"Das Zugangspasswort ist falsch, bitte geben Sie es erneut ein."</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"Zertifikatsüberprüfung fehlgeschlagen · Klicken Sie, um es erneut zu versuchen"</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"Bitte füllen Sie zunächst die Online-Autorisierung aus"</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"Bitte aktivieren Sie den CrossDesk-Systemdienst auf der Gegenseite und versuchen Sie es erneut"</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"Die Anfrage ist abgelaufen und der Datenschutzstatus des Computers wurde nicht bestätigt."</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"Bitte warten Sie, bis die aktuelle Datei gesendet wird."</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"Bitte geben Sie einen Port im Bereich 1–65535 ein"</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"Geben Sie eine 9-stellige Geräte-ID ein"</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"Bitte geben Sie ein gültiges Zugangspasswort ein"</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"Einstellungen konnten nicht angewendet werden. Versuchen Sie es erneut."</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"Softwarelizenz"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"Zurück"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"Zurück zur Steuerleiste"</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"Noch kein Verbindungsdatensatz"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"Diese Ankündigung wurde aktualisiert oder gelöscht. Bitte kehren Sie zur Ankündigungsliste zurück, um sie anzuzeigen."</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"Remote-Aufgabe ist geschlossen"</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"Remotedesktop"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"Remote-Desktop-Touchbereich"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"Remote-Desktop: Berühren und klicken, zum Verschieben ziehen, die rechte Taste gedrückt halten, zum Zoomen zusammenziehen, hineinzoomen und den Bildschirm mit zwei Fingern ziehen, mit zwei Fingern nach oben und unten streichen, um im Originalmaßstab zu scrollen"</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"Remote-Computer"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"Das Remote-Fenster wurde geschlossen oder der Hintergrund-Keepalive konnte nicht gestartet werden und die Sitzung wurde beendet."</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"Remote-Verbindung"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"Falsches Remote-Passwort"</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"Die Gegenstelle hat die Verbindung geschlossen"</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"Die Gegenstelle ist derzeit nicht verfügbar"</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"Das Passwort für den Fernzugriff ist abgelaufen. Bitte verbinden Sie sich erneut."</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"Verbinden"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"Verbindung "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"Verbindung fehlgeschlagen"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"Verbindung fehlgeschlagen · Klicken Sie, um es erneut zu versuchen"</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"Verbindung verloren"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"Verbindung beendet"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"Verbindungsverzögerung (RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"Verfügbar bei Verbindung mit einem unterstützten Remote-Gerät."</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"Querformatbildschirm sperren, nachdem die Verbindung hergestellt wurde"</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"Verbindungsaufforderung"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"Verbindungsmethode"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"Mit dem Server verbinden"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"Stellen Sie eine Verbindung zum Server her, um die neuesten Ankündigungen zu erhalten."</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"Zeitüberschreitung bei der Verbindung. Bitte überprüfen Sie die Geräte-ID, den Server und den Remote-Online-Status"</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"Mit Remote-Desktop verbinden"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"Rücktaste"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"Mitteilungen"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"Einige Vorschauen konnten nicht gelöscht werden. Bitte versuchen Sie es erneut."</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"Versuchen Sie es erneut"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"Versuchen Sie erneut, den Lesestatus zu speichern"</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"Sperren"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"Tastatur"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"Lesen und autorisieren"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"Datenschutz"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"Datenschutz und Autorisierung"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"Sichtschutz"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"Sichtschutz aus"</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"Sichtschutz an"</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"Datenschutzbildschirm entsperrt"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"Anomalie des Sichtschutzbildschirms"</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"Der Datenschutzbildschirm ist vorübergehend nicht verfügbar"</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"Der Status des Datenschutzbildschirms ist abgelaufen und wartet auf Computerbestätigung"</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"Status des Datenschutzbildschirms muss bestätigt werden"</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"Datenschutzrichtlinie"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"Die Datenschutzerklärung kann vorübergehend nicht gelesen werden. Bitte verweigern Sie die Verbindung zum Internet und installieren Sie die Anwendung neu."</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"Stumm"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"Audio"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"Projektwebsite"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"Vorschauen können persönliche oder geschäftliche Informationen vom Remote-Computer enthalten. Nach dem Einschalten speichert jede Verbindung automatisch ein Bild auf dem Gerät und zeigt es in den letzten Verbindungen an. Sie können jederzeit schließen und löschen."</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"Hoch"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"Standard"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"Standardmäßig deaktiviert. Wenn es eingeschaltet ist, wird der Bildschirm angezeigt und bleibt im Querformat, wenn das Telefon angeschlossen ist. Wenn es ausgeschaltet ist, folgt es der Richtung des Geräts. Tablets und Multi-Windows sind nicht betroffen."</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"Rechte Maustaste, drücken und ziehen, loslassen, um das Ziehen zu beenden"</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"Linke Maustaste, drücken und ziehen, loslassen, um das Ziehen zu beenden"</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"Maussteuerung"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"Mausrad, nach oben und unten schieben, um aus dem Tastenbereich zu gleiten"</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">", Paketverlustrate "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">", senden "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">", Passwort gespeichert"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">", empfangen "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">", klicken Sie auf Wiederholen"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" · Completado"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" Transferencia fallida"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" dispositivos"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" Artículo · "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" elementos no leídos"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"Conexión remota CrossDesk"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL versión 3"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"Aviso OpenFEC"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"Licencia y fuente OpenFEC"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"Conexión directa P2P"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRTP habilitado"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"Relé de GIRO"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"Error en la transmisión {0}"</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"Dispositivos: {0}"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"{0} elementos no leídos"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0}, recepción de {1}, envío de {2}, tasa de pérdida de paquetes {3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● Conectado"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"Tire hacia abajo para actualizar"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"Tasa de pérdidas"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"Media"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"Da prioridad a mantener la imagen fluida y reducirá la resolución de manera más agresiva cuando el ancho de banda o la presión de codificación sean altos."</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"Baja"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"Usando un servidor personalizado"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"Guardar contraseña"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"Guardar archivo recibido"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"Guardar vistas previas de pantalla remotas"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"¿Guardar vista previa de pantalla remota?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"La conexión de señalización está interrumpida, vuelva a conectarse."</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"Error al cargar el anuncio, inténtelo de nuevo."</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"Detalles del anuncio"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"totales "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"Total de elementos {0}· Elementos no leídos {1}"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"Acerca de"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"Acerca de CrossDesk"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"Acerca de, hay una nueva versión."</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"Cerrar"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"Cerrar barra de control"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"Desactivar la pantalla de privacidad"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"Equilibra la claridad y la suavidad, reduciendo solo gradualmente la resolución bajo presión constante."</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"Resolución"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"Eliminar"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"Eliminar anuncio"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"Eliminar registro"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"¿Eliminar este anuncio?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"Actualizar anuncio"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"Portapapeles"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"Cargando…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"Cargar más"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"Actualización recomendada"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"Falló la inicialización de la conexión nativa"</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"Enviar"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"Enviar "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"Después de enviar, haga clic en \"Pegar\" en el teclado remoto. El texto remoto recibido se puede copiar manualmente a la máquina local."</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"Enviar archivo"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"Enviar texto"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"Cancelar"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"Cancelar conexión"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"Cada componente está sujeto a la licencia de código abierto enumerada y los derechos otorgados no están sujetos a los términos generales de uso de esta aplicación. Para ver el código fuente se requiere una conexión a Internet."</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"Total"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"Aceptar y continuar"</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"Manténgase conectado en segundo plano, haga clic para regresar al escritorio remoto"</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"No se pudo iniciar el mantenimiento en segundo plano; mantenga la aplicación en primer plano"</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"No se pudo iniciar el mantenimiento en segundo plano. Mantenga la aplicación en primer plano y vuelva a conectarse."</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"La conexión en segundo plano ha finalizado; vuelva a conectarse."</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"El servicio de conexión en segundo plano se detuvo; vuelva a conectarse."</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"Las nuevas vistas previas aún se guardarán en conexiones posteriores."</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"En línea"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"Complete el nombre del host o la dirección IPv4, sin protocolo ni ruta"</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"Sonido"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"Copiar texto remoto"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"Mantenga siempre una resolución más alta y reduzca la velocidad de fotogramas para garantizar la claridad de la imagen cuando el ancho de banda sea insuficiente."</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"Cifrado de medios"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"Letras y números"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"Error en el almacenamiento seguro o en la inicialización del certificado. Inténtelo de nuevo o borre los datos de la aplicación."</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"Hecho"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"Puede buscar actualizaciones después de completar la autorización de privacidad"</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"Registre la identidad local después de completar la autorización de privacidad"</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"Error al guardar la contraseña, esta conexión aún se puede utilizar"</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"Las contraseñas se guardan cifradas en el almacén de claves nativo de Android"</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"Identificación remota"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"Identificación de pares "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"ID de igual {0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"Se eliminarán todas las vistas previas remotas guardadas en esta máquina y se conservarán los registros de conexión y las contraseñas."</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"Orientación de la pantalla"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"Ampliar el menú del control remoto"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"Conexión remota desconectada de la notificación"</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"Contraseña guardada"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"La contraseña guardada no se puede leer; vuelva a ingresarla."</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"La contraseña guardada no se puede conservar; vuelva a ingresarla."</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"Ctrl+Alt+Del enviado"</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"Mantener vivo en segundo plano está habilitado, cambiar de aplicación puede continuar manteniendo la conexión"</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"Keepalive en segundo plano habilitado para regresar o desconectarse de las notificaciones de conexión"</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"Concedido"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"Leer"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"Conectado "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"Conectado al servidor"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"Velocidad de fotogramas"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"Fluidez"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"Equilibrado"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"El archivo de licencia de la aplicación falta o está dañado; reinstale la aplicación."</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"La versión de desarrollo contiene modificaciones que aún no han sido publicadas. El siguiente enlace es el código fuente de la versión básica."</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"Activar guardado"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"Habilitar pantalla de privacidad"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"Componentes de código abierto"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"Descripción de los derechos del software de código abierto"</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"Esta es actualmente la última versión."</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"Actualmente, el extremo remoto no admite la entrada directa de este texto. Utilice una versión del extremo controlado que admita la entrada de texto o envíela a través del portapapeles."</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"Tras una conexión exitosa, aquí se mostrará el dispositivo remoto más cercano."</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"He leído y acepto la Política de Privacidad"</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"Archivo seleccionado (no se puede leer o no hay suficiente espacio)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"No se encontró el ID del dispositivo"</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"Error al enviar el comando, inténtelo de nuevo"</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"El comando ha sido enviado, esperando confirmación por computadora..."</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"Estado de autorización"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"Recibir"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"recibir "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"Decodificador local"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"Retirar"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"Después de retirarse, se detendrá la conexión remota, se cerrará la vista previa de la pantalla y se borrará la vista previa existente. El registro de conexión y la contraseña se conservarán y se requerirá autorización nuevamente antes de volver a conectarse."</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"Retirar la autorización de red"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"¿Retirar la autorización de la red?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"Contraer menú de control remoto"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"Ocultar teclado"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"Ocultar ratón"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"Datos"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"Error al guardar el archivo, inténtelo de nuevo"</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"Archivo guardado"</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"Se agotó el tiempo de selección de archivos y la sesión finalizó"</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"El texto supera los 128 KB"</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"Desconectar"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"Volverá a la página de inicio después de desconectarse"</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"¿Desconectar la conexión remota?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"Desconectar"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"Nueva versión"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"Nueva versión disponible :v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"Nueva versión disponible: v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"No hay anuncios no leídos"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"No se puede guardar el estado de lectura localmente. Por favor inténtalo de nuevo."</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"No se puede eliminar este anuncio. Por favor inténtalo de nuevo."</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"No se puede iniciar la conexión remota"</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"No se puede leer la licencia de código abierto"</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"Pantalla"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"Mostrar contraseña"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"Ahora no"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"Sin anuncios"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"Conexiones recientes"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"Servidor"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"Error en la verificación del certificado Server TLS"</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"Dirección del servidor"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"Error al iniciar sesión en el servidor; verifique la dirección y la red del servidor"</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"Puerto del servidor"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"Se agotó el tiempo de espera de la conexión del servidor; verifique la dirección y la red del servidor."</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"No habilitado"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"No se encontró ningún navegador para abrir el enlace."</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"No autorizado"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"No leído"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"Servidor no conectado · Haga clic para intentar nuevamente"</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"Esta aplicación utiliza OpenFEC para la corrección de errores directos bajoCeCILL-C1.0, que proporciona garantías limitadas y limita la responsabilidad. Consulte la licencia completa para obtener más detalles."</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"Identificación local "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"ID local {0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"Código fuente e instrucciones de compilación para esta versión"</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"La revisión fuente no está disponible para esta compilación."</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"Suelte para actualizar"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"Instrucciones de construcción"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"Crear recetas y parches de proyectos"</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"Ver código fuente"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"La verificación falló. Inténtelo de nuevo más tarde."</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"Buscar actualizaciones"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"Cerrando la pantalla de privacidad..."</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"Estableciendo una conexión segura..."</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"Activando la pantalla de privacidad..."</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"Buscando actualizaciones..."</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"Borrando..."</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"Obtención de identificación local..."</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"Leyendo el archivo seleccionado..."</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"Conexión"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"Conectándose al servidor de señalización..."</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"Conectándose al servidor..."</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"Reconectándose al servidor..."</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"No hay selector de archivos disponible para este dispositivo"</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"Borrar"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"Borrar vistas previas"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"¿Borrar vistas previas?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"Código fuente e instrucciones de compilación"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"Versión del código fuente"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"Versión"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"Declaración de derechos de autor y licencia"</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"Teclado de computadora"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"Calidad"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"Preferencia de vídeo"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"Latencia de vídeo"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"Configuración de vídeo"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"Calidad de vídeo"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"Velocidad de fotogramas de captura de vídeo"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"Panel táctil"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"Ratón relativo"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"Entendido"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"Aceptar"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"Confirmar"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"Confirmar conexión"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"Sin conexión"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"Espacio"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"El rango de puertos es 1–65535"</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"Símbolos y teclas"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"Espere a que la computadora vuelva al estado de pantalla de privacidad; Es posible que las computadoras más antiguas no lo admitan."</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"Esperando información de la pantalla remota"</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"Esperando la pantalla remota..."</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"Esperando autorización de privacidad"</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"Teclado del sistema"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"Toque directo"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"Ratón absoluto"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"Red"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"Personalizado"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"Ratón virtual"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"La pantalla de privacidad remota se desbloqueó usando el acceso directo local. Bloquéelo nuevamente para restaurar la privacidad y deshabilitar el desbloqueo de accesos directos locales."</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"La versión controlada es demasiado baja; actualice a la última versión."</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"Codificador remoto"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"Texto a enviar al extremo remoto"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"Vídeo"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"Licencia"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"Licencias y avisos de derechos de autor"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"La licencia y el aviso de derechos de autor están disponibles sin conexión."</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"No se pudo guardar la identidad del dispositivo; verifique el almacenamiento seguro"</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"La identidad del dispositivo y las contraseñas de acceso guardadas seleccionadas se cifran mediante Android Keystore. Los registros de conexión solo se guardan localmente y no se incluyen en la copia de seguridad del dispositivo. El contenido del portapapeles se borra cuando finaliza la sesión. Manténgase en sesión con imagen en imagen o notificaciones conectadas después de salir de la aplicación y desconéctese dentro de la aplicación o las notificaciones."</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"Ajustes"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"Configuración, hay una nueva versión."</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"Contraseña de acceso"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"La contraseña de acceso es incorrecta, por favor vuelva a ingresarla."</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"Error en la verificación del certificado · Haga clic para intentar nuevamente"</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"Primero complete la autorización en línea"</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"Habilite el servicio del sistema CrossDesk en el extremo remoto y vuelva a intentarlo."</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"Se agotó el tiempo de espera de la solicitud y no se ha confirmado el estado de la pantalla de privacidad de la computadora."</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"Espere a que se envíe el archivo actual."</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"Ingrese un puerto en el rango 1–65535"</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"Ingrese una ID de dispositivo de 9 dígitos"</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"Por favor ingrese una contraseña de acceso válida"</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"No se pudieron aplicar las configuraciones. Intentar otra vez."</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"Licencia de software"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"Volver"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"Volver a la barra de control"</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"Aún no hay registro de conexión"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"Este anuncio ha sido actualizado o eliminado, regrese a la lista de anuncios para verlo."</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"La tarea remota está cerrada"</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"Escritorio remoto"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"Área táctil del escritorio remoto"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"Escritorio remoto: toque y haga clic, arrastre para mover, presione y mantenga presionado el botón derecho, pellizque para hacer zoom, acerque y arrastre la pantalla con dos dedos, deslice hacia arriba y hacia abajo con dos dedos para desplazarse a la escala original"</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"Computadora remota"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"La ventana remota se cerró o el mantenimiento en segundo plano no se pudo iniciar y la sesión finalizó."</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"Conexión remota"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"Contraseña remota incorrecta"</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"El extremo remoto ha cerrado la conexión"</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"El extremo remoto no está disponible actualmente"</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"La contraseña de acceso remoto ha caducado, vuelva a conectarse."</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"Conectar"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"Conexión "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"Error de conexión"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"Error de conexión · Haga clic para intentarlo de nuevo"</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"Conexión perdida"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"Conexión finalizada"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"Retardo de conexión (RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"Disponible cuando se conecta a un dispositivo remoto compatible."</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"Bloquear la pantalla horizontal después de establecer la conexión"</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"Aviso de conexión"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"Método de conexión"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"Conectarse al servidor"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"Conéctese al servidor para obtener los últimos anuncios."</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"Se agotó el tiempo de conexión; verifique el ID del dispositivo, el servidor y el estado en línea remoto"</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"Conectarse al escritorio remoto"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"Retroceso"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"Anuncios"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"Algunas vistas previas no se pudieron borrar, inténtalo de nuevo."</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"Inténtalo de nuevo"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"Reintentar guardar el estado de lectura"</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"Bloquear"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"Teclado"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"Leer y autorizar"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"Privacidad"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"Privacidad y autorización"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"Pantalla de privacidad"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"Privacidad desactivada"</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"Privacidad activada"</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"Pantalla de privacidad desbloqueada"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"Anormalidad en la pantalla de privacidad"</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"La pantalla de privacidad no está disponible temporalmente"</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"El estado de la pantalla de privacidad ha caducado, esperando confirmación por computadora"</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"Estado de la pantalla de privacidad por confirmar"</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"Política de privacidad"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"La política de privacidad no se puede leer temporalmente. Rechace conectarse a Internet y reinstale la aplicación."</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"Silencio"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"Audio"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"Sitio web del proyecto"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"Las vistas previas pueden contener información personal o laboral de la computadora remota. Después de encenderlo, cada conexión guardará automáticamente una imagen en la máquina y la mostrará en las conexiones recientes. Puedes cerrar y borrar en cualquier momento."</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"Alta"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"Predeterminado"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"Desactivado de forma predeterminada. Cuando esté encendido, la pantalla entrará y permanecerá en orientación horizontal cuando el teléfono esté conectado; cuando esté apagado, seguirá la dirección del dispositivo. Las tabletas y las ventanas múltiples no se ven afectadas."</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"Botón derecho del mouse, presione y arrastre, suelte para finalizar el arrastre"</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"Botón izquierdo del mouse, presione y arrastre, suelte para finalizar el arrastre"</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"Control del ratón"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"Rueda del mouse, deslícela hacia arriba y hacia abajo para salir del rango de teclas"</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">", tasa de pérdida de paquetes "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">", enviar "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">", contraseña guardada"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">", recibir "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">", haga clic en Reintentar"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" · Terminé"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" Échec du transfert"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" appareils"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" Article · "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" éléments non lus"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"Connexion à distance CrossDesk"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL version 3"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"Avis OpenFEC"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"Licence et source OpenFEC"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"Connexion directe P2P"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRTP activé"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"Relais TOUR"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"Échec de la transmission {0}"</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"Appareils : {0}"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"Éléments non lus {0}"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0}, réception de {1}, envoi de {2}, taux de perte de paquets {3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● Connecté"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"Déroulez pour actualiser"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"Taux de perte"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"Moyenne"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"Donne la priorité au maintien de la fluidité de l\'image et réduira la résolution de manière plus agressive lorsque la bande passante ou la pression d\'encodage est élevée."</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"Faible"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"Utilisation d\'un serveur personnalisé"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"Enregistrer le mot de passe"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"Enregistrer le fichier reçu"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"Enregistrer les aperçus de l\'écran distant"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"Enregistrer l\'aperçu de l\'écran distant ?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"La connexion de signalisation est interrompue, veuillez vous reconnecter."</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"Le chargement de l\'annonce a échoué, veuillez réessayer."</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"Détails de l\'annonce"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"Total "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"Total des éléments {0}· Éléments non lus {1}"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"À propos"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"À propos de CrossDesk"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"À propos, il existe une nouvelle version"</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"Fermer"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"Fermer la barre de contrôle"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"Désactiver l\'écran de confidentialité"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"Équilibre la clarté et la douceur, en réduisant seulement progressivement la résolution sous une pression constante."</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"Résolution"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"Supprimer"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"Supprimer l\'annonce"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"Supprimer l\'enregistrement"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"Supprimer cette annonce ?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"Actualiser l\'annonce"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"Presse-papiers"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"Chargement…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"Charger plus"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"Mise à jour conseillée"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"L\'initialisation de la connexion native a échoué"</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"Envoi"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"Envoyer "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"Après l\'envoi, cliquez sur \"Coller\" sur le clavier distant. Le texte distant reçu peut être copié manuellement sur la machine locale."</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"Envoyer le fichier"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"Envoyer un SMS"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"Annuler"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"Annuler la connexion"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"Chaque composant est soumis à la licence open source répertoriée, et les droits accordés ne sont pas soumis aux conditions générales d\'utilisation de cette application. La visualisation du code source nécessite une connexion Internet."</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"Total"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"Accepter et continuer"</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"Restez connecté en arrière-plan, cliquez pour revenir au bureau distant"</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"Échec du démarrage de Keepalive en arrière-plan, veuillez conserver l\'application au premier plan"</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"L\'application Keepalive en arrière-plan n\'a pas pu démarrer. Veuillez conserver l\'application au premier plan et vous reconnecter."</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"La connexion en arrière-plan est terminée, veuillez vous reconnecter."</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"Le service de connexion en arrière-plan s\'est arrêté, veuillez vous reconnecter."</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"Les nouveaux aperçus seront toujours enregistrés lors des connexions ultérieures."</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"En ligne"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"Remplissez le nom d\'hôte ou l\'adresse IPv4, sans protocole ni chemin"</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"Son"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"Copier le texte distant"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"Maintenez toujours une résolution plus élevée et réduisez la fréquence d\'images pour garantir la clarté de l\'image lorsque la bande passante est insuffisante."</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"Cryptage multimédia"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"Lettres et chiffres"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"Échec du stockage sécurisé ou de l\'initialisation du certificat. Veuillez réessayer ou effacer les données de l\'application."</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"Terminé"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"Vous pouvez vérifier les mises à jour après avoir terminé l\'autorisation de confidentialité"</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"Enregistrez l\'identité locale après avoir terminé l\'autorisation de confidentialité"</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"L\'enregistrement du mot de passe a échoué, cette connexion peut toujours être utilisée"</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"Les mots de passe sont enregistrés cryptés dans le Keystore Android natif"</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"ID à distance"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"Identification des pairs "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"ID homologue {0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"Tous les aperçus à distance enregistrés sur cette machine seront supprimés et les enregistrements de connexion et les mots de passe seront conservés."</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"Orientation de l\'écran"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"Développer le menu de la télécommande"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"Connexion à distance déconnectée de la notification"</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"Mot de passe enregistré"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"Le mot de passe enregistré ne peut pas être lu, veuillez le saisir à nouveau."</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"Le mot de passe enregistré ne peut pas être conservé, veuillez le saisir à nouveau."</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"Ctrl+Alt+Del envoyé"</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"Le maintien de l\'activité en arrière-plan est activé, les applications de commutation peuvent continuer à maintenir la connexion."</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"Keepalive en arrière-plan activé pour revenir ou se déconnecter des notifications de connexion"</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"Accordé"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"Lire"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"Connecté "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"Connecté au serveur"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"Fréquence d\'images"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"Fluidité"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"Équilibré"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"Le fichier de licence dans l\'application est manquant ou endommagé, veuillez réinstaller l\'application."</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"La version de développement contient des modifications qui n\'ont pas encore été publiées. Le lien suivant est le code source de la version de base."</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"Activer l’enregistrement"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"Activer l\'écran de confidentialité"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"Composants open source"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"Description des droits sur les logiciels open source"</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"Il s\'agit actuellement de la dernière version"</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"Actuellement, l\'extrémité distante ne prend pas en charge la saisie directe de ce texte. Veuillez utiliser une version de l\'extrémité contrôlée qui prend en charge la saisie de texte ou envoyez-la via le presse-papiers."</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"Une fois la connexion réussie, l\'appareil distant le plus proche sera affiché ici."</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"J\'ai lu et accepté la politique de confidentialité"</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"Fichier sélectionné (impossible en lecture ou espace insuffisant)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"L\'ID de l\'appareil n\'a pas été trouvé"</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"L\'envoi de la commande a échoué, veuillez réessayer"</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"La commande a été envoyée, en attente de confirmation informatique..."</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"Statut d\'autorisation"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"Réception"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"recevoir "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"Décodeur local"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"Retirer"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"Après le retrait, la connexion à distance sera arrêtée, l\'aperçu de l\'écran sera fermé et l\'aperçu existant sera effacé. L\'enregistrement de connexion et le mot de passe seront conservés et une autorisation sera à nouveau requise avant de se reconnecter."</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"Retirer l\'autorisation réseau"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"Retirer l\'autorisation réseau ?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"Réduire le menu de la télécommande"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"Masquer le clavier"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"Masquer la souris"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"Données"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"L\'enregistrement du fichier a échoué, veuillez réessayer"</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"Fichier enregistré"</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"La sélection de fichier a expiré et la session s\'est terminée"</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"Le texte dépasse 128 Ko"</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"Déconnecter"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"Reviendra à la page d\'accueil après la déconnexion"</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"Déconnecter la connexion à distance ?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"Déconnecter"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"Nouvelle version"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"Nouvelle version disponible : v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"Nouvelle version disponible : v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"Aucune annonce non lue"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"Impossible d\'enregistrer l\'état de lecture localement. Veuillez réessayer."</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"Impossible de supprimer cette annonce. Veuillez réessayer."</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"Impossible d\'établir une connexion à distance"</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"Impossible de lire la licence open source"</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"Écran"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"Afficher le mot de passe"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"Pas maintenant"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"Aucune annonce"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"Connexions récentes"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"Serveur"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"La vérification du certificat Server TLS a échoué"</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"Adresse du serveur"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"Échec de la connexion au serveur, veuillez vérifier l\'adresse du serveur et le réseau"</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"Port du serveur"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"La connexion au serveur a expiré, veuillez vérifier l\'adresse du serveur et le réseau"</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"Non activé"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"Aucun navigateur trouvé pour ouvrir le lien"</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"Non autorisé"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"Non lu"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"Serveur non connecté · Cliquez pour réessayer"</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"Cette application utilise OpenFEC pour la correction d\'erreur directe sousCeCILL-C1.0, qui offre des garanties limitées et limite la responsabilité. Consultez la licence complète pour plus de détails."</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"ID local "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"ID local {0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"Code source et instructions de construction pour cette version"</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"La révision source n\'est pas disponible pour cette version."</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"Relâchez pour actualiser"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"Instructions de construction"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"Créer des recettes et des correctifs de projet"</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"Afficher le code source"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"La vérification a échoué. Veuillez réessayer plus tard."</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"Rechercher des mises à jour"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"Fermeture de l\'écran de confidentialité…"</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"Établir une connexion sécurisée…"</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"Activation de l\'écran de confidentialité..."</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"Vérification des mises à jour…"</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"Effacement..."</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"Obtention d\'une pièce d\'identité locale..."</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"Lecture du fichier sélectionné..."</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"Connexion"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"Connexion au serveur de signalisation..."</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"Connexion au serveur..."</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"Reconnexion au serveur..."</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"Aucun sélecteur de fichiers disponible pour cet appareil"</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"Effacer"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"Supprimer les aperçus"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"Supprimer les aperçus ?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"Code source et instructions de construction"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"Version du code source"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"Version"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"Déclaration de droits d\'auteur et de licence"</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"Clavier d\'ordinateur"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"Qualité"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"Préférence vidéo"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"Latence vidéo"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"Paramètres vidéo"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"Qualité vidéo"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"Fréquence d\'images de capture vidéo"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"Pavé tactile"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"Souris relative"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"Compris"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"D\'accord"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"Confirmer"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"Confirmer la connexion"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"Hors ligne"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"Espace"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"La plage de ports est comprise entre 1 et 65 535."</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"Symboles et touches"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"Attendez que l\'ordinateur revienne à l\'état de l\'écran de confidentialité ; les ordinateurs plus anciens peuvent ne pas le prendre en charge."</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"En attente d\'informations sur l\'écran distant"</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"En attente de l\'écran distant..."</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"En attente d\'une autorisation de confidentialité"</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"Clavier système"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"Toucher direct"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"Souris absolue"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"Réseau"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"Personnalisé"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"Souris virtuelle"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"L\'écran de confidentialité à distance a été déverrouillé à l\'aide du raccourci local. Verrouillez-le à nouveau pour restaurer la confidentialité et désactiver le déverrouillage des raccourcis locaux."</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"La version contrôlée est trop basse, veuillez mettre à niveau vers la dernière version."</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"Encodeur déporté"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"Texte à envoyer à l\'extrémité distante"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"Vidéo"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"Licence"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"Licences et mentions de droits d\'auteur"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"La licence et la notice de copyright sont disponibles hors ligne."</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"L\'identité de l\'appareil n\'a pas pu être enregistrée. Veuillez vérifier le stockage sécurisé."</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"L\'identité de l\'appareil et les mots de passe d\'accès enregistrés sélectionnés sont cryptés à l\'aide d\'Android Keystore. Les enregistrements de connexion sont uniquement enregistrés localement et ne sont pas inclus dans la sauvegarde de l\'appareil. Le contenu du presse-papiers est effacé à la fin de la session. Restez en session avec des notifications d\'image dans l\'image ou connectées après avoir quitté l\'application, et déconnectez-vous de l\'application ou des notifications."</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"Réglages"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"Paramètres, il existe une nouvelle version"</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"Mot de passe d\'accès"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"Le mot de passe d\'accès est erroné, veuillez le saisir à nouveau."</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"Échec de la vérification du certificat · Cliquez pour réessayer"</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"Veuillez d\'abord remplir l\'autorisation en ligne"</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"Veuillez activer le service système CrossDesk sur l\'extrémité distante et réessayer."</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"Le délai de demande a expiré et l\'état de l\'écran de confidentialité de l\'ordinateur n\'a pas été confirmé."</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"Veuillez attendre que le fichier actuel soit envoyé."</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"Veuillez saisir un port compris entre 1 et 65535."</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"Saisissez un identifiant d\'appareil à 9 chiffres"</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"Veuillez saisir un mot de passe d\'accès valide"</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"Impossible d\'appliquer les paramètres. Essayer à nouveau."</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"Licence du logiciel"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"Retour"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"Retour à la barre de contrôle"</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"Aucun enregistrement de connexion pour l\'instant"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"Cette annonce a été mise à jour ou supprimée, veuillez revenir à la liste des annonces pour la consulter."</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"La tâche distante est fermée"</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"Bureau à distance"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"Zone tactile du bureau à distance"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"Bureau à distance : touchez et cliquez, faites glisser pour déplacer, appuyez et maintenez le bouton droit enfoncé, pincez pour zoomer, zoomez et faites glisser l\'écran avec deux doigts, faites glisser de haut en bas avec deux doigts pour faire défiler à l\'échelle d\'origine."</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"Ordinateur distant"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"La fenêtre distante a été fermée ou le keepalive en arrière-plan n\'a pas pu démarrer et la session est terminée."</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"Connexion à distance"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"Mot de passe distant incorrect"</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"L\'extrémité distante a fermé la connexion"</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"L\'extrémité distante est actuellement indisponible"</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"Le mot de passe d\'accès à distance a expiré, veuillez vous reconnecter."</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"Connecter"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"Connexion "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"Échec de la connexion"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"Échec de la connexion · Cliquez pour réessayer"</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"Connexion perdue"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"Connexion terminée"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"Délai de connexion (RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"Disponible lors de la connexion à un appareil distant pris en charge."</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"Verrouiller l\'écran paysage une fois la connexion établie"</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"Invite de connexion"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"Méthode de connexion"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"Connectez-vous au serveur"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"Connectez-vous au serveur pour recevoir les dernières annonces."</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"La connexion a expiré. Veuillez vérifier l\'ID de l\'appareil, le serveur et l\'état en ligne à distance."</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"Connectez-vous au bureau à distance"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"Retour arrière"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"Annonces"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"Certains aperçus n\'ont pas pu être effacés, veuillez réessayer."</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"Réessayez"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"Réessayez d\'enregistrer l\'état de lecture"</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"Verrouillage"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"Clavier"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"Lire et autoriser"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"Confidentialité"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"Confidentialité et autorisation"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"Écran de confidentialité"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"Confidentialité inactive"</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"Confidentialité active"</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"Écran de confidentialité déverrouillé"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"Anomalie de l\'écran de confidentialité"</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"L\'écran de confidentialité est temporairement indisponible"</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"L\'état de l\'écran de confidentialité a expiré, en attente de confirmation par l\'ordinateur"</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"Statut de l\'écran de confidentialité à confirmer"</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"Politique de confidentialité"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"La politique de confidentialité ne peut pas être lue temporairement. Veuillez refuser de vous connecter à Internet et réinstaller l\'application."</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"Muet"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"Audio"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"Site Web du projet"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"Les aperçus peuvent contenir des informations personnelles ou professionnelles provenant de l\'ordinateur distant. Après l\'avoir allumé, chaque connexion enregistrera automatiquement une image sur la machine et l\'affichera dans les connexions récentes. Vous pouvez fermer et effacer à tout moment."</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"Élevée"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"Par défaut"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"Désactivé par défaut. Lorsqu\'il est allumé, l\'écran entre et reste en orientation paysage lorsque le téléphone est connecté ; lorsqu\'il est éteint, il suivra la direction de l\'appareil. Les tablettes et multi-fenêtres ne sont pas concernées."</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"Bouton droit de la souris, appuyez et faites glisser, relâchez pour terminer le glisser"</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"Bouton gauche de la souris, appuyez et faites glisser, relâchez pour terminer le glisser"</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"Contrôle de la souris"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"Molette de la souris, faites-la glisser de haut en bas pour sortir de la plage de touches"</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">", taux de perte de paquets "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">", envoyer "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">", mot de passe enregistré"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">", recevoir "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">", cliquez sur Réessayer"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" ・完了"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" 転送に失敗しました"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" 台のデバイス"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" 記事 ・ "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" 未読項目"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"CrossDeskリモート接続"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL バージョン 3"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"OpenFEC通知"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"OpenFECライセンスとソース"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"P2P直接接続"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRT有効"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"ターンリレー"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"{0}送信失敗"</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"デバイス数: {0}"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"{0}の未読アイテム"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0}、受信{1}、送信{2}、パケットロス率{3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● 接続されました"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"プルダウンして更新します"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"損失率"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"中"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"画像を滑らかに保つことを優先し、帯域幅やエンコード圧力が高い場合には、より積極的に解像度を下げます。"</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"低"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"カスタムサーバーの使用"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"パスワードの保存"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"受信したファイルを保存する"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"リモート画面のプレビューを保存する"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"リモート画面のプレビューを保存しますか?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"信号接続が中断されました。再接続してください。"</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"お知らせを読み込めませんでした。再試行してください。"</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"発表内容"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"合計 "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"合計{0}個のアイテム ·{1}個の未読アイテム"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"概要"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"CrossDesk について"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"について、新しいバージョンがあります"</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"閉じる"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"コントロールバーを閉じる"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"プライバシー画面を無効にする"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"明瞭さと滑らかさのバランスを保ち、一定の圧力下で解像度を徐々に低下させるだけです。"</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"解像度"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"削除"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"お知らせの削除"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"レコードの削除"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"このお知らせを削除しますか?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"リフレッシュのお知らせ"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"クリップボード"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"読み込み中…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"さらに読み込む"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"更新のご案内"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"ネイティブ接続の初期化に失敗しました"</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"送信"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"送信 "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"送信後、リモートキーボードの「貼り付け」をクリックしてください。受信したリモート テキストはローカル マシンに手動でコピーできます。"</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"ファイル送信"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"テキストを送信する"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"キャンセル"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"接続をキャンセルする"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"各コンポーネントは、リストされているオープン ソース ライセンスの対象となり、付与された権利は、このアプリケーションの一般的な使用条件の対象ではありません。ソース コードを表示するには、インターネット接続が必要です。"</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"合計"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"同意して続行"</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"バックグラウンドで接続を維持し、クリックしてリモート デスクトップに戻ります"</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"バックグラウンドのキープアライブを開始できませんでした。アプリケーションをフォアグラウンドで維持してください。"</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"バックグラウンド キープアライブの開始に失敗しました。アプリケーションをフォアグラウンドに保ち、再接続してください。"</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"バックグラウンド接続が終了しました。再接続してください。"</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"バックグラウンド接続サービスが停止しました。再接続してください。"</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"新しいプレビューはその後の接続でも保存されます。"</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"オンライン"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"プロトコルとパスを除いて、ホスト名または IPv4 アドレスを入力します。"</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"サウンド"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"リモートテキストをコピーする"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"帯域幅が不十分な場合は、常に高い解像度を維持し、フレーム レートを下げて画像の鮮明さを確保します。"</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"メディアの暗号化"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"英数字"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"安全なストレージまたは証明書の初期化に失敗しました。再試行するか、アプリケーション データをクリアしてください。"</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"完了"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"プライバシー認証を完了すると、アップデートを確認できます"</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"プライバシー認証完了後、ローカル ID を登録します"</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"パスワードの保存に失敗しました。この接続はまだ使用できます"</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"パスワードは暗号化されてネイティブ Android キーストアに保存されます"</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"リモートID"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"ピアID "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"ピアID{0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"本機に保存されているリモートプレビューはすべて削除され、接続記録とパスワードは保持されます。"</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"画面の向き"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"リモコンメニューを展開する"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"通知によりリモート接続が切断されました"</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"パスワード保存済み"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"保存したパスワードが読み取れないため、再入力してください。"</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"保存したパスワードは保持できませんので、再度入力してください。"</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"Ctrl+Alt+Delを送信しました"</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"バックグラウンドキープアライブが有効になり、アプリケーションを切り替えて接続を維持し続けることができます。"</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"バックグラウンドキープアライブを有効にして、接続通知を返すか接続を切断することができます"</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"認められる"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"読む"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"接続されました "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"サーバーに接続しました"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"フレームレート"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"滑らかさ"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"バランス"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"アプリケーション内のライセンス ファイルが見つからないか破損しています。アプリケーションを再インストールしてください。"</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"開発バージョンには、まだリリースされていない修正が含まれています。以下のリンクは基本バージョンのソースコードです。"</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"保存を有効にする"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"プライバシー画面を有効にする"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"オープンソースコンポーネント"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"オープンソース ソフトウェアの権利の説明"</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"これが現在の最新バージョンです"</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"現在、リモート エンドはこのテキストの直接入力をサポートしていません。テキスト入力をサポートするバージョンのコントロール エンドを使用するか、クリップボード経由で送信してください。"</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"接続に成功すると、最も近いリモート デバイスがここに表示されます。"</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"プライバシーポリシーを読んで同意します"</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"選択したファイル(読み込み不可、または容量不足)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"デバイスIDが見つかりませんでした"</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"コマンドの送信に失敗しました。もう一度お試しください。"</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"コマンドが送信され、コンピューターの確認を待っています..."</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"認可ステータス"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"受信"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"受け取る "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"ローカルデコーダ"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"撤回"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"退会後はリモート接続が停止され、画面プレビューが閉じられ、既存のプレビューが消去されます。接続記録とパスワードは保持され、再接続する前に再度認証が必要になります。"</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"ネットワーク認証の撤回"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"ネットワーク認証を取り消しますか?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"リモコンメニューを折りたたむ"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"キーボードを隠す"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"マウスを隠す"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"データ"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"ファイルの保存に失敗しました。もう一度お試しください。"</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"ファイルを保存しました"</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"ファイル選択がタイムアウトし、セッションが終了しました"</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"テキストが128KBを超えています"</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"切断"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"切断後ホームページに戻ります"</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"リモート接続を切断しますか?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"切断"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"新しいバージョン"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"利用可能な新しいバージョン: v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"利用可能な新しいバージョン: v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"未読のお知らせはありません"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"読み取りステータスをローカルに保存できません。もう一度試してください。"</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"このお知らせは削除できません。もう一度試してください。"</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"リモート接続を開始できません"</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"オープンソースライセンスを読み取れません"</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"ディスプレイ"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"パスワードを表示"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"今は同意しない"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"アナウンスはありません"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"最近の接続"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"サーバー"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"ServerTLS証明書の検証に失敗しました"</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"サーバーアドレス"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"サーバーへのログインに失敗しました。サーバーのアドレスとネットワークを確認してください。"</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"サーバーポート"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"サーバー接続がタイムアウトしました。サーバーのアドレスとネットワークを確認してください。"</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"有効ではありません"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"リンクを開くブラウザが見つかりませんでした"</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"無許可"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"未読"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"サーバーが接続されていません · クリックして再試行してください"</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"このアプリケーションは、限定保証を提供し、責任を制限するCeCILL-C1.0 に基づく前方誤り訂正にOpenFECを使用します。詳細については、完全なライセンスを参照してください。"</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"ローカルID "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"ローカルID{0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"このバージョンのソース コードとビルド手順"</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"このビルドではソース リビジョンが利用できません。"</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"放してリフレッシュ"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"組み立て説明書"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"ビルドレシピとプロジェクトパッチ"</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"ソースコードを表示する"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"チェックに失敗しました。後でもう一度試してください。"</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"アップデートを確認する"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"プライバシー画面を閉じています…"</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"安全な接続を確立中…"</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"プライバシー画面を有効化中…"</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"アップデートを確認しています…"</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"クリア中..."</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"ローカルIDを取得中..."</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"選択したファイルを読み込み中..."</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"接続する"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"シグナリングサーバーに接続しています..."</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"サーバーに接続中..."</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"サーバーに再接続中..."</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"このデバイスでは使用できるファイル ピッカーがありません"</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"クリア"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"プレビューを削除"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"プレビューを削除しますか?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"ソースコードとビルド手順"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"ソースコードのバージョン"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"バージョン"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"著作権およびライセンスに関する声明"</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"コンピューターのキーボード"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"画質"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"ビデオ設定"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"映像遅延"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"ビデオ設定"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"ビデオ品質"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"ビデオキャプチャフレームレート"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"トラックパッド"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"相対マウス"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"分かりました"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"OK"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"確認する"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"接続を確認する"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"オフライン"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"スペース"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"ポート範囲は 1 ~ 65535 です。"</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"記号・機能"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"コンピューターがプライバシー画面の状態に戻るまで待ちます。古いコンピューターではサポートされていない可能性があります。"</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"リモート画面情報待ち"</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"リモート画面を待っています..."</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"プライバシー認証待ち"</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"システムキーボード"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"直接タッチ"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"アブソリュートマウス"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"ネットワーク"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"カスタム"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"仮想マウス"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"ローカル ショートカットを使用してリモート プライバシー画面のロックが解除されました。再度ロックするとプライバシーが回復され、ローカル ショートカットのロック解除が無効になります。"</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"管理されているバージョンが低すぎるため、最新バージョンにアップグレードしてください。"</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"リモートエンコーダー"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"相手側に送信するテキスト"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"ビデオ"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"ライセンス"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"ライセンスと著作権表示"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"ライセンスと著作権表示はオフラインでも利用できます。"</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"デバイス ID を保存できませんでした。安全なストレージを確認してください。"</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"デバイス ID と選択された保存されたアクセス パスワードは、Android キーストアを使用して暗号化されます。接続記録はローカルにのみ保存され、デバイスのバックアップには含まれません。クリップボードの内容はセッションが終了するとクリアされます。アプリを終了した後もピクチャーインピクチャーまたは接続された通知でセッションを維持し、アプリまたは通知内で切断します。"</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"設定"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"設定、新しいバージョンがあります"</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"アクセスパスワード"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"アクセスパスワードが間違っていますので、再度入力してください。"</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"証明書の検証に失敗しました · クリックして再試行してください"</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"最初にオンライン認証を完了してください"</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"リモート エンドでCrossDeskシステム サービスを有効にして、再試行してください。"</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"リクエストがタイムアウトし、コンピュータのプライバシー画面の状態が確認できませんでした。"</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"現在のファイルが送信されるまでお待ちください。"</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"1 ~ 65535 の範囲でポートを入力してください"</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"9桁のデバイスIDを入力"</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"有効なアクセスパスワードを入力してください"</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"設定を適用できませんでした。もう一度やり直してください。"</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"ソフトウェアライセンス"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"戻る"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"コントロールバーに戻る"</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"まだ接続実績がありません"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"このお知らせは更新または削除されました。表示するにはお知らせリストに戻ってください。"</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"リモートタスクが終了しました"</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"リモートデスクトップ"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"リモートデスクトップタッチエリア"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"リモート デスクトップ: タッチしてクリック、ドラッグして移動、右ボタンを押したまま、ピンチしてズーム、ズームインして 2 本の指で画面をドラッグ、2 本の指で上下にスライドして元のスケールでスクロール"</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"リモートコンピュータ"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"リモート ウィンドウが閉じられたか、バックグラウンド キープアライブの開始に失敗し、セッションが終了しました。"</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"リモート接続"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"リモートパスワードが正しくありません"</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"リモートエンドが接続を閉じました"</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"リモートエンドは現在利用できません"</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"リモートアクセスパスワードの有効期限が切れています。再接続してください。"</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"接続"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"接続 "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"接続に失敗しました"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"接続に失敗しました · クリックして再試行してください"</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"接続が失われました"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"接続が終了しました"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"接続遅延 (RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"サポートされているリモート デバイスに接続されている場合に使用できます。"</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"接続完了後、横画面をロックする"</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"接続プロンプト"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"接続方法"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"サーバーに接続する"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"サーバーに接続して最新のお知らせを取得します。"</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"接続がタイムアウトしました。デバイス ID、サーバー、リモートのオンライン ステータスを確認してください。"</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"リモートデスクトップに接続する"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"バックスペース"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"お知らせ"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"一部のプレビューをクリアできませんでした。もう一度お試しください。"</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"もう一度お試しください"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"読み取りステータスの保存をリトライする"</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"ロック"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"キーボード"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"読んで承認する"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"プライバシー"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"プライバシーと認証"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"プライバシー画面"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"プライバシー画面:オフ"</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"プライバシー画面:オン"</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"プライバシー画面のロックが解除されました"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"プライバシー画面異常"</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"プライバシー画面が一時的に利用できなくなります"</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"プライバシー画面のステータスが期限切れになり、コンピュータの確認を待っています"</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"プライバシー画面のステータスを確認する"</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"プライバシーポリシー"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"一時的にプライバシーポリシーが閲覧できなくなります。インターネットへの接続を拒否し、アプリケーションを再インストールしてください。"</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"ミュート"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"オーディオ"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"プロジェクトWebサイト"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"プレビューには、リモート コンピューターからの個人情報または仕事情報が含まれる場合があります。オンにすると、接続するたびにマシンに写真が自動的に保存され、最近の接続で表示されます。いつでも閉じてクリアできます。"</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"高"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"デフォルト"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"デフォルトではオフです。オンにすると、電話が接続されているときに画面が横向きになり、横向きのままになります。オフにすると、デバイスの方向に従います。タブレットやマルチウィンドウは影響を受けません。"</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"マウスの右ボタンを押してドラッグし、放してドラッグを終了します"</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"マウスの左ボタンを押してドラッグし、放してドラッグを終了します"</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"マウスコントロール"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"マウスホイール、上下にスライドしてキー範囲外にスライドします"</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">"、パケットロス率 "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">"、送信します "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">"、パスワードが保存されました"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">"、受け取ります "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">"、「再試行」をクリックします"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" · 완료"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" 전송 실패"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" 장치"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" 기사 · "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" 읽지 않은 항목"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"CrossDesk원격접속"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL 버전 3"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"OpenFEC공지"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"OpenFEC라이선스 및 소스"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"P2P 직접 연결"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRTP 활성화됨"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"TURN 릴레이"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"{0}전송 실패"</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"기기: {0}"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"{0}읽지 않은 항목"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0}, 수신{1}, 송신{2}, 패킷 손실률{3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● 연결됨"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"아래로 당겨 새로고침"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"손실률"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"중간"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"영상을 부드럽게 유지하는 데 우선 순위를 두며 대역폭이나 인코딩 압력이 높을 때 해상도를 더욱 적극적으로 줄입니다."</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"낮음"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"커스텀 서버 이용하기"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"비밀번호 저장"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"수신된 파일 저장"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"원격 화면 미리보기 저장"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"원격 화면 미리보기를 저장하시겠습니까?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"신호 연결이 중단되었습니다. 다시 연결하십시오."</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"공지사항 로딩에 실패했습니다. 다시 시도해주세요."</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"공지사항"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"합계 "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"총{0}항목 ·{1}읽지 않은 항목"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"소개"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"CrossDesk 정보"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"새 버전이 있습니다."</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"닫기"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"컨트롤 바 닫기"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"개인 정보 보호 화면 비활성화"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"선명도와 부드러움의 균형을 유지하며 일정한 압력을 가하면 해상도가 점차 감소합니다."</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"해상도"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"삭제"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"공지사항 삭제"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"기록 삭제"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"이 공지를 삭제하시겠습니까?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"새로고침 안내"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"클립보드"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"로드 중…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"더 로드하기"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"업데이트 안내"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"기본 연결 초기화 실패"</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"송신"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"보내기 "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"전송 후 원격 키보드에서 \"붙여넣기\"를 클릭하세요. 수신된 원격 텍스트를 로컬 시스템에 수동으로 복사할 수 있습니다."</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"파일 보내기"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"문자 보내기"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"취소"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"연결 취소"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"각 구성 요소에는 나열된 오픈 소스 라이선스가 적용되며 부여된 권리에는 이 애플리케이션의 일반 사용 약관이 적용되지 않습니다. 소스 코드를 보려면 인터넷 연결이 필요합니다."</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"합계"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"동의하고 계속하기"</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"백그라운드에서 연결을 유지하고 원격 데스크톱으로 돌아가려면 클릭하세요."</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"백그라운드 연결 유지를 시작하지 못했습니다. 애플리케이션을 포그라운드에 유지하세요."</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"백그라운드 연결 유지를 시작하지 못했습니다. 애플리케이션을 포그라운드에 유지하고 다시 연결하세요."</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"백그라운드 연결이 종료되었습니다. 다시 연결해 주세요."</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"백그라운드 연결 서비스가 중지되었습니다. 다시 연결해 주세요."</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"새로운 미리보기는 후속 연결에도 계속 저장됩니다."</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"온라인"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"프로토콜 및 경로 없이 호스트 이름 또는 IPv4 주소를 입력하세요."</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"소리"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"원격 텍스트 복사"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"대역폭이 부족할 때 영상 선명도를 보장하려면 항상 더 높은 해상도를 유지하고 프레임 속도를 낮추십시오."</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"미디어 암호화"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"문자·숫자"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"보안 저장 또는 인증서 초기화에 실패했습니다. 다시 시도하거나 애플리케이션 데이터를 삭제하세요."</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"완료"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"개인정보 동의 완료 후 업데이트 내용을 확인할 수 있습니다."</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"개인정보 인증 완료 후 로컬 신원 등록"</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"비밀번호 저장에 실패했습니다. 이 연결은 계속 사용할 수 있습니다."</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"비밀번호는 기본 Android 키 저장소에 암호화되어 저장됩니다."</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"원격 ID"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"피어 ID "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"피어 ID{0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"이 기기에 저장된 원격 미리보기는 모두 삭제되며, 연결 기록과 비밀번호는 유지됩니다."</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"화면 방향"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"원격제어 메뉴 확장"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"알림으로 인해 원격 연결이 끊어졌습니다."</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"비밀번호 저장됨"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"저장된 비밀번호를 읽을 수 없습니다. 다시 입력해 주세요."</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"저장된 비밀번호는 보관할 수 없으므로 다시 입력해 주세요."</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"Ctrl+Alt+Del가 전송되었습니다."</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"백그라운드 연결 유지가 활성화되어 전환 애플리케이션이 계속 연결을 유지할 수 있습니다."</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"연결 알림에서 돌아가거나 연결을 끊기 위해 백그라운드 연결 유지가 활성화되었습니다."</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"승인됨"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"읽기"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"연결됨 "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"서버에 연결됨"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"프레임 속도"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"부드러움"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"균형"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"애플리케이션의 라이센스 파일이 없거나 손상되었습니다. 애플리케이션을 다시 설치하십시오."</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"개발 버전에는 아직 출시되지 않은 수정 사항이 포함되어 있습니다. 다음 링크는 기본 버전의 소스 코드입니다."</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"저장 사용"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"개인 정보 보호 화면 활성화"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"오픈 소스 구성 요소"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"오픈소스 소프트웨어 권리 설명"</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"현재 최신 버전입니다."</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"현재 원격측에서는 이 텍스트를 직접 입력하는 기능을 지원하지 않습니다. 텍스트 입력을 지원하는 제어된 버전을 사용하거나 클립보드를 통해 보내주세요."</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"성공적으로 연결되면 가장 가까운 원격 장치가 여기에 표시됩니다."</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"개인정보 보호정책을 읽었으며 이에 동의합니다."</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"선택한 파일(읽을 수 없거나 공간이 부족함)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"장치 ID를 찾을 수 없습니다"</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"명령 전송에 실패했습니다. 다시 시도해 주세요."</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"명령이 전송되었으며 컴퓨터 확인을 기다리고 있습니다..."</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"인증상태"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"수신"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"받다 "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"로컬 디코더"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"철회"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"탈퇴 후에는 원격접속이 중단되며 화면 미리보기가 닫히고 기존 미리보기는 지워집니다. 접속 기록과 비밀번호는 유지되며, 재접속 전 다시 인증이 필요합니다."</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"네트워크 승인 철회"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"네트워크 승인을 철회하시겠습니까?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"리모컨 메뉴 접기"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"키보드 숨기기"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"마우스 숨기기"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"데이터"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"파일 저장에 실패했습니다. 다시 시도해 주세요."</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"파일이 저장되었습니다"</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"파일 선택 시간이 초과되어 세션이 종료되었습니다."</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"텍스트가 128KB를 초과합니다."</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"연결 끊기"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"연결을 끊으면 홈페이지로 돌아갑니다."</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"원격 연결을 끊으시겠습니까?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"연결 해제"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"새 버전"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"새 버전 사용 가능: v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"새 버전 사용 가능: v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"읽지 않은 공지사항 없음"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"읽기 상태를 로컬에 저장할 수 없습니다. 다시 시도해 주세요."</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"이 공지를 삭제할 수 없습니다. 다시 시도해 주세요."</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"원격 연결을 시작할 수 없습니다"</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"오픈소스 라이선스를 읽을 수 없습니다."</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"디스플레이"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"비밀번호 표시"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"나중에"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"공지 없음"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"최근 연결"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"서버"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"ServerTLS인증서 확인 실패"</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"서버 주소"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"서버 로그인에 실패했습니다. 서버 주소와 네트워크를 확인해주세요."</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"서버 포트"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"서버 연결 시간이 초과되었습니다. 서버 주소와 네트워크를 확인하세요."</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"활성화되지 않음"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"링크를 열 수 있는 브라우저가 없습니다."</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"무단"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"읽지 않음"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"서버가 연결되지 않았습니다. · 다시 시도하려면 클릭하세요."</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"이 애플리케이션은 제한된 보증과 책임 제한을 제공하는CeCILL-C1.0에 따라 순방향 오류 수정을 위해OpenFEC를 사용합니다. 자세한 내용은 전체 라이센스를 참조하세요."</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"로컬 ID "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"로컬 ID{0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"이 버전의 소스 코드 및 빌드 지침"</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"이 빌드에는 소스 개정을 사용할 수 없습니다."</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"새로고침하려면 릴리즈하세요"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"빌드 지침"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"레시피 및 프로젝트 패치 빌드"</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"소스코드 보기"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"확인에 실패했습니다. 나중에 다시 시도해 주세요."</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"업데이트 확인"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"개인정보 보호 화면을 닫는 중…"</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"보안 연결 설정 중…"</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"개인정보 보호 화면을 켜는 중..."</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"업데이트 확인 중…"</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"지우는 중..."</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"로컬 ID를 얻는 중..."</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"선택한 파일을 읽는 중..."</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"연결 중"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"시그널링 서버에 연결 중..."</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"서버에 연결하는 중..."</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"서버에 재접속하는 중..."</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"이 장치에 사용할 수 있는 파일 선택기가 없습니다."</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"클리어"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"미리보기 삭제"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"미리보기를 삭제할까요?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"소스 코드 및 빌드 지침"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"소스코드 버전"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"버전"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"저작권 및 라이선스 정책"</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"컴퓨터 키보드"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"화질"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"영상 선호도"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"영상 지연"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"영상 설정"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"비디오 품질"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"비디오 캡처 프레임 속도"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"트랙패드"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"상대 마우스"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"알겠습니다"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"알았어"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"확인"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"연결 확인"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"오프라인"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"스페이스"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"포트 범위는 1~65535입니다."</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"기호·기능"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"컴퓨터가 개인 정보 보호 화면 상태로 돌아갈 때까지 기다립니다. 오래된 컴퓨터에서는 이를 지원하지 않을 수 있습니다."</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"원격 화면 정보를 기다리는 중"</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"원격 화면을 기다리는 중..."</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"개인정보 인증 대기 중"</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"시스템 키보드"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"직접 터치"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"앱솔루트 마우스"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"네트워크"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"커스텀"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"가상마우스"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"로컬 단축키를 사용하여 원격 개인정보 보호 화면을 잠금 해제했습니다. 개인정보 보호를 복원하고 로컬 바로가기 잠금 해제를 비활성화하려면 다시 잠그세요."</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"제어 버전이 너무 낮습니다. 최신 버전으로 업그레이드하세요."</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"리모트 엔코더"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"원격지로 보낼 텍스트"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"영상"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"라이센스"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"라이센스 및 저작권 고지"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"라이센스 및 저작권 고지는 오프라인에서 확인할 수 있습니다."</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"기기 ID를 저장하지 못했습니다. 보안 저장소를 확인하세요."</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"기기 ID와 선택된 저장된 액세스 비밀번호는 Android Keystore를 사용하여 암호화됩니다. 연결 기록은 로컬에만 저장되며 장치 백업에는 포함되지 않습니다. 세션이 종료되면 클립보드 내용이 지워집니다. 앱을 종료한 후에도 PIP(picture-in-picture) 또는 연결된 알림으로 세션을 유지하고 앱 또는 알림 내에서 연결을 끊습니다."</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"설정"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"설정, 새 버전이 있습니다"</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"접속 비밀번호"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"접속비밀번호가 잘못되었습니다. 다시 입력해 주세요."</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"인증서 확인 실패 · 다시 시도하려면 클릭하세요."</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"먼저 온라인 인증을 완료해주세요."</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"원격 측에서CrossDesk시스템 서비스를 활성화하고 다시 시도하십시오."</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"요청 시간이 초과되어 컴퓨터의 개인 정보 보호 화면 상태가 확인되지 않았습니다."</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"현재 파일이 전송될 때까지 기다려 주십시오."</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"1~65535 범위의 포트를 입력하세요."</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"기기 ID 9자리를 입력하세요."</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"유효한 접속 비밀번호를 입력해주세요"</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"설정을 적용할 수 없습니다. 다시 시도해 보세요."</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"소프트웨어 라이센스"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"뒤로"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"컨트롤 바로 돌아가기"</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"아직 접속기록이 없습니다"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"본 공지사항은 업데이트 또는 삭제되었습니다. 공지사항 목록으로 돌아가서 확인하시기 바랍니다."</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"원격 작업이 종료되었습니다."</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"원격 데스크톱"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"원격 데스크톱 터치 영역"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"원격 데스크톱: 터치하고 클릭하고, 드래그하여 이동하고, 오른쪽 버튼을 길게 누르고, 핀치하여 확대/축소하고, 두 손가락으로 화면을 확대하고 드래그하고, 두 손가락으로 위아래로 밀어 원래 크기로 스크롤합니다."</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"원격 컴퓨터"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"원격 창이 닫혔거나 백그라운드 Keepalive 시작에 실패하여 세션이 종료되었습니다."</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"원격 접속"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"원격 비밀번호가 올바르지 않습니다."</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"원격 측에서 연결을 종료했습니다."</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"현재 원격 끝을 사용할 수 없습니다."</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"원격 접속 비밀번호가 만료되었습니다. 다시 접속해 주세요."</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"연결"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"연결 "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"연결 실패"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"연결 실패 · 다시 시도하려면 클릭하세요."</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"연결 끊김"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"연결이 종료되었습니다"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"연결 지연(RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"지원되는 원격 장치에 연결된 경우 사용할 수 있습니다."</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"연결 완료 후 가로 화면 잠금"</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"연결 프롬프트"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"연결 방법"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"서버에 접속"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"서버에 접속하여 최신 공지사항을 받아보세요."</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"연결 시간이 초과되었습니다. 장치 ID, 서버 및 원격 온라인 상태를 확인하십시오."</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"원격 데스크톱에 연결"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"백스페이스"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"공지사항"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"일부 미리보기를 삭제할 수 없습니다. 다시 시도해 주세요."</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"다시 시도해 보세요"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"읽은 상태 저장 다시 시도"</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"잠금"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"키보드"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"읽기 및 승인"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"개인 정보 보호"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"개인 정보 보호 및 승인"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"개인정보 보호 화면"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"화면 보호 꺼짐"</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"화면 보호 켜짐"</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"개인정보 보호 화면 잠금 해제됨"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"개인정보 화면 이상"</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"개인 정보 보호 화면을 일시적으로 사용할 수 없습니다."</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"개인 정보 보호 화면 상태가 만료되어 컴퓨터 확인을 기다리는 중입니다."</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"개인정보 보호 화면 상태 확인 예정"</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"개인정보 보호정책"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"개인정보취급방침은 일시적으로 열람할 수 없습니다. 인터넷 연결을 거부하고 애플리케이션을 다시 설치하십시오."</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"음소거"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"오디오"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"프로젝트 홈페이지"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"미리보기에는 원격 컴퓨터의 개인 정보나 업무 정보가 포함될 수 있습니다. 전원을 켜면 각 연결이 자동으로 기기에 사진을 저장하고 최근 연결에 표시됩니다. 언제든지 닫고 지울 수 있습니다."</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"높음"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"기본값"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"기본적으로 꺼져 있습니다. 전원을 켜면 휴대폰이 연결되면 화면이 가로 방향으로 전환되고 유지됩니다. 꺼지면 장치의 방향을 따릅니다. 태블릿과 멀티 윈도우는 영향을 받지 않습니다."</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"마우스 오른쪽 버튼을 누르고 드래그한 후 떼면 드래그가 종료됩니다."</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"마우스 왼쪽 버튼을 누르고 드래그한 후 떼면 드래그가 종료됩니다."</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"마우스 제어"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"마우스 휠, 위아래로 밀어 키 범위 밖으로 슬라이드"</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">", 패킷 손실률 "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">", 보내다 "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">", 비밀번호가 저장되었습니다"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">", 수신 "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">", 재시도를 클릭하세요."</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" · Concluído"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" Falha na transferência"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" dispositivos"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" Artigo · "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" itens não lidos"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"Conexão remota CrossDesk"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL versão 3"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"Aviso OpenFEC"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"Licença e fonte OpenFEC"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"Conexão direta P2P"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRT habilitado"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"GIRAR relé"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"Falha na transmissão {0}"</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"Dispositivos: {0}"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"Itens não lidos {0}"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0}, recebendo {1}, enviando {2}, taxa de perda de pacotes {3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● Conectado"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"Puxe para baixo para atualizar"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"Taxa de perda"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"Média"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"Prioriza manter a imagem suave e reduzirá a resolução de forma mais agressiva quando a largura de banda ou a pressão de codificação forem altas."</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"Baixa"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"Usando um servidor personalizado"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"Salvar senha"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"Salvar arquivo recebido"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"Salvar visualizações de tela remotas"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"Salvar visualização remota da tela?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"A conexão de sinalização foi interrompida, reconecte."</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"Falha ao carregar o anúncio. Tente novamente."</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"Detalhes do anúncio"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"Total "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"Total de itens {0}· Itens não lidos {1}"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"Sobre"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"Sobre o CrossDesk"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"Sobre, há uma nova versão"</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"Fechar"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"Fechar barra de controle"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"Desativar tela de privacidade"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"Equilibra clareza e suavidade, reduzindo apenas gradualmente a resolução sob pressão constante."</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"Resolução"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"Excluir"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"Excluir anúncio"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"Excluir registro"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"Excluir este anúncio?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"Anúncio de atualização"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"Área de transferência"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"Carregando…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"Carregue mais"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"Atualização recomendada"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"Falha na inicialização da conexão nativa"</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"Enviar"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"Enviar "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"Após o envio, clique em “Colar” no teclado remoto. O texto remoto recebido pode ser copiado manualmente para a máquina local."</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"Enviar arquivo"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"Enviar texto"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"Cancelar"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"Cancelar conexão"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"Cada componente está sujeito à licença de código aberto listada e os direitos concedidos não estão sujeitos aos termos gerais de uso deste aplicativo. A visualização do código-fonte requer uma conexão com a Internet."</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"Total"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"Concorde e continue"</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"Mantenha-se conectado em segundo plano, clique para retornar à área de trabalho remota"</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"Falha ao iniciar a manutenção de atividade em segundo plano, mantenha o aplicativo em primeiro plano"</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"Falha ao iniciar a manutenção de atividade em segundo plano. Mantenha o aplicativo em primeiro plano e reconecte."</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"A conexão em segundo plano foi encerrada. Reconecte."</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"O serviço de conexão em segundo plano foi interrompido, reconecte."</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"Novas visualizações ainda serão salvas nas conexões subsequentes."</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"On-line"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"Preencha o nome do host ou endereço IPv4, sem protocolo e caminho"</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"Som"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"Copiar texto remoto"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"Sempre mantenha uma resolução mais alta e reduza a taxa de quadros para garantir a clareza da imagem quando a largura de banda for insuficiente."</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"Criptografia de mídia"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"Letras e números"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"Falha no armazenamento seguro ou na inicialização do certificado. Tente novamente ou limpe os dados do aplicativo"</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"Feito"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"Você pode verificar se há atualizações após concluir a autorização de privacidade"</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"Registre a identidade local após concluir a autorização de privacidade"</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"Falha ao salvar a senha, esta conexão ainda pode ser usada"</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"As senhas são salvas criptografadas no Android Keystore nativo"</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"ID remoto"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"ID do par "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"ID de par {0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"Todas as visualizações remotas salvas nesta máquina serão excluídas e os registros de conexão e senhas serão mantidos."</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"Orientação da tela"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"Expandir menu de controle remoto"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"Conexão remota desconectada da notificação"</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"Senha salva"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"A senha salva não pode ser lida, digite-a novamente."</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"A senha salva não pode ser mantida, digite-a novamente."</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"Ctrl+Alt+Del enviado"</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"O keep-alive em segundo plano está habilitado, a troca de aplicativos pode continuar a manter a conexão"</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"Manutenção de atividade em segundo plano habilitada para retornar ou desconectar das notificações de conexão"</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"Concedido"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"Leia"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"Conectado "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"Conectado ao servidor"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"Taxa de quadros"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"Fluidez"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"Equilibrado"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"O arquivo de licença do aplicativo está ausente ou danificado. Reinstale o aplicativo."</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"A versão de desenvolvimento contém modificações que ainda não foram lançadas. O link a seguir é o código fonte da versão básica."</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"Ativar salvamento"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"Habilitar tela de privacidade"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"Componentes de código aberto"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"Descrição dos direitos de software de código aberto"</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"Esta é atualmente a versão mais recente"</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"Atualmente, a extremidade remota não suporta a entrada direta deste texto. Use uma versão do final controlado que suporte entrada de texto ou envie-o através da área de transferência."</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"Após a conexão bem-sucedida, o dispositivo remoto mais próximo será exibido aqui."</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"Li e concordo com a Política de Privacidade"</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"Arquivo selecionado (não pode ser lido ou espaço insuficiente)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"O ID do dispositivo não foi encontrado"</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"Falha no envio do comando, tente novamente"</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"O comando foi enviado, aguardando confirmação do computador..."</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"Status de autorização"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"Receber"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"receber "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"Decodificador local"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"Revogar"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"Após a retirada, a conexão remota será interrompida, a visualização da tela será fechada e a visualização existente será apagada. O registro de conexão e a senha serão mantidos e será necessária autorização novamente antes de reconectar."</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"Retirar autorização de rede"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"Retirar a autorização da rede?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"Recolher menu de controle remoto"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"Ocultar teclado"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"Ocultar mouse"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"Dados"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"Falha ao salvar o arquivo, tente novamente"</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"Arquivo salvo"</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"A seleção do arquivo expirou e a sessão foi encerrada"</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"O texto excede 128 KB"</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"Desconectar"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"Retornará à página inicial após desconectar"</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"Desconectar a conexão remota?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"Desconectar"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"Nova versão"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"Nova versão disponível: v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"Nova versão disponível: v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"Nenhum anúncio não lido"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"Não é possível salvar o status de leitura localmente. Por favor, tente novamente."</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"Não é possível excluir este anúncio. Por favor, tente novamente."</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"Não é possível iniciar a conexão remota"</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"Não é possível ler a licença de código aberto"</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"Tela"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"Mostrar senha"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"Agora não"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"Sem anúncios"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"Conexões recentes"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"Servidor"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"Falha na verificação do certificado Server TLS"</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"Endereço do servidor"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"Falha no login do servidor, verifique o endereço do servidor e a rede"</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"Porta do servidor"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"A conexão do servidor expirou, verifique o endereço do servidor e a rede"</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"Não habilitado"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"Nenhum navegador encontrado para abrir o link"</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"Não autorizado"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"Não lido"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"Servidor não conectado · Clique para tentar novamente"</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"Este aplicativo usa OpenFEC para correção direta de erros noCeCILL-C1.0, que fornece garantias limitadas e limita a responsabilidade. Veja a licença completa para detalhes."</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"ID local "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"ID local {0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"Código-fonte e instruções de construção para esta versão"</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"A revisão fonte não está disponível para esta compilação."</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"Solte para atualizar"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"Instruções de construção"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"Crie receitas e patches de projeto"</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"Ver código-fonte"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"Falha na verificação. Por favor, tente novamente mais tarde."</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"Verifique se há atualizações"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"Fechando a tela de privacidade…"</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"Estabelecendo conexão segura…"</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"Ativando a tela de privacidade..."</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"Verificando atualizações…"</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"Limpando..."</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"Obtendo ID local..."</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"Lendo arquivo selecionado..."</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"Conectando"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"Conectando ao servidor de sinalização..."</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"Conectando ao servidor..."</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"Reconectando ao servidor..."</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"Nenhum seletor de arquivos disponível para este dispositivo"</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"Limpar"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"Limpar prévias"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"Limpar prévias?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"Código-fonte e instruções de construção"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"Versão do código-fonte"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"Versão"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"Declaração de direitos autorais e licença"</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"Teclado de computador"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"Qualidade"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"Preferência de vídeo"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"Latência do vídeo"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"Configurações de vídeo"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"Qualidade de vídeo"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"Taxa de quadros de captura de vídeo"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"Touchpad"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"Rato relativo"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"Entendi"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"OK"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"Confirmar"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"Confirme a conexão"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"Off-line"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"Espaço"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"O intervalo de portas é 1–65535"</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"Símbolos e teclas"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"Aguarde o computador retornar ao estado da tela de privacidade; computadores mais antigos podem não suportá-lo."</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"Aguardando informações da tela remota"</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"Aguardando a tela remota..."</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"Aguardando autorização de privacidade"</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"Teclado do sistema"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"Toque direto"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"Rato Absoluto"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"Rede"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"Personalizado"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"Rato virtual"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"A tela de privacidade remota foi desbloqueada usando o atalho local. Bloqueie-o novamente para restaurar a privacidade e desativar o desbloqueio de atalhos locais."</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"A versão controlada é muito baixa, atualize para a versão mais recente."</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"Codificador remoto"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"Texto a ser enviado para o terminal remoto"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"Vídeo"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"Licença"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"Licenças e avisos de direitos autorais"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"A licença e o aviso de direitos autorais estão disponíveis offline."</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"Falha ao salvar a identidade do dispositivo. Verifique o armazenamento seguro"</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"A identidade do dispositivo e as senhas de acesso salvas selecionadas são criptografadas usando o Android Keystore. Os registros de conexão são salvos apenas localmente e não são incluídos no backup do dispositivo. O conteúdo da área de transferência é apagado quando a sessão é encerrada. Mantenha a sessão com notificações picture-in-picture ou conectadas depois de sair do aplicativo e desconecte-se do aplicativo ou das notificações."</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"Configurações"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"Configurações, há uma nova versão"</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"Senha de acesso"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"A senha de acesso está errada, digite-a novamente."</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"Falha na verificação do certificado · Clique para tentar novamente"</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"Por favor, preencha primeiro a autorização online"</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"Ative o serviço do sistema CrossDesk na extremidade remota e tente novamente"</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"A solicitação expirou e o status da tela de privacidade do computador não foi confirmado."</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"Aguarde o envio do arquivo atual."</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"Insira uma porta no intervalo 1–65535"</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"Insira um ID de dispositivo de 9 dígitos"</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"Por favor, insira uma senha de acesso válida"</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"Não foi possível aplicar as configurações. Tente novamente."</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"Licença de software"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"Voltar"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"Retornar à barra de controle"</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"Ainda não há registro de conexão"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"Este anúncio foi atualizado ou excluído. Volte à lista de anúncios para visualizá-lo."</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"A tarefa remota está fechada"</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"Área de Trabalho Remota"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"Área de toque da área de trabalho remota"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"Área de trabalho remota: toque e clique, arraste para mover, pressione e segure o botão direito, aperte para ampliar, amplie e arraste a tela com dois dedos, deslize para cima e para baixo com dois dedos para rolar na escala original"</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"Computador remoto"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"A janela remota foi fechada ou a manutenção de atividade em segundo plano falhou ao iniciar e a sessão foi encerrada."</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"Conexão remota"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"Senha remota incorreta"</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"A extremidade remota fechou a conexão"</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"A extremidade remota está indisponível no momento"</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"A senha de acesso remoto expirou, reconecte."</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"Conectar"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"Conexão "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"Falha na conexão"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"Falha na conexão · Clique para tentar novamente"</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"Conexão perdida"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"Conexão encerrada"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"Atraso de conexão (RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"Disponível quando conectado a um dispositivo remoto compatível."</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"Bloqueie a tela paisagem após a conexão ser estabelecida"</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"Solicitação de conexão"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"Método de conexão"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"Conecte-se ao servidor"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"Conecte-se ao servidor para obter os anúncios mais recentes."</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"A conexão expirou, verifique o ID do dispositivo, o servidor e o status on-line remoto"</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"Conecte-se à área de trabalho remota"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"Retrocesso"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"Avisos"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"Não foi possível limpar algumas visualizações. Tente novamente."</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"Tente novamente"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"Tente salvar novamente o status de leitura"</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"Bloqueio"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"Teclado"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"Ler e autorizar"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"Privacidade"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"Privacidade e Autorização"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"Tela de privacidade"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"Privacidade desativada"</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"Privacidade ativada"</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"Tela de privacidade desbloqueada"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"Anormalidade na tela de privacidade"</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"A tela de privacidade está temporariamente indisponível"</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"O status da tela de privacidade expirou, aguardando confirmação do computador"</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"Status da tela de privacidade a ser confirmado"</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"Política de Privacidade"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"A política de privacidade não pode ser lida temporariamente. Recuse-se a se conectar à Internet e reinstale o aplicativo."</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"Mudo"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"Áudio"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"Site do projeto"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"As visualizações podem conter informações pessoais ou de trabalho do computador remoto. Após ligá-lo, cada conexão salvará automaticamente uma imagem na máquina e a exibirá nas conexões recentes. Você pode fechar e limpar a qualquer momento."</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"Alta"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"Padrão"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"Desligado por padrão. Quando ligado, a tela entrará e permanecerá na orientação paisagem quando o telefone estiver conectado; quando desligado, seguirá a direção do dispositivo. Tablets e janelas múltiplas não são afetados."</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"Botão direito do mouse, pressione e arraste, solte para finalizar o arrasto"</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"Botão esquerdo do mouse, pressione e arraste, solte para finalizar o arrasto"</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"Controle do mouse"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"Roda do mouse, deslize para cima e para baixo para sair do intervalo de teclas"</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">", taxa de perda de pacotes "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">", enviar "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">", senha salva"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">", receba "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">", clique em Tentar novamente"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" · Завершено"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" Передача не удалась"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" устройства"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" Статья · "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" непрочитанные элементы"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"Удаленное подключение CrossDesk"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL версия 3"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"Уведомление OpenFEC"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"Лицензия и исходный код OpenFEC"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"Прямое соединение P2P"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRTP включен"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"Реле ПОВОРОТА"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"Передача {0} не удалась."</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"Устройства: {0}"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"{0} непрочитанные элементы"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0}, прием {1}, отправка {2}, скорость потери пакетов {3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● Подключено"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"Потяните вниз, чтобы обновить"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"Потери пакетов"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"Среднее"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"Отдает приоритет сохранению плавности изображения и более агрессивно снижает разрешение при высокой пропускной способности или нагрузке на кодирование."</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"Низкое"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"Использование пользовательского сервера"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"Сохранить пароль"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"Сохранить полученный файл"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"Сохранять превью удалённого экрана"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"Сохранить предварительный просмотр удаленного экрана?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"Сигнальное соединение прервано, пожалуйста, переподключитесь."</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"Не удалось загрузить объявление. Повторите попытку."</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"Подробности объявления"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"Итого "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"Всего элементов {0}· Непрочитанные элементы {1}"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"О программе"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"О CrossDesk"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"О, вышла новая версия"</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"Закрыть"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"Закрыть панель управления"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"Выключить приватный экран"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"Балансирует четкость и плавность изображения, постепенно снижая разрешение под постоянным давлением."</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"Разрешение"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"Удалить"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"Удалить объявление"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"Удалить запись"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"Удалить это объявление?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"Обновить объявление"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"Буфер обмена"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"Загрузка…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"Загрузить еще"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"Рекомендуется обновление"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"Не удалось инициализировать собственное соединение."</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"Отправка"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"Отправить "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"После отправки нажмите «Вставить» на удаленной клавиатуре. Полученный удаленный текст можно вручную скопировать на локальный компьютер."</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"Отправить файл"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"Отправить текст"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"Отмена"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"Отмена соединения"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"На каждый компонент распространяется действие указанной лицензии с открытым исходным кодом, а предоставленные права не подпадают под действие общих условий использования этого приложения. Для просмотра исходного кода требуется подключение к Интернету."</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"Итого"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"Согласитесь и продолжайте."</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"Оставайтесь на связи в фоновом режиме, нажмите, чтобы вернуться к удаленному рабочему столу."</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"Не удалось запустить фоновую поддержку активности, оставьте приложение на переднем плане."</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"Не удалось запустить фоновую поддержку активности. Оставьте приложение в активном режиме и переподключитесь."</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"Фоновое соединение прервано, подключитесь заново."</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"Служба фонового подключения остановлена, подключитесь повторно."</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"Новые превью по-прежнему будут сохраняться при последующих подключениях."</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"В сети"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"Введите имя хоста или IPv4-адрес без протокола и пути."</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"Звук"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"Копирование удаленного текста"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"Всегда поддерживайте более высокое разрешение и уменьшайте частоту кадров, чтобы обеспечить четкость изображения при недостаточной полосе пропускания."</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"Шифрование мультимедиа"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"Буквы и цифры"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"Не удалось инициализировать безопасное хранилище или сертификат. Повторите попытку или очистите данные приложения."</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"Готово"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"Вы можете проверить наличие обновлений после завершения авторизации конфиденциальности."</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"Зарегистрируйте локальную личность после завершения авторизации конфиденциальности."</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"Не удалось сохранить пароль, это соединение все еще можно использовать."</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"Пароли сохраняются в зашифрованном виде в собственном хранилище ключей Android."</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"Удаленный ID"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"Идентификатор узла "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"Идентификатор узла {0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"Все удаленные просмотры, сохраненные на этом компьютере, будут удалены, а записи подключений и пароли сохранятся."</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"Ориентация экрана"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"Развернуть меню пульта дистанционного управления"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"Удаленное соединение отключено из-за уведомления"</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"Пароль сохранён"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"Сохраненный пароль невозможно прочитать, введите его еще раз."</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"Сохраненный пароль невозможно сохранить, введите его еще раз."</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"Ctrl+Alt+Del отправлено"</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"Фоновая поддержка активности включена, переключающиеся приложения могут продолжать поддерживать соединение."</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"Фоновая поддержка активности включена для возврата или отключения от уведомлений о соединении."</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"Разрешено"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"Прочитано"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"Подключено "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"Подключено к серверу"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"Частота кадров"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"Плавность"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"Баланс"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"Файл лицензии в приложении отсутствует или поврежден, переустановите приложение."</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"Версия для разработки содержит модификации, которые еще не были выпущены. По следующей ссылке приведен исходный код базовой версии."</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"Включить сохранение"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"Включить приватный экран"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"Компоненты с открытым кодом"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"Описание прав на программное обеспечение с открытым исходным кодом."</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"На данный момент это последняя версия."</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"В настоящее время удаленный конец не поддерживает прямой ввод этого текста. Пожалуйста, используйте версию управляемого конца, поддерживающую ввод текста, или отправьте его через буфер обмена."</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"При успешном подключении здесь будет отображено ближайшее удаленное устройство."</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"Я прочитал и согласен с Политикой конфиденциальности."</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"Выбранный файл (невозможно прочитать или недостаточно места)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"Идентификатор устройства не найден."</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"Не удалось отправить команду. Повторите попытку."</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"Команда отправлена, ждём подтверждения от компьютера..."</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"Статус авторизации"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"Приём"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"получить "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"Локальный декодер"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"Отозвать"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"После выхода удаленное соединение будет остановлено, предварительный просмотр экрана закроется, а существующий предварительный просмотр будет очищен. Запись о подключении и пароль будут сохранены, и перед повторным подключением потребуется повторная авторизация."</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"Отозвать сетевую авторизацию"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"Отозвать сетевую авторизацию?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"Свернуть меню пульта дистанционного управления"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"Скрыть клавиатуру"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"Скрыть мышь"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"Данные"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"Не удалось сохранить файл. Повторите попытку."</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"Файл сохранен."</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"Тайм-аут выбора файла истек, сеанс завершен."</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"Размер текста превышает 128 КБ."</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"Отключить"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"Вернется на домашнюю страницу после отключения."</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"Отключить удаленное соединение?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"Отключить"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"Новая версия"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"Доступна новая версия: v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"Доступна новая версия: v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"Нет непрочитанных объявлений"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"Не удалось сохранить статус чтения локально. Повторите попытку."</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"Не удалось удалить объявление. Повторите попытку."</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"Невозможно инициировать удаленное соединение."</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"Невозможно прочитать лицензию с открытым исходным кодом."</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"Экран"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"Показать пароль"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"Не сейчас"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"Нет объявлений"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"Недавние связи"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"Сервер"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"Проверка Server TLS certificate не удалась."</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"Адрес сервера"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"Не удалось войти на сервер, проверьте адрес сервера и сеть."</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"Порт сервера"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"Тайм-аут подключения к серверу истек, проверьте адрес сервера и сеть."</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"Не включено"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"Не найден браузер, в котором можно было бы открыть ссылку."</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"Несанкционированное"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"Не прочитано"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"Сервер не подключен · Нажмите, чтобы повторить попытку."</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"Приложение использует OpenFEC для прямого исправления ошибок по лицензии CeCILL-C 1.0 с ограниченной гарантией и ответственностью. Подробности — в тексте лицензии."</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"Местный идентификатор "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"Локальный идентификатор {0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"Исходный код и инструкции по сборке этой версии."</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"Ревизия исходного кода этой сборки недоступна."</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"Отпустите, чтобы обновить"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"Инструкции по сборке"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"Рецепты сборки и исправления проектов."</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"Открыть исходный код"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"Не удалось проверить обновления. Повторите позже."</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"Проверить обновления"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"Закрытие экрана конфиденциальности…"</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"Установление безопасного соединения…"</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"Включение экрана конфиденциальности..."</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"Проверка обновлений…"</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"Очистка..."</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"Получение локального идентификатора..."</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"Чтение выбранного файла..."</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"Подключение"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"Подключение к серверу сигнализации..."</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"Подключение к серверу..."</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"Повторное подключение к серверу..."</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"Для этого устройства средство выбора файлов недоступно."</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"Очистить"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"Удалить превью"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"Удалить превью?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"Исходный код и сборка"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"Версия исходного кода"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"Версия"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"Положение об авторских правах и лицензии."</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"Компьютерная клавиатура"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"Качество"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"Режим видео"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"Задержка видео"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"Настройки видео"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"Качество видео"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"Частота захвата видео"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"Тачпад"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"Родственная мышь"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"Понятно"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"ОК"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"Подтвердить"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"Подтвердите соединение"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"Не в сети"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"Пробел"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"Диапазон портов: 1–65535."</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"Символы и клавиши"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"Подождите, пока компьютер вернется в состояние экрана конфиденциальности; старые компьютеры могут его не поддерживать."</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"Ожидание информации об удаленном экране."</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"Ожидание удаленного экрана..."</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"Ожидание разрешения на конфиденциальность."</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"Системная клавиатура"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"Касание"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"Абсолютная мышь"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"Сеть"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"Пользовательский"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"Виртуальная мышь"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"Приватный экран отключён локальным сочетанием клавиш. Заблокируйте его снова, чтобы восстановить защиту и запретить локальную разблокировку."</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"Управляемая версия слишком низкая, обновите ее до последней версии."</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"Кодер удалённой стороны"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"Текст для отправки на удаленный конец"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"Видео"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"Лицензия"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"Лицензии и авторские права"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"Лицензия и уведомление об авторских правах доступны в автономном режиме."</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"Не удалось сохранить идентификационные данные устройства. Проверьте безопасное хранилище."</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"Идентификатор устройства и выбранные сохраненные пароли доступа шифруются с помощью хранилища ключей Android. Записи подключений сохраняются только локально и не включаются в резервную копию устройства. Содержимое буфера обмена очищается после завершения сеанса. Оставайтесь в сеансе с помощью «картинка в картинке» или подключенных уведомлений после выхода из приложения и отключайтесь внутри приложения или уведомлений."</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"Настройки"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"Настройки, есть новая версия"</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"Пароль доступа"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"Неверный пароль доступа, пожалуйста, введите его еще раз."</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"Не удалось проверить сертификат · Нажмите, чтобы повторить попытку."</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"Сначала пройдите онлайн-авторизацию."</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"Пожалуйста, включите системную службу CrossDesk на удаленном конце и повторите попытку."</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"Время ожидания запроса истекло, а состояние экрана конфиденциальности компьютера не подтверждено."</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"Пожалуйста, дождитесь отправки текущего файла."</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"Введите порт в диапазоне 1–65535."</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"Введите ID из 9 цифр"</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"Пожалуйста, введите действительный пароль доступа."</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"Не удалось применить настройки. Повторите попытку."</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"Лицензия программы"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"Назад"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"Возврат к панели управления."</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"Пока нет записей о подключении"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"Это объявление было обновлено или удалено. Чтобы просмотреть его, вернитесь к списку объявлений."</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"Удаленная задача закрыта."</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"Удаленный рабочий стол"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"Сенсорная область удаленного рабочего стола"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"Удаленный рабочий стол: коснитесь и щелкните, перетащите для перемещения, нажмите и удерживайте правую кнопку, сведите пальцы для масштабирования, увеличьте масштаб и перетащите экран двумя пальцами, проведите двумя пальцами вверх и вниз для прокрутки в исходном масштабе."</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"Удаленный компьютер"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"Удаленное окно было закрыто, или не удалось запустить фоновую проверку активности, и сеанс завершился."</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"Удаленное подключение"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"Неверный удаленный пароль."</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"Удаленная сторона закрыла соединение."</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"Удаленный конец в настоящее время недоступен."</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"Срок действия пароля удаленного доступа истек, подключитесь повторно."</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"Подключиться"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"Подключение "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"Соединение не удалось"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"Не удалось подключиться · Нажмите, чтобы повторить попытку."</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"Соединение потеряно"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"Соединение завершено"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"Задержка соединения (RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"Доступно при подключении к поддерживаемому устройству."</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"Блокировка горизонтального экрана после установления соединения."</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"Запрос на подключение"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"Способ подключения"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"Подключиться к серверу"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"Подключитесь к серверу, чтобы получать последние объявления."</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"Тайм-аут соединения истек, проверьте идентификатор устройства, сервер и удаленный онлайн-статус."</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"Подключение к удаленному рабочему столу"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"Возврат"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"Объявления"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"Некоторые предварительные просмотры не удалось удалить. Повторите попытку."</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"Попробуйте еще раз"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"Повторить попытку сохранения статуса чтения."</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"Заблокировать"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"Клавиатура"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"Прочитать и авторизоваться"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"Конфиденциальность"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"Конфиденциальность и авторизация"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"Приватный экран"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"Приватный экран выкл."</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"Приватный экран вкл."</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"Приватный экран разблокирован"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"Неисправность экрана конфиденциальности."</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"Экран конфиденциальности временно недоступен."</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"Срок действия статуса экрана конфиденциальности истек, ожидание подтверждения от компьютера."</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"Статус экрана конфиденциальности подлежит подтверждению."</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"Политика конфиденциальности"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"Политику конфиденциальности временно невозможно прочитать. Пожалуйста, откажитесь от подключения к Интернету и переустановите приложение."</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"Без звука"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"Аудио"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"Сайт проекта"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"Предварительный просмотр может содержать личную или рабочую информацию с удаленного компьютера. После его включения каждое подключение будет автоматически сохранять изображение на устройстве и отображать его при последних подключениях. Вы можете закрыть и очистить в любое время."</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"Высокое"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"По умолчанию"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"По умолчанию выключено. При включении экран войдет и останется в альбомной ориентации при подключении телефона; в выключенном состоянии он будет следовать направлению устройства. Планшеты и мультиокна не затронуты."</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"Правая кнопка мыши, нажмите и перетащите, отпустите, чтобы завершить перетаскивание."</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"Левую кнопку мыши нажмите и перетащите, отпустите, чтобы завершить перетаскивание."</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"Управление мышью"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"Колесо мыши: проведите вверх и вниз, чтобы выйти за пределы диапазона клавиш."</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">", коэффициент потери пакетов "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">", отправить "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">", пароль сохранен"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">", получить "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">"нажмите «Повторить»."</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by localization/generate.py. -->
|
||||||
|
<resources>
|
||||||
|
<string name="text_58b6df1059088699" formatted="false">" · Completed"</string>
|
||||||
|
<string name="text_53af552fb3af7f6e" formatted="false">" Transfer failed"</string>
|
||||||
|
<string name="text_23ebae95d26dfe5e" formatted="false">" devices"</string>
|
||||||
|
<string name="text_a7f4dfd234a43b16" formatted="false">" Article · "</string>
|
||||||
|
<string name="text_2c894057fc526576" formatted="false">" unread items"</string>
|
||||||
|
<string name="text_d0ac753c6681e578" formatted="false">"CrossDesk remote connection"</string>
|
||||||
|
<string name="text_0991c6fc1de4ffd6" formatted="false">"GNU GPL version 3"</string>
|
||||||
|
<string name="text_5ae7dc4079223967" formatted="false">"OpenFEC notice"</string>
|
||||||
|
<string name="text_69ad29820a259526" formatted="false">"OpenFEC license and source"</string>
|
||||||
|
<string name="text_63ae4a0c7a448d21" formatted="false">"P2P direct connection"</string>
|
||||||
|
<string name="text_70cae3c6aad8e152" formatted="false">"SRTP enabled"</string>
|
||||||
|
<string name="text_ef813fec7d6d8a3b" formatted="false">"TURN relay"</string>
|
||||||
|
<string name="text_ebede975edd52aee" formatted="false">"{0} transmission failed"</string>
|
||||||
|
<string name="text_b3ffb6e982ecf5df" formatted="false">"Devices: {0}"</string>
|
||||||
|
<string name="text_fc66a78b9e27fecf" formatted="false">"{0} unread items"</string>
|
||||||
|
<string name="text_dcb59cdcf66150a1" formatted="false">"{0}, receiving {1}, sending {2}, packet loss rate {3}"</string>
|
||||||
|
<string name="text_01965b3249458d01" formatted="false">"● Connected"</string>
|
||||||
|
<string name="text_eb4236fe6a926a5a" formatted="false">"Pull down to refresh"</string>
|
||||||
|
<string name="text_37772187ab854191" formatted="false">"Loss Rate"</string>
|
||||||
|
<string name="text_a567bdaa11367f26" formatted="false">"Medium"</string>
|
||||||
|
<string name="text_e6ff95a9fe6acc7f" formatted="false">"Prioritizes keeping the picture smooth, and will reduce resolution more aggressively when bandwidth or encoding pressure is high."</string>
|
||||||
|
<string name="text_aa9e366f68d3d097" formatted="false">"Low"</string>
|
||||||
|
<string name="text_f03d03eb35e3330c" formatted="false">"Using a custom server"</string>
|
||||||
|
<string name="text_2e9f4819e064ce12" formatted="false">"Save password"</string>
|
||||||
|
<string name="text_10019cef593819ed" formatted="false">"Save received file"</string>
|
||||||
|
<string name="text_3bcdcd330bb3044c" formatted="false">"Save remote screen previews"</string>
|
||||||
|
<string name="text_0f197074373e38fd" formatted="false">"Save remote screen preview?"</string>
|
||||||
|
<string name="text_71ca53f95a8484da" formatted="false">"The signaling connection is interrupted, please reconnect."</string>
|
||||||
|
<string name="text_789f22a8b1b34797" formatted="false">"Announcement loading failed, please try again."</string>
|
||||||
|
<string name="text_00554b17c432937e" formatted="false">"Announcement details"</string>
|
||||||
|
<string name="text_2428420e407fe2e8" formatted="false">"Total "</string>
|
||||||
|
<string name="text_d977c7568cc5201e" formatted="false">"Total {0} items ·{1} unread items"</string>
|
||||||
|
<string name="text_52d25a9e30ba94f1" formatted="false">"About"</string>
|
||||||
|
<string name="text_459c1e980c1c8ce2" formatted="false">"About CrossDesk"</string>
|
||||||
|
<string name="text_390a95b65eb33052" formatted="false">"About, there is a new version"</string>
|
||||||
|
<string name="text_3fd47edce45b3603" formatted="false">"Close"</string>
|
||||||
|
<string name="text_56a18c93e12fdc4e" formatted="false">"Close control bar"</string>
|
||||||
|
<string name="text_4eb0565cc4658d7f" formatted="false">"Disable privacy screen"</string>
|
||||||
|
<string name="text_7d466b69be42fc43" formatted="false">"Balances clarity and smoothness, only gradually reducing resolution under constant pressure."</string>
|
||||||
|
<string name="text_b92421822c213c2b" formatted="false">"Resolution"</string>
|
||||||
|
<string name="text_2f9daa828907b93f" formatted="false">"Delete"</string>
|
||||||
|
<string name="text_0e46cdb3442edfba" formatted="false">"Delete announcement"</string>
|
||||||
|
<string name="text_f2cf9101bb2d8816" formatted="false">"Delete record"</string>
|
||||||
|
<string name="text_d087671ca7df7f0f" formatted="false">"Delete this announcement?"</string>
|
||||||
|
<string name="text_33e7584cd5c252d7" formatted="false">"Refresh announcement"</string>
|
||||||
|
<string name="text_8c10e2c1728ddf8e" formatted="false">"Clipboard"</string>
|
||||||
|
<string name="text_4927a53bcc886afb" formatted="false">"Loading…"</string>
|
||||||
|
<string name="text_af90a08fec8ee28d" formatted="false">"Load more"</string>
|
||||||
|
<string name="text_a739f24a9fd8a5d9" formatted="false">"Update recommended"</string>
|
||||||
|
<string name="text_861521c9e107c648" formatted="false">"Native connection initialization failed"</string>
|
||||||
|
<string name="text_edecf0ae6e5144f9" formatted="false">"Send"</string>
|
||||||
|
<string name="text_43b7b60867ca51e7" formatted="false">"Send "</string>
|
||||||
|
<string name="text_da7df5a5543c4ba1" formatted="false">"After sending, click \"Paste\" on the remote keyboard. The received remote text can be manually copied to the local machine."</string>
|
||||||
|
<string name="text_e7281fbca7af369c" formatted="false">"Send file"</string>
|
||||||
|
<string name="text_ce6c02d19022e016" formatted="false">"Send text"</string>
|
||||||
|
<string name="text_2cd0f3be8738a86c" formatted="false">"Cancel"</string>
|
||||||
|
<string name="text_3687f953cfe85fb8" formatted="false">"Cancel connection"</string>
|
||||||
|
<string name="text_72814b15a6fa152c" formatted="false">"Each component is subject to the listed open source license, and the rights granted are not subject to the general terms of use of this application. Viewing source code requires an internet connection."</string>
|
||||||
|
<string name="text_e009c148d1890dac" formatted="false">"Total"</string>
|
||||||
|
<string name="text_81c92013219dd115" formatted="false">"Agree and continue"</string>
|
||||||
|
<string name="text_409696774c9998ce" formatted="false">"Keep connected in the background, click to return to the remote desktop"</string>
|
||||||
|
<string name="text_dcb8d4e2e6973912" formatted="false">"Background keepalive failed to start, please keep the application in the foreground"</string>
|
||||||
|
<string name="text_d96a21463557171d" formatted="false">"Background keepalive failed to start, please keep the application in the foreground and reconnect."</string>
|
||||||
|
<string name="text_952f7300de0a7bd9" formatted="false">"The background connection has ended, please reconnect."</string>
|
||||||
|
<string name="text_da904f715e1cff87" formatted="false">"The background connection service has stopped, please reconnect."</string>
|
||||||
|
<string name="text_0812359e18082071" formatted="false">"New previews will still be saved on subsequent connections."</string>
|
||||||
|
<string name="text_b9086662b1df1f20" formatted="false">"Online"</string>
|
||||||
|
<string name="text_39f2f444a042643e" formatted="false">"Fill in the host name or IPv4 address, without protocol and path"</string>
|
||||||
|
<string name="text_473400815c0f687d" formatted="false">"Sound"</string>
|
||||||
|
<string name="text_ab5ca1fab212f4ca" formatted="false">"Copy remote text"</string>
|
||||||
|
<string name="text_c92269b2039e7aa3" formatted="false">"Always maintain a higher resolution and reduce the frame rate to ensure picture clarity when bandwidth is insufficient."</string>
|
||||||
|
<string name="text_7ff73de85ec16d15" formatted="false">"Media encryption"</string>
|
||||||
|
<string name="text_22dc6217af77bc37" formatted="false">"Letters & numbers"</string>
|
||||||
|
<string name="text_9ca1cdaf831182e3" formatted="false">"Secure storage or certificate initialization failed, please try again or clear application data"</string>
|
||||||
|
<string name="text_c0b3fbff51ccc40b" formatted="false">"Done"</string>
|
||||||
|
<string name="text_678328f6f02881c7" formatted="false">"You can check for updates after completing privacy authorization"</string>
|
||||||
|
<string name="text_c4361f9abdba4882" formatted="false">"Register the local identity after completing privacy authorization"</string>
|
||||||
|
<string name="text_c29b888e357d727d" formatted="false">"Password saving failed, this connection can still be used"</string>
|
||||||
|
<string name="text_3f31300a23afb390" formatted="false">"Passwords are saved encrypted in the native Android Keystore"</string>
|
||||||
|
<string name="text_8d3b052c4c67acda" formatted="false">"Remote ID"</string>
|
||||||
|
<string name="text_0512084e130984bc" formatted="false">"Peer ID "</string>
|
||||||
|
<string name="text_c5147d65960196b0" formatted="false">"Peer ID {0}"</string>
|
||||||
|
<string name="text_882b705d31ac7e2a" formatted="false">"All remote previews saved on this machine will be deleted, and the connection records and passwords will be retained."</string>
|
||||||
|
<string name="text_60cc99cc0013972e" formatted="false">"Screen orientation"</string>
|
||||||
|
<string name="text_9e7294d264afecb7" formatted="false">"Expand remote control menu"</string>
|
||||||
|
<string name="text_9343612ee473edd6" formatted="false">"Remote connection disconnected from notification"</string>
|
||||||
|
<string name="text_9d32476fe83aef60" formatted="false">"Password saved"</string>
|
||||||
|
<string name="text_1cbd391383283924" formatted="false">"The saved password cannot be read, please re-enter it."</string>
|
||||||
|
<string name="text_6792b9f341380c68" formatted="false">"The saved password cannot be retained, please re-enter it."</string>
|
||||||
|
<string name="text_74d3f0927d465047" formatted="false">"Ctrl+Alt+Del sent"</string>
|
||||||
|
<string name="text_c28d86acbedba3b6" formatted="false">"Background keep-alive is enabled, switching applications can continue to maintain the connection"</string>
|
||||||
|
<string name="text_506409ec08cb2a79" formatted="false">"Background keepalive enabled to return or disconnect from connection notifications"</string>
|
||||||
|
<string name="text_521e65ffc7d0ff2d" formatted="false">"Granted"</string>
|
||||||
|
<string name="text_efdd1a47e4beb2f9" formatted="false">"Read"</string>
|
||||||
|
<string name="text_1f672b9b18d18e87" formatted="false">"Connected "</string>
|
||||||
|
<string name="text_9c05d9d7fa1b0abd" formatted="false">"Connected to server"</string>
|
||||||
|
<string name="text_f9404bf335bc14cf" formatted="false">"Frame rate"</string>
|
||||||
|
<string name="text_9ae0c69e26db1d6a" formatted="false">"Smooth"</string>
|
||||||
|
<string name="text_df544e46aa4d5c17" formatted="false">"Balanced"</string>
|
||||||
|
<string name="text_3362e8fa7f65cc2f" formatted="false">"The license file in the application is missing or damaged, please reinstall the application."</string>
|
||||||
|
<string name="text_3f2904435942cc0f" formatted="false">"The development version contains modifications that have not yet been released. The following link is the source code of the basic version."</string>
|
||||||
|
<string name="text_4338afaad52ea445" formatted="false">"Enable saving"</string>
|
||||||
|
<string name="text_c8bb8255390df3b6" formatted="false">"Enable privacy screen"</string>
|
||||||
|
<string name="text_c6ecaca362ae1c80" formatted="false">"Open-source components"</string>
|
||||||
|
<string name="text_6099db946bdb5ee4" formatted="false">"Open source software rights description"</string>
|
||||||
|
<string name="text_6e86570183c3cdd0" formatted="false">"This is currently the latest version"</string>
|
||||||
|
<string name="text_24a52788cf474bf6" formatted="false">"Currently, the remote end does not support direct input of this text. Please use a version of the controlled end that supports text input, or send it through the clipboard."</string>
|
||||||
|
<string name="text_33f25b500697a7d9" formatted="false">"Upon successful connection, the nearest remote device will be displayed here."</string>
|
||||||
|
<string name="text_70341256d7317e7d" formatted="false">"I have read and agreed to the Privacy Policy"</string>
|
||||||
|
<string name="text_85c4c8fda0461f35" formatted="false">"Selected file (cannot be read or insufficient space)"</string>
|
||||||
|
<string name="text_9b58c9ff5f6a856f" formatted="false">"The device ID was not found"</string>
|
||||||
|
<string name="text_97b7b18df07b9787" formatted="false">"Command sending failed, please try again"</string>
|
||||||
|
<string name="text_cb782027ca5986c7" formatted="false">"The command has been sent, waiting for computer confirmation..."</string>
|
||||||
|
<string name="text_3ca40a586103f6b7" formatted="false">"Authorization status"</string>
|
||||||
|
<string name="text_6e684586884ebee6" formatted="false">"Receive"</string>
|
||||||
|
<string name="text_84ae92e54e3078aa" formatted="false">"receive "</string>
|
||||||
|
<string name="text_d16488b987723161" formatted="false">"Local decoder"</string>
|
||||||
|
<string name="text_6b58198955259066" formatted="false">"Withdraw"</string>
|
||||||
|
<string name="text_c52e42ee856825db" formatted="false">"After withdrawing, the remote connection will be stopped, the screen preview will be closed and the existing preview will be cleared. The connection record and password will be retained, and authorization is required again before reconnecting."</string>
|
||||||
|
<string name="text_61f4788032ec596b" formatted="false">"Withdraw network authorization"</string>
|
||||||
|
<string name="text_e736e419008ac10a" formatted="false">"Withdraw network authorization?"</string>
|
||||||
|
<string name="text_73431dcf0afb7a65" formatted="false">"Collapse remote control menu"</string>
|
||||||
|
<string name="text_d826c4ece4dd4031" formatted="false">"Hide keyboard"</string>
|
||||||
|
<string name="text_6e9a457c0c3c34ed" formatted="false">"Hide mouse"</string>
|
||||||
|
<string name="text_5440f7424f2865bf" formatted="false">"Data"</string>
|
||||||
|
<string name="text_94244f41d0a41378" formatted="false">"File saving failed, please try again"</string>
|
||||||
|
<string name="text_2553a785fef2a405" formatted="false">"File saved"</string>
|
||||||
|
<string name="text_d573b3f9a810ea04" formatted="false">"File selection timed out and session ended"</string>
|
||||||
|
<string name="text_985a5c380be49f4f" formatted="false">"Text exceeds 128 KB"</string>
|
||||||
|
<string name="text_f33ac04eece6a6a6" formatted="false">"Disconnect"</string>
|
||||||
|
<string name="text_09b1395f50589327" formatted="false">"Will return to the homepage after disconnecting"</string>
|
||||||
|
<string name="text_a73c0680db0d2e06" formatted="false">"Disconnect remote connection?"</string>
|
||||||
|
<string name="text_a33f3804768e68ee" formatted="false">"Disconnect"</string>
|
||||||
|
<string name="text_47ceaf502c5a05d6" formatted="false">"New version"</string>
|
||||||
|
<string name="text_78823e6cf1a8afe5" formatted="false">"New version available: v"</string>
|
||||||
|
<string name="text_b7e58b67fa8f7e78" formatted="false">"New version available: v{0}"</string>
|
||||||
|
<string name="text_ef23787ec32c5588" formatted="false">"No unread announcements"</string>
|
||||||
|
<string name="text_dca2c418ac967366" formatted="false">"Unable to save reading status locally. Please try again."</string>
|
||||||
|
<string name="text_5c92da76360056cc" formatted="false">"Unable to delete this announcement. Please try again."</string>
|
||||||
|
<string name="text_615709dd3129313c" formatted="false">"Unable to initiate remote connection"</string>
|
||||||
|
<string name="text_a5c4f4ad79a640ae" formatted="false">"Unable to read open source license"</string>
|
||||||
|
<string name="text_1a13ea5910a0c5e6" formatted="false">"Display"</string>
|
||||||
|
<string name="text_ffe21a671190323e" formatted="false">"Show password"</string>
|
||||||
|
<string name="text_a94460e8a04f04c5" formatted="false">"Not now"</string>
|
||||||
|
<string name="text_6d9a7758610a2432" formatted="false">"No announcements"</string>
|
||||||
|
<string name="text_0b290c6600d92f43" formatted="false">"Recent connections"</string>
|
||||||
|
<string name="text_2388cbc6a486af9f" formatted="false">"Server"</string>
|
||||||
|
<string name="text_dfe408e686175095" formatted="false">"Server TLS certificate verification failed"</string>
|
||||||
|
<string name="text_d3716cc5a2f5a810" formatted="false">"Server Address"</string>
|
||||||
|
<string name="text_2a9608d4465d0f70" formatted="false">"Server login failed, please check the server address and network"</string>
|
||||||
|
<string name="text_d270868960e68629" formatted="false">"Server port"</string>
|
||||||
|
<string name="text_7a4e4a6a1b479a52" formatted="false">"Server connection timed out, please check the server address and network"</string>
|
||||||
|
<string name="text_f95ea7f4c063eea7" formatted="false">"Not enabled"</string>
|
||||||
|
<string name="text_c8f7ed6f2186fb98" formatted="false">"No browser found to open the link"</string>
|
||||||
|
<string name="text_94bc3d40defef3e9" formatted="false">"Unauthorized"</string>
|
||||||
|
<string name="text_4d0680f9efaef147" formatted="false">"Unread"</string>
|
||||||
|
<string name="text_d1fcb66a323a16f5" formatted="false">"Server not connected · Click to try again"</string>
|
||||||
|
<string name="text_7eea7966c0b746c5" formatted="false">"This application uses OpenFEC for forward error correction under CeCILL-C 1.0, which provides limited warranties and limits liability. See the full license for details."</string>
|
||||||
|
<string name="text_c611b4e50b0dbc08" formatted="false">"Local ID "</string>
|
||||||
|
<string name="text_97d10220f08dd7fc" formatted="false">"Local ID {0}"</string>
|
||||||
|
<string name="text_91f15d505770d7a0" formatted="false">"Source code and build instructions for this version"</string>
|
||||||
|
<string name="text_5b2c8ca2c5a87241" formatted="false">"Source revision is unavailable for this build."</string>
|
||||||
|
<string name="text_f0e274aa130e08a2" formatted="false">"Release to refresh"</string>
|
||||||
|
<string name="text_47077144715b7074" formatted="false">"Build instructions"</string>
|
||||||
|
<string name="text_837ab2d0b2f4cb98" formatted="false">"Build recipes and project patches"</string>
|
||||||
|
<string name="text_921c3b62f1f7d6f4" formatted="false">"View source code"</string>
|
||||||
|
<string name="text_fdd1059f10c4b979" formatted="false">"Check failed. Please try again later."</string>
|
||||||
|
<string name="text_7f68ebad19ba6bcd" formatted="false">"Check for updates"</string>
|
||||||
|
<string name="text_4f4d1e704e38c161" formatted="false">"Closing privacy screen…"</string>
|
||||||
|
<string name="text_2a05f6fbd3f4436e" formatted="false">"Establishing secure connection…"</string>
|
||||||
|
<string name="text_0da575e3c92de73a" formatted="false">"Turning on privacy screen..."</string>
|
||||||
|
<string name="text_91834aebecabcbc3" formatted="false">"Checking for updates…"</string>
|
||||||
|
<string name="text_0571a5f5f8ec4c83" formatted="false">"Clearing..."</string>
|
||||||
|
<string name="text_0e6336c8fe918730" formatted="false">"Obtaining local ID..."</string>
|
||||||
|
<string name="text_faebd20c2801fd0f" formatted="false">"Reading selected file..."</string>
|
||||||
|
<string name="text_694186911ae3e4da" formatted="false">"Connecting"</string>
|
||||||
|
<string name="text_c3ccc7f7d37533e3" formatted="false">"Connecting to signaling server..."</string>
|
||||||
|
<string name="text_4024d3c0f467a4f6" formatted="false">"Connecting to server..."</string>
|
||||||
|
<string name="text_46fe1ef52ec3a10f" formatted="false">"Reconnecting to server..."</string>
|
||||||
|
<string name="text_4e4fce649fc00238" formatted="false">"No file picker available for this device"</string>
|
||||||
|
<string name="text_bce2377283c2455a" formatted="false">"Clear"</string>
|
||||||
|
<string name="text_ee0e76b8507212de" formatted="false">"Clear previews"</string>
|
||||||
|
<string name="text_ecc809e44637de18" formatted="false">"Clear previews?"</string>
|
||||||
|
<string name="text_2782e4485ad23d34" formatted="false">"Source code and build instructions"</string>
|
||||||
|
<string name="text_1f566fe8f8b718f3" formatted="false">"Source code version"</string>
|
||||||
|
<string name="text_5f76b2bf82dd2c5d" formatted="false">"Version"</string>
|
||||||
|
<string name="text_16410ee9663fad10" formatted="false">"Copyright and License Statement"</string>
|
||||||
|
<string name="text_11de77e1e41e680f" formatted="false">"Computer keyboard"</string>
|
||||||
|
<string name="text_20f57900234debd7" formatted="false">"Quality"</string>
|
||||||
|
<string name="text_43b04aba53a54cf5" formatted="false">"Video Preference"</string>
|
||||||
|
<string name="text_82a5600c470179fa" formatted="false">"Video latency"</string>
|
||||||
|
<string name="text_33b458faaf967e15" formatted="false">"Video settings"</string>
|
||||||
|
<string name="text_afb5acdf8f07fadb" formatted="false">"Video Quality"</string>
|
||||||
|
<string name="text_12b41b7a3fbcf8ca" formatted="false">"Video Capture Frame Rate"</string>
|
||||||
|
<string name="text_66062971234f422d" formatted="false">"Trackpad"</string>
|
||||||
|
<string name="text_c421dc051bea3f11" formatted="false">"Relative mouse"</string>
|
||||||
|
<string name="text_de32e20193ad1042" formatted="false">"Got it"</string>
|
||||||
|
<string name="text_fac2a67ad87807c4" formatted="false">"OK"</string>
|
||||||
|
<string name="text_36f33adaf0942634" formatted="false">"Confirm"</string>
|
||||||
|
<string name="text_a5b7fba4dbd8f622" formatted="false">"Confirm connection"</string>
|
||||||
|
<string name="text_be1b4f3c6c1cda1e" formatted="false">"Offline"</string>
|
||||||
|
<string name="text_bfa4a6e0fe42ac28" formatted="false">"Space"</string>
|
||||||
|
<string name="text_471a579e8d87faac" formatted="false">"Port range is 1–65535"</string>
|
||||||
|
<string name="text_c7d0c77862e32b52" formatted="false">"Symbols & keys"</string>
|
||||||
|
<string name="text_a562f07bd38c1a49" formatted="false">"Wait for the computer to return to the privacy screen state; older computers may not support it."</string>
|
||||||
|
<string name="text_5ee880e2524a983a" formatted="false">"Waiting for remote screen information"</string>
|
||||||
|
<string name="text_674c723a85ee7f57" formatted="false">"Waiting for the remote screen..."</string>
|
||||||
|
<string name="text_119f0483da4756c4" formatted="false">"Waiting for privacy authorization"</string>
|
||||||
|
<string name="text_7916071c1a8ee458" formatted="false">"System keyboard"</string>
|
||||||
|
<string name="text_02e9ba6fda719e2e" formatted="false">"Direct touch"</string>
|
||||||
|
<string name="text_8527c8c6d1ccc1ca" formatted="false">"Absolute Mouse"</string>
|
||||||
|
<string name="text_872428a7210d5e0c" formatted="false">"Network"</string>
|
||||||
|
<string name="text_4eafa9e925b30bcd" formatted="false">"Custom"</string>
|
||||||
|
<string name="text_dffd0c12b8f27247" formatted="false">"Virtual mouse"</string>
|
||||||
|
<string name="text_62c199f37bf95869" formatted="false">"The remote privacy screen was unlocked using the local shortcut. Lock it again to restore privacy and disable local shortcut unlocking."</string>
|
||||||
|
<string name="text_a569ce729c1d6e19" formatted="false">"The controlled version is too low, please upgrade to the latest version."</string>
|
||||||
|
<string name="text_d814476826827b78" formatted="false">"Remote encoder"</string>
|
||||||
|
<string name="text_488402883e27f6f0" formatted="false">"Text to be sent to the remote end"</string>
|
||||||
|
<string name="text_c20f7618d330a854" formatted="false">"Video"</string>
|
||||||
|
<string name="text_310c625425f014d8" formatted="false">"License"</string>
|
||||||
|
<string name="text_5c34f7d0e2124dca" formatted="false">"Licenses and copyright notices"</string>
|
||||||
|
<string name="text_e24134b643a17975" formatted="false">"The license and copyright notice are available offline."</string>
|
||||||
|
<string name="text_5e7628f043ce71d0" formatted="false">"Device identity failed to save, please check secure storage"</string>
|
||||||
|
<string name="text_5d35ee0d7945fcbc" formatted="false">"Device identity and selected saved access passwords are encrypted using Android Keystore. Connection records are only saved locally and are not included in device backup. The clipboard contents are cleared when the session is ended. Stay in session with picture-in-picture or connected notifications after leaving the app, and disconnect within the app or notifications."</string>
|
||||||
|
<string name="text_df3d58c7d84b85f2" formatted="false">"Settings"</string>
|
||||||
|
<string name="text_4f8771952222a011" formatted="false">"Settings, there is a new version"</string>
|
||||||
|
<string name="text_f4f712d5be91721a" formatted="false">"Access password"</string>
|
||||||
|
<string name="text_dd6c465847af1b60" formatted="false">"The access password is wrong, please re-enter it."</string>
|
||||||
|
<string name="text_defba1d2989bfab4" formatted="false">"Certificate verification failed · Click to try again"</string>
|
||||||
|
<string name="text_44f3d4d52347095a" formatted="false">"Please complete the online authorization first"</string>
|
||||||
|
<string name="text_2a07ca002bc5093d" formatted="false">"Please enable the CrossDesk system service on the remote end and try again"</string>
|
||||||
|
<string name="text_990fd23c62de5ccf" formatted="false">"The request timed out and the computer\'s privacy screen status has not been confirmed."</string>
|
||||||
|
<string name="text_e5c8fa6dcf7b2a8b" formatted="false">"Please wait for the current file to be sent."</string>
|
||||||
|
<string name="text_9305d9d72b06110d" formatted="false">"Please enter a port in the range 1–65535"</string>
|
||||||
|
<string name="text_3c7223dfb8dde5cf" formatted="false">"Enter a 9-digit device ID"</string>
|
||||||
|
<string name="text_285a0714894d2899" formatted="false">"Please enter a valid access password"</string>
|
||||||
|
<string name="text_c808bb77ffd2b4cd" formatted="false">"Could not apply settings. Try again."</string>
|
||||||
|
<string name="text_a762e7e650e285fa" formatted="false">"Software license"</string>
|
||||||
|
<string name="text_572cf45ba43634b3" formatted="false">"Back"</string>
|
||||||
|
<string name="text_7565f94031582c7c" formatted="false">"Return to control bar"</string>
|
||||||
|
<string name="text_55113b6925c362fd" formatted="false">"No connection record yet"</string>
|
||||||
|
<string name="text_3aa527b2106cc987" formatted="false">"This announcement has been updated or deleted, please return to the announcement list to view it."</string>
|
||||||
|
<string name="text_e357aa9a0fb28c16" formatted="false">"Remote task is closed"</string>
|
||||||
|
<string name="text_c255841f68096d3b" formatted="false">"Remote Desktop"</string>
|
||||||
|
<string name="text_133f4c7dabd88b97" formatted="false">"Remote desktop touch area"</string>
|
||||||
|
<string name="text_5931aeb3778f28f1" formatted="false">"Remote desktop: Touch and click, drag to move, press and hold the right button, pinch to zoom, zoom in and drag the screen with two fingers, slide up and down with two fingers to scroll at the original scale"</string>
|
||||||
|
<string name="text_3535087994d050de" formatted="false">"Remote computer"</string>
|
||||||
|
<string name="text_ed2e03072ce131de" formatted="false">"The remote window has been closed, or the background keepalive failed to start, and the session has ended."</string>
|
||||||
|
<string name="text_678b783fb578172b" formatted="false">"Remote connection"</string>
|
||||||
|
<string name="text_deccbd3eb9ba45f2" formatted="false">"Incorrect remote password"</string>
|
||||||
|
<string name="text_a2ce499295e358ae" formatted="false">"The remote end has closed the connection"</string>
|
||||||
|
<string name="text_176e117df3ace734" formatted="false">"The remote end is currently unavailable"</string>
|
||||||
|
<string name="text_d2c0694243f93249" formatted="false">"The remote access password has expired, please reconnect."</string>
|
||||||
|
<string name="text_a5574109f0208e89" formatted="false">"Connect"</string>
|
||||||
|
<string name="text_1d54bfbae5f8ac08" formatted="false">"Connection "</string>
|
||||||
|
<string name="text_89c4766afc85125f" formatted="false">"Connection failed"</string>
|
||||||
|
<string name="text_04bf1a61ba1369f6" formatted="false">"Connection failed · Click to try again"</string>
|
||||||
|
<string name="text_2ab7d89557e779ab" formatted="false">"Connection lost"</string>
|
||||||
|
<string name="text_c1d20bf4c3e471c1" formatted="false">"Connection ended"</string>
|
||||||
|
<string name="text_e57d2cbc0e9908e6" formatted="false">"Connection delay (RTT)"</string>
|
||||||
|
<string name="text_db4d4f0fab78498a" formatted="false">"Available when connected to a supported remote device."</string>
|
||||||
|
<string name="text_2123a9ed718f2c40" formatted="false">"Lock landscape screen after connection is established"</string>
|
||||||
|
<string name="text_f741e0536210eafa" formatted="false">"Connection prompt"</string>
|
||||||
|
<string name="text_485a26050ce57431" formatted="false">"Connection method"</string>
|
||||||
|
<string name="text_5a048351a77fe88d" formatted="false">"Connect to server"</string>
|
||||||
|
<string name="text_5854c4add7ab0f7a" formatted="false">"Connect to the server to get the latest announcements."</string>
|
||||||
|
<string name="text_32cd27b5a72515f3" formatted="false">"Connection timed out, please check the device ID, server and remote online status"</string>
|
||||||
|
<string name="text_6edc4b54cc1a9838" formatted="false">"Connect to remote desktop"</string>
|
||||||
|
<string name="text_d5407b4f3c666681" formatted="false">"Backspace"</string>
|
||||||
|
<string name="text_e02926784fcedb91" formatted="false">"Announcements"</string>
|
||||||
|
<string name="text_465a092680d132b3" formatted="false">"Some previews could not be cleared, please try again."</string>
|
||||||
|
<string name="text_b8784c8dd5636ff2" formatted="false">"Try again"</string>
|
||||||
|
<string name="text_a504fb6f162bdb20" formatted="false">"Retry saving reading status"</string>
|
||||||
|
<string name="text_ff9872a023e3a8aa" formatted="false">"Lock"</string>
|
||||||
|
<string name="text_864200bbb241e067" formatted="false">"Keyboard"</string>
|
||||||
|
<string name="text_1f0f014d4177a0cc" formatted="false">"Read and authorize"</string>
|
||||||
|
<string name="text_86651d17a401c55b" formatted="false">"Privacy"</string>
|
||||||
|
<string name="text_26c6e011e343708c" formatted="false">"Privacy and Authorization"</string>
|
||||||
|
<string name="text_6cad42fd3da85585" formatted="false">"Privacy screen"</string>
|
||||||
|
<string name="text_e157fafb76aad954" formatted="false">"Privacy screen off"</string>
|
||||||
|
<string name="text_bbef0eac0c04f214" formatted="false">"Privacy screen on"</string>
|
||||||
|
<string name="text_0c7535f7c5e43b6f" formatted="false">"Privacy screen unlocked"</string>
|
||||||
|
<string name="text_b62ac3dfb606c00b" formatted="false">"Privacy screen abnormality"</string>
|
||||||
|
<string name="text_598a2747dbd21dd5" formatted="false">"Privacy screen is temporarily unavailable"</string>
|
||||||
|
<string name="text_4d59be3c75189032" formatted="false">"Privacy screen status has expired, waiting for computer confirmation"</string>
|
||||||
|
<string name="text_1674532c70ed1db2" formatted="false">"Privacy screen status to be confirmed"</string>
|
||||||
|
<string name="text_e464b41fd5c45ba5" formatted="false">"Privacy Policy"</string>
|
||||||
|
<string name="text_554e957e8e3c00af" formatted="false">"The privacy policy cannot be read temporarily. Please refuse to connect to the Internet and reinstall the application."</string>
|
||||||
|
<string name="text_7db2e189ecb01065" formatted="false">"Mute"</string>
|
||||||
|
<string name="text_296c632ec857a0ba" formatted="false">"Audio"</string>
|
||||||
|
<string name="text_0cadb49096a382e0" formatted="false">"Project website"</string>
|
||||||
|
<string name="text_3beca60bd0c654f7" formatted="false">"Previews may contain personal or work information from the remote computer. After turning it on, each connection will automatically save a picture on the machine and display it in recent connections. You can close and clear at any time."</string>
|
||||||
|
<string name="text_b1c27820fec23edb" formatted="false">"High"</string>
|
||||||
|
<string name="text_844b8cc8dff7c1d8" formatted="false">"Default"</string>
|
||||||
|
<string name="text_4db8737054f3b0cc" formatted="false">"Off by default. When turned on, the screen will enter and remain in landscape orientation when the phone is connected; when turned off, it will follow the direction of the device. Tablets and multi-windows are not affected."</string>
|
||||||
|
<string name="text_1184a97edd8e1646" formatted="false">"Right mouse button, press and drag, release to end dragging"</string>
|
||||||
|
<string name="text_926eaa3af0b5e48e" formatted="false">"Left mouse button, press and drag, release to end dragging"</string>
|
||||||
|
<string name="text_269f39a07b4a4e01" formatted="false">"Mouse control"</string>
|
||||||
|
<string name="text_cba62aeeb4a63a23" formatted="false">"Mouse wheel, slide up and down to slide out of the key range"</string>
|
||||||
|
<string name="text_9aa238fc6121dede" formatted="false">", packet loss rate "</string>
|
||||||
|
<string name="text_0b5d16c21095fe10" formatted="false">", send "</string>
|
||||||
|
<string name="text_ba9a792d063a8c86" formatted="false">", password saved"</string>
|
||||||
|
<string name="text_f4c1f9d7896760a7" formatted="false">", receive "</string>
|
||||||
|
<string name="text_42237b5ec70c26f3" formatted="false">", click Retry"</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<locale android:name="en" />
|
||||||
|
<locale android:name="zh-Hans" />
|
||||||
|
<locale android:name="zh-Hant" />
|
||||||
|
<locale android:name="ja" />
|
||||||
|
<locale android:name="ko" />
|
||||||
|
<locale android:name="de" />
|
||||||
|
<locale android:name="fr" />
|
||||||
|
<locale android:name="es" />
|
||||||
|
<locale android:name="pt-BR" />
|
||||||
|
<locale android:name="ru" />
|
||||||
|
</locale-config>
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "../../../libs/controller/include/mobile_controller_protocol.h"
|
#include "../../../libs/controller/include/mobile_controller_protocol.h"
|
||||||
namespace crossdesk::android_controller {
|
namespace crossdesk::android_controller {
|
||||||
using mobile_controller::SecureAttentionMessage;
|
using mobile_controller::SecureAttentionMessage;
|
||||||
|
using mobile_controller::PrivacyMessage;
|
||||||
using mobile_controller::VideoSettingsMessage;
|
using mobile_controller::VideoSettingsMessage;
|
||||||
using mobile_controller::NetworkReport;
|
using mobile_controller::NetworkReport;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -370,7 +370,8 @@ extern "C" JNIEXPORT jboolean JNICALL JNI_METHOD(nConnect)(
|
|||||||
}
|
}
|
||||||
extern "C" JNIEXPORT void JNICALL JNI_METHOD(nReady)(JNIEnv*, jclass, jlong handle) {
|
extern "C" JNIEXPORT void JNICALL JNI_METHOD(nReady)(JNIEnv*, jclass, jlong handle) {
|
||||||
auto* s = From(handle); if (!s || !s->controller) return;
|
auto* s = From(handle); if (!s || !s->controller) return;
|
||||||
auto action = MakeHostInformation("CrossDesk Android", {}, false, "1.6.0", HostPlatform::Android);
|
auto action = MakeHostInformation("CrossDesk Android", {}, true, "1.6.0", HostPlatform::Android);
|
||||||
|
action.i.supports_video_codec_status = true;
|
||||||
s->Send(kControlStream, action.to_json()); FreeRemoteAction(action);
|
s->Send(kControlStream, action.to_json()); FreeRemoteAction(action);
|
||||||
}
|
}
|
||||||
extern "C" JNIEXPORT void JNICALL JNI_METHOD(nPointer)(
|
extern "C" JNIEXPORT void JNICALL JNI_METHOD(nPointer)(
|
||||||
@@ -429,6 +430,14 @@ extern "C" JNIEXPORT jboolean JNICALL JNI_METHOD(nSecureAttention)(JNIEnv*, jcla
|
|||||||
const auto data = android_controller::SecureAttentionMessage();
|
const auto data = android_controller::SecureAttentionMessage();
|
||||||
return SendReliableDataFrame(s->controller, data.data(), data.size(), kControlStream) == 0;
|
return SendReliableDataFrame(s->controller, data.data(), data.size(), kControlStream) == 0;
|
||||||
}
|
}
|
||||||
|
extern "C" JNIEXPORT jboolean JNICALL JNI_METHOD(nPrivacy)(
|
||||||
|
JNIEnv*, jclass, jlong handle, jint flag, jboolean block_input) {
|
||||||
|
auto* s = From(handle);
|
||||||
|
if (!s || !s->controller || s->stopping) return false;
|
||||||
|
const auto data = android_controller::PrivacyMessage(flag, block_input);
|
||||||
|
return !data.empty() &&
|
||||||
|
SendReliableDataFrame(s->controller, data.data(), data.size(), kControlStream) == 0;
|
||||||
|
}
|
||||||
extern "C" JNIEXPORT void JNICALL JNI_METHOD(nFileDirectory)(JNIEnv* env, jclass, jlong handle, jstring directory) {
|
extern "C" JNIEXPORT void JNICALL JNI_METHOD(nFileDirectory)(JNIEnv* env, jclass, jlong handle, jstring directory) {
|
||||||
auto* s = From(handle); if (s) s->files.SetDirectory(String(env, directory));
|
auto* s = From(handle); if (s) s->files.SetDirectory(String(env, directory));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ ANDROID = Path(__file__).resolve().parents[1]
|
|||||||
ROOT = ANDROID.parents[1]
|
ROOT = ANDROID.parents[1]
|
||||||
SOURCE = ROOT / 'deps/submodules/minirtc'
|
SOURCE = ROOT / 'deps/submodules/minirtc'
|
||||||
CACHE = ANDROID / '.native'
|
CACHE = ANDROID / '.native'
|
||||||
PACKAGE_REVISION = 'eda39de3fb99b420c168f1ab9ab2d1791e11b662'
|
|
||||||
XMAKE_MIN_VERSION = (3, 1, 1)
|
XMAKE_MIN_VERSION = (3, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
@@ -38,18 +37,12 @@ def main():
|
|||||||
raise SystemExit('Initialize the MiniRTC submodule before building.')
|
raise SystemExit('Initialize the MiniRTC submodule before building.')
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['XMAKE_PKG_INSTALLDIR'] = str(CACHE / 'packages')
|
env['XMAKE_PKG_INSTALLDIR'] = str(CACHE / 'packages')
|
||||||
env['XMAKE_GLOBALDIR'] = str(CACHE / 'global')
|
|
||||||
env['ANDROID_NDK_HOME'] = str(ndk)
|
env['ANDROID_NDK_HOME'] = str(ndk)
|
||||||
env['ANDROID_NDK_ROOT'] = str(ndk)
|
env['ANDROID_NDK_ROOT'] = str(ndk)
|
||||||
env['NO_COLOR'] = '1'
|
env['NO_COLOR'] = '1'
|
||||||
work = ANDROID / 'native'
|
work = ANDROID / 'native'
|
||||||
repository = CACHE / 'global/.xmake/repositories/xmake-repo'
|
# Use the desktop repository policy; Xmake manages recipe initialization
|
||||||
if not (repository / '.git').is_dir():
|
# and updates. Only installed Android packages/build products are isolated.
|
||||||
subprocess.run([xmake, 'repo', '--update'], cwd=work, env=env, check=True)
|
|
||||||
revision = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=repository, text=True).strip()
|
|
||||||
if revision != PACKAGE_REVISION:
|
|
||||||
subprocess.run(['git', 'fetch', 'origin', PACKAGE_REVISION], cwd=repository, check=True)
|
|
||||||
subprocess.run(['git', 'checkout', '--detach', PACKAGE_REVISION], cwd=repository, check=True)
|
|
||||||
subprocess.run([xmake, 'f', '-P', str(work), '-y', '-p', 'android', '-a', 'arm64-v8a',
|
subprocess.run([xmake, 'f', '-P', str(work), '-y', '-p', 'android', '-a', 'arm64-v8a',
|
||||||
'-m', 'release', '--ndk=' + str(ndk), '--ndk_sdkver=26',
|
'-m', 'release', '--ndk=' + str(ndk), '--ndk_sdkver=26',
|
||||||
'--runtimes=c++_static', '--policies=package.precompiled:n',
|
'--runtimes=c++_static', '--policies=package.precompiled:n',
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ int main(int argc, char* argv[]) {
|
|||||||
CSimpleIniA config;
|
CSimpleIniA config;
|
||||||
config.SetUnicode(true);
|
config.SetUnicode(true);
|
||||||
config.LoadFile((paths.GetCachePath() / "config.ini").string().c_str());
|
config.LoadFile((paths.GetCachePath() / "config.ini").string().c_str());
|
||||||
console.SetLanguage(static_cast<int>(config.GetLongValue("Settings", "language", 0)));
|
console.SetLanguage(static_cast<int>(config.GetLongValue("Settings", "language", 10)));
|
||||||
std::cout << console.Text("console_cli_help");
|
std::cout << console.Text("console_cli_help");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ int ConfigCenter::Load() {
|
|||||||
const long language_value =
|
const long language_value =
|
||||||
ini_->GetLongValue(section_, "language", static_cast<long>(language_));
|
ini_->GetLongValue(section_, "language", static_cast<long>(language_));
|
||||||
if (language_value < static_cast<long>(LANGUAGE::CHINESE) ||
|
if (language_value < static_cast<long>(LANGUAGE::CHINESE) ||
|
||||||
language_value > static_cast<long>(LANGUAGE::RUSSIAN)) {
|
language_value > static_cast<long>(LANGUAGE::SYSTEM)) {
|
||||||
language_ = LANGUAGE::ENGLISH;
|
language_ = LANGUAGE::ENGLISH;
|
||||||
} else {
|
} else {
|
||||||
language_ = static_cast<LANGUAGE>(language_value);
|
language_ = static_cast<LANGUAGE>(language_value);
|
||||||
@@ -137,6 +137,8 @@ int ConfigCenter::Load() {
|
|||||||
enable_daemon_ = ini_->GetBoolValue(section_, "enable_daemon", enable_daemon_);
|
enable_daemon_ = ini_->GetBoolValue(section_, "enable_daemon", enable_daemon_);
|
||||||
enable_privacy_screen_.store(
|
enable_privacy_screen_.store(
|
||||||
ini_->GetBoolValue(section_, "enable_privacy_screen", true));
|
ini_->GetBoolValue(section_, "enable_privacy_screen", true));
|
||||||
|
privacy_local_unlock_allowed_.store(
|
||||||
|
ini_->GetBoolValue(section_, "privacy_local_unlock_allowed", true));
|
||||||
lock_on_disconnect_.store(
|
lock_on_disconnect_.store(
|
||||||
ini_->GetBoolValue(section_, "lock_on_disconnect", true));
|
ini_->GetBoolValue(section_, "lock_on_disconnect", true));
|
||||||
save_remote_previews_.store(
|
save_remote_previews_.store(
|
||||||
@@ -234,6 +236,8 @@ int ConfigCenter::Save() {
|
|||||||
ini_->SetBoolValue(section_, "enable_daemon", enable_daemon_);
|
ini_->SetBoolValue(section_, "enable_daemon", enable_daemon_);
|
||||||
ini_->SetBoolValue(section_, "enable_privacy_screen",
|
ini_->SetBoolValue(section_, "enable_privacy_screen",
|
||||||
enable_privacy_screen_.load());
|
enable_privacy_screen_.load());
|
||||||
|
ini_->SetBoolValue(section_, "privacy_local_unlock_allowed",
|
||||||
|
privacy_local_unlock_allowed_.load());
|
||||||
ini_->SetBoolValue(section_, "lock_on_disconnect", lock_on_disconnect_.load());
|
ini_->SetBoolValue(section_, "lock_on_disconnect", lock_on_disconnect_.load());
|
||||||
ini_->SetBoolValue(section_, "save_remote_previews",
|
ini_->SetBoolValue(section_, "save_remote_previews",
|
||||||
save_remote_previews_.load());
|
save_remote_previews_.load());
|
||||||
@@ -257,7 +261,7 @@ int ConfigCenter::Save() {
|
|||||||
|
|
||||||
int ConfigCenter::SetLanguage(LANGUAGE language) {
|
int ConfigCenter::SetLanguage(LANGUAGE language) {
|
||||||
const int value = static_cast<int>(language);
|
const int value = static_cast<int>(language);
|
||||||
if (value < 0 || value > 2 || StoreValues({{"language", std::to_string(value)}}) != 0) return -1;
|
if (value < 0 || value > static_cast<int>(LANGUAGE::SYSTEM) || StoreValues({{"language", std::to_string(value)}}) != 0) return -1;
|
||||||
language_ = language;
|
language_ = language;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -409,6 +413,18 @@ int ConfigCenter::SetPrivacyScreen(bool enable_privacy_screen) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ConfigCenter::SetPrivacyLocalUnlock(bool allowed) {
|
||||||
|
if (privacy_local_unlock_allowed_.load() == allowed) return 0;
|
||||||
|
if (StoreValues({{"privacy_local_unlock_allowed", allowed ? "true" : "false"}}) != 0)
|
||||||
|
return -1;
|
||||||
|
privacy_local_unlock_allowed_.store(allowed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigCenter::IsPrivacyLocalUnlockAllowed() const {
|
||||||
|
return privacy_local_unlock_allowed_.load();
|
||||||
|
}
|
||||||
|
|
||||||
int ConfigCenter::SetLockOnDisconnect(bool enabled) {
|
int ConfigCenter::SetLockOnDisconnect(bool enabled) {
|
||||||
if (StoreValues({{"lock_on_disconnect", enabled ? "true" : "false"}}) != 0)
|
if (StoreValues({{"lock_on_disconnect", enabled ? "true" : "false"}}) != 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ namespace crossdesk {
|
|||||||
|
|
||||||
class ConfigCenter {
|
class ConfigCenter {
|
||||||
public:
|
public:
|
||||||
enum class LANGUAGE { CHINESE = 0, ENGLISH = 1, RUSSIAN = 2 };
|
enum class LANGUAGE {
|
||||||
|
CHINESE = 0, ENGLISH = 1, RUSSIAN = 2, TRADITIONAL_CHINESE = 3,
|
||||||
|
JAPANESE = 4, KOREAN = 5, GERMAN = 6, FRENCH = 7, SPANISH = 8,
|
||||||
|
BRAZILIAN_PORTUGUESE = 9, SYSTEM = 10
|
||||||
|
};
|
||||||
enum class CONNECTION_MODE { DEVICE_ID = 0, DIRECT_IP = 1 };
|
enum class CONNECTION_MODE { DEVICE_ID = 0, DIRECT_IP = 1 };
|
||||||
enum class VIDEO_ENCODE_FORMAT { H264 = 0, AV1 = 1 };
|
enum class VIDEO_ENCODE_FORMAT { H264 = 0, AV1 = 1 };
|
||||||
enum class TURN_MODE {
|
enum class TURN_MODE {
|
||||||
@@ -49,6 +53,7 @@ class ConfigCenter {
|
|||||||
int SetAutostart(bool enable_autostart);
|
int SetAutostart(bool enable_autostart);
|
||||||
int SetDaemon(bool enable_daemon);
|
int SetDaemon(bool enable_daemon);
|
||||||
int SetPrivacyScreen(bool enable_privacy_screen);
|
int SetPrivacyScreen(bool enable_privacy_screen);
|
||||||
|
int SetPrivacyLocalUnlock(bool allowed);
|
||||||
int SetLockOnDisconnect(bool enabled);
|
int SetLockOnDisconnect(bool enabled);
|
||||||
int SetSaveRemotePreviews(bool enabled);
|
int SetSaveRemotePreviews(bool enabled);
|
||||||
int SetPortableServicePromptSuppressed(bool suppressed);
|
int SetPortableServicePromptSuppressed(bool suppressed);
|
||||||
@@ -73,6 +78,7 @@ class ConfigCenter {
|
|||||||
bool IsEnableAutostart() const;
|
bool IsEnableAutostart() const;
|
||||||
bool IsEnableDaemon() const;
|
bool IsEnableDaemon() const;
|
||||||
bool IsEnablePrivacyScreen() const;
|
bool IsEnablePrivacyScreen() const;
|
||||||
|
bool IsPrivacyLocalUnlockAllowed() const;
|
||||||
bool IsLockOnDisconnect() const;
|
bool IsLockOnDisconnect() const;
|
||||||
bool IsSaveRemotePreviews() const;
|
bool IsSaveRemotePreviews() const;
|
||||||
bool IsPortableServicePromptSuppressed() const;
|
bool IsPortableServicePromptSuppressed() const;
|
||||||
@@ -91,7 +97,7 @@ class ConfigCenter {
|
|||||||
std::unique_ptr<IniStorage> ini_;
|
std::unique_ptr<IniStorage> ini_;
|
||||||
const char* section_ = "Settings";
|
const char* section_ = "Settings";
|
||||||
|
|
||||||
LANGUAGE language_ = LANGUAGE::CHINESE;
|
LANGUAGE language_ = LANGUAGE::SYSTEM;
|
||||||
CONNECTION_MODE connection_mode_ = CONNECTION_MODE::DEVICE_ID;
|
CONNECTION_MODE connection_mode_ = CONNECTION_MODE::DEVICE_ID;
|
||||||
bool enable_ip_direct_ = true;
|
bool enable_ip_direct_ = true;
|
||||||
ScreenCaptureMethod screen_capture_method_ = ScreenCaptureMethod::Auto;
|
ScreenCaptureMethod screen_capture_method_ = ScreenCaptureMethod::Auto;
|
||||||
@@ -105,6 +111,7 @@ class ConfigCenter {
|
|||||||
bool enable_autostart_ = false;
|
bool enable_autostart_ = false;
|
||||||
bool enable_daemon_ = false;
|
bool enable_daemon_ = false;
|
||||||
std::atomic<bool> enable_privacy_screen_{true};
|
std::atomic<bool> enable_privacy_screen_{true};
|
||||||
|
std::atomic<bool> privacy_local_unlock_allowed_{true};
|
||||||
std::atomic<bool> lock_on_disconnect_{true};
|
std::atomic<bool> lock_on_disconnect_{true};
|
||||||
std::atomic<bool> save_remote_previews_{true};
|
std::atomic<bool> save_remote_previews_{true};
|
||||||
bool portable_service_prompt_suppressed_ = false;
|
bool portable_service_prompt_suppressed_ = false;
|
||||||
|
|||||||
@@ -71,6 +71,7 @@
|
|||||||
#elif (defined(__linux__) && !defined(__OHOS__))
|
#elif (defined(__linux__) && !defined(__OHOS__))
|
||||||
#include <GL/gl.h>
|
#include <GL/gl.h>
|
||||||
#include <X11/Xatom.h>
|
#include <X11/Xatom.h>
|
||||||
|
#include <X11/Xcursor/Xcursor.h>
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
@@ -100,6 +101,41 @@ namespace {
|
|||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
bool SupportsControlHandCursor([[maybe_unused]] const slint::Window& window) {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return LoadCursorW(nullptr, IDC_HAND) != nullptr;
|
||||||
|
#elif defined(__linux__) && !defined(__OHOS__)
|
||||||
|
// Match winit's pointer aliases, without using any move/grab fallback.
|
||||||
|
constexpr const char* names[] = {"pointer", "hand2", "hand1", "hand",
|
||||||
|
"pointing_hand"};
|
||||||
|
if (window.wayland_surface()) {
|
||||||
|
const char* theme = std::getenv("XCURSOR_THEME");
|
||||||
|
for (const char* name : names) {
|
||||||
|
if (auto* images = XcursorLibraryLoadImages(name, theme, 24)) {
|
||||||
|
const bool available = images->nimage > 0;
|
||||||
|
XcursorImagesDestroy(images);
|
||||||
|
if (available) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::unique_ptr<Display, decltype(&XCloseDisplay)> display{
|
||||||
|
XOpenDisplay(nullptr), XCloseDisplay};
|
||||||
|
if (!display) return false;
|
||||||
|
for (const char* name : names) {
|
||||||
|
const Cursor cursor = XcursorLibraryLoadCursor(display.get(), name);
|
||||||
|
if (cursor != None) {
|
||||||
|
XFreeCursor(display.get(), cursor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
// AppKit supplies pointingHandCursor; other adapters resolve pointer.
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<std::string> GenerateRandomPassword(std::string_view current) {
|
std::optional<std::string> GenerateRandomPassword(std::string_view current) {
|
||||||
constexpr std::string_view alphabet =
|
constexpr std::string_view alphabet =
|
||||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
@@ -1201,9 +1237,8 @@ void GuiApplication::InitializeSettings() {
|
|||||||
settings_.LoadRecentConnectionAliases();
|
settings_.LoadRecentConnectionAliases();
|
||||||
localization_language_index_ =
|
localization_language_index_ =
|
||||||
localization::detail::ClampLanguageIndex(language_button_value_);
|
localization::detail::ClampLanguageIndex(language_button_value_);
|
||||||
language_button_value_ = localization_language_index_;
|
|
||||||
localization_language_ =
|
localization_language_ =
|
||||||
static_cast<ConfigCenter::LANGUAGE>(localization_language_index_);
|
static_cast<ConfigCenter::LANGUAGE>(language_button_value_);
|
||||||
privacy_.SetText({
|
privacy_.SetText({
|
||||||
localization::privacy_screen_unlock_hint[localization_language_index_]});
|
localization::privacy_screen_unlock_hint[localization_language_index_]});
|
||||||
}
|
}
|
||||||
@@ -1504,6 +1539,7 @@ void GuiApplication::ResetSettingsUi(bool gallery_window) {
|
|||||||
main->set_save_remote_previews(config_center_->IsSaveRemotePreviews());
|
main->set_save_remote_previews(config_center_->IsSaveRemotePreviews());
|
||||||
main->set_privacy_on_connect_enabled(
|
main->set_privacy_on_connect_enabled(
|
||||||
config_center_->IsEnablePrivacyScreen());
|
config_center_->IsEnablePrivacyScreen());
|
||||||
|
main->set_privacy_local_unlock_allowed(config_center_->IsPrivacyLocalUnlockAllowed());
|
||||||
main->set_lock_on_disconnect_enabled(config_center_->IsLockOnDisconnect());
|
main->set_lock_on_disconnect_enabled(config_center_->IsLockOnDisconnect());
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
main->set_capture_method_visible(true);
|
main->set_capture_method_visible(true);
|
||||||
@@ -1562,11 +1598,7 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
|||||||
if (config_center_->SetConnectionMode(mode) != 0) {
|
if (config_center_->SetConnectionMode(mode) != 0) {
|
||||||
LOG_WARN("Could not save connection mode preference");
|
LOG_WARN("Could not save connection mode preference");
|
||||||
main->set_offline_warning(UiText(
|
main->set_offline_warning(UiText(
|
||||||
localization_language_index_ == 0
|
localization::Literal("无法保存连接模式,下次启动可能不会恢复当前模式。", localization_language_index_)));
|
||||||
? "无法保存连接模式,下次启动可能不会恢复当前模式。"
|
|
||||||
: localization_language_index_ == 2
|
|
||||||
? "Не удалось сохранить режим подключения. При следующем запуске он может не восстановиться."
|
|
||||||
: "Could not save the connection mode. It may not be restored on the next launch."));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
main->on_toggle_gallery_fullscreen(
|
main->on_toggle_gallery_fullscreen(
|
||||||
@@ -2144,7 +2176,7 @@ void GuiApplication::BindStreamCallbacks() {
|
|||||||
if (name != "Ctrl+Alt+Del" && name != "Win+L") return;
|
if (name != "Ctrl+Alt+Del" && name != "Win+L") return;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(props->remote_version_mutex_);
|
std::lock_guard lock(props->remote_version_mutex_);
|
||||||
if (name == "Ctrl+Alt+Del" && props->remote_platform_ == HostPlatform::HarmonyOS) return;
|
if (props->remote_platform_ != HostPlatform::Windows) return;
|
||||||
}
|
}
|
||||||
RemoteAction action{};
|
RemoteAction action{};
|
||||||
action.type = ControlType::service_command;
|
action.type = ControlType::service_command;
|
||||||
@@ -2155,9 +2187,9 @@ void GuiApplication::BindStreamCallbacks() {
|
|||||||
SendReliableDataFrame(props->peer_, message.c_str(), message.size(),
|
SendReliableDataFrame(props->peer_, message.c_str(), message.size(),
|
||||||
props->control_data_label_.c_str());
|
props->control_data_label_.c_str());
|
||||||
});
|
});
|
||||||
stream->on_toggle_privacy([this] {
|
const auto send_privacy = [this](bool relock) {
|
||||||
auto props = SelectedSession();
|
auto props = SelectedSession();
|
||||||
if (!props || !props->peer_ || !props->control_mouse_ ||
|
if (!props || !props->peer_ || (!relock && !props->control_mouse_) ||
|
||||||
props->connection_status_.load() != ConnectionStatus::Connected) return;
|
props->connection_status_.load() != ConnectionStatus::Connected) return;
|
||||||
RemoteAction action{};
|
RemoteAction action{};
|
||||||
action.type = ControlType::privacy_command;
|
action.type = ControlType::privacy_command;
|
||||||
@@ -2168,9 +2200,12 @@ void GuiApplication::BindStreamCallbacks() {
|
|||||||
const auto controls = GetPrivacyControlState(
|
const auto controls = GetPrivacyControlState(
|
||||||
status, props->privacy_status_received_, props->privacy_command_pending_,
|
status, props->privacy_status_received_, props->privacy_command_pending_,
|
||||||
now - props->privacy_status_tick_, now - props->privacy_command_tick_);
|
now - props->privacy_status_tick_, now - props->privacy_command_tick_);
|
||||||
if (!controls.can_toggle) return;
|
const bool can_lock = props->privacy_status_received_ && status.supported &&
|
||||||
action.pc.flag = controls.active ? PrivacyCommandFlag::disable
|
now - props->privacy_status_tick_ <= 5000 &&
|
||||||
: PrivacyCommandFlag::enable;
|
(!props->privacy_command_pending_ || now - props->privacy_command_tick_ >= 15000);
|
||||||
|
if (!props->privacy_desktop_available_ || !(relock ? can_lock : controls.can_toggle)) return;
|
||||||
|
action.pc.flag = relock ? PrivacyCommandFlag::lock
|
||||||
|
: controls.active ? PrivacyCommandFlag::disable : PrivacyCommandFlag::enable;
|
||||||
action.pc.block_local_input = true;
|
action.pc.block_local_input = true;
|
||||||
props->privacy_command_pending_ = true;
|
props->privacy_command_pending_ = true;
|
||||||
props->privacy_command_tick_ = now;
|
props->privacy_command_tick_ = now;
|
||||||
@@ -2181,9 +2216,17 @@ void GuiApplication::BindStreamCallbacks() {
|
|||||||
props->control_data_label_.c_str()) != 0) {
|
props->control_data_label_.c_str()) != 0) {
|
||||||
std::lock_guard lock(props->privacy_status_mutex_);
|
std::lock_guard lock(props->privacy_status_mutex_);
|
||||||
props->privacy_command_pending_ = false;
|
props->privacy_command_pending_ = false;
|
||||||
props->privacy_status_received_ = false;
|
if (!relock) props->privacy_status_received_ = false;
|
||||||
LOG_WARN("Privacy command send failed, remote_id={}", props->remote_id_);
|
LOG_WARN("Privacy command send failed, remote_id={}", props->remote_id_);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
stream->on_toggle_privacy([send_privacy] { send_privacy(false); });
|
||||||
|
stream->on_lock_privacy([send_privacy] { send_privacy(true); });
|
||||||
|
stream->on_dismiss_privacy_unlock([this] {
|
||||||
|
if (auto props = SelectedSession()) {
|
||||||
|
std::lock_guard lock(props->privacy_status_mutex_);
|
||||||
|
props->privacy_unlock_notice_ = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
stream->on_toggle_mouse_control([this] {
|
stream->on_toggle_mouse_control([this] {
|
||||||
auto props = SelectedSession();
|
auto props = SelectedSession();
|
||||||
@@ -2590,6 +2633,11 @@ void GuiApplication::Tick() {
|
|||||||
HandlePendingPresenceProbe();
|
HandlePendingPresenceProbe();
|
||||||
HandlePresenceProbeTimeout();
|
HandlePresenceProbeTimeout();
|
||||||
HandlePrivacy();
|
HandlePrivacy();
|
||||||
|
if (privacy_settings_changed_) {
|
||||||
|
for (const auto& window : {ui_->main, ui_->wall})
|
||||||
|
window->set_privacy_local_unlock_allowed(config_center_->IsPrivacyLocalUnlockAllowed());
|
||||||
|
privacy_settings_changed_ = false;
|
||||||
|
}
|
||||||
HandleScreenLock();
|
HandleScreenLock();
|
||||||
HandleWindowsServiceIntegration();
|
HandleWindowsServiceIntegration();
|
||||||
#if (defined(__linux__) && !defined(__OHOS__)) && !defined(__APPLE__)
|
#if (defined(__linux__) && !defined(__OHOS__)) && !defined(__APPLE__)
|
||||||
@@ -3061,13 +3109,9 @@ void GuiApplication::UpdateLocalization() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
// All views refer to static literals; selecting the text needs no allocation
|
static std::vector<std::wstring> shutdown_reasons(localization::GetSupportedLanguages().size());
|
||||||
// and only runs when the language changes. Tick still checks connection state.
|
ui_->shutdown_reason = localization::GetWindowsLocalizedLabel(
|
||||||
ui_->shutdown_reason = language == 0
|
language, "shutdown_reason", L"Remote session active.", shutdown_reasons);
|
||||||
? L"远程控制连接中,保留连接以确认或取消关机。"
|
|
||||||
: language == 2
|
|
||||||
? L"Удалённый сеанс активен. Соединение сохранено для подтверждения или отмены выключения."
|
|
||||||
: L"Remote session active. Keeping the connection available to confirm or cancel shutdown.";
|
|
||||||
#endif
|
#endif
|
||||||
ui_localization::ApplyMainWindowStrings(ui_->main, language);
|
ui_localization::ApplyMainWindowStrings(ui_->main, language);
|
||||||
ui_localization::ApplyStreamWindowStrings(ui_->main, language);
|
ui_localization::ApplyStreamWindowStrings(ui_->main, language);
|
||||||
@@ -3230,13 +3274,12 @@ void GuiApplication::SyncMainWindow() {
|
|||||||
|
|
||||||
// Keep this filter separate from DeskHub's current group, keyed by name so
|
// Keep this filter separate from DeskHub's current group, keyed by name so
|
||||||
// adding/removing groups does not silently select a different one by index.
|
// adding/removing groups does not silently select a different one by index.
|
||||||
const auto tr_group = [this](const char* zh, const char* en, const char* ru) {
|
const auto tr_group = [this](const char* zh) {
|
||||||
return UiText(localization_language_index_ == 0 ? zh
|
return UiText(localization::Literal(zh, localization_language_index_).c_str());
|
||||||
: localization_language_index_ == 2 ? ru : en);
|
|
||||||
};
|
};
|
||||||
std::vector<slint::SharedString> recent_groups{
|
std::vector<slint::SharedString> recent_groups{
|
||||||
tr_group("全部设备", "All devices", "Все устройства"),
|
tr_group("全部设备"),
|
||||||
tr_group("未分组", "Ungrouped", "Без группы")};
|
tr_group("未分组")};
|
||||||
const bool lan_mode = ui_->main->get_lan_mode();
|
const bool lan_mode = ui_->main->get_lan_mode();
|
||||||
int recent_group_index = 0;
|
int recent_group_index = 0;
|
||||||
if (!lan_mode && ui_->recent_group_filter) {
|
if (!lan_mode && ui_->recent_group_filter) {
|
||||||
@@ -3569,9 +3612,9 @@ void GuiApplication::SyncXWaylandWindowActivation() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void GuiApplication::SetStreamKeyboardFocus(bool focused) {
|
void GuiApplication::SetStreamKeyboardFocus(bool focused) {
|
||||||
// The local upgrade dialog owns input even while the native window is active.
|
// Local dialogs own input even while the native window is active.
|
||||||
if (focused && ui_ && ui_->stream &&
|
if (focused && ui_ && ui_->stream &&
|
||||||
(*ui_->stream)->get_remote_update_visible()) {
|
(*ui_->stream)->get_modal_visible()) {
|
||||||
focused = false;
|
focused = false;
|
||||||
}
|
}
|
||||||
if (focus_on_stream_window_ == focused) {
|
if (focus_on_stream_window_ == focused) {
|
||||||
@@ -3633,6 +3676,8 @@ void GuiApplication::SyncStreamWindow() {
|
|||||||
if (!has_sessions) need_to_create_stream_window_ = false;
|
if (!has_sessions) need_to_create_stream_window_ = false;
|
||||||
if (need_to_create_stream_window_ && !ui_->stream) {
|
if (need_to_create_stream_window_ && !ui_->stream) {
|
||||||
ui_->stream.emplace(ui::StreamWindow::create());
|
ui_->stream.emplace(ui::StreamWindow::create());
|
||||||
|
(*ui_->stream)->set_control_hand_cursor_supported(
|
||||||
|
SupportsControlHandCursor(ui_->main->window()));
|
||||||
#if (defined(__linux__) && !defined(__OHOS__))
|
#if (defined(__linux__) && !defined(__OHOS__))
|
||||||
(*ui_->stream)->set_custom_titlebar(use_xwayland_gui_);
|
(*ui_->stream)->set_custom_titlebar(use_xwayland_gui_);
|
||||||
#endif
|
#endif
|
||||||
@@ -3796,9 +3841,7 @@ void GuiApplication::SyncStreamWindow() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(*ui_->stream)->set_remote_update_visible(remote_update_available);
|
(*ui_->stream)->set_remote_update_visible(remote_update_available);
|
||||||
// All desktop clients use the same menu for a Harmony host. Windows SAS is
|
(*ui_->stream)->set_secure_attention_supported(remote_platform == HostPlatform::Windows);
|
||||||
// not an ordinary key chord and has no public Harmony implementation.
|
|
||||||
(*ui_->stream)->set_secure_attention_supported(remote_platform != HostPlatform::HarmonyOS);
|
|
||||||
if (remote_update_available) {
|
if (remote_update_available) {
|
||||||
SetStreamKeyboardFocus(false);
|
SetStreamKeyboardFocus(false);
|
||||||
}
|
}
|
||||||
@@ -3828,11 +3871,18 @@ void GuiApplication::SyncStreamWindow() {
|
|||||||
now - props->privacy_status_tick_, now - props->privacy_command_tick_);
|
now - props->privacy_status_tick_, now - props->privacy_command_tick_);
|
||||||
const bool connected =
|
const bool connected =
|
||||||
props->connection_status_.load() == ConnectionStatus::Connected;
|
props->connection_status_.load() == ConnectionStatus::Connected;
|
||||||
|
(*ui_->stream)->set_privacy_unlock_visible(connected && props->privacy_unlock_notice_);
|
||||||
|
(*ui_->stream)->set_privacy_can_lock(connected && props->privacy_desktop_available_ &&
|
||||||
|
props->privacy_status_received_ && status.supported &&
|
||||||
|
now - props->privacy_status_tick_ <= 5000 &&
|
||||||
|
(!props->privacy_command_pending_ || now - props->privacy_command_tick_ >= 15000));
|
||||||
(*ui_->stream)->set_privacy_active(controls.active);
|
(*ui_->stream)->set_privacy_active(controls.active);
|
||||||
(*ui_->stream)->set_privacy_off(connected && controls.off);
|
(*ui_->stream)->set_privacy_off(connected && controls.off);
|
||||||
(*ui_->stream)->set_privacy_unsupported(controls.unsupported);
|
(*ui_->stream)->set_privacy_unsupported(controls.unsupported);
|
||||||
|
(*ui_->stream)->set_privacy_visible(props->privacy_desktop_available_);
|
||||||
(*ui_->stream)->set_privacy_can_toggle(
|
(*ui_->stream)->set_privacy_can_toggle(
|
||||||
props->control_mouse_ && connected && controls.can_toggle);
|
props->privacy_desktop_available_ && props->control_mouse_ &&
|
||||||
|
connected && controls.can_toggle);
|
||||||
const int language = localization_language_index_;
|
const int language = localization_language_index_;
|
||||||
std::string text = !connected || !props->privacy_status_received_
|
std::string text = !connected || !props->privacy_status_received_
|
||||||
? localization::privacy_screen[language]
|
? localization::privacy_screen[language]
|
||||||
@@ -4543,9 +4593,7 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
|||||||
// common save succeeds. Restore the previous wall file if it fails.
|
// common save succeeds. Restore the previous wall file if it fails.
|
||||||
if (!wall_settings || (wall_changed && !updated_wall.Save(ui_->gallery_path))) {
|
if (!wall_settings || (wall_changed && !updated_wall.Save(ui_->gallery_path))) {
|
||||||
main->set_gallery_settings_error(UiText(
|
main->set_gallery_settings_error(UiText(
|
||||||
localization_language_index_ == 0 ? "保存失败,请重试"
|
localization::Literal("保存失败,请重试", localization_language_index_)));
|
||||||
: localization_language_index_ == 2 ? "Не удалось сохранить. Повторите попытку"
|
|
||||||
: "Could not save; try again"));
|
|
||||||
main->set_settings_tab(1);
|
main->set_settings_tab(1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -4564,9 +4612,7 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
|||||||
if (wall_changed && !ui_->gallery.Save(ui_->gallery_path))
|
if (wall_changed && !ui_->gallery.Save(ui_->gallery_path))
|
||||||
LOG_ERROR("Failed to restore DeskHub settings after IP direct save failure");
|
LOG_ERROR("Failed to restore DeskHub settings after IP direct save failure");
|
||||||
main->set_settings_save_error(UiText(
|
main->set_settings_save_error(UiText(
|
||||||
localization_language_index_ == 0 ? "无法保存 IP 连接设置,请重试。"
|
localization::Literal("无法保存 IP 连接设置,请重试。", localization_language_index_)));
|
||||||
: localization_language_index_ == 2 ? "Не удалось сохранить настройки подключения по IP. Повторите попытку."
|
|
||||||
: "Could not save IP connection settings. Try again."));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#if defined(_WIN32) || defined(__APPLE__) || (defined(__linux__) && !defined(__OHOS__))
|
#if defined(_WIN32) || defined(__APPLE__) || (defined(__linux__) && !defined(__OHOS__))
|
||||||
@@ -4574,14 +4620,18 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
|||||||
if (wall_changed && !ui_->gallery.Save(ui_->gallery_path))
|
if (wall_changed && !ui_->gallery.Save(ui_->gallery_path))
|
||||||
LOG_ERROR("Failed to restore DeskHub settings after lock preference save failure");
|
LOG_ERROR("Failed to restore DeskHub settings after lock preference save failure");
|
||||||
main->set_settings_save_error(UiText(
|
main->set_settings_save_error(UiText(
|
||||||
localization_language_index_ == 0 ? "无法保存断开后锁屏设置,请重试。"
|
localization::Literal("无法保存断开后锁屏设置,请重试。", localization_language_index_)));
|
||||||
: localization_language_index_ == 2 ? "Не удалось сохранить настройку блокировки при отключении. Повторите попытку."
|
|
||||||
: "Could not save lock-on-disconnect settings. Try again."));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (config_center_->SetPrivacyLocalUnlock(main->get_privacy_local_unlock_allowed()) != 0) {
|
||||||
|
if (wall_changed) ui_->gallery.Save(ui_->gallery_path);
|
||||||
|
main->set_settings_save_error(UiText(
|
||||||
|
localization::Literal("无法保存隐私屏解锁设置,请重试。", localization_language_index_)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
language_button_value_ =
|
language_button_value_ =
|
||||||
localization::detail::ClampLanguageIndex(main->get_language_index());
|
std::clamp(main->get_language_index(), 0, localization::kSystemLanguage);
|
||||||
video_encode_format_button_value_ = std::clamp(main->get_codec_index(), 0, 1);
|
video_encode_format_button_value_ = std::clamp(main->get_codec_index(), 0, 1);
|
||||||
enable_self_hosted_ = main->get_self_hosted_enabled();
|
enable_self_hosted_ = main->get_self_hosted_enabled();
|
||||||
#if !defined(__OHOS__)
|
#if !defined(__OHOS__)
|
||||||
@@ -4591,10 +4641,12 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
|||||||
|
|
||||||
localization_language_ =
|
localization_language_ =
|
||||||
static_cast<ConfigCenter::LANGUAGE>(language_button_value_);
|
static_cast<ConfigCenter::LANGUAGE>(language_button_value_);
|
||||||
localization_language_index_ = language_button_value_;
|
localization_language_index_ = localization::detail::ClampLanguageIndex(language_button_value_);
|
||||||
config_center_->SetLanguage(localization_language_);
|
config_center_->SetLanguage(localization_language_);
|
||||||
privacy_.SetText(
|
privacy_.SetText(
|
||||||
{localization::privacy_screen_unlock_hint[localization_language_index_]});
|
{localization::privacy_screen_unlock_hint[localization_language_index_],
|
||||||
|
localization::privacy_screen_locked_hint[localization_language_index_],
|
||||||
|
config_center_->IsPrivacyLocalUnlockAllowed()});
|
||||||
config_center_->SetVideoEncodeFormat(
|
config_center_->SetVideoEncodeFormat(
|
||||||
static_cast<ConfigCenter::VIDEO_ENCODE_FORMAT>(
|
static_cast<ConfigCenter::VIDEO_ENCODE_FORMAT>(
|
||||||
video_encode_format_button_value_));
|
video_encode_format_button_value_));
|
||||||
@@ -4667,7 +4719,9 @@ void GuiApplication::ApplySettingsFromConfig(bool reconnect) {
|
|||||||
enable_self_hosted_ = config_center_->IsSelfHosted();
|
enable_self_hosted_ = config_center_->IsSelfHosted();
|
||||||
enable_autostart_ = config_center_->IsEnableAutostart();
|
enable_autostart_ = config_center_->IsEnableAutostart();
|
||||||
enable_daemon_ = config_center_->IsEnableDaemon();
|
enable_daemon_ = config_center_->IsEnableDaemon();
|
||||||
privacy_.SetText({localization::privacy_screen_unlock_hint[localization_language_index_]});
|
privacy_.SetText({localization::privacy_screen_unlock_hint[localization_language_index_],
|
||||||
|
localization::privacy_screen_locked_hint[localization_language_index_],
|
||||||
|
config_center_->IsPrivacyLocalUnlockAllowed()});
|
||||||
const auto path = config_center_->GetFileTransferSavePath();
|
const auto path = config_center_->GetFileTransferSavePath();
|
||||||
std::snprintf(file_transfer_save_path_buf_, sizeof(file_transfer_save_path_buf_), "%s", path.c_str());
|
std::snprintf(file_transfer_save_path_buf_, sizeof(file_transfer_save_path_buf_), "%s", path.c_str());
|
||||||
const auto host = config_center_->GetSignalServerHost();
|
const auto host = config_center_->GetSignalServerHost();
|
||||||
@@ -4739,10 +4793,7 @@ void GuiApplication::ConnectFromUi(const std::string& input) {
|
|||||||
const std::string remote_id = CompactPeerId(input);
|
const std::string remote_id = CompactPeerId(input);
|
||||||
if (!gallery::Workspace::ValidId(remote_id)) {
|
if (!gallery::Workspace::ValidId(remote_id)) {
|
||||||
ui_->main->set_connection_input_error(
|
ui_->main->set_connection_input_error(
|
||||||
UiText(localization_language_index_ == 0 ? "请输入有效的 9 位设备 ID"
|
UiText(localization::Literal("请输入有效的 9 位设备 ID", localization_language_index_)));
|
||||||
: localization_language_index_ == 2
|
|
||||||
? "Введите корректный 9-значный ID устройства"
|
|
||||||
: "Enter a valid 9-digit device ID"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ui_->main->set_connection_input_error("");
|
ui_->main->set_connection_input_error("");
|
||||||
@@ -5296,41 +5347,31 @@ bool GuiApplication::AddGalleryDevice(const std::string& input,
|
|||||||
const std::string& password,
|
const std::string& password,
|
||||||
const std::string& group,
|
const std::string& group,
|
||||||
bool remember_password) {
|
bool remember_password) {
|
||||||
const auto error = [this](const char* zh, const char* en, const char* ru) {
|
const auto error = [this](const char* zh) {
|
||||||
ui_->wall->set_gallery_notice(UiText(
|
ui_->wall->set_gallery_notice(UiText(
|
||||||
localization_language_index_ == 0 ? zh
|
localization::Literal(zh, localization_language_index_).c_str()));
|
||||||
: localization_language_index_ == 2 ? ru : en));
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const auto id = gallery::Workspace::NormalizeId(input);
|
const auto id = gallery::Workspace::NormalizeId(input);
|
||||||
if (!gallery::Workspace::ValidId(id))
|
if (!gallery::Workspace::ValidId(id))
|
||||||
return error("请输入有效的 9 位设备 ID",
|
return error("请输入有效的 9 位设备 ID");
|
||||||
"Enter a valid 9-digit device ID",
|
|
||||||
"Введите корректный 9-значный ID устройства");
|
|
||||||
if (password.empty() || password.size() > 6 ||
|
if (password.empty() || password.size() > 6 ||
|
||||||
password.find_first_not_of(
|
password.find_first_not_of(
|
||||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") !=
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") !=
|
||||||
std::string::npos)
|
std::string::npos)
|
||||||
return error("请输入 1–6 位英文字母或数字密码",
|
return error("请输入 1–6 位英文字母或数字密码");
|
||||||
"Use 1–6 letters (A–Z, a–z) or digits (0–9)",
|
|
||||||
"Пароль: 1–6 латинских букв или цифр");
|
|
||||||
if (!signal_connected_)
|
if (!signal_connected_)
|
||||||
return error("尚未连接服务器,请稍后重试",
|
return error("尚未连接服务器,请稍后重试");
|
||||||
"Not connected to the server; try again shortly",
|
|
||||||
"Нет соединения с сервером. Повторите позже");
|
|
||||||
using State = SlintUi::BatchOperation::State;
|
using State = SlintUi::BatchOperation::State;
|
||||||
for (const auto& operation : ui_->batch_operations)
|
for (const auto& operation : ui_->batch_operations)
|
||||||
if (operation.id == id && operation.action == "connect" &&
|
if (operation.id == id && operation.action == "connect" &&
|
||||||
(operation.state == State::Queued || operation.state == State::Running))
|
(operation.state == State::Queued || operation.state == State::Running))
|
||||||
return error("该设备正在连接,请稍候",
|
return error("该设备正在连接,请稍候");
|
||||||
"This device is already connecting",
|
|
||||||
"Устройство уже подключается");
|
|
||||||
|
|
||||||
auto updated = ui_->gallery;
|
auto updated = ui_->gallery;
|
||||||
updated.Add(id, Trim(group));
|
updated.Add(id, Trim(group));
|
||||||
if (!updated.Save(ui_->gallery_path))
|
if (!updated.Save(ui_->gallery_path))
|
||||||
return error("保存失败,请重试", "Could not save; try again",
|
return error("保存失败,请重试");
|
||||||
"Не удалось сохранить. Повторите попытку");
|
|
||||||
ui_->gallery = std::move(updated);
|
ui_->gallery = std::move(updated);
|
||||||
ui_->gallery_hidden_active_ids.erase(id);
|
ui_->gallery_hidden_active_ids.erase(id);
|
||||||
if (auto props = FindRemoteSession(id)) {
|
if (auto props = FindRemoteSession(id)) {
|
||||||
@@ -5372,9 +5413,7 @@ bool GuiApplication::ImportGalleryRecent(const std::string& input_group) {
|
|||||||
updated.AddMany(ids, group);
|
updated.AddMany(ids, group);
|
||||||
if (!updated.Save(ui_->gallery_path)) {
|
if (!updated.Save(ui_->gallery_path)) {
|
||||||
ui_->wall->set_gallery_notice(UiText(
|
ui_->wall->set_gallery_notice(UiText(
|
||||||
localization_language_index_ == 0 ? "保存失败,请重试"
|
localization::Literal("保存失败,请重试", localization_language_index_)));
|
||||||
: localization_language_index_ == 2 ? "Не удалось сохранить. Повторите попытку"
|
|
||||||
: "Could not save; try again"));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ui_->gallery = std::move(updated);
|
ui_->gallery = std::move(updated);
|
||||||
@@ -5454,9 +5493,7 @@ bool GuiApplication::SaveDeviceGroups(const std::string& new_group,
|
|||||||
auto updated = ui_->gallery;
|
auto updated = ui_->gallery;
|
||||||
if (!updated.SetGroups(id, names) || !updated.Save(ui_->gallery_path)) {
|
if (!updated.SetGroups(id, names) || !updated.Save(ui_->gallery_path)) {
|
||||||
main->set_device_group_error(UiText(
|
main->set_device_group_error(UiText(
|
||||||
localization_language_index_ == 0 ? "保存失败,请重试"
|
localization::Literal("保存失败,请重试", localization_language_index_)));
|
||||||
: localization_language_index_ == 2 ? "Не удалось сохранить. Повторите попытку"
|
|
||||||
: "Could not save; try again"));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Removing an empty group shifts indices. Retain the filter by name instead
|
// Removing an empty group shifts indices. Retain the filter by name instead
|
||||||
@@ -5494,10 +5531,8 @@ void GuiApplication::GalleryAction(const std::string& action,
|
|||||||
ui_->gallery_network_synced = {};
|
ui_->gallery_network_synced = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto tr = [this](const char* zh, const char* en, const char* ru) {
|
const auto tr = [this](const char* zh) {
|
||||||
return UiText(localization_language_index_ == 0 ? zh
|
return UiText(localization::Literal(zh, localization_language_index_).c_str());
|
||||||
: localization_language_index_ == 2 ? ru
|
|
||||||
: en);
|
|
||||||
};
|
};
|
||||||
if (action == "recent-open") {
|
if (action == "recent-open") {
|
||||||
ReleaseControl();
|
ReleaseControl();
|
||||||
@@ -5715,7 +5750,7 @@ void GuiApplication::GalleryAction(const std::string& action,
|
|||||||
SyncGallery();
|
SyncGallery();
|
||||||
};
|
};
|
||||||
if (action == "file") {
|
if (action == "file") {
|
||||||
OpenFileDialog(std::string(tr("选择分发文件", "Select a file to send", "Выберите файл для отправки")),
|
OpenFileDialog(std::string(tr("选择分发文件")),
|
||||||
[enqueue](std::string file) {
|
[enqueue](std::string file) {
|
||||||
if (!file.empty()) enqueue(file);
|
if (!file.empty()) enqueue(file);
|
||||||
});
|
});
|
||||||
@@ -5992,11 +6027,8 @@ void GuiApplication::SyncGallery() {
|
|||||||
ui_->wall->set_gallery_recent_available(available);
|
ui_->wall->set_gallery_recent_available(available);
|
||||||
ui_->wall->set_gallery_recent_selected(selected);
|
ui_->wall->set_gallery_recent_selected(selected);
|
||||||
}
|
}
|
||||||
const auto tr = [this](const char* zh, const char* en,
|
const auto tr = [this](const char* zh) -> std::string {
|
||||||
const char* ru) -> std::string {
|
return localization::Literal(zh, localization_language_index_).c_str();
|
||||||
return localization_language_index_ == 0 ? zh
|
|
||||||
: localization_language_index_ == 2 ? ru
|
|
||||||
: en;
|
|
||||||
};
|
};
|
||||||
const auto name = [this](const std::string& id) {
|
const auto name = [this](const std::string& id) {
|
||||||
for (const auto& [_, recent] : recent_connections_)
|
for (const auto& [_, recent] : recent_connections_)
|
||||||
@@ -6036,10 +6068,10 @@ void GuiApplication::SyncGallery() {
|
|||||||
device.id = UiText(FormatPeerId(id.c_str()));
|
device.id = UiText(FormatPeerId(id.c_str()));
|
||||||
const auto mode = props->traversal_mode_.load();
|
const auto mode = props->traversal_mode_.load();
|
||||||
device.mode = UiText(mode == TraversalMode::P2P
|
device.mode = UiText(mode == TraversalMode::P2P
|
||||||
? tr("直连", "Direct", "Прямой")
|
? tr("直连")
|
||||||
: mode == TraversalMode::UnknownMode
|
: mode == TraversalMode::UnknownMode
|
||||||
? tr("等待统计", "Awaiting stats", "Ожидание статистики")
|
? tr("等待统计")
|
||||||
: tr("中继", "Relay", "Ретрансляция"));
|
: tr("中继"));
|
||||||
device.inbound = device.outbound = device.loss = "—";
|
device.inbound = device.outbound = device.loss = "—";
|
||||||
device.video_latency = "—";
|
device.video_latency = "—";
|
||||||
// Prefer the wall's own frame path. An independently displayed stream
|
// Prefer the wall's own frame path. An independently displayed stream
|
||||||
@@ -6207,7 +6239,7 @@ void GuiApplication::SyncGallery() {
|
|||||||
label += member.group;
|
label += member.group;
|
||||||
}
|
}
|
||||||
std::vector<slint::SharedString> groups{
|
std::vector<slint::SharedString> groups{
|
||||||
UiText(tr("全部设备", "All devices", "Все устройства"))};
|
UiText(tr("全部设备"))};
|
||||||
for (const auto& group : wall.groups) groups.push_back(UiText(group));
|
for (const auto& group : wall.groups) groups.push_back(UiText(group));
|
||||||
const auto old_groups = ui_->wall->get_gallery_groups();
|
const auto old_groups = ui_->wall->get_gallery_groups();
|
||||||
bool changed_groups = !old_groups || old_groups->row_count() != groups.size();
|
bool changed_groups = !old_groups || old_groups->row_count() != groups.size();
|
||||||
@@ -6291,8 +6323,8 @@ void GuiApplication::SyncGallery() {
|
|||||||
tile.online = tile.connected || online.value_or(false);
|
tile.online = tile.connected || online.value_or(false);
|
||||||
tile.presence_known = tile.connected || online.has_value();
|
tile.presence_known = tile.connected || online.has_value();
|
||||||
tile.status = UiText(
|
tile.status = UiText(
|
||||||
tile.connected ? tr("等待画面", "Waiting for video", "Ожидание видео")
|
tile.connected ? tr("等待画面")
|
||||||
: tr("未连接", "Not connected", "Нет соединения"));
|
: tr("未连接"));
|
||||||
const bool queued = !tile.connected && std::any_of(
|
const bool queued = !tile.connected && std::any_of(
|
||||||
ui_->batch_operations.begin(), ui_->batch_operations.end(),
|
ui_->batch_operations.begin(), ui_->batch_operations.end(),
|
||||||
[&](const auto& operation) {
|
[&](const auto& operation) {
|
||||||
@@ -6301,9 +6333,9 @@ void GuiApplication::SyncGallery() {
|
|||||||
});
|
});
|
||||||
// A queued retry takes precedence over errors from the previous session.
|
// A queued retry takes precedence over errors from the previous session.
|
||||||
if (queued)
|
if (queued)
|
||||||
tile.status = UiText(tr("队列中", "Queued", "В очереди"));
|
tile.status = UiText(tr("队列中"));
|
||||||
else if (props && props->connection_auth_.Get().password_required)
|
else if (props && props->connection_auth_.Get().password_required)
|
||||||
tile.status = UiText(tr("需要密码", "Password required", "Нужен пароль"));
|
tile.status = UiText(tr("需要密码"));
|
||||||
else if (props && !tile.connected)
|
else if (props && !tile.connected)
|
||||||
tile.status = UiText(ConnectionStatusText(
|
tile.status = UiText(ConnectionStatusText(
|
||||||
props->connection_status_.load(), localization_language_index_));
|
props->connection_status_.load(), localization_language_index_));
|
||||||
@@ -6331,8 +6363,8 @@ void GuiApplication::SyncGallery() {
|
|||||||
tile.has_frame = true;
|
tile.has_frame = true;
|
||||||
tile.stale = now - props->gallery_frame_at_ > 5s;
|
tile.stale = now - props->gallery_frame_at_ > 5s;
|
||||||
tile.status =
|
tile.status =
|
||||||
UiText(tile.stale ? tr("画面已停留", "Frame stale", "Кадр устарел")
|
UiText(tile.stale ? tr("画面已停留")
|
||||||
: tr("只读预览", "View only", "Просмотр"));
|
: tr("只读预览"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tile;
|
return tile;
|
||||||
|
|||||||
@@ -1,116 +1,91 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: DI JUNKUN
|
* @Author: DI JUNKUN
|
||||||
* @Date: 2024-05-29
|
* @Date: 2024-05-29
|
||||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
#ifndef _LOCALIZATION_H_
|
#ifndef _LOCALIZATION_H_
|
||||||
#define _LOCALIZATION_H_
|
#define _LOCALIZATION_H_
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "localization_data.h"
|
#include "localization_data.h"
|
||||||
|
#include "system_language.h"
|
||||||
|
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
namespace localization {
|
namespace localization {
|
||||||
|
|
||||||
struct LanguageOption {
|
struct LanguageOption {
|
||||||
std::string code;
|
std::string code;
|
||||||
std::string display_name;
|
std::string display_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LocalizedString {
|
class LocalizedString {
|
||||||
public:
|
public:
|
||||||
constexpr explicit LocalizedString(const char* key) : key_(key) {}
|
constexpr explicit LocalizedString(const char* key) : key_(key) {}
|
||||||
const std::string& operator[](int language_index) const;
|
const std::string& operator[](int language_index) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const char* key_;
|
const char* key_;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline const std::vector<LanguageOption>& GetSupportedLanguages() {
|
inline const std::vector<LanguageOption>& GetSupportedLanguages() {
|
||||||
static const std::vector<LanguageOption> kSupportedLanguages = {
|
static const std::vector<LanguageOption> kSupportedLanguages = {
|
||||||
{"zh-CN", reinterpret_cast<const char*>(u8"中文")},
|
{"zh-CN", "简体中文"}, {"en-US", "English"}, {"ru-RU", "Русский"},
|
||||||
{"en-US", "English"},
|
{"zh-Hant", "繁體中文"}, {"ja", "日本語"}, {"ko", "한국어"},
|
||||||
{"ru-RU", reinterpret_cast<const char*>(u8"Русский")}};
|
{"de", "Deutsch"}, {"fr", "Français"}, {"es", "Español"},
|
||||||
return kSupportedLanguages;
|
{"pt-BR", "Português (Brasil)"}};
|
||||||
}
|
return kSupportedLanguages;
|
||||||
|
}
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
inline int ClampLanguageIndex(int language_index) {
|
inline int ClampLanguageIndex(int language_index) {
|
||||||
if (language_index >= 0 &&
|
if (language_index == kSystemLanguage) return SystemLanguage();
|
||||||
language_index < static_cast<int>(GetSupportedLanguages().size())) {
|
return language_index >= 0 && language_index < kSystemLanguage
|
||||||
return language_index;
|
? language_index : kEnglishLanguage;
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using TranslationTable =
|
using TranslationTable = std::unordered_map<std::string, std::vector<std::string>>;
|
||||||
std::unordered_map<std::string,
|
inline const TranslationTable& LiteralTable() {
|
||||||
std::unordered_map<std::string, std::string>>;
|
static const TranslationTable table = [] {
|
||||||
|
TranslationTable result;
|
||||||
inline std::unordered_map<std::string, std::string> MakeLocalizedValues(
|
for (const auto& row : kTranslationRows)
|
||||||
const TranslationRow& row) {
|
result.emplace(row.key, std::vector<std::string>(std::begin(row.text), std::end(row.text)));
|
||||||
return {{"zh-CN", reinterpret_cast<const char*>(row.zh)},
|
return result;
|
||||||
{"en-US", reinterpret_cast<const char*>(row.en)},
|
}();
|
||||||
{"ru-RU", reinterpret_cast<const char*>(row.ru)}};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline TranslationTable BuildTranslationTable() {
|
|
||||||
TranslationTable table;
|
|
||||||
for (const auto& row : kTranslationRows) {
|
|
||||||
table[row.key] = MakeLocalizedValues(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
inline const std::string& GetLiteralText(const std::string& key, int language) {
|
||||||
inline const TranslationTable& GetTranslationTable() {
|
static const std::string empty;
|
||||||
static const TranslationTable table = BuildTranslationTable();
|
const auto it = LiteralTable().find(key);
|
||||||
return table;
|
if (it == LiteralTable().end()) return empty;
|
||||||
}
|
const auto& values = it->second;
|
||||||
|
return values[ClampLanguageIndex(language)];
|
||||||
inline const std::string& GetTranslatedText(const std::string& key,
|
}
|
||||||
int language_index) {
|
inline const std::string& GetTranslatedText(const std::string& key, int language) {
|
||||||
static const std::string kEmptyText = "";
|
static const std::unordered_map<std::string, std::string> names = [] {
|
||||||
|
std::unordered_map<std::string, std::string> result;
|
||||||
const auto& table = GetTranslationTable();
|
for (const auto& row : kNamedTranslations) result.emplace(row.name, row.literal);
|
||||||
const auto key_it = table.find(key);
|
return result;
|
||||||
if (key_it == table.end()) {
|
}();
|
||||||
return kEmptyText;
|
static const std::string empty;
|
||||||
}
|
const auto it = names.find(key);
|
||||||
|
return it == names.end() ? empty : GetLiteralText(it->second, language);
|
||||||
const auto& localized_values = key_it->second;
|
}
|
||||||
const std::string& language_code =
|
|
||||||
GetSupportedLanguages()[ClampLanguageIndex(language_index)].code;
|
} // namespace detail
|
||||||
|
|
||||||
const auto exact_it = localized_values.find(language_code);
|
inline const std::string& Literal(const std::string& key, int language) {
|
||||||
if (exact_it != localized_values.end()) {
|
return detail::GetLiteralText(key, language);
|
||||||
return exact_it->second;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const auto english_it = localized_values.find("en-US");
|
|
||||||
if (english_it != localized_values.end()) {
|
|
||||||
return english_it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto chinese_it = localized_values.find("zh-CN");
|
|
||||||
if (chinese_it != localized_values.end()) {
|
|
||||||
return chinese_it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return kEmptyText;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
inline const std::string& LocalizedString::operator[](
|
inline const std::string& LocalizedString::operator[](
|
||||||
int language_index) const {
|
int language_index) const {
|
||||||
return detail::GetTranslatedText(key_, language_index);
|
return detail::GetTranslatedText(key_, language_index);
|
||||||
@@ -130,11 +105,11 @@ inline std::string FormatDisplayLabel(size_t display_index,
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CROSSDESK_DECLARE_LOCALIZED_STRING(name, zh, en, ru) \
|
#define CROSSDESK_DECLARE_LOCALIZED_STRING(name) \
|
||||||
inline const LocalizedString name(#name);
|
inline const LocalizedString name(#name);
|
||||||
CROSSDESK_LOCALIZATION_ALL(CROSSDESK_DECLARE_LOCALIZED_STRING)
|
CROSSDESK_LOCALIZATION_ALL(CROSSDESK_DECLARE_LOCALIZED_STRING)
|
||||||
#undef CROSSDESK_DECLARE_LOCALIZED_STRING
|
#undef CROSSDESK_DECLARE_LOCALIZED_STRING
|
||||||
|
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
inline const wchar_t* GetWindowsLocalizedLabel(
|
inline const wchar_t* GetWindowsLocalizedLabel(
|
||||||
int language_index, const char* key, const wchar_t* fallback,
|
int language_index, const char* key, const wchar_t* fallback,
|
||||||
@@ -187,8 +162,8 @@ inline const wchar_t* GetExitProgramLabel(int language_index) {
|
|||||||
cache);
|
cache);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace localization
|
} // namespace localization
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-10-27
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SYSTEM_LANGUAGE_H_
|
||||||
|
#define _SYSTEM_LANGUAGE_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <Windows.h>
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
namespace localization {
|
||||||
|
|
||||||
|
inline constexpr int kSystemLanguage = 10;
|
||||||
|
inline constexpr int kEnglishLanguage = 1;
|
||||||
|
|
||||||
|
// Keep 0/1/2 compatible with existing Chinese/English/Russian config files.
|
||||||
|
inline int MatchLanguage(std::string tag) {
|
||||||
|
for (char& c : tag) {
|
||||||
|
if (c == '_') c = '-';
|
||||||
|
else if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
|
||||||
|
}
|
||||||
|
tag = tag.substr(0, tag.find_first_of(".@"));
|
||||||
|
const auto base = tag.substr(0, tag.find('-'));
|
||||||
|
if (base == "zh") {
|
||||||
|
const auto parts = "-" + tag + "-";
|
||||||
|
if (parts.find("-hans-") != std::string::npos) return 0;
|
||||||
|
return parts.find("-hant-") != std::string::npos ||
|
||||||
|
parts.find("-tw-") != std::string::npos ||
|
||||||
|
parts.find("-hk-") != std::string::npos ||
|
||||||
|
parts.find("-mo-") != std::string::npos ? 3 : 0;
|
||||||
|
}
|
||||||
|
const char* codes[] = {"zh", "en", "ru", "zh", "ja", "ko", "de", "fr", "es", "pt"};
|
||||||
|
for (int i = 1; i < kSystemLanguage; ++i)
|
||||||
|
if (i != 3 && base == codes[i]) return i;
|
||||||
|
if (base == "c" || base == "posix") return kEnglishLanguage;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int MatchPreferredLanguages(const std::vector<std::string>& tags) {
|
||||||
|
for (const auto& tag : tags) {
|
||||||
|
const int match = MatchLanguage(tag);
|
||||||
|
if (match >= 0) return match;
|
||||||
|
}
|
||||||
|
return kEnglishLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int DetectSystemLanguage() {
|
||||||
|
std::vector<std::string> tags;
|
||||||
|
#if defined(_WIN32)
|
||||||
|
ULONG count = 0, length = 0;
|
||||||
|
GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &count, nullptr, &length);
|
||||||
|
std::vector<wchar_t> names(length);
|
||||||
|
if (length && GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &count, names.data(), &length)) {
|
||||||
|
for (const wchar_t* name = names.data(); *name; name += wcslen(name) + 1) {
|
||||||
|
std::string tag;
|
||||||
|
for (const wchar_t* c = name; *c; ++c) tag += static_cast<char>(*c);
|
||||||
|
tags.push_back(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
if (CFArrayRef names = CFLocaleCopyPreferredLanguages()) {
|
||||||
|
for (CFIndex i = 0; i < CFArrayGetCount(names); ++i) {
|
||||||
|
char tag[128] = {};
|
||||||
|
auto name = static_cast<CFStringRef>(CFArrayGetValueAtIndex(names, i));
|
||||||
|
if (CFStringGetCString(name, tag, sizeof(tag), kCFStringEncodingUTF8)) tags.emplace_back(tag);
|
||||||
|
}
|
||||||
|
CFRelease(names);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// The Harmony desktop host supplies LANGUAGE from the system i18n API.
|
||||||
|
const char* locale = std::getenv("LC_ALL");
|
||||||
|
if (!locale || !*locale) locale = std::getenv("LC_MESSAGES");
|
||||||
|
if (!locale || !*locale) locale = std::getenv("LANG");
|
||||||
|
if (locale && (std::string(locale) == "C" || std::string(locale) == "POSIX")) return kEnglishLanguage;
|
||||||
|
if (const char* preferred = std::getenv("LANGUAGE")) {
|
||||||
|
std::string list(preferred);
|
||||||
|
size_t start = 0;
|
||||||
|
do {
|
||||||
|
const auto end = list.find(':', start);
|
||||||
|
tags.push_back(list.substr(start, end - start));
|
||||||
|
if (end == std::string::npos) break;
|
||||||
|
start = end + 1;
|
||||||
|
} while (start < list.size());
|
||||||
|
}
|
||||||
|
if (locale) tags.emplace_back(locale);
|
||||||
|
#endif
|
||||||
|
return MatchPreferredLanguages(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int SystemLanguage() {
|
||||||
|
// Language changes take effect on the next application launch; explicit
|
||||||
|
// application preferences always win over this process-wide default.
|
||||||
|
static const int language = DetectSystemLanguage();
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace localization
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -21,6 +21,8 @@
|
|||||||
#include "interactive_desktop.h"
|
#include "interactive_desktop.h"
|
||||||
#include "service_host.h"
|
#include "service_host.h"
|
||||||
#include "unattended_config.h"
|
#include "unattended_config.h"
|
||||||
|
#include "platform/windows/input/windows_input_injector.h"
|
||||||
|
#include "platform/windows/input/windows_text_input.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
@@ -275,11 +277,51 @@ InputOutcome KeyboardController::ApplyRemoteText(const std::string& remote_id,
|
|||||||
std::lock_guard input_lock(remote_input_mutex_);
|
std::lock_guard input_lock(remote_input_mutex_);
|
||||||
if (!owner_.IsAuthorizedController(remote_id)) return InputOutcome::ignored;
|
if (!owner_.IsAuthorizedController(remote_id)) return InputOutcome::ignored;
|
||||||
if (!owner_.devices_.SupportsTextInput()) return InputOutcome::failed;
|
if (!owner_.devices_.SupportsTextInput()) return InputOutcome::failed;
|
||||||
|
#if _WIN32
|
||||||
|
std::vector<INPUT> inputs;
|
||||||
|
if (!BuildWindowsTextInputs(text, inputs)) return InputOutcome::failed;
|
||||||
|
if (owner_.privacy_.Engaged() && !IsWindowsPrivacyDesktopAvailable())
|
||||||
|
owner_.privacy_.SuspendForDesktop();
|
||||||
|
static const bool process_elevated = IsAdministratorProcess();
|
||||||
|
const bool injected = DispatchDesktopInput(
|
||||||
|
[&] {
|
||||||
|
return PreferUserDesktopInput(IsCurrentSessionUserDesktopActive(),
|
||||||
|
owner_.windows_consent_ui_.load(),
|
||||||
|
owner_.local_service_available_.load(),
|
||||||
|
process_elevated);
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
const UINT sent = SendWindowsTextInputs(
|
||||||
|
inputs, [](INPUT* batch, UINT count) {
|
||||||
|
return SendInputOnUserDesktop(batch, count);
|
||||||
|
});
|
||||||
|
const DWORD error = GetLastError();
|
||||||
|
const bool blocked =
|
||||||
|
error == ERROR_ACCESS_DENIED || error == ERROR_GEN_FAILURE;
|
||||||
|
// A text commit is a batch: only zero insertion permits a retry.
|
||||||
|
return DesktopInputResult{
|
||||||
|
sent == inputs.size(),
|
||||||
|
sent == 0 && (IsDesktopTransitionInputError(error) || blocked),
|
||||||
|
sent == 0 && blocked};
|
||||||
|
},
|
||||||
|
[&]() -> DesktopInputResult {
|
||||||
|
const auto response = SendCrossDeskSecureDesktopTextInput(text, 1000);
|
||||||
|
const auto json = nlohmann::json::parse(response, nullptr, false);
|
||||||
|
if (json.is_object() && json.value("ok", false)) return {true, false};
|
||||||
|
const std::string error =
|
||||||
|
json.is_object() ? json.value("error", "") : "invalid_response";
|
||||||
|
// No text or raw reply in diagnostics; commits may be passwords.
|
||||||
|
LOG_WARN("Secure desktop text injection failed: error={}", error);
|
||||||
|
return {false, IsDesktopInputSetupPending(error)};
|
||||||
|
});
|
||||||
|
return injected ? InputOutcome::applied : InputOutcome::failed;
|
||||||
|
#else
|
||||||
if (!owner_.devices_.SendTextCommand(text)) {
|
if (!owner_.devices_.SendTextCommand(text)) {
|
||||||
LOG_WARN("Failed to inject committed remote text");
|
LOG_WARN("Failed to inject committed remote text");
|
||||||
return InputOutcome::failed;
|
return InputOutcome::failed;
|
||||||
}
|
}
|
||||||
return InputOutcome::applied;
|
return InputOutcome::applied;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
InputOutcome KeyboardController::ApplyRemoteState(const std::string& remote_id,
|
InputOutcome KeyboardController::ApplyRemoteState(const std::string& remote_id,
|
||||||
|
|||||||
@@ -461,8 +461,7 @@ int SettingsManager::Load() {
|
|||||||
std::make_shared<Thumbnail>(owner_.cache_path_ + "/thumbnails/",
|
std::make_shared<Thumbnail>(owner_.cache_path_ + "/thumbnails/",
|
||||||
owner_.aes128_key_, owner_.aes128_iv_);
|
owner_.aes128_key_, owner_.aes128_iv_);
|
||||||
|
|
||||||
owner_.language_button_value_ = localization::detail::ClampLanguageIndex(
|
owner_.language_button_value_ = static_cast<int>(owner_.config_center_->GetLanguage());
|
||||||
static_cast<int>(owner_.config_center_->GetLanguage()));
|
|
||||||
owner_.video_encode_format_button_value_ =
|
owner_.video_encode_format_button_value_ =
|
||||||
static_cast<int>(owner_.config_center_->GetVideoEncodeFormat());
|
static_cast<int>(owner_.config_center_->GetVideoEncodeFormat());
|
||||||
owner_.enable_self_hosted_ = owner_.config_center_->IsSelfHosted();
|
owner_.enable_self_hosted_ = owner_.config_center_->IsSelfHosted();
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ class GuiRuntime : protected gui_detail::GuiState {
|
|||||||
DisconnectLockPolicy disconnect_lock_;
|
DisconnectLockPolicy disconnect_lock_;
|
||||||
std::mutex privacy_commands_mutex_;
|
std::mutex privacy_commands_mutex_;
|
||||||
std::deque<std::pair<std::string, PrivacyCommand>> privacy_commands_;
|
std::deque<std::pair<std::string, PrivacyCommand>> privacy_commands_;
|
||||||
|
bool privacy_settings_changed_ = false;
|
||||||
uint32_t last_privacy_revision_ = 0;
|
uint32_t last_privacy_revision_ = 0;
|
||||||
uint64_t last_privacy_status_tick_ = 0;
|
uint64_t last_privacy_status_tick_ = 0;
|
||||||
ClipboardController clipboard_;
|
ClipboardController clipboard_;
|
||||||
|
|||||||
@@ -316,6 +316,12 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
|||||||
std::lock_guard lock(props->privacy_status_mutex_);
|
std::lock_guard lock(props->privacy_status_mutex_);
|
||||||
if (!props->privacy_status_received_ ||
|
if (!props->privacy_status_received_ ||
|
||||||
static_cast<int32_t>(remote_action.ps.revision - props->privacy_status_.revision) >= 0) {
|
static_cast<int32_t>(remote_action.ps.revision - props->privacy_status_.revision) >= 0) {
|
||||||
|
if (props->privacy_status_received_ &&
|
||||||
|
static_cast<int32_t>(remote_action.ps.local_unlock_revision -
|
||||||
|
props->privacy_status_.local_unlock_revision) > 0)
|
||||||
|
props->privacy_unlock_notice_ = true;
|
||||||
|
if (remote_action.ps.state == PrivacyState::on)
|
||||||
|
props->privacy_unlock_notice_ = false;
|
||||||
props->privacy_status_ = remote_action.ps;
|
props->privacy_status_ = remote_action.ps;
|
||||||
props->privacy_status_received_ = true;
|
props->privacy_status_received_ = true;
|
||||||
props->privacy_status_tick_ = SDL_GetTicks();
|
props->privacy_status_tick_ = SDL_GetTicks();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
|
#include "localization.h"
|
||||||
#include "runtime/gui_runtime.h"
|
#include "runtime/gui_runtime.h"
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
@@ -71,6 +72,14 @@ void GuiRuntime::QueuePrivacyCommand(const std::string& remote_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GuiRuntime::HandlePrivacy() {
|
void GuiRuntime::HandlePrivacy() {
|
||||||
|
const auto sync_text = [this] {
|
||||||
|
const int language = localization::detail::ClampLanguageIndex(
|
||||||
|
static_cast<int>(config_center_->GetLanguage()));
|
||||||
|
privacy_.SetText({localization::privacy_screen_unlock_hint[language],
|
||||||
|
localization::privacy_screen_locked_hint[language],
|
||||||
|
config_center_->IsPrivacyLocalUnlockAllowed()});
|
||||||
|
};
|
||||||
|
sync_text();
|
||||||
std::deque<std::pair<std::string, PrivacyCommand>> commands;
|
std::deque<std::pair<std::string, PrivacyCommand>> commands;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(privacy_commands_mutex_);
|
std::lock_guard lock(privacy_commands_mutex_);
|
||||||
@@ -87,6 +96,16 @@ void GuiRuntime::HandlePrivacy() {
|
|||||||
if (command.flag == PrivacyCommandFlag::enable) {
|
if (command.flag == PrivacyCommandFlag::enable) {
|
||||||
if (privacy_sessions_.CanEnable())
|
if (privacy_sessions_.CanEnable())
|
||||||
privacy_.Enable(command.block_local_input);
|
privacy_.Enable(command.block_local_input);
|
||||||
|
} else if (command.flag == PrivacyCommandFlag::lock) {
|
||||||
|
if (privacy_sessions_.CanEnable()) {
|
||||||
|
if (config_center_->SetPrivacyLocalUnlock(false) == 0) {
|
||||||
|
privacy_settings_changed_ = true;
|
||||||
|
sync_text();
|
||||||
|
privacy_.Enable(true, true);
|
||||||
|
} else {
|
||||||
|
privacy_.RejectCommand("Could not save the local privacy unlock setting; try again");
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (command.flag == PrivacyCommandFlag::disable) {
|
} else if (command.flag == PrivacyCommandFlag::disable) {
|
||||||
privacy_.Disable();
|
privacy_.Disable();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ struct RemoteSession {
|
|||||||
std::mutex privacy_status_mutex_;
|
std::mutex privacy_status_mutex_;
|
||||||
PrivacyStatus privacy_status_{};
|
PrivacyStatus privacy_status_{};
|
||||||
bool privacy_status_received_ = false;
|
bool privacy_status_received_ = false;
|
||||||
|
bool privacy_unlock_notice_ = false;
|
||||||
|
bool privacy_desktop_available_ = true;
|
||||||
bool privacy_command_pending_ = false;
|
bool privacy_command_pending_ = false;
|
||||||
uint64_t privacy_command_tick_ = 0;
|
uint64_t privacy_command_tick_ = 0;
|
||||||
uint32_t privacy_request_revision_ = 0;
|
uint32_t privacy_request_revision_ = 0;
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ export struct AboutComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export global AboutStrings {
|
export global AboutStrings {
|
||||||
out property <string> software-license: GalleryStrings.tr("软件许可", "Software license", "Лицензия программы");
|
out property <string> software-license: GalleryStrings.texts[130];
|
||||||
out property <string> components: GalleryStrings.tr("开源组件", "Open-source components", "Компоненты с открытым кодом");
|
out property <string> components: GalleryStrings.texts[131];
|
||||||
out property <string> source: GalleryStrings.tr("源码与构建说明", "Source code and build instructions", "Исходный код и сборка");
|
out property <string> source: GalleryStrings.texts[132];
|
||||||
out property <string> notices: GalleryStrings.tr("许可与版权声明", "Licenses and copyright notices", "Лицензии и авторские права");
|
out property <string> notices: GalleryStrings.texts[133];
|
||||||
out property <string> view-source: GalleryStrings.tr("查看源码", "View source code", "Открыть исходный код");
|
out property <string> view-source: GalleryStrings.texts[134];
|
||||||
out property <string> back: GalleryStrings.tr("返回", "Back", "Назад");
|
out property <string> back: GalleryStrings.texts[135];
|
||||||
}
|
}
|
||||||
|
|
||||||
export component AboutNavigationGroup inherits FlatSurface {
|
export component AboutNavigationGroup inherits FlatSurface {
|
||||||
@@ -137,7 +137,7 @@ export component AboutPanel inherits SettingsDialog {
|
|||||||
if root.page == AboutPage.components: VerticalLayout {
|
if root.page == AboutPage.components: VerticalLayout {
|
||||||
padding: 0px; spacing: 10px;
|
padding: 0px; spacing: 10px;
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("以下列出本版本的主要开源组件。各组件遵循各自的开源许可。", "Main open-source components used in this build. Each component is covered by its own license.", "Основные открытые компоненты этой сборки. Каждый распространяется по своей лицензии.");
|
text: GalleryStrings.texts[136];
|
||||||
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -146,9 +146,9 @@ export component AboutPanel inherits SettingsDialog {
|
|||||||
notice := VerticalLayout {
|
notice := VerticalLayout {
|
||||||
x: 16px; y: 16px; width: parent.width - 32px;
|
x: 16px; y: 16px; width: parent.width - 32px;
|
||||||
height: self.preferred-height; spacing: 12px;
|
height: self.preferred-height; spacing: 12px;
|
||||||
Text { text: GalleryStrings.tr("OpenFEC 告知", "OpenFEC notice", "Уведомление OpenFEC"); color: WorkbenchStyle.text; font-size: 14px; font-weight: 600; }
|
Text { text: GalleryStrings.texts[137]; color: WorkbenchStyle.text; font-size: 14px; font-weight: 600; }
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("本应用使用 OpenFEC 提供前向纠错功能,遵循 CeCILL-C 1.0 许可。该许可规定了有限保证及责任限制,详情见许可全文。", "This application uses OpenFEC for forward error correction under CeCILL-C 1.0, which provides limited warranties and limits liability. See the full license for details.", "Приложение использует OpenFEC для прямого исправления ошибок по лицензии CeCILL-C 1.0 с ограниченной гарантией и ответственностью. Подробности — в тексте лицензии.");
|
text: GalleryStrings.texts[138];
|
||||||
color: WorkbenchStyle.muted; font-size: 13px; wrap: word-wrap;
|
color: WorkbenchStyle.muted; font-size: 13px; wrap: word-wrap;
|
||||||
font-family: "CrossDesk Message Spacing";
|
font-family: "CrossDesk Message Spacing";
|
||||||
}
|
}
|
||||||
@@ -158,7 +158,7 @@ export component AboutPanel inherits SettingsDialog {
|
|||||||
font-family: "CrossDesk Message Spacing";
|
font-family: "CrossDesk Message Spacing";
|
||||||
}
|
}
|
||||||
if root.openfec-index >= 0: AboutNavigationRow {
|
if root.openfec-index >= 0: AboutNavigationRow {
|
||||||
title: GalleryStrings.tr("OpenFEC 许可与源码", "OpenFEC license and source", "Лицензия и исходный код OpenFEC");
|
title: GalleryStrings.texts[139];
|
||||||
clicked => { root.component-index = root.openfec-index; root.page = AboutPage.component; }
|
clicked => { root.component-index = root.openfec-index; root.page = AboutPage.component; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,24 +185,24 @@ export component AboutPanel inherits SettingsDialog {
|
|||||||
clicked => { root.document-index = index; root.page = AboutPage.document; }
|
clicked => { root.document-index = index; root.page = AboutPage.document; }
|
||||||
}
|
}
|
||||||
if root.selected.documents.length == 0: Text {
|
if root.selected.documents.length == 0: Text {
|
||||||
text: GalleryStrings.tr("本组件未内置许可文本,请通过“查看源码”查阅许可与版权声明。", "License text is not bundled for this component. Use View source code to read its license and copyright notices.", "Текст лицензии не включён. Откройте исходный код для просмотра лицензии и авторских прав.");
|
text: GalleryStrings.texts[140];
|
||||||
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if root.page == AboutPage.source: VerticalLayout {
|
if root.page == AboutPage.source: VerticalLayout {
|
||||||
padding: 0px; spacing: 12px;
|
padding: 0px; spacing: 12px;
|
||||||
Text {
|
Text {
|
||||||
text: root.revision != "" ? GalleryStrings.tr("源码版本:", "Source revision: ", "Ревизия исходного кода: ") + root.revision
|
text: root.revision != "" ? GalleryStrings.texts[141] + root.revision
|
||||||
: GalleryStrings.tr("本版本源码信息暂不可用。", "Source revision is unavailable for this build.", "Ревизия исходного кода этой сборки недоступна.");
|
: GalleryStrings.texts[142];
|
||||||
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
AboutNavigationRow { title: AboutStrings.view-source; external: true; clicked => { root.open-link(root.source-url); } }
|
AboutNavigationRow { title: AboutStrings.view-source; external: true; clicked => { root.open-link(root.source-url); } }
|
||||||
AboutNavigationRow {
|
AboutNavigationRow {
|
||||||
title: GalleryStrings.tr("构建说明", "Build instructions", "Инструкции по сборке"); external: true;
|
title: GalleryStrings.texts[143]; external: true;
|
||||||
clicked => { root.open-link(root.build-url); }
|
clicked => { root.open-link(root.build-url); }
|
||||||
}
|
}
|
||||||
AboutNavigationRow {
|
AboutNavigationRow {
|
||||||
title: GalleryStrings.tr("项目主页", "Project website", "Сайт проекта"); external: true;
|
title: GalleryStrings.texts[144]; external: true;
|
||||||
clicked => { root.open-link("https://crossdesk.cn"); }
|
clicked => { root.open-link("https://crossdesk.cn"); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,37 @@
|
|||||||
import { FlatSurface, UiSurface } from "ui_surface.slint";
|
import { FlatSurface, UiSurface } from "ui_surface.slint";
|
||||||
import { UiRadius } from "ui_radius.slint";
|
import { UiRadius } from "ui_radius.slint";
|
||||||
|
|
||||||
|
// Keep codec names readable in narrow status panels without changing row height.
|
||||||
|
export component ScrollingText inherits Rectangle {
|
||||||
|
in property <string> text;
|
||||||
|
in property <color> text-color;
|
||||||
|
in property <length> font-size: 11px;
|
||||||
|
in property <int> font-weight: 400;
|
||||||
|
in property <bool> align-right: false;
|
||||||
|
private property <length> distance: max(0px, label.preferred-width - root.width);
|
||||||
|
private property <float> phase: 0;
|
||||||
|
min-width: 0px;
|
||||||
|
clip: true;
|
||||||
|
accessible-role: AccessibleRole.text;
|
||||||
|
accessible-label: root.text;
|
||||||
|
changed text => { root.phase = 0; }
|
||||||
|
changed width => { root.phase = 0; }
|
||||||
|
Timer {
|
||||||
|
interval: 16ms;
|
||||||
|
running: root.visible && root.width > 0px && root.distance > 0px;
|
||||||
|
// 30 logical pixels/second, with a one-second pause at each end.
|
||||||
|
triggered => { root.phase = mod(root.phase + 0.48, root.distance / 1px + 60); }
|
||||||
|
}
|
||||||
|
label := Text {
|
||||||
|
x: root.distance > 0px ? -min(root.distance, max(0, root.phase - 30) * 1px)
|
||||||
|
: root.align-right ? root.width - self.preferred-width : 0px;
|
||||||
|
width: self.preferred-width; height: root.height;
|
||||||
|
text: root.text; color: root.text-color;
|
||||||
|
font-size: root.font-size; font-weight: root.font-weight;
|
||||||
|
wrap: no-wrap; vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export global FloatingBarStyle {
|
export global FloatingBarStyle {
|
||||||
out property <length> corner-radius: UiRadius.menu;
|
out property <length> corner-radius: UiRadius.menu;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ export component DeviceCardActions inherits Rectangle {
|
|||||||
spacing: 0px;
|
spacing: 0px;
|
||||||
CardAction {
|
CardAction {
|
||||||
icon: FontAwesomeIcons.pen;
|
icon: FontAwesomeIcons.pen;
|
||||||
tooltip: GalleryStrings.tr("修改名称", "Rename", "Переименовать");
|
tooltip: GalleryStrings.texts[35];
|
||||||
clicked => { root.edit-alias(); }
|
clicked => { root.edit-alias(); }
|
||||||
}
|
}
|
||||||
CardAction {
|
CardAction {
|
||||||
icon: FontAwesomeIcons.tags;
|
icon: FontAwesomeIcons.tags;
|
||||||
tooltip: GalleryStrings.tr("修改分组", "Edit groups", "Изменить группы");
|
tooltip: GalleryStrings.texts[30];
|
||||||
clicked => { root.edit-groups(); }
|
clicked => { root.edit-groups(); }
|
||||||
}
|
}
|
||||||
CardAction {
|
CardAction {
|
||||||
@@ -51,7 +51,7 @@ export component DeviceCardActions inherits Rectangle {
|
|||||||
CardAction {
|
CardAction {
|
||||||
icon: root.connected ? FontAwesomeIcons.xmark : FontAwesomeIcons.arrow-right-long;
|
icon: root.connected ? FontAwesomeIcons.xmark : FontAwesomeIcons.arrow-right-long;
|
||||||
icon-size: 13px;
|
icon-size: 13px;
|
||||||
tooltip: root.connected ? GalleryStrings.tr("断开", "Disconnect", "Отключить") : GalleryStrings.tr("连接", "Connect", "Подключить");
|
tooltip: root.connected ? GalleryStrings.texts[36] : GalleryStrings.texts[37];
|
||||||
clicked => {
|
clicked => {
|
||||||
if root.connected { root.disconnect-device(); }
|
if root.connected { root.disconnect-device(); }
|
||||||
else { root.connect-device(); }
|
else { root.connect-device(); }
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export component DeviceGroupDialog inherits Rectangle {
|
|||||||
TouchArea {}
|
TouchArea {}
|
||||||
Text {
|
Text {
|
||||||
x: 18px; y: 16px; height: 24px;
|
x: 18px; y: 16px; height: 24px;
|
||||||
text: GalleryStrings.tr("修改分组", "Edit groups", "Изменить группы");
|
text: GalleryStrings.texts[30];
|
||||||
color: #24272d; font-size: 17px; font-weight: 600;
|
color: #24272d; font-size: 17px; font-weight: 600;
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
@@ -64,7 +64,7 @@ export component DeviceGroupDialog inherits Rectangle {
|
|||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
x: 39px; width: parent.width - 48px; height: parent.height;
|
x: 39px; width: parent.width - 48px; height: parent.height;
|
||||||
text: option.name == "" ? GalleryStrings.tr("未分组", "Ungrouped", "Без группы") : option.name;
|
text: option.name == "" ? GalleryStrings.texts[31] : option.name;
|
||||||
color: parent.selected ? ControlStyle.selected-text : #30343b;
|
color: parent.selected ? ControlStyle.selected-text : #30343b;
|
||||||
font-weight: parent.selected ? 600 : 400;
|
font-weight: parent.selected ? 600 : 400;
|
||||||
font-size: 12px; overflow: elide; vertical-alignment: center;
|
font-size: 12px; overflow: elide; vertical-alignment: center;
|
||||||
@@ -91,7 +91,7 @@ export component DeviceGroupDialog inherits Rectangle {
|
|||||||
x: 18px; y: group-list.y + group-list.height + 12px;
|
x: 18px; y: group-list.y + group-list.height + 12px;
|
||||||
width: parent.width - 36px; height: 30px;
|
width: parent.width - 36px; height: 30px;
|
||||||
text <=> root.new-group;
|
text <=> root.new-group;
|
||||||
placeholder-text: GalleryStrings.tr("新建分组(可选)", "New group (optional)", "Новая группа (необязательно)");
|
placeholder-text: GalleryStrings.texts[32];
|
||||||
}
|
}
|
||||||
if root.error != "": Text {
|
if root.error != "": Text {
|
||||||
x: 18px; y: group-list.y + group-list.height + 48px;
|
x: 18px; y: group-list.y + group-list.height + 48px;
|
||||||
@@ -102,8 +102,8 @@ export component DeviceGroupDialog inherits Rectangle {
|
|||||||
x: 18px; y: parent.height - 46px; width: parent.width - 36px; height: 28px;
|
x: 18px; y: parent.height - 46px; width: parent.width - 36px; height: 28px;
|
||||||
spacing: 10px;
|
spacing: 10px;
|
||||||
Rectangle { horizontal-stretch: 1; }
|
Rectangle { horizontal-stretch: 1; }
|
||||||
CompactButton { width: 72px; height: 28px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { root.cancel(); } }
|
CompactButton { width: 72px; height: 28px; text: GalleryStrings.texts[33]; clicked => { root.cancel(); } }
|
||||||
CompactButton { width: 88px; height: 28px; primary: true; text: GalleryStrings.tr("保存", "Save", "Сохранить"); clicked => { root.save(root.new-group); } }
|
CompactButton { width: 88px; height: 28px; primary: true; text: GalleryStrings.texts[34]; clicked => { root.save(root.new-group); } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
export global GalleryStrings {
|
export global GalleryStrings {
|
||||||
in-out property <int> language: 0;
|
in-out property <int> language: 0;
|
||||||
|
in-out property <[string]> texts;
|
||||||
|
in-out property <string> retry-prefix;
|
||||||
|
in-out property <string> retry-suffix;
|
||||||
|
public pure function retry-after(seconds: int) -> string {
|
||||||
|
return self.retry-prefix + seconds + self.retry-suffix;
|
||||||
|
}
|
||||||
|
// Compatibility for embedding callers with their own three-language text.
|
||||||
public pure function tr(zh: string, en: string, ru: string) -> string {
|
public pure function tr(zh: string, en: string, ru: string) -> string {
|
||||||
return self.language == 0 ? zh : self.language == 2 ? ru : en;
|
return self.language == 0 ? zh : self.language == 2 ? ru : en;
|
||||||
}
|
}
|
||||||
public pure function device-count(count: int) -> string {
|
public pure function device-count(count: int) -> string {
|
||||||
if self.language == 0 { return count + " 台设备"; }
|
if self.language == 0 { return count + " 台设备"; }
|
||||||
|
if self.language == 3 { return count + " 臺裝置"; }
|
||||||
|
if self.language == 4 { return count + " 台"; }
|
||||||
|
if self.language == 5 { return count + "개 기기"; }
|
||||||
|
if self.language == 6 { return count + (count == 1 ? " Gerät" : " Geräte"); }
|
||||||
|
if self.language == 7 { return count + (count == 1 ? " appareil" : " appareils"); }
|
||||||
|
if self.language == 8 { return count + (count == 1 ? " dispositivo" : " dispositivos"); }
|
||||||
|
if self.language == 9 { return count + (count == 1 ? " dispositivo" : " dispositivos"); }
|
||||||
if self.language == 2 {
|
if self.language == 2 {
|
||||||
let last-two = mod(count, 100);
|
let last-two = mod(count, 100);
|
||||||
let last = mod(count, 10);
|
let last = mod(count, 10);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { SurfaceSeparator, FlatSurface, UiSurface } from "ui_surface.slint";
|
import { SurfaceSeparator, FlatSurface, UiSurface } from "ui_surface.slint";
|
||||||
import { UiRadius } from "ui_radius.slint";
|
import { UiRadius } from "ui_radius.slint";
|
||||||
import { GalleryStrings } from "device_strings.slint";
|
import { GalleryStrings } from "device_strings.slint";
|
||||||
import { CompactButton } from "common.slint";
|
import { CompactButton, ScrollingText } from "common.slint";
|
||||||
import { ScrollView } from "std-widgets.slint";
|
import { ScrollView } from "std-widgets.slint";
|
||||||
|
|
||||||
export struct GalleryNetworkDevice {
|
export struct GalleryNetworkDevice {
|
||||||
@@ -65,10 +65,10 @@ component NetworkRow inherits Rectangle {
|
|||||||
encryption-text := Text {
|
encryption-text := Text {
|
||||||
x: 6px; width: parent.width - 12px; height: parent.height;
|
x: 6px; width: parent.width - 12px; height: parent.height;
|
||||||
text: !root.device.encryption-known
|
text: !root.device.encryption-known
|
||||||
? GalleryStrings.tr("待确认", "Unknown", "Неизвестно")
|
? GalleryStrings.texts[38]
|
||||||
: root.device.encryption-enabled
|
: root.device.encryption-enabled
|
||||||
? GalleryStrings.tr("已加密", "Encrypted", "Зашифровано")
|
? GalleryStrings.texts[39]
|
||||||
: GalleryStrings.tr("未加密", "Unencrypted", "Без шифр.");
|
: GalleryStrings.texts[40];
|
||||||
color: !root.device.encryption-known ? #7a8490
|
color: !root.device.encryption-known ? #7a8490
|
||||||
: root.device.encryption-enabled ? #18794e : #8a6428;
|
: root.device.encryption-enabled ? #18794e : #8a6428;
|
||||||
font-size: 10px; horizontal-alignment: center;
|
font-size: 10px; horizontal-alignment: center;
|
||||||
@@ -83,7 +83,7 @@ component NetworkRow inherits Rectangle {
|
|||||||
Text {
|
Text {
|
||||||
y: root.heading ? 0px : 8px; height: root.heading ? parent.height : 20px;
|
y: root.heading ? 0px : 8px; height: root.heading ? parent.height : 20px;
|
||||||
width: parent.width;
|
width: parent.width;
|
||||||
text: root.heading ? GalleryStrings.tr("画面", "Video", "Видео") : root.device.resolution;
|
text: root.heading ? GalleryStrings.texts[41] : root.device.resolution;
|
||||||
font-size: root.heading ? 11px : 12px; font-weight: root.heading ? 600 : 400;
|
font-size: root.heading ? 11px : 12px; font-weight: root.heading ? 600 : 400;
|
||||||
color: root.heading ? #6b7785 : #293746;
|
color: root.heading ? #6b7785 : #293746;
|
||||||
vertical-alignment: center; overflow: elide;
|
vertical-alignment: center; overflow: elide;
|
||||||
@@ -98,7 +98,7 @@ component NetworkRow inherits Rectangle {
|
|||||||
width: max(100px, root.width * 0.18);
|
width: max(100px, root.width * 0.18);
|
||||||
if root.heading: Text {
|
if root.heading: Text {
|
||||||
x: 0px; width: parent.width;
|
x: 0px; width: parent.width;
|
||||||
text: GalleryStrings.tr("收发速率", "Transfer rate", "Скорость");
|
text: GalleryStrings.texts[42];
|
||||||
font-size: 11px; font-weight: 600; color: #6b7785;
|
font-size: 11px; font-weight: 600; color: #6b7785;
|
||||||
vertical-alignment: center; overflow: elide;
|
vertical-alignment: center; overflow: elide;
|
||||||
}
|
}
|
||||||
@@ -126,14 +126,14 @@ component NetworkRow inherits Rectangle {
|
|||||||
Text {
|
Text {
|
||||||
y: root.heading ? 0px : 8px; height: root.heading ? parent.height : 20px;
|
y: root.heading ? 0px : 8px; height: root.heading ? parent.height : 20px;
|
||||||
width: parent.width;
|
width: parent.width;
|
||||||
text: root.heading ? GalleryStrings.tr("视频延时", "Video latency", "Задержка видео") : root.device.video-latency;
|
text: root.heading ? GalleryStrings.texts[43] : root.device.video-latency;
|
||||||
font-size: root.heading ? 11px : 12px; font-weight: root.heading ? 600 : 400;
|
font-size: root.heading ? 11px : 12px; font-weight: root.heading ? 600 : 400;
|
||||||
color: root.heading ? #6b7785 : #293746;
|
color: root.heading ? #6b7785 : #293746;
|
||||||
vertical-alignment: center; overflow: elide;
|
vertical-alignment: center; overflow: elide;
|
||||||
}
|
}
|
||||||
if !root.heading: Text {
|
if !root.heading: Text {
|
||||||
y: 30px; height: 17px; width: parent.width;
|
y: 30px; height: 17px; width: parent.width;
|
||||||
text: GalleryStrings.tr("丢包 ", "Loss ", "Потери ") + root.device.loss;
|
text: GalleryStrings.texts[44] + root.device.loss;
|
||||||
font-size: 11px; color: #7a8490;
|
font-size: 11px; color: #7a8490;
|
||||||
vertical-alignment: center; overflow: elide;
|
vertical-alignment: center; overflow: elide;
|
||||||
}
|
}
|
||||||
@@ -153,18 +153,26 @@ component NetworkRow inherits Rectangle {
|
|||||||
spacing: 12px;
|
spacing: 12px;
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: (parent.width - 12px) / 2;
|
width: (parent.width - 12px) / 2;
|
||||||
Text {
|
encoder-label := Text {
|
||||||
width: parent.width; height: parent.height;
|
height: parent.height; width: self.preferred-width;
|
||||||
text: GalleryStrings.tr("被控端编码器 ", "Remote encoder ", "Удалённый кодер ") + root.device.encoder;
|
text: GalleryStrings.texts[45];
|
||||||
font-size: 10px; color: #586f87; vertical-alignment: center; overflow: elide;
|
font-size: 10px; color: #586f87; vertical-alignment: center;
|
||||||
|
}
|
||||||
|
ScrollingText {
|
||||||
|
x: encoder-label.width; width: max(0px, parent.width - self.x); height: parent.height;
|
||||||
|
text: root.device.encoder; font-size: 10px; text-color: #586f87;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: (parent.width - 12px) / 2;
|
width: (parent.width - 12px) / 2;
|
||||||
Text {
|
decoder-label := Text {
|
||||||
width: parent.width; height: parent.height;
|
height: parent.height; width: self.preferred-width;
|
||||||
text: GalleryStrings.tr("控制端解码器 ", "Local decoder ", "Локальный декодер ") + root.device.decoder;
|
text: GalleryStrings.texts[46];
|
||||||
font-size: 10px; color: #586f87; vertical-alignment: center; overflow: elide;
|
font-size: 10px; color: #586f87; vertical-alignment: center;
|
||||||
|
}
|
||||||
|
ScrollingText {
|
||||||
|
x: decoder-label.width; width: max(0px, parent.width - self.x); height: parent.height;
|
||||||
|
text: root.device.decoder; font-size: 10px; text-color: #586f87;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,11 +182,11 @@ component NetworkEmptyState inherits Rectangle {
|
|||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
alignment: center; spacing: 8px;
|
alignment: center; spacing: 8px;
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("暂无已连接设备", "No connected devices", "Нет подключённых устройств");
|
text: GalleryStrings.texts[47];
|
||||||
font-size: 13px; color: #596878; horizontal-alignment: center;
|
font-size: 13px; color: #596878; horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("建立连接后,网络和画面信息将在此显示", "Network and video details appear here once connected", "После подключения здесь появятся данные сети и видео");
|
text: GalleryStrings.texts[48];
|
||||||
font-size: 11px; color: #8a95a1; horizontal-alignment: center;
|
font-size: 11px; color: #8a95a1; horizontal-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,25 +216,25 @@ export component GalleryNetworkDialog inherits Rectangle {
|
|||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
x: 20px; y: 17px; width: parent.width - 120px; height: 24px;
|
x: 20px; y: 17px; width: parent.width - 120px; height: 24px;
|
||||||
text: GalleryStrings.tr("设备网络状态", "Device network status", "Состояние сети устройств");
|
text: GalleryStrings.texts[49];
|
||||||
font-size: 17px; font-weight: 600; color: #293746;
|
font-size: 17px; font-weight: 600; color: #293746;
|
||||||
}
|
}
|
||||||
CompactButton {
|
CompactButton {
|
||||||
border-radius: UiRadius.control;
|
border-radius: UiRadius.control;
|
||||||
x: parent.width - 80px; y: 17px; width: 60px; height: 26px;
|
x: parent.width - 80px; y: 17px; width: 60px; height: 26px;
|
||||||
text: GalleryStrings.tr("关闭", "Close", "Закрыть"); clicked => { root.close(); }
|
text: GalleryStrings.texts[50]; clicked => { root.close(); }
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
x: 20px; y: 48px; width: parent.width - 40px; height: 18px;
|
x: 20px; y: 48px; width: parent.width - 40px; height: 18px;
|
||||||
text: GalleryStrings.tr("已连接设备 ", "Connected devices ", "Подключённые устройства ") + root.devices.length
|
text: GalleryStrings.texts[51] + root.devices.length
|
||||||
+ GalleryStrings.tr(" 台", "", "");
|
+ GalleryStrings.texts[52];
|
||||||
font-size: 11px; color: #7a8490; overflow: elide;
|
font-size: 11px; color: #7a8490; overflow: elide;
|
||||||
}
|
}
|
||||||
NetworkRow {
|
NetworkRow {
|
||||||
x: 12px; y: 78px; width: parent.width - 38px; height: 30px;
|
x: 12px; y: 78px; width: parent.width - 38px; height: 30px;
|
||||||
heading: true; background: #f3f5f8; border-radius: UiRadius.control;
|
heading: true; background: #f3f5f8; border-radius: UiRadius.control;
|
||||||
device: {
|
device: {
|
||||||
name: GalleryStrings.tr("设备 / ID", "Device / ID", "Устройство / ID"),
|
name: GalleryStrings.texts[53],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
list := ScrollView {
|
list := ScrollView {
|
||||||
|
|||||||
@@ -9,31 +9,31 @@ export component GallerySettings inherits Rectangle {
|
|||||||
in property <string> error;
|
in property <string> error;
|
||||||
SettingsGroup {
|
SettingsGroup {
|
||||||
y: 0px; width: parent.width;
|
y: 0px; width: parent.width;
|
||||||
title: GalleryStrings.tr("画面传输", "Video transmission", "Передача видео"); rows: 3;
|
title: GalleryStrings.texts[145]; rows: 3;
|
||||||
SettingsRow {
|
SettingsRow {
|
||||||
label: GalleryStrings.tr("分辨率上限", "Resolution limit", "Макс. разрешение");
|
label: GalleryStrings.texts[146];
|
||||||
label-reserve: self.width - 112px;
|
label-reserve: self.width - 112px;
|
||||||
SettingsSelect {
|
SettingsSelect {
|
||||||
border-radius: UiRadius.control;
|
border-radius: UiRadius.control;
|
||||||
x: 136px; y: 4px; width: parent.width - 150px;
|
x: 136px; y: 4px; width: parent.width - 150px;
|
||||||
current-index <=> root.resolution-index;
|
current-index <=> root.resolution-index;
|
||||||
model: [GalleryStrings.tr("自动", "Auto", "Авто"),
|
model: [GalleryStrings.texts[147],
|
||||||
"320 × 180", "640 × 360", "1280 × 720", "1920 × 1080", "2560 × 1440", "3840 × 2160",
|
"320 × 180", "640 × 360", "1280 × 720", "1920 × 1080", "2560 × 1440", "3840 × 2160",
|
||||||
GalleryStrings.tr("原始分辨率", "Original resolution", "Исходное разрешение")];
|
GalleryStrings.texts[148]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsRow {
|
SettingsRow {
|
||||||
label: GalleryStrings.tr("帧率", "Frame rate", "Частота кадров");
|
label: GalleryStrings.texts[149];
|
||||||
label-reserve: self.width - 112px;
|
label-reserve: self.width - 112px;
|
||||||
SettingsSelect {
|
SettingsSelect {
|
||||||
border-radius: UiRadius.control;
|
border-radius: UiRadius.control;
|
||||||
x: 136px; y: 4px; width: parent.width - 150px;
|
x: 136px; y: 4px; width: parent.width - 150px;
|
||||||
current-index <=> root.frame-rate-index;
|
current-index <=> root.frame-rate-index;
|
||||||
model: [GalleryStrings.tr("跟随设备设置", "Follow device settings", "Настройки устройства"), "15 FPS", "30 FPS", "60 FPS"];
|
model: [GalleryStrings.texts[150], "15 FPS", "30 FPS", "60 FPS"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsRow {
|
SettingsRow {
|
||||||
label: GalleryStrings.tr("视频总带宽", "Total bandwidth", "Общий битрейт");
|
label: GalleryStrings.texts[151];
|
||||||
label-reserve: self.width - 112px; divider: false;
|
label-reserve: self.width - 112px; divider: false;
|
||||||
SettingsSelect {
|
SettingsSelect {
|
||||||
border-radius: UiRadius.control;
|
border-radius: UiRadius.control;
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
in property <int> count: 0;
|
in property <int> count: 0;
|
||||||
in property <int> connected-count: 0;
|
in property <int> connected-count: 0;
|
||||||
private property <string> connected-summary: root.connected-count + " / " + root.count
|
private property <string> connected-summary: root.connected-count + " / " + root.count
|
||||||
+ GalleryStrings.tr(" 台已连接", " connected", " подключено");
|
+ GalleryStrings.texts[152];
|
||||||
callback network-open;
|
callback network-open;
|
||||||
in property <int> selected-count: 0;
|
in property <int> selected-count: 0;
|
||||||
in property <bool> selected-audio-available: false;
|
in property <bool> selected-audio-available: false;
|
||||||
@@ -297,10 +297,10 @@ export component GalleryView inherits Rectangle {
|
|||||||
add-menu := GalleryMenuButton {
|
add-menu := GalleryMenuButton {
|
||||||
horizontal-padding: root.toolbar-button-padding;
|
horizontal-padding: root.toolbar-button-padding;
|
||||||
width: self.preferred-button-width;
|
width: self.preferred-button-width;
|
||||||
text: GalleryStrings.tr("添加", "Add", "Добавить"); menu-width: 232px;
|
text: GalleryStrings.texts[153]; menu-width: 232px;
|
||||||
items: [
|
items: [
|
||||||
{label: GalleryStrings.tr("通过设备 ID 添加", "Add by device ID", "Добавить по ID устройства"), enabled: true},
|
{label: GalleryStrings.texts[154], enabled: true},
|
||||||
{label: GalleryStrings.tr("从近期连接添加", "Add from recent", "Добавить из недавних"), enabled: true},
|
{label: GalleryStrings.texts[155], enabled: true},
|
||||||
];
|
];
|
||||||
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
||||||
selected(index) => {
|
selected(index) => {
|
||||||
@@ -316,12 +316,12 @@ export component GalleryView inherits Rectangle {
|
|||||||
horizontal-stretch: 1; min-width: 0px;
|
horizontal-stretch: 1; min-width: 0px;
|
||||||
Text {
|
Text {
|
||||||
x: 0px; y: 0px; width: parent.width; height: root.hidden-selected > 0 ? 13px : parent.height;
|
x: 0px; y: 0px; width: parent.width; height: root.hidden-selected > 0 ? 13px : parent.height;
|
||||||
text: GalleryStrings.tr("已选 ", "Selected ", "Выбрано ") + root.selected-count + GalleryStrings.tr(" 台", "", "");
|
text: GalleryStrings.texts[156] + root.selected-count + GalleryStrings.texts[52];
|
||||||
color: #62666d; vertical-alignment: center; font-size: 11px; overflow: elide;
|
color: #62666d; vertical-alignment: center; font-size: 11px; overflow: elide;
|
||||||
}
|
}
|
||||||
if root.hidden-selected > 0: Text {
|
if root.hidden-selected > 0: Text {
|
||||||
y: 14px; width: parent.width; height: 10px;
|
y: 14px; width: parent.width; height: 10px;
|
||||||
text: GalleryStrings.tr("跨页 ", "Other pages ", "Вне страницы ") + root.hidden-selected;
|
text: GalleryStrings.texts[157] + root.hidden-selected;
|
||||||
color: #80858d; font-size: 10px; overflow: elide;
|
color: #80858d; font-size: 10px; overflow: elide;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,27 +329,27 @@ export component GalleryView inherits Rectangle {
|
|||||||
horizontal-padding: root.toolbar-button-padding;
|
horizontal-padding: root.toolbar-button-padding;
|
||||||
enabled: root.count > 0;
|
enabled: root.count > 0;
|
||||||
text: root.count > 0 && root.selected-count == root.count
|
text: root.count > 0 && root.selected-count == root.count
|
||||||
? GalleryStrings.tr("取消全选", "Deselect all", "Снять выбор")
|
? GalleryStrings.texts[158]
|
||||||
: GalleryStrings.tr("全选", "Select all", "Выбрать все");
|
: GalleryStrings.texts[159];
|
||||||
alternate-text: root.count > 0 && root.selected-count == root.count
|
alternate-text: root.count > 0 && root.selected-count == root.count
|
||||||
? GalleryStrings.tr("全选", "Select all", "Выбрать все")
|
? GalleryStrings.texts[159]
|
||||||
: GalleryStrings.tr("取消全选", "Deselect all", "Снять выбор");
|
: GalleryStrings.texts[158];
|
||||||
clicked => { root.action("all-select", ""); }
|
clicked => { root.action("all-select", ""); }
|
||||||
}
|
}
|
||||||
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; primary: true; enabled: root.selected-count > 0; text: GalleryStrings.tr("连接", "Connect", "Подключить"); clicked => { root.action("connect", ""); } }
|
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; primary: true; enabled: root.selected-count > 0; text: GalleryStrings.texts[37]; clicked => { root.action("connect", ""); } }
|
||||||
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; enabled: root.selected-count > 0; text: GalleryStrings.tr("断开", "Disconnect", "Отключить"); clicked => { root.confirm-action("disconnect", self.text); } }
|
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; enabled: root.selected-count > 0; text: GalleryStrings.texts[36]; clicked => { root.confirm-action("disconnect", self.text); } }
|
||||||
batch-menu := GalleryMenuButton {
|
batch-menu := GalleryMenuButton {
|
||||||
horizontal-padding: root.toolbar-button-padding;
|
horizontal-padding: root.toolbar-button-padding;
|
||||||
width: self.preferred-button-width; enabled: root.selected-count > 0;
|
width: self.preferred-button-width; enabled: root.selected-count > 0;
|
||||||
text: GalleryStrings.tr("更多", "More", "Ещё");
|
text: GalleryStrings.texts[68];
|
||||||
items: [
|
items: [
|
||||||
{label: root.selected-audio-muted ? GalleryStrings.tr("取消静音", "Unmute", "Со звуком") : GalleryStrings.tr("静音", "Mute", "Без звука"), enabled: root.selected-audio-available},
|
{label: root.selected-audio-muted ? GalleryStrings.texts[160] : GalleryStrings.texts[161], enabled: root.selected-audio-available},
|
||||||
{label: GalleryStrings.tr("分发文件", "Send file", "Отправить файл"), enabled: root.selected-count > 0},
|
{label: GalleryStrings.texts[162], enabled: root.selected-count > 0},
|
||||||
];
|
];
|
||||||
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
||||||
selected(index) => {
|
selected(index) => {
|
||||||
if index == 0 { root.action(root.selected-audio-muted ? "unmute" : "mute", ""); }
|
if index == 0 { root.action(root.selected-audio-muted ? "unmute" : "mute", ""); }
|
||||||
else { root.confirm-action("file", GalleryStrings.tr("分发文件", "Send file", "Отправить файл")); }
|
else { root.confirm-action("file", GalleryStrings.texts[162]); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,8 +357,8 @@ export component GalleryView inherits Rectangle {
|
|||||||
x: parent.width - self.width; y: 0px;
|
x: parent.width - self.width; y: 0px;
|
||||||
horizontal-padding: root.toolbar-button-padding;
|
horizontal-padding: root.toolbar-button-padding;
|
||||||
primary: root.batch;
|
primary: root.batch;
|
||||||
alternate-text: GalleryStrings.tr("选择", "Select", "Выбрать");
|
alternate-text: GalleryStrings.texts[163];
|
||||||
text: root.batch ? GalleryStrings.tr("完成", "Done", "Готово") : GalleryStrings.tr("选择", "Select", "Выбрать");
|
text: root.batch ? GalleryStrings.texts[164] : GalleryStrings.texts[163];
|
||||||
clicked => { root.batch = !root.batch; root.action("selection", ""); }
|
clicked => { root.batch = !root.batch; root.action("selection", ""); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,7 +405,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
function show-hint() {
|
function show-hint() {
|
||||||
if self.has-hover && !self.pressed && (self.edge-x != 0 || self.edge-y != 0) {
|
if self.has-hover && !self.pressed && (self.edge-x != 0 || self.edge-y != 0) {
|
||||||
root.device-tooltip-id = "resize-grid";
|
root.device-tooltip-id = "resize-grid";
|
||||||
root.device-tooltip-text = GalleryStrings.tr("拖动表格分隔线,同步调整所有设备大小", "Drag a table divider to resize all devices", "Потяните за разделитель таблицы, чтобы изменить размер всех устройств");
|
root.device-tooltip-text = GalleryStrings.texts[165];
|
||||||
root.device-tooltip-x = grid.x + self.mouse-x;
|
root.device-tooltip-x = grid.x + self.mouse-x;
|
||||||
root.device-tooltip-y = grid.y + self.mouse-y + 16px;
|
root.device-tooltip-y = grid.y + self.mouse-y + 16px;
|
||||||
} else if root.device-tooltip-id == "resize-grid" { root.device-tooltip-id = ""; }
|
} else if root.device-tooltip-id == "resize-grid" { root.device-tooltip-id = ""; }
|
||||||
@@ -461,8 +461,8 @@ export component GalleryView inherits Rectangle {
|
|||||||
else if root.drag-source != "" { root.move-device-drag(self.mouse-x, self.mouse-y); }
|
else if root.drag-source != "" { root.move-device-drag(self.mouse-x, self.mouse-y); }
|
||||||
}
|
}
|
||||||
for tile[index] in root.tiles: tile-card := Rectangle {
|
for tile[index] in root.tiles: tile-card := Rectangle {
|
||||||
private property <string> presence: tile.online ? GalleryStrings.tr("在线", "Online", "В сети")
|
private property <string> presence: tile.online ? GalleryStrings.texts[166]
|
||||||
: tile.presence-known ? GalleryStrings.tr("离线", "Offline", "Не в сети") : GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
: tile.presence-known ? GalleryStrings.texts[167] : GalleryStrings.texts[100];
|
||||||
private property <string> details: tile.name + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence;
|
private property <string> details: tile.name + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence;
|
||||||
accessible-role: button; accessible-label: self.details;
|
accessible-role: button; accessible-label: self.details;
|
||||||
accessible-action-default => { root.action(root.batch && !root.simplified ? "select" : "focus", tile.id); }
|
accessible-action-default => { root.action(root.batch && !root.simplified ? "select" : "focus", tile.id); }
|
||||||
@@ -522,7 +522,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
changed over-actions => { if self.has-hover { self.show-details(); } }
|
changed over-actions => { if self.has-hover { self.show-details(); } }
|
||||||
if tile-touch.has-hover && !root.dragging && !root.resizing && !root.batch && !root.simplified: DeviceCardActions {
|
if tile-touch.has-hover && !root.dragging && !root.resizing && !root.batch && !root.simplified: DeviceCardActions {
|
||||||
x: parent.width - self.width; y: parent.height - 23px;
|
x: parent.width - self.width; y: parent.height - 23px;
|
||||||
remove-label: GalleryStrings.tr("从桌面墙移除", "Remove from DeskHub", "Убрать из DeskHub");
|
remove-label: GalleryStrings.texts[168];
|
||||||
connected: tile.connected;
|
connected: tile.connected;
|
||||||
edit-alias => { root.action("release", ""); root.edit-alias(tile.id, tile.name); }
|
edit-alias => { root.action("release", ""); root.edit-alias(tile.id, tile.name); }
|
||||||
edit-groups => { root.edit-groups(tile.id, tile.name); }
|
edit-groups => { root.edit-groups(tile.id, tile.name); }
|
||||||
@@ -557,8 +557,8 @@ export component GalleryView inherits Rectangle {
|
|||||||
}
|
}
|
||||||
if root.count == 0: Text {
|
if root.count == 0: Text {
|
||||||
text: root.simplified
|
text: root.simplified
|
||||||
? GalleryStrings.tr("暂无设备,退出精简模式后可添加", "No devices. Exit compact mode to add devices", "Нет устройств. Выйдите из компактного режима, чтобы добавить их")
|
? GalleryStrings.texts[169]
|
||||||
: GalleryStrings.tr("添加设备,建立你的桌面墙", "Add devices to build your DeskHub", "Добавьте устройства в DeskHub");
|
: GalleryStrings.texts[170];
|
||||||
color: #808080; horizontal-alignment: center; vertical-alignment: center;
|
color: #808080; horizontal-alignment: center; vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -568,7 +568,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
height: footer.y - 12px - self.y;
|
height: footer.y - 12px - self.y;
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
x: 0px; y: 0px; width: parent.width - root.view-controls-space; height: 24px; spacing: 8px;
|
x: 0px; y: 0px; width: parent.width - root.view-controls-space; height: 24px; spacing: 8px;
|
||||||
GalleryButton { width: 52px; text: GalleryStrings.tr("返回", "Back", "Назад"); clicked => { root.action("focus", ""); } }
|
GalleryButton { width: 52px; text: GalleryStrings.texts[135]; clicked => { root.action("focus", ""); } }
|
||||||
Rectangle {
|
Rectangle {
|
||||||
horizontal-stretch: 1; min-width: 0px;
|
horizontal-stretch: 1; min-width: 0px;
|
||||||
Text { x: 0px; width: parent.width; height: parent.height; text: root.focused.name; color: root.focused.online ? #303943 : DeviceCardStyle.offline-text; font-size: 12px; font-weight: 600; overflow: elide; horizontal-alignment: left; vertical-alignment: center; }
|
Text { x: 0px; width: parent.width; height: parent.height; text: root.focused.name; color: root.focused.online ? #303943 : DeviceCardStyle.offline-text; font-size: 12px; font-weight: 600; overflow: elide; horizontal-alignment: left; vertical-alignment: center; }
|
||||||
@@ -579,10 +579,10 @@ export component GalleryView inherits Rectangle {
|
|||||||
background: root.controlling ? #e7f1fd : #f0f2f5;
|
background: root.controlling ? #e7f1fd : #f0f2f5;
|
||||||
focus-state := Text {
|
focus-state := Text {
|
||||||
x: 8px; width: parent.width - 16px; height: parent.height;
|
x: 8px; width: parent.width - 16px; height: parent.height;
|
||||||
text: root.group-controlling ? GalleryStrings.tr("群控中", "Group control", "Групп. управление")
|
text: root.group-controlling ? GalleryStrings.texts[171]
|
||||||
: root.controlling ? GalleryStrings.tr("正在操控", "Controlling", "Управление")
|
: root.controlling ? GalleryStrings.texts[172]
|
||||||
: root.focused.connected && root.focused.has-frame && !root.focused.stale
|
: root.focused.connected && root.focused.has-frame && !root.focused.stale
|
||||||
? GalleryStrings.tr("只读预览", "View only", "Просмотр") : root.focused.status;
|
? GalleryStrings.texts[173] : root.focused.status;
|
||||||
font-size: 10px; color: root.controlling ? #3e77b3 : #6b7480; vertical-alignment: center; overflow: elide;
|
font-size: 10px; color: root.controlling ? #3e77b3 : #6b7480; vertical-alignment: center; overflow: elide;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -590,14 +590,14 @@ export component GalleryView inherits Rectangle {
|
|||||||
horizontal-padding: root.toolbar-button-padding;
|
horizontal-padding: root.toolbar-button-padding;
|
||||||
primary: root.group-controlling;
|
primary: root.group-controlling;
|
||||||
enabled: root.group-controlling || (root.focused.connected && root.focused.has-frame && !root.focused.stale);
|
enabled: root.group-controlling || (root.focused.connected && root.focused.has-frame && !root.focused.stale);
|
||||||
text: root.group-controlling ? GalleryStrings.tr("关闭群控", "Stop group control", "Откл. групп. ввод") : GalleryStrings.tr("开启群控", "Group control", "Групп. ввод");
|
text: root.group-controlling ? GalleryStrings.texts[174] : GalleryStrings.texts[175];
|
||||||
alternate-text: root.group-controlling ? GalleryStrings.tr("开启群控", "Group control", "Групп. ввод") : GalleryStrings.tr("关闭群控", "Stop group control", "Откл. групп. ввод");
|
alternate-text: root.group-controlling ? GalleryStrings.texts[175] : GalleryStrings.texts[174];
|
||||||
accessible-role: button;
|
accessible-role: button;
|
||||||
accessible-label: self.text + GalleryStrings.tr(":同步键鼠到所有已连接设备", ": mirror input to all connected devices", ": ввод на всех подключённых устройствах");
|
accessible-label: self.text + GalleryStrings.texts[176];
|
||||||
accessible-action-default => { self.clicked(); }
|
accessible-action-default => { self.clicked(); }
|
||||||
clicked => { root.action(root.group-controlling ? "group-release" : "group-control", root.focused.id); }
|
clicked => { root.action(root.group-controlling ? "group-release" : "group-control", root.focused.id); }
|
||||||
}
|
}
|
||||||
GalleryButton { width: 100px; primary: true; enabled: !root.focused.connected || root.controlling || (root.focused.has-frame && !root.focused.stale); text: root.focused.connected ? (root.controlling ? GalleryStrings.tr("释放操控", "Release", "Отпустить") : GalleryStrings.tr("接管操控", "Control", "Управлять")) : GalleryStrings.tr("连接", "Connect", "Подключить"); clicked => { root.action(root.focused.connected ? (root.controlling ? "release" : "control") : "connect", root.focused.id); } }
|
GalleryButton { width: 100px; primary: true; enabled: !root.focused.connected || root.controlling || (root.focused.has-frame && !root.focused.stale); text: root.focused.connected ? (root.controlling ? GalleryStrings.texts[177] : GalleryStrings.texts[178]) : GalleryStrings.texts[37]; clicked => { root.action(root.focused.connected ? (root.controlling ? "release" : "control") : "connect", root.focused.id); } }
|
||||||
}
|
}
|
||||||
FlatSurface {
|
FlatSurface {
|
||||||
x: 0px; y: rail.visible ? focus-divider.y + focus-divider.height : 36px;
|
x: 0px; y: rail.visible ? focus-divider.y + focus-divider.height : 36px;
|
||||||
@@ -671,8 +671,8 @@ export component GalleryView inherits Rectangle {
|
|||||||
width: tile.id == root.focused.id ? 0px : root.rail-slot-width;
|
width: tile.id == root.focused.id ? 0px : root.rail-slot-width;
|
||||||
height: root.rail-card-height; visible: tile.id != root.focused.id;
|
height: root.rail-card-height; visible: tile.id != root.focused.id;
|
||||||
rail-card := FlatSurface {
|
rail-card := FlatSurface {
|
||||||
private property <string> presence: tile.online ? GalleryStrings.tr("在线", "Online", "В сети")
|
private property <string> presence: tile.online ? GalleryStrings.texts[166]
|
||||||
: tile.presence-known ? GalleryStrings.tr("离线", "Offline", "Не в сети") : GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
: tile.presence-known ? GalleryStrings.texts[167] : GalleryStrings.texts[100];
|
||||||
private property <string> details: tile.name + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence;
|
private property <string> details: tile.name + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence;
|
||||||
width: root.rail-preview-width + 10px; height: root.rail-card-height; border-radius: UiRadius.surface;
|
width: root.rail-preview-width + 10px; height: root.rail-card-height; border-radius: UiRadius.surface;
|
||||||
background: tile.online ? #f5f6f8 : DeviceCardStyle.offline-background; outline-color: UiSurface.edge;
|
background: tile.online ? #f5f6f8 : DeviceCardStyle.offline-background; outline-color: UiSurface.edge;
|
||||||
@@ -744,7 +744,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
function show-hint() {
|
function show-hint() {
|
||||||
if self.has-hover && !root.focus-resizing {
|
if self.has-hover && !root.focus-resizing {
|
||||||
root.device-tooltip-id = "resize-focus";
|
root.device-tooltip-id = "resize-focus";
|
||||||
root.device-tooltip-text = GalleryStrings.tr("上下拖动,调整缩略图与主画面大小", "Drag up or down to resize the thumbnails and main preview", "Потяните вверх или вниз, чтобы изменить размер миниатюр и основного экрана");
|
root.device-tooltip-text = GalleryStrings.texts[179];
|
||||||
root.device-tooltip-x = focus-view.x + self.mouse-x;
|
root.device-tooltip-x = focus-view.x + self.mouse-x;
|
||||||
root.device-tooltip-y = focus-view.y + focus-divider.y + self.mouse-y + 16px;
|
root.device-tooltip-y = focus-view.y + focus-divider.y + self.mouse-y + 16px;
|
||||||
} else if root.device-tooltip-id == "resize-focus" { root.device-tooltip-id = ""; }
|
} else if root.device-tooltip-id == "resize-focus" { root.device-tooltip-id = ""; }
|
||||||
@@ -788,7 +788,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
connection-count := Text {
|
connection-count := Text {
|
||||||
x: 0px; width: min(self.preferred-width, parent.width - network-button.width - 12px); height: parent.height;
|
x: 0px; width: min(self.preferred-width, parent.width - network-button.width - 12px); height: parent.height;
|
||||||
text: root.group-controlling
|
text: root.group-controlling
|
||||||
? GalleryStrings.tr("群控中 · 所有已连接设备", "Group control · All connected devices", "Групповое управление · Все подключения")
|
? GalleryStrings.texts[180]
|
||||||
: root.connected-summary;
|
: root.connected-summary;
|
||||||
color: #62666d; vertical-alignment: center; font-size: 11px; overflow: elide;
|
color: #62666d; vertical-alignment: center; font-size: 11px; overflow: elide;
|
||||||
}
|
}
|
||||||
@@ -796,7 +796,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
x: connection-count.x + connection-count.width + 12px;
|
x: connection-count.x + connection-count.width + 12px;
|
||||||
width: 64px; height: parent.height;
|
width: 64px; height: parent.height;
|
||||||
outline-color: UiSurface.edge; border-radius: UiRadius.control;
|
outline-color: UiSurface.edge; border-radius: UiRadius.control;
|
||||||
text: GalleryStrings.tr("网络状态", "Network", "Сеть");
|
text: GalleryStrings.texts[181];
|
||||||
accessible-role: button;
|
accessible-role: button;
|
||||||
accessible-label: self.text;
|
accessible-label: self.text;
|
||||||
accessible-action-default => { self.clicked(); }
|
accessible-action-default => { self.clicked(); }
|
||||||
@@ -817,9 +817,9 @@ export component GalleryView inherits Rectangle {
|
|||||||
compact-toggle := GalleryToolbarButton {
|
compact-toggle := GalleryToolbarButton {
|
||||||
horizontal-padding: root.toolbar-button-padding;
|
horizontal-padding: root.toolbar-button-padding;
|
||||||
primary: root.simplified;
|
primary: root.simplified;
|
||||||
alternate-text: GalleryStrings.tr("退出精简", "Exit compact", "Обычный вид");
|
alternate-text: GalleryStrings.texts[182];
|
||||||
text: root.simplified ? GalleryStrings.tr("退出精简", "Exit compact", "Обычный вид")
|
text: root.simplified ? GalleryStrings.texts[182]
|
||||||
: GalleryStrings.tr("精简模式", "Compact", "Компактно");
|
: GalleryStrings.texts[183];
|
||||||
clicked => { root.toggle-simplified(); }
|
clicked => { root.toggle-simplified(); }
|
||||||
}
|
}
|
||||||
IconButton {
|
IconButton {
|
||||||
@@ -827,7 +827,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
border-radius: UiRadius.control;
|
border-radius: UiRadius.control;
|
||||||
normal-background: ControlStyle.surface;
|
normal-background: ControlStyle.surface;
|
||||||
icon: root.fullscreen ? FontAwesomeIcons.compress : FontAwesomeIcons.expand;
|
icon: root.fullscreen ? FontAwesomeIcons.compress : FontAwesomeIcons.expand;
|
||||||
tooltip: root.fullscreen ? GalleryStrings.tr("退出全屏", "Exit fullscreen", "Выйти из полноэкранного режима") : GalleryStrings.tr("全屏", "Fullscreen", "На весь экран");
|
tooltip: root.fullscreen ? GalleryStrings.texts[184] : GalleryStrings.texts[185];
|
||||||
clicked => { root.toggle-fullscreen(); }
|
clicked => { root.toggle-fullscreen(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -859,9 +859,9 @@ export component GalleryView inherits Rectangle {
|
|||||||
Text { x: 80px; y: 8px; width: parent.width - 88px; height: 18px; text: root.dragged-tile.name; font-size: 12px; color: #303943; overflow: elide; }
|
Text { x: 80px; y: 8px; width: parent.width - 88px; height: 18px; text: root.dragged-tile.name; font-size: 12px; color: #303943; overflow: elide; }
|
||||||
drag-hint := Text {
|
drag-hint := Text {
|
||||||
x: 80px; y: 29px; width: parent.width - 88px; height: max(16px, self.preferred-height);
|
x: 80px; y: 29px; width: parent.width - 88px; height: max(16px, self.preferred-height);
|
||||||
text: root.drag-page-direction != 0 ? GalleryStrings.tr("停留翻页,松开取消", "Hold to turn page; release to cancel", "Удерживайте для перелистывания; отпустите для отмены")
|
text: root.drag-page-direction != 0 ? GalleryStrings.texts[186]
|
||||||
: root.drag-target >= 0 ? GalleryStrings.tr("松开鼠标交换位置", "Release to swap", "Отпустите для обмена")
|
: root.drag-target >= 0 ? GalleryStrings.texts[187]
|
||||||
: GalleryStrings.tr("拖到另一台设备上", "Drag onto another device", "Перетащите на другое устройство");
|
: GalleryStrings.texts[188];
|
||||||
font-size: 10px; color: #527ca8; wrap: word-wrap;
|
font-size: 10px; color: #527ca8; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -870,7 +870,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
width: min(420px, parent.width - 32px); height: 36px; z: 10;
|
width: min(420px, parent.width - 32px); height: 36px; z: 10;
|
||||||
background: #fff2f0; outline-color: UiSurface.edge; border-radius: UiRadius.surface;
|
background: #fff2f0; outline-color: UiSurface.edge; border-radius: UiRadius.surface;
|
||||||
Text { x: 12px; y: 0px; width: parent.width - 24px; height: parent.height; font-size: 12px; color: #9d4339; vertical-alignment: center;
|
Text { x: 12px; y: 0px; width: parent.width - 24px; height: parent.height; font-size: 12px; color: #9d4339; vertical-alignment: center;
|
||||||
text: GalleryStrings.tr("换位未保存,请重试", "Couldn't save positions. Please try again.", "Не удалось сохранить порядок. Повторите попытку."); overflow: elide;
|
text: GalleryStrings.texts[189]; overflow: elide;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Device details are rendered by MainWindow; resize hints stay local.
|
// Device details are rendered by MainWindow; resize hints stay local.
|
||||||
@@ -896,36 +896,36 @@ export component GalleryView inherits Rectangle {
|
|||||||
root.adding = false;
|
root.adding = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.tr("添加设备", "Add device", "Добавить устройство"); font-size: 16px; }
|
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.texts[190]; font-size: 16px; }
|
||||||
Text { x: 18px; y: 56px; width: 72px; height: 30px; text: GalleryStrings.tr("设备 ID", "Device ID", "ID"); vertical-alignment: center; font-size: 12px; }
|
Text { x: 18px; y: 56px; width: 72px; height: 30px; text: GalleryStrings.texts[191]; vertical-alignment: center; font-size: 12px; }
|
||||||
device-id := RoundedLineEdit {
|
device-id := RoundedLineEdit {
|
||||||
x: 96px; y: 56px; width: 286px; height: 30px;
|
x: 96px; y: 56px; width: 286px; height: 30px;
|
||||||
placeholder-text: GalleryStrings.tr("9 位设备 ID", "9-digit device ID", "ID из 9 цифр");
|
placeholder-text: GalleryStrings.texts[192];
|
||||||
input-type: InputType.number;
|
input-type: InputType.number;
|
||||||
accepted => { password.focus(); }
|
accepted => { password.focus(); }
|
||||||
}
|
}
|
||||||
Text { x: 18px; y: 100px; width: 72px; height: 30px; text: GalleryStrings.tr("密码", "Password", "Пароль"); vertical-alignment: center; font-size: 12px; }
|
Text { x: 18px; y: 100px; width: 72px; height: 30px; text: GalleryStrings.texts[193]; vertical-alignment: center; font-size: 12px; }
|
||||||
password := RoundedLineEdit {
|
password := RoundedLineEdit {
|
||||||
x: 96px; y: 100px; width: 286px; height: 30px;
|
x: 96px; y: 100px; width: 286px; height: 30px;
|
||||||
placeholder-text: GalleryStrings.tr("设备连接密码", "Device password", "Пароль устройства");
|
placeholder-text: GalleryStrings.texts[194];
|
||||||
input-type: InputType.password;
|
input-type: InputType.password;
|
||||||
accepted => { group.focus(); }
|
accepted => { group.focus(); }
|
||||||
}
|
}
|
||||||
Text { x: 18px; y: 144px; width: 72px; height: 30px; text: GalleryStrings.tr("分组", "Group", "Группа"); vertical-alignment: center; font-size: 12px; }
|
Text { x: 18px; y: 144px; width: 72px; height: 30px; text: GalleryStrings.texts[101]; vertical-alignment: center; font-size: 12px; }
|
||||||
group := RoundedLineEdit {
|
group := RoundedLineEdit {
|
||||||
x: 96px; y: 144px; width: 286px; height: 30px;
|
x: 96px; y: 144px; width: 286px; height: 30px;
|
||||||
placeholder-text: GalleryStrings.tr("分组名称(可选)", "Group name (optional)", "Группа (необязательно)");
|
placeholder-text: GalleryStrings.texts[195];
|
||||||
accepted => { add-dialog.submit(); }
|
accepted => { add-dialog.submit(); }
|
||||||
}
|
}
|
||||||
remember := ImGuiCheckBox { x: 96px; y: 188px; }
|
remember := ImGuiCheckBox { x: 96px; y: 188px; }
|
||||||
Text { x: 123px; y: 188px; height: 21px; text: GalleryStrings.tr("记住密码", "Remember password", "Запомнить пароль"); font-size: 12px; vertical-alignment: center;
|
Text { x: 123px; y: 188px; height: 21px; text: GalleryStrings.texts[196]; font-size: 12px; vertical-alignment: center;
|
||||||
TouchArea { clicked => { remember.checked = !remember.checked; } }
|
TouchArea { clicked => { remember.checked = !remember.checked; } }
|
||||||
}
|
}
|
||||||
Text { x: 18px; y: 216px; width: 364px; height: 28px; text: root.notice; color: #8e4740; font-size: 11px; wrap: word-wrap; overflow: elide; }
|
Text { x: 18px; y: 216px; width: 364px; height: 28px; text: root.notice; color: #8e4740; font-size: 11px; wrap: word-wrap; overflow: elide; }
|
||||||
GalleryButton { x: 136px; y: 260px; width: 72px; height: 28px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { password.text = ""; root.adding = false; } }
|
GalleryButton { x: 136px; y: 260px; width: 72px; height: 28px; text: GalleryStrings.texts[33]; clicked => { password.text = ""; root.adding = false; } }
|
||||||
GalleryButton {
|
GalleryButton {
|
||||||
x: 218px; y: 260px; width: 164px; height: 28px; primary: true;
|
x: 218px; y: 260px; width: 164px; height: 28px; primary: true;
|
||||||
text: GalleryStrings.tr("添加并连接", "Add and connect", "Добавить и подключить");
|
text: GalleryStrings.texts[197];
|
||||||
enabled: device-id.text != "" && password.text != "";
|
enabled: device-id.text != "" && password.text != "";
|
||||||
clicked => { add-dialog.submit(); }
|
clicked => { add-dialog.submit(); }
|
||||||
}
|
}
|
||||||
@@ -937,12 +937,12 @@ export component GalleryView inherits Rectangle {
|
|||||||
FlatSurface {
|
FlatSurface {
|
||||||
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 440px; height: 378px;
|
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 440px; height: 378px;
|
||||||
background: white; border-radius: UiRadius.surface; outline-color: UiSurface.edge;
|
background: white; border-radius: UiRadius.surface; outline-color: UiSurface.edge;
|
||||||
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.tr("从近期连接添加", "Add from recent connections", "Добавить из недавних"); font-size: 16px; }
|
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.texts[155]; font-size: 16px; }
|
||||||
Text { x: 18px; y: 44px; width: 404px; height: 18px; text: GalleryStrings.tr("加入桌面墙后,可通过批量选择连接", "Add to DeskHub, then select devices to connect.", "Добавьте в DeskHub, затем выберите устройства для подключения."); color: #62666d; font-size: 11px; }
|
Text { x: 18px; y: 44px; width: 404px; height: 18px; text: GalleryStrings.texts[198]; color: #62666d; font-size: 11px; }
|
||||||
ImGuiCheckBox { x: 18px; y: 74px; enabled: root.recent-available > 0; checked: root.recent-devices.length > 0 && root.recent-selected == root.recent-available; }
|
ImGuiCheckBox { x: 18px; y: 74px; enabled: root.recent-available > 0; checked: root.recent-devices.length > 0 && root.recent-selected == root.recent-available; }
|
||||||
Text { x: 45px; y: 74px; height: 21px; text: GalleryStrings.tr("全选", "Select all", "Выбрать все"); color: root.recent-available > 0 ? #24272d : #92969e; font-size: 12px; vertical-alignment: center; }
|
Text { x: 45px; y: 74px; height: 21px; text: GalleryStrings.texts[159]; color: root.recent-available > 0 ? #24272d : #92969e; font-size: 12px; vertical-alignment: center; }
|
||||||
TouchArea { x: 18px; y: 74px; width: 144px; height: 21px; enabled: root.recent-available > 0; clicked => { root.action("recent-all", ""); } }
|
TouchArea { x: 18px; y: 74px; width: 144px; height: 21px; enabled: root.recent-available > 0; clicked => { root.action("recent-all", ""); } }
|
||||||
Text { x: 224px; y: 74px; width: 198px; height: 21px; text: GalleryStrings.tr("待添加 ", "To add ", "К добавлению ") + root.recent-selected + " / " + root.recent-available; horizontal-alignment: right; vertical-alignment: center; font-size: 12px; color: #62666d; }
|
Text { x: 224px; y: 74px; width: 198px; height: 21px; text: GalleryStrings.texts[199] + root.recent-selected + " / " + root.recent-available; horizontal-alignment: right; vertical-alignment: center; font-size: 12px; color: #62666d; }
|
||||||
FlatSurface {
|
FlatSurface {
|
||||||
x: 18px; y: 102px; width: 404px; height: 160px;
|
x: 18px; y: 102px; width: 404px; height: 160px;
|
||||||
outline-color: UiSurface.edge; border-radius: UiRadius.surface; clip: true;
|
outline-color: UiSurface.edge; border-radius: UiRadius.surface; clip: true;
|
||||||
@@ -961,13 +961,13 @@ export component GalleryView inherits Rectangle {
|
|||||||
Rectangle { x: parent.width - 74px; y: 19px; width: 6px; height: 6px; border-radius: 3px; background: !device.added && device.online ? #21b567 : #92969e; }
|
Rectangle { x: parent.width - 74px; y: 19px; width: 6px; height: 6px; border-radius: 3px; background: !device.added && device.online ? #21b567 : #92969e; }
|
||||||
Text {
|
Text {
|
||||||
x: parent.width - 61px; y: 12px; width: 57px; height: 20px;
|
x: parent.width - 61px; y: 12px; width: 57px; height: 20px;
|
||||||
text: device.added ? GalleryStrings.tr("已添加", "Added", "Добавлено") : device.online ? GalleryStrings.tr("在线", "Online", "В сети") : GalleryStrings.tr("离线", "Offline", "Не в сети");
|
text: device.added ? GalleryStrings.texts[200] : device.online ? GalleryStrings.texts[166] : GalleryStrings.texts[167];
|
||||||
color: device.added ? #92969e : device.online ? #17854c : #808080; font-size: 11px; vertical-alignment: center;
|
color: device.added ? #92969e : device.online ? #17854c : #808080; font-size: 11px; vertical-alignment: center;
|
||||||
}
|
}
|
||||||
row-touch := TouchArea { enabled: !device.added; clicked => { root.action("recent-select", device.id); } }
|
row-touch := TouchArea { enabled: !device.added; clicked => { root.action("recent-select", device.id); } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if root.recent-devices.length == 0: Text { text: GalleryStrings.tr("暂无近期连接", "No recent connections", "Нет недавних подключений"); color: #808080; font-size: 12px; horizontal-alignment: center; vertical-alignment: center; }
|
if root.recent-devices.length == 0: Text { text: GalleryStrings.texts[89]; color: #808080; font-size: 12px; horizontal-alignment: center; vertical-alignment: center; }
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: -UiRadius.surface; y: -UiRadius.surface;
|
x: -UiRadius.surface; y: -UiRadius.surface;
|
||||||
width: parent.width + 2 * UiRadius.surface;
|
width: parent.width + 2 * UiRadius.surface;
|
||||||
@@ -981,11 +981,11 @@ export component GalleryView inherits Rectangle {
|
|||||||
outline-color: UiSurface.edge;
|
outline-color: UiSurface.edge;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text { x: 18px; y: 274px; width: 60px; height: 30px; text: GalleryStrings.tr("分组", "Group", "Группа"); font-size: 12px; vertical-alignment: center; }
|
Text { x: 18px; y: 274px; width: 60px; height: 30px; text: GalleryStrings.texts[101]; font-size: 12px; vertical-alignment: center; }
|
||||||
import-group := RoundedLineEdit { x: 84px; y: 274px; width: 338px; height: 30px; text: root.import-group; placeholder-text: GalleryStrings.tr("分组名称(可选)", "Group name (optional)", "Группа (необязательно)"); }
|
import-group := RoundedLineEdit { x: 84px; y: 274px; width: 338px; height: 30px; text: root.import-group; placeholder-text: GalleryStrings.texts[195]; }
|
||||||
Text { x: 18px; y: 309px; width: 404px; height: 20px; text: root.notice; font-size: 11px; color: #8e4740; overflow: elide; }
|
Text { x: 18px; y: 309px; width: 404px; height: 20px; text: root.notice; font-size: 11px; color: #8e4740; overflow: elide; }
|
||||||
GalleryButton { x: 192px; y: 336px; width: 72px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { root.importing = false; } }
|
GalleryButton { x: 192px; y: 336px; width: 72px; text: GalleryStrings.texts[33]; clicked => { root.importing = false; } }
|
||||||
GalleryButton { x: 274px; y: 336px; width: 148px; primary: true; text: GalleryStrings.tr("加入桌面墙", "Add to DeskHub", "В DeskHub"); enabled: root.recent-selected > 0; clicked => { if root.import-recent(import-group.text) { root.importing = false; } } }
|
GalleryButton { x: 274px; y: 336px; width: 148px; primary: true; text: GalleryStrings.texts[201]; enabled: root.recent-selected > 0; clicked => { if root.import-recent(import-group.text) { root.importing = false; } } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if root.confirming: Rectangle {
|
if root.confirming: Rectangle {
|
||||||
@@ -996,7 +996,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
background: white; border-radius: UiRadius.surface; outline-color: UiSurface.edge;
|
background: white; border-radius: UiRadius.surface; outline-color: UiSurface.edge;
|
||||||
Text {
|
Text {
|
||||||
x: 20px; y: 18px; width: parent.width - 132px; height: 24px;
|
x: 20px; y: 18px; width: parent.width - 132px; height: 24px;
|
||||||
text: root.action-code == "disconnect" ? GalleryStrings.tr("断开连接", "Disconnect devices", "Отключение") : root.action-name;
|
text: root.action-code == "disconnect" ? GalleryStrings.texts[96] : root.action-name;
|
||||||
color: #24272d; font-size: 16px; font-weight: 600; vertical-alignment: center;
|
color: #24272d; font-size: 16px; font-weight: 600; vertical-alignment: center;
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -1021,16 +1021,16 @@ export component GalleryView inherits Rectangle {
|
|||||||
width: GalleryStrings.language == 0 ? 68px : 104px; height: 20px; border-radius: 10px;
|
width: GalleryStrings.language == 0 ? 68px : 104px; height: 20px; border-radius: 10px;
|
||||||
background: device.connected ? #eaf6ee : #f0f2f5;
|
background: device.connected ? #eaf6ee : #f0f2f5;
|
||||||
Rectangle { x: 9px; y: 7px; width: 6px; height: 6px; border-radius: 3px; background: device.connected ? #27a762 : #a0a6b0; }
|
Rectangle { x: 9px; y: 7px; width: 6px; height: 6px; border-radius: 3px; background: device.connected ? #27a762 : #a0a6b0; }
|
||||||
Text { x: 21px; width: parent.width - 27px; height: parent.height; text: device.connected ? GalleryStrings.tr("已连接", "Connected", "Подключено") : GalleryStrings.tr("未连接", "Not connected", "Нет соединения"); color: device.connected ? #348259 : #858d98; font-size: 10px; vertical-alignment: center; }
|
Text { x: 21px; width: parent.width - 27px; height: parent.height; text: device.connected ? GalleryStrings.texts[202] : GalleryStrings.texts[203]; color: device.connected ? #348259 : #858d98; font-size: 10px; vertical-alignment: center; }
|
||||||
}
|
}
|
||||||
if index + 1 < root.selected-devices.length: SurfaceSeparator { x: 12px; y: parent.height - 1px; width: parent.width - 24px; height: 1px; outline-color: UiSurface.separator; }
|
if index + 1 < root.selected-devices.length: SurfaceSeparator { x: 12px; y: parent.height - 1px; width: parent.width - 24px; height: 1px; outline-color: UiSurface.separator; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GalleryButton { x: parent.width - 208px; y: parent.height - 46px; width: 72px; height: 28px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { root.confirming = false; } }
|
GalleryButton { x: parent.width - 208px; y: parent.height - 46px; width: 72px; height: 28px; text: GalleryStrings.texts[33]; clicked => { root.confirming = false; } }
|
||||||
GalleryButton {
|
GalleryButton {
|
||||||
x: parent.width - 126px; y: parent.height - 46px; width: 108px; height: 28px; primary: true;
|
x: parent.width - 126px; y: parent.height - 46px; width: 108px; height: 28px; primary: true;
|
||||||
text: root.action-code == "disconnect" ? GalleryStrings.tr("确认断开", "Disconnect", "Отключить") : GalleryStrings.tr("确认执行", "Continue", "Продолжить");
|
text: root.action-code == "disconnect" ? GalleryStrings.texts[204] : GalleryStrings.texts[205];
|
||||||
clicked => { root.confirming = false; root.action(root.action-code, ""); }
|
clicked => { root.confirming = false; root.action(root.action-code, ""); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,24 @@ export global LanStrings {
|
|||||||
// Slint Text does not expose a line-height property.
|
// Slint Text does not expose a line-height property.
|
||||||
public pure function access-error-lines(code: string) -> [string] {
|
public pure function access-error-lines(code: string) -> [string] {
|
||||||
if code == "LanPortInUse" { return [
|
if code == "LanPortInUse" { return [
|
||||||
GalleryStrings.tr("此端口已被占用,", "This port is in use.", "Порт занят."),
|
GalleryStrings.texts[0],
|
||||||
GalleryStrings.tr("请更换端口。", "Choose another port.", "Выберите другой порт."),
|
GalleryStrings.texts[1],
|
||||||
GalleryStrings.tr("原设置已保留。", "Your previous settings are unchanged.", "Прежние настройки сохранены.")
|
GalleryStrings.texts[2]
|
||||||
]; }
|
]; }
|
||||||
if code == "LanPortCheckFailed" { return [
|
if code == "LanPortCheckFailed" { return [
|
||||||
GalleryStrings.tr("无法确认此端口是否可用,", "Could not check this port.", "Не удалось проверить порт."),
|
GalleryStrings.texts[3],
|
||||||
GalleryStrings.tr("请重试或检查本机防火墙。", "Retry or check the local firewall.", "Повторите попытку или проверьте брандмауэр."),
|
GalleryStrings.texts[4],
|
||||||
GalleryStrings.tr("原设置已保留。", "Your previous settings are unchanged.", "Прежние настройки сохранены.")
|
GalleryStrings.texts[2]
|
||||||
]; }
|
]; }
|
||||||
if code == "LanPortPermissionDenied" { return [
|
if code == "LanPortPermissionDenied" { return [
|
||||||
GalleryStrings.tr("无法使用此端口,", "Cannot use this port.", "Порт недоступен."),
|
GalleryStrings.texts[5],
|
||||||
GalleryStrings.tr("请选择其他端口或检查系统权限。", "Choose another port or check system permissions.", "Смените порт или проверьте разрешения."),
|
GalleryStrings.texts[6],
|
||||||
GalleryStrings.tr("原设置已保留。", "Your previous settings are unchanged.", "Прежние настройки сохранены.")
|
GalleryStrings.texts[2]
|
||||||
]; }
|
]; }
|
||||||
if code == "Selected LAN interface is unavailable" { return [
|
if code == "Selected LAN interface is unavailable" { return [
|
||||||
GalleryStrings.tr("未找到可用的局域网连接。", "No local network connection was found.", "Нет доступного подключения к LAN."),
|
GalleryStrings.texts[7],
|
||||||
GalleryStrings.tr("请连接 Wi-Fi 或网线,", "Connect to Wi-Fi or Ethernet,", "Подключитесь к Wi-Fi или Ethernet"),
|
GalleryStrings.texts[8],
|
||||||
GalleryStrings.tr("或在主界面选择本机 IP。", "or select a local IP on the main screen.", "или выберите локальный IP в главном окне.")
|
GalleryStrings.texts[9]
|
||||||
]; }
|
]; }
|
||||||
// Keep all branches the same size for Slint's C++ model generator.
|
// Keep all branches the same size for Slint's C++ model generator.
|
||||||
return ["", "", ""];
|
return ["", "", ""];
|
||||||
@@ -30,29 +30,29 @@ export global LanStrings {
|
|||||||
public pure function error-message(code: string) -> string {
|
public pure function error-message(code: string) -> string {
|
||||||
let lines = LanStrings.access-error-lines(code);
|
let lines = LanStrings.access-error-lines(code);
|
||||||
if lines[0] != "" {
|
if lines[0] != "" {
|
||||||
let separator = GalleryStrings.tr("", " ", " ");
|
let separator = GalleryStrings.texts[10];
|
||||||
return lines[0] + separator + lines[1] + separator + lines[2];
|
return lines[0] + separator + lines[1] + separator + lines[2];
|
||||||
}
|
}
|
||||||
if code == "" { return ""; }
|
if code == "" { return ""; }
|
||||||
if code == "InvalidCredentials" { return GalleryStrings.tr("密码不正确,或对方密码已更新。请查看对方的局域网访问密码后重试。", "The password is incorrect or has changed. Check the other desktop's current LAN password and try again.", "Пароль неверен или изменён. Проверьте текущий пароль LAN другого компьютера и повторите попытку."); }
|
if code == "InvalidCredentials" { return GalleryStrings.texts[11]; }
|
||||||
if code == "AuthenticationRateLimited" { return GalleryStrings.tr("尝试次数过多,请等待倒计时结束后重试。", "Too many attempts. Try again when the countdown ends.", "Слишком много попыток. Дождитесь окончания обратного отсчёта."); }
|
if code == "AuthenticationRateLimited" { return GalleryStrings.texts[12]; }
|
||||||
if code == "CredentialsChanged" { return GalleryStrings.tr("对方密码已更新,请使用新密码重试。", "The password changed. Try again with the new password.", "Пароль изменился. Повторите с новым паролем."); }
|
if code == "CredentialsChanged" { return GalleryStrings.texts[13]; }
|
||||||
if code == "Busy" { return GalleryStrings.tr("设备正忙,请稍后重试。", "The desktop is busy. Try again shortly.", "Компьютер занят. Повторите позже."); }
|
if code == "Busy" { return GalleryStrings.texts[14]; }
|
||||||
if code == "LAN password must contain exactly 6 ASCII letters or digits" { return GalleryStrings.tr("访问密码必须为 6 位英文字母或数字,区分大小写。", "Use exactly 6 letters (A–Z, a–z) or digits (0–9). Case-sensitive.", "Пароль: ровно 6 латинских букв или цифр. Регистр учитывается."); }
|
if code == "LAN password must contain exactly 6 ASCII letters or digits" { return GalleryStrings.texts[15]; }
|
||||||
if code == "ConnectionFailed" || code == "ConnectionTimeout" || code == "IceConnectionTimeout" || code == "Timeout" || code == "ConnectionClosed" || code == "HeartbeatTimeout" || code == "SignalSendFailed" { return GalleryStrings.tr("无法连接,请检查对方 IP、程序是否运行,以及防火墙是否放行。", "Could not connect. Check the IP, whether the other desktop is running CrossDesk, and its firewall settings.", "Не удалось подключиться. Проверьте IP, работу CrossDesk на другом компьютере и брандмауэр."); }
|
if code == "ConnectionFailed" || code == "ConnectionTimeout" || code == "IceConnectionTimeout" || code == "Timeout" || code == "ConnectionClosed" || code == "HeartbeatTimeout" || code == "SignalSendFailed" { return GalleryStrings.texts[16]; }
|
||||||
if code == "Enter a numeric IPv4 address or IPv4:port" || code == "Invalid LAN endpoint" { return GalleryStrings.tr("请输入有效的 IPv4,例如 192.168.1.20;指定端口可用 IPv4:端口,端口范围 1–65535。", "Enter a valid IPv4 address such as 192.168.1.20, or IPv4:port (1–65535).", "Введите IPv4, например 192.168.1.20, или IPv4:порт (1–65535)."); }
|
if code == "Enter a numeric IPv4 address or IPv4:port" || code == "Invalid LAN endpoint" { return GalleryStrings.texts[17]; }
|
||||||
if code == "Invalid LAN port" { return GalleryStrings.tr("请输入 1–65535 之间的整数端口。", "Enter a port number from 1 to 65535.", "Введите целый номер порта от 1 до 65535."); }
|
if code == "Invalid LAN port" { return GalleryStrings.texts[18]; }
|
||||||
if code == "LAN password is not ready" { return GalleryStrings.tr("请点击主界面密码旁的修改按钮设置密码。", "Set a password using Edit next to the password on the main screen.", "Задайте пароль кнопкой редактирования рядом с паролем в главном окне."); }
|
if code == "LAN password is not ready" { return GalleryStrings.texts[19]; }
|
||||||
if code == "LAN connection limit reached" { return GalleryStrings.tr("IP 连接数量已达上限,请先断开部分连接后重试。", "The IP connection limit has been reached. Disconnect some sessions and try again.", "Достигнут лимит IP-подключений. Завершите часть сеансов и повторите попытку."); }
|
if code == "LAN connection limit reached" { return GalleryStrings.texts[20]; }
|
||||||
if code == "UnsupportedNetworkPath" || code == "NetworkPolicyFailed" { return GalleryStrings.tr("当前网络路径不支持 IP 连接,请检查双方的局域网连接。", "This network path does not support IP connections. Check both devices' LAN connections.", "Этот сетевой маршрут не поддерживает IP-подключения. Проверьте подключения обоих устройств к LAN."); }
|
if code == "UnsupportedNetworkPath" || code == "NetworkPolicyFailed" { return GalleryStrings.texts[21]; }
|
||||||
if code == "IncompatibleProtocol" || code == "RequiredCryptoUnsupported" || code == "TLS 1.3 is required for LAN connections" { return GalleryStrings.tr("双方的协议或加密能力不兼容,请将双方 CrossDesk 升级至最新版本后重试。", "The protocol or encryption capabilities are incompatible. Update CrossDesk on both devices and try again.", "Протоколы или возможности шифрования несовместимы. Обновите CrossDesk на обоих устройствах и повторите попытку."); }
|
if code == "IncompatibleProtocol" || code == "RequiredCryptoUnsupported" || code == "TLS 1.3 is required for LAN connections" { return GalleryStrings.texts[22]; }
|
||||||
if code == "InvalidCertificate" || code == "FingerprintMissing" || code == "FingerprintMismatch" { return GalleryStrings.tr("无法验证对方设备的身份,请确认连接地址和对方设备后重试。", "Could not verify the remote device's identity. Check the address and the remote device before retrying.", "Не удалось проверить подлинность удалённого устройства. Проверьте адрес и устройство перед повторной попыткой."); }
|
if code == "InvalidCertificate" || code == "FingerprintMissing" || code == "FingerprintMismatch" { return GalleryStrings.texts[23]; }
|
||||||
if code == "DtlsHandshakeFailed" || code == "SrtpInstallFailed" || code == "SrtpKeyExportFailed" || code == "SecureTransportFailed" { return GalleryStrings.tr("无法建立加密连接,请检查双方网络并重试。", "Could not establish an encrypted connection. Check both devices' networks and try again.", "Не удалось установить зашифрованное соединение. Проверьте сеть на обоих устройствах и повторите попытку."); }
|
if code == "DtlsHandshakeFailed" || code == "SrtpInstallFailed" || code == "SrtpKeyExportFailed" || code == "SecureTransportFailed" { return GalleryStrings.texts[24]; }
|
||||||
if code == "InvalidSignal" || code == "InvalidLocalSignal" || code == "Invalid LAN protocol message" { return GalleryStrings.tr("连接协商数据无效,请重试并确认双方 CrossDesk 版本兼容。", "Invalid connection negotiation data. Try again and check that both CrossDesk versions are compatible.", "Неверные данные согласования соединения. Повторите попытку и проверьте совместимость версий CrossDesk."); }
|
if code == "InvalidSignal" || code == "InvalidLocalSignal" || code == "Invalid LAN protocol message" { return GalleryStrings.texts[25]; }
|
||||||
if code == "ListenerChanged" { return GalleryStrings.tr("连接地址或监听端口已更改,请确认最新地址后重试。", "The connection address or listening port has changed. Check the current address and try again.", "Адрес подключения или порт прослушивания изменился. Проверьте текущий адрес и повторите попытку."); }
|
if code == "ListenerChanged" { return GalleryStrings.texts[26]; }
|
||||||
if code == "PeerPreparationFailed" { return GalleryStrings.tr("无法准备远程会话,请稍后重试。", "Could not prepare the remote session. Try again shortly.", "Не удалось подготовить удалённый сеанс. Повторите позже."); }
|
if code == "PeerPreparationFailed" { return GalleryStrings.texts[27]; }
|
||||||
if code == "Cannot copy text" { return GalleryStrings.tr("复制失败,请重试。", "Could not copy. Try again.", "Не удалось скопировать. Повторите попытку."); }
|
if code == "Cannot copy text" { return GalleryStrings.texts[28]; }
|
||||||
// Keep diagnostic details for unexpected system errors, with a localized explanation.
|
// Keep diagnostic details for unexpected system errors, with a localized explanation.
|
||||||
return GalleryStrings.tr("IP 连接操作失败,详情:", "IP connection operation failed. Details: ", "Ошибка операции IP-подключения. Подробности: ") + code;
|
return GalleryStrings.texts[29] + code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,8 +79,9 @@ export global UiStrings {
|
|||||||
in-out property <string> self-hosted: "Self-hosted server";
|
in-out property <string> self-hosted: "Self-hosted server";
|
||||||
in-out property <string> autostart: "Start with system";
|
in-out property <string> autostart: "Start with system";
|
||||||
in-out property <string> daemon: "Run in background";
|
in-out property <string> daemon: "Run in background";
|
||||||
in-out property <string> save-remote-previews: "Save remote screen previews:";
|
in-out property <string> save-remote-previews: "Save remote screen previews";
|
||||||
in-out property <string> unknown-platform: "Unknown platform";
|
in-out property <string> unknown-platform: "Unknown platform";
|
||||||
|
in-out property <string> privacy-local-unlock: "Allow privacy screen unlock while controlled";
|
||||||
in-out property <string> privacy-on-connect: "Privacy screen on connect";
|
in-out property <string> privacy-on-connect: "Privacy screen on connect";
|
||||||
in-out property <string> lock-on-disconnect: "Lock screen on disconnect";
|
in-out property <string> lock-on-disconnect: "Lock screen on disconnect";
|
||||||
in-out property <string> file-save-path: "File save path";
|
in-out property <string> file-save-path: "File save path";
|
||||||
@@ -124,7 +125,7 @@ export global UiStrings {
|
|||||||
in-out property <string> wayland-notice-acknowledge: "Got it";
|
in-out property <string> wayland-notice-acknowledge: "Got it";
|
||||||
in-out property <string> service-setup-title: "Install CrossDesk Service";
|
in-out property <string> service-setup-title: "Install CrossDesk Service";
|
||||||
in-out property <string> service-setup-message: "Administrator permission is required to control this device while it is locked.";
|
in-out property <string> service-setup-message: "Administrator permission is required to control this device while it is locked.";
|
||||||
in-out property <string> service-settings-label: "Lock-screen control service:";
|
in-out property <string> service-settings-label: "Lock-screen control service";
|
||||||
in-out property <string> install-service: "Install";
|
in-out property <string> install-service: "Install";
|
||||||
in-out property <string> service-installed: "Installed";
|
in-out property <string> service-installed: "Installed";
|
||||||
in-out property <string> do-not-remind: "Do not remind again";
|
in-out property <string> do-not-remind: "Do not remind again";
|
||||||
@@ -264,7 +265,7 @@ component PasswordInput inherits FlatSurface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export component MainWindow inherits Window {
|
export component MainWindow inherits Window {
|
||||||
title: root.gallery-mode ? "CrossDesk " + GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub") : "CrossDesk";
|
title: root.gallery-mode ? "CrossDesk " + GalleryStrings.texts[54] : "CrossDesk";
|
||||||
icon: @image-url("../../../resources/linux/crossdesk_256x256.png");
|
icon: @image-url("../../../resources/linux/crossdesk_256x256.png");
|
||||||
preferred-width: root.minimum-window-width;
|
preferred-width: root.minimum-window-width;
|
||||||
preferred-height: root.minimum-window-height;
|
preferred-height: root.minimum-window-height;
|
||||||
@@ -454,7 +455,7 @@ export component MainWindow inherits Window {
|
|||||||
root.lan-remember-password = false;
|
root.lan-remember-password = false;
|
||||||
let error = root.lan-prepare-connect(address);
|
let error = root.lan-prepare-connect(address);
|
||||||
root.connection-input-error = error == "Enter a numeric IPv4 address or IPv4:port"
|
root.connection-input-error = error == "Enter a numeric IPv4 address or IPv4:port"
|
||||||
? GalleryStrings.tr("请输入有效的 IPv4 或 IPv4:端口", "Enter a valid IPv4 or IPv4:port", "Укажите IPv4 или IPv4:порт")
|
? GalleryStrings.texts[55]
|
||||||
: LanStrings.error-message(error);
|
: LanStrings.error-message(error);
|
||||||
if error == "" {
|
if error == "" {
|
||||||
root.lan-open = true;
|
root.lan-open = true;
|
||||||
@@ -526,8 +527,8 @@ export component MainWindow inherits Window {
|
|||||||
private property <bool> prompt-validating: root.lan-open ? root.lan-connecting : root.connection-validating;
|
private property <bool> prompt-validating: root.lan-open ? root.lan-connecting : root.connection-validating;
|
||||||
private property <bool> prompt-pending: root.lan-open ? root.lan-connecting : root.connection-pending;
|
private property <bool> prompt-pending: root.lan-open ? root.lan-connecting : root.connection-pending;
|
||||||
private property <string> prompt-status-text: !root.lan-open ? root.connection-status-text
|
private property <string> prompt-status-text: !root.lan-open ? root.connection-status-text
|
||||||
: root.lan-connecting ? GalleryStrings.tr("正在连接…", "Connecting…", "Подключение…")
|
: root.lan-connecting ? GalleryStrings.texts[56]
|
||||||
: root.lan-retry-after > 0 ? GalleryStrings.tr("请在 " + root.lan-retry-after + " 秒后重试", "Try again in " + root.lan-retry-after + "s", "Повторите через " + root.lan-retry-after + " с")
|
: root.lan-retry-after > 0 ? GalleryStrings.retry-after(root.lan-retry-after)
|
||||||
: root.lan-message == "InvalidCredentials" ? UiStrings.reinput-password
|
: root.lan-message == "InvalidCredentials" ? UiStrings.reinput-password
|
||||||
: root.lan-message == "" || root.lan-message == "AuthenticationRateLimited" ? UiStrings.input-password
|
: root.lan-message == "" || root.lan-message == "AuthenticationRateLimited" ? UiStrings.input-password
|
||||||
: LanStrings.error-message(root.lan-message);
|
: LanStrings.error-message(root.lan-message);
|
||||||
@@ -555,7 +556,7 @@ export component MainWindow inherits Window {
|
|||||||
in property <[RecentConnection]> recent-connections;
|
in property <[RecentConnection]> recent-connections;
|
||||||
out property <length> recent-preview-width: max(1px, (recent-scroll.width - 24px) / 3);
|
out property <length> recent-preview-width: max(1px, (recent-scroll.width - 24px) / 3);
|
||||||
out property <length> recent-preview-height: 80px;
|
out property <length> recent-preview-height: 80px;
|
||||||
in property <[string]> recent-groups: [GalleryStrings.tr("全部设备", "All devices", "Все устройства"), GalleryStrings.tr("未分组", "Ungrouped", "Без группы")];
|
in property <[string]> recent-groups: [GalleryStrings.texts[57], GalleryStrings.texts[31]];
|
||||||
in-out property <int> recent-group-index: 0;
|
in-out property <int> recent-group-index: 0;
|
||||||
callback recent-group-selected(int);
|
callback recent-group-selected(int);
|
||||||
changed recent-group-index => {
|
changed recent-group-index => {
|
||||||
@@ -627,7 +628,8 @@ export component MainWindow inherits Window {
|
|||||||
in property <string> portable-service-status: "";
|
in property <string> portable-service-status: "";
|
||||||
in-out property <bool> portable-service-do-not-remind: false;
|
in-out property <bool> portable-service-do-not-remind: false;
|
||||||
|
|
||||||
in-out property <int> language-index: 0;
|
in-out property <int> language-index: 10;
|
||||||
|
in property <[string]> language-options;
|
||||||
in property <bool> capture-method-visible: false;
|
in property <bool> capture-method-visible: false;
|
||||||
in-out property <int> capture-method-index: 0;
|
in-out property <int> capture-method-index: 0;
|
||||||
in-out property <int> codec-index: 0;
|
in-out property <int> codec-index: 0;
|
||||||
@@ -639,11 +641,11 @@ export component MainWindow inherits Window {
|
|||||||
in property <bool> autostart-system-managed: false;
|
in property <bool> autostart-system-managed: false;
|
||||||
in property <AutostartStatus> autostart-status: AutostartStatus.checking;
|
in property <AutostartStatus> autostart-status: AutostartStatus.checking;
|
||||||
out property <string> autostart-status-text:
|
out property <string> autostart-status-text:
|
||||||
root.autostart-status == AutostartStatus.checking ? GalleryStrings.tr("正在读取…", "Checking…", "Проверка…") :
|
root.autostart-status == AutostartStatus.checking ? GalleryStrings.texts[58] :
|
||||||
root.autostart-status == AutostartStatus.enabled ? GalleryStrings.tr("已开启", "Enabled", "Включён") :
|
root.autostart-status == AutostartStatus.enabled ? GalleryStrings.texts[59] :
|
||||||
root.autostart-status == AutostartStatus.disabled ? GalleryStrings.tr("未开启", "Disabled", "Выключен") :
|
root.autostart-status == AutostartStatus.disabled ? GalleryStrings.texts[60] :
|
||||||
root.autostart-status == AutostartStatus.unsupported ? GalleryStrings.tr("设备不支持", "Unsupported", "Не поддерживается") :
|
root.autostart-status == AutostartStatus.unsupported ? GalleryStrings.texts[61] :
|
||||||
GalleryStrings.tr("读取失败", "Read failed", "Ошибка чтения");
|
GalleryStrings.texts[62];
|
||||||
in-out property <bool> autostart-guide-open: false;
|
in-out property <bool> autostart-guide-open: false;
|
||||||
changed autostart-guide-open => { if root.autostart-guide-open { root.dialog-focus-pending = true; } }
|
changed autostart-guide-open => { if root.autostart-guide-open { root.dialog-focus-pending = true; } }
|
||||||
in-out property <bool> daemon-enabled: false;
|
in-out property <bool> daemon-enabled: false;
|
||||||
@@ -651,6 +653,7 @@ export component MainWindow inherits Window {
|
|||||||
// Show the preference on every platform; only adapted hosts can change it.
|
// Show the preference on every platform; only adapted hosts can change it.
|
||||||
in property <bool> privacy-setting-available: false;
|
in property <bool> privacy-setting-available: false;
|
||||||
in-out property <bool> privacy-on-connect-enabled: true;
|
in-out property <bool> privacy-on-connect-enabled: true;
|
||||||
|
in-out property <bool> privacy-local-unlock-allowed: true;
|
||||||
in property <bool> lock-on-disconnect-available: false;
|
in property <bool> lock-on-disconnect-available: false;
|
||||||
in-out property <bool> lock-on-disconnect-enabled: true;
|
in-out property <bool> lock-on-disconnect-enabled: true;
|
||||||
in-out property <string> file-save-path: "";
|
in-out property <string> file-save-path: "";
|
||||||
@@ -768,8 +771,10 @@ export component MainWindow inherits Window {
|
|||||||
focus-sink.focus();
|
focus-sink.focus();
|
||||||
}
|
}
|
||||||
private property <CopyFeedback> copy-feedback: idle;
|
private property <CopyFeedback> copy-feedback: idle;
|
||||||
private property <bool> compact-language: root.language-index == 0;
|
private property <bool> compact-language: GalleryStrings.language == 0 || GalleryStrings.language == 3 || GalleryStrings.language == 4 || GalleryStrings.language == 5;
|
||||||
private property <bool> recent-tooltip-visible: false;
|
private property <bool> recent-tooltip-visible: false;
|
||||||
|
private property <int> recent-tooltip-index: -1;
|
||||||
|
private property <length> recent-tooltip-card-x: 0px;
|
||||||
private property <string> recent-tooltip-name: "";
|
private property <string> recent-tooltip-name: "";
|
||||||
private property <string> recent-tooltip-host: "";
|
private property <string> recent-tooltip-host: "";
|
||||||
private property <string> recent-tooltip-id: "";
|
private property <string> recent-tooltip-id: "";
|
||||||
@@ -1112,9 +1117,9 @@ export component MainWindow inherits Window {
|
|||||||
width: 136px; height: 28px;
|
width: 136px; height: 28px;
|
||||||
background: WorkbenchStyle.field-surface;
|
background: WorkbenchStyle.field-surface;
|
||||||
enabled: !root.connection-pending && !root.lan-connecting;
|
enabled: !root.connection-pending && !root.lan-connecting;
|
||||||
tooltip: root.lan-mode ? GalleryStrings.tr("切换到 ID 连接模式", "Switch to ID connection mode", "Переключить в режим ID") : GalleryStrings.tr("切换到 IP 连接模式", "Switch to IP connection mode", "Переключить в режим IP");
|
tooltip: root.lan-mode ? GalleryStrings.texts[63] : GalleryStrings.texts[64];
|
||||||
accessible-role: switch;
|
accessible-role: switch;
|
||||||
accessible-label: GalleryStrings.tr("IP 连接模式", "IP connection mode", "Режим IP");
|
accessible-label: GalleryStrings.texts[65];
|
||||||
accessible-checked: root.lan-mode;
|
accessible-checked: root.lan-mode;
|
||||||
z: 8;
|
z: 8;
|
||||||
clicked => {
|
clicked => {
|
||||||
@@ -1136,7 +1141,7 @@ export component MainWindow inherits Window {
|
|||||||
y: 2px;
|
y: 2px;
|
||||||
width: (parent.width - 6px) / 2;
|
width: (parent.width - 6px) / 2;
|
||||||
height: parent.height - 4px;
|
height: parent.height - 4px;
|
||||||
text: lan ? GalleryStrings.tr("IP 连接", "IP mode", "Режим IP") : GalleryStrings.tr("ID 连接", "ID mode", "Режим ID");
|
text: lan ? GalleryStrings.texts[66] : GalleryStrings.texts[67];
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: root.lan-mode == lan ? white : WorkbenchStyle.muted;
|
color: root.lan-mode == lan ? white : WorkbenchStyle.muted;
|
||||||
@@ -1152,7 +1157,7 @@ export component MainWindow inherits Window {
|
|||||||
y: connection-mode-switch.y;
|
y: connection-mode-switch.y;
|
||||||
width: 80px; height: 28px;
|
width: 80px; height: 28px;
|
||||||
normal-background: white;
|
normal-background: white;
|
||||||
text: GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub");
|
text: GalleryStrings.texts[54];
|
||||||
z: 8;
|
z: 8;
|
||||||
clicked => { root.open-gallery(); }
|
clicked => { root.open-gallery(); }
|
||||||
}
|
}
|
||||||
@@ -1162,7 +1167,7 @@ export component MainWindow inherits Window {
|
|||||||
width: 36px; height: 28px;
|
width: 36px; height: 28px;
|
||||||
normal-background: white;
|
normal-background: white;
|
||||||
icon: FontAwesomeIcons.bars;
|
icon: FontAwesomeIcons.bars;
|
||||||
tooltip: GalleryStrings.tr("更多", "More", "Ещё");
|
tooltip: GalleryStrings.texts[68];
|
||||||
z: 8;
|
z: 8;
|
||||||
clicked => { root.gallery-action("release", ""); root.menu-open = !root.menu-open; }
|
clicked => { root.gallery-action("release", ""); root.menu-open = !root.menu-open; }
|
||||||
}
|
}
|
||||||
@@ -1245,16 +1250,16 @@ export component MainWindow inherits Window {
|
|||||||
x: 0px; y: 0px;
|
x: 0px; y: 0px;
|
||||||
width: (parent.width - 16px) / 2; height: parent.height;
|
width: (parent.width - 16px) / 2; height: parent.height;
|
||||||
Text { x: 16px; y: 14px; width: parent.width - 32px; height: 24px; overflow: elide; text: UiStrings.local-desktop; font-size: 16px; font-weight: 600; color: WorkbenchStyle.text; }
|
Text { x: 16px; y: 14px; width: parent.width - 32px; height: 24px; overflow: elide; text: UiStrings.local-desktop; font-size: 16px; font-weight: 600; color: WorkbenchStyle.text; }
|
||||||
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("将 IP 和密码分享给连接方", "Share your IP and password to get connected", "Передайте IP и пароль для подключения") : GalleryStrings.tr("将 ID 和密码分享给连接方", "Share your ID and password to get connected", "Передайте ID и пароль для подключения"); font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
|
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.texts[69] : GalleryStrings.texts[70]; font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
|
||||||
local-address-label := Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("本机 IP / 端口", "Local IP / Port", "IP / Порт") : UiStrings.local-id; font-size: 12px; color: WorkbenchStyle.muted; vertical-alignment: top; }
|
local-address-label := Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.texts[71] : UiStrings.local-id; font-size: 12px; color: WorkbenchStyle.muted; vertical-alignment: top; }
|
||||||
lan-switch-ip-metrics := Text {
|
lan-switch-ip-metrics := Text {
|
||||||
visible: false; accessible-role: none;
|
visible: false; accessible-role: none;
|
||||||
text: GalleryStrings.tr("切换 IP", "Switch IP", "Сменить IP");
|
text: GalleryStrings.texts[72];
|
||||||
font-size: 12px; font-weight: 500;
|
font-size: 12px; font-weight: 500;
|
||||||
}
|
}
|
||||||
lan-edit-port-metrics := Text {
|
lan-edit-port-metrics := Text {
|
||||||
visible: false; accessible-role: none;
|
visible: false; accessible-role: none;
|
||||||
text: GalleryStrings.tr("修改端口", "Edit port", "Изменить порт");
|
text: GalleryStrings.texts[73];
|
||||||
font-size: 12px; font-weight: 500;
|
font-size: 12px; font-weight: 500;
|
||||||
}
|
}
|
||||||
lan-switch-ip-action := WorkbenchButton {
|
lan-switch-ip-action := WorkbenchButton {
|
||||||
@@ -1326,7 +1331,7 @@ export component MainWindow inherits Window {
|
|||||||
normal-outline-color: UiSurface.edge;
|
normal-outline-color: UiSurface.edge;
|
||||||
hover-outline-color: UiSurface.hover;
|
hover-outline-color: UiSurface.hover;
|
||||||
focus-outline-color: WorkbenchStyle.accent;
|
focus-outline-color: WorkbenchStyle.accent;
|
||||||
accessible-label: GalleryStrings.tr("切换本机 IP", "Switch local IP", "Сменить локальный IP");
|
accessible-label: GalleryStrings.texts[74];
|
||||||
selected(index) => {
|
selected(index) => {
|
||||||
root.copy-feedback = CopyFeedback.idle;
|
root.copy-feedback = CopyFeedback.idle;
|
||||||
// Save the draft for its original IP before changing
|
// Save the draft for its original IP before changing
|
||||||
@@ -1382,7 +1387,7 @@ export component MainWindow inherits Window {
|
|||||||
font-size: 18px * local-address-field.lan-font-scale; font-weight: 600;
|
font-size: 18px * local-address-field.lan-font-scale; font-weight: 600;
|
||||||
input-type: InputType.number; color: WorkbenchStyle.text;
|
input-type: InputType.number; color: WorkbenchStyle.text;
|
||||||
enabled: root.lan-mode && !root.lan-password-pending;
|
enabled: root.lan-mode && !root.lan-password-pending;
|
||||||
accessible-label: GalleryStrings.tr("局域网端口", "LAN port", "Порт LAN");
|
accessible-label: GalleryStrings.texts[75];
|
||||||
vertical-alignment: center; horizontal-alignment: left;
|
vertical-alignment: center; horizontal-alignment: left;
|
||||||
selection-foreground-color: white; selection-background-color: WorkbenchStyle.accent;
|
selection-foreground-color: white; selection-background-color: WorkbenchStyle.accent;
|
||||||
changed has-focus => {
|
changed has-focus => {
|
||||||
@@ -1401,7 +1406,7 @@ export component MainWindow inherits Window {
|
|||||||
x: parent.width - 42px; y: 2px; width: 36px; quiet: true;
|
x: parent.width - 42px; y: 2px; width: 36px; quiet: true;
|
||||||
icon: root.copy-feedback == CopyFeedback.success ? FontAwesomeIcons.check : FontAwesomeIcons.copy;
|
icon: root.copy-feedback == CopyFeedback.success ? FontAwesomeIcons.check : FontAwesomeIcons.copy;
|
||||||
icon-color: root.copy-feedback == CopyFeedback.success ? WorkbenchStyle.success : WorkbenchStyle.muted;
|
icon-color: root.copy-feedback == CopyFeedback.success ? WorkbenchStyle.success : WorkbenchStyle.muted;
|
||||||
tooltip: root.lan-mode ? GalleryStrings.tr("复制 IP 和端口", "Copy IP and port", "Копировать IP и порт") : GalleryStrings.tr("复制本机 ID", "Copy local ID", "Копировать ID");
|
tooltip: root.lan-mode ? GalleryStrings.texts[76] : GalleryStrings.texts[77];
|
||||||
enabled: !root.lan-mode || root.lan-listener-address != "";
|
enabled: !root.lan-mode || root.lan-listener-address != "";
|
||||||
tooltip-suppressed: root.copy-feedback != CopyFeedback.idle;
|
tooltip-suppressed: root.copy-feedback != CopyFeedback.idle;
|
||||||
// Let other controls receive the original click; moving
|
// Let other controls receive the original click; moving
|
||||||
@@ -1418,8 +1423,8 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
private property <string> edit-hint: !root.lan-mode || !root.sponsorship-unobstructed
|
private property <string> edit-hint: !root.lan-mode || !root.sponsorship-unobstructed
|
||||||
|| root.lan-password-pending ? ""
|
|| root.lan-password-pending ? ""
|
||||||
: lan-ip-picker.has-hover ? GalleryStrings.tr("点击切换本机 IP", "Click to switch the local IP", "Нажмите, чтобы сменить IP")
|
: lan-ip-picker.has-hover ? GalleryStrings.texts[78]
|
||||||
: lan-port-hover.has-hover && !lan-port-editor.has-focus ? GalleryStrings.tr("点击修改端口", "Click to edit the port", "Нажмите, чтобы изменить порт") : "";
|
: lan-port-hover.has-hover && !lan-port-editor.has-focus ? GalleryStrings.texts[79] : "";
|
||||||
private property <bool> edit-hint-visible;
|
private property <bool> edit-hint-visible;
|
||||||
changed edit-hint => { local-address-field.edit-hint-visible = false; }
|
changed edit-hint => { local-address-field.edit-hint-visible = false; }
|
||||||
Timer {
|
Timer {
|
||||||
@@ -1451,7 +1456,7 @@ export component MainWindow inherits Window {
|
|||||||
WorkbenchButton {
|
WorkbenchButton {
|
||||||
x: parent.width - 112px; y: 0px; width: 36px; quiet: true;
|
x: parent.width - 112px; y: 0px; width: 36px; quiet: true;
|
||||||
icon: root.active-password-visible ? FontAwesomeIcons.eye : FontAwesomeIcons.eye-slash;
|
icon: root.active-password-visible ? FontAwesomeIcons.eye : FontAwesomeIcons.eye-slash;
|
||||||
tooltip: root.active-password-visible ? GalleryStrings.tr("隐藏密码", "Hide password", "Скрыть пароль") : GalleryStrings.tr("显示密码", "Show password", "Показать пароль");
|
tooltip: root.active-password-visible ? GalleryStrings.texts[80] : GalleryStrings.texts[81];
|
||||||
clicked => { if root.lan-mode { root.lan-password-visible = !root.lan-password-visible; } else { root.toggle-password-visibility(); } }
|
clicked => { if root.lan-mode { root.lan-password-visible = !root.lan-password-visible; } else { root.toggle-password-visibility(); } }
|
||||||
}
|
}
|
||||||
WorkbenchButton {
|
WorkbenchButton {
|
||||||
@@ -1463,7 +1468,7 @@ export component MainWindow inherits Window {
|
|||||||
WorkbenchButton {
|
WorkbenchButton {
|
||||||
x: parent.width - 36px; y: 0px; width: 36px; quiet: true;
|
x: parent.width - 36px; y: 0px; width: 36px; quiet: true;
|
||||||
icon: FontAwesomeIcons.pen;
|
icon: FontAwesomeIcons.pen;
|
||||||
tooltip: GalleryStrings.tr("修改密码", "Change password", "Изменить пароль");
|
tooltip: GalleryStrings.texts[82];
|
||||||
enabled: root.lan-mode ? !root.lan-password-pending : !root.password-change-pending;
|
enabled: root.lan-mode ? !root.lan-password-pending : !root.password-change-pending;
|
||||||
clicked => { root.reset-password-open = true; }
|
clicked => { root.reset-password-open = true; }
|
||||||
}
|
}
|
||||||
@@ -1472,8 +1477,8 @@ export component MainWindow inherits Window {
|
|||||||
WorkbenchCard {
|
WorkbenchCard {
|
||||||
x: local-card.width + 16px; y: 0px; width: local-card.width; height: parent.height;
|
x: local-card.width + 16px; y: 0px; width: local-card.width; height: parent.height;
|
||||||
Text { x: 16px; y: 14px; width: parent.width - 32px; height: 24px; overflow: elide; text: UiStrings.remote-desktop; font-size: 16px; font-weight: 600; color: WorkbenchStyle.text; }
|
Text { x: 16px; y: 14px; width: parent.width - 32px; height: 24px; overflow: elide; text: UiStrings.remote-desktop; font-size: 16px; font-weight: 600; color: WorkbenchStyle.text; }
|
||||||
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("输入 IP:端口,通过局域网连接", "Enter IP:port to connect over LAN", "Введите IP:порт для подключения по LAN") : GalleryStrings.tr("输入设备 ID,连接远程桌面", "Enter a device ID to start a remote session", "Введите ID для удалённого подключения"); font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
|
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.texts[83] : GalleryStrings.texts[84]; font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
|
||||||
Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("远端 IP / 端口", "Remote IP / Port", "Удалённый IP / Порт") : UiStrings.remote-id; font-size: 12px; color: WorkbenchStyle.muted; }
|
Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.texts[85] : UiStrings.remote-id; font-size: 12px; color: WorkbenchStyle.muted; }
|
||||||
FlatSurface {
|
FlatSurface {
|
||||||
x: 16px; y: 90px; width: parent.width - 32px; height: 40px;
|
x: 16px; y: 90px; width: parent.width - 32px; height: 40px;
|
||||||
background: remote-id-editor.has-focus ? ControlStyle.input-focused : ControlStyle.input-surface;
|
background: remote-id-editor.has-focus ? ControlStyle.input-focused : ControlStyle.input-surface;
|
||||||
@@ -1483,14 +1488,14 @@ export component MainWindow inherits Window {
|
|||||||
animate outline-color { duration: 120ms; }
|
animate outline-color { duration: 120ms; }
|
||||||
if root.remote-id-input == "": Text {
|
if root.remote-id-input == "": Text {
|
||||||
x: 12px; width: parent.width - 24px; height: parent.height;
|
x: 12px; width: parent.width - 24px; height: parent.height;
|
||||||
text: root.lan-mode ? "192.168.1.20:52888" : GalleryStrings.tr("请输入 9 位设备 ID", "Enter a 9-digit device ID", "Введите ID из 9 цифр");
|
text: root.lan-mode ? "192.168.1.20:52888" : GalleryStrings.texts[86];
|
||||||
font-size: 13px; color: ControlStyle.input-placeholder; vertical-alignment: center;
|
font-size: 13px; color: ControlStyle.input-placeholder; vertical-alignment: center;
|
||||||
}
|
}
|
||||||
remote-id-editor := TextInput {
|
remote-id-editor := TextInput {
|
||||||
x: 12px; width: parent.width - 24px; height: parent.height;
|
x: 12px; width: parent.width - 24px; height: parent.height;
|
||||||
text <=> root.remote-id-input; font-size: root.lan-mode ? 18px : 24px; font-weight: 600; letter-spacing: root.lan-mode ? 0px : 1px;
|
text <=> root.remote-id-input; font-size: root.lan-mode ? 18px : 24px; font-weight: 600; letter-spacing: root.lan-mode ? 0px : 1px;
|
||||||
input-type: root.lan-mode ? InputType.text : InputType.number; color: WorkbenchStyle.text;
|
input-type: root.lan-mode ? InputType.text : InputType.number; color: WorkbenchStyle.text;
|
||||||
accessible-label: root.lan-mode ? GalleryStrings.tr("远端 IP / 端口", "Remote IP / Port", "Удалённый IP / Порт") : UiStrings.remote-id;
|
accessible-label: root.lan-mode ? GalleryStrings.texts[85] : UiStrings.remote-id;
|
||||||
enabled: !root.connection-pending && !root.lan-connecting;
|
enabled: !root.connection-pending && !root.lan-connecting;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
selection-foreground-color: white; selection-background-color: WorkbenchStyle.accent;
|
selection-foreground-color: white; selection-background-color: WorkbenchStyle.accent;
|
||||||
@@ -1510,13 +1515,13 @@ export component MainWindow inherits Window {
|
|||||||
Text {
|
Text {
|
||||||
x: 16px; y: 133px; width: parent.width - 32px; height: 24px;
|
x: 16px; y: 133px; width: parent.width - 32px; height: 24px;
|
||||||
text: root.connection-input-error != "" ? root.connection-input-error
|
text: root.connection-input-error != "" ? root.connection-input-error
|
||||||
: root.lan-mode ? GalleryStrings.tr("不填端口时,默认使用 52888", "Port defaults to 52888 if omitted", "Без порта используется 52888") : "";
|
: root.lan-mode ? GalleryStrings.texts[87] : "";
|
||||||
font-size: 11px; color: root.connection-input-error != "" ? #be3d42 : WorkbenchStyle.muted; wrap: word-wrap;
|
font-size: 11px; color: root.connection-input-error != "" ? #be3d42 : WorkbenchStyle.muted; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
WorkbenchButton {
|
WorkbenchButton {
|
||||||
x: 16px; y: 158px; width: parent.width - 32px; height: 36px;
|
x: 16px; y: 158px; width: parent.width - 32px; height: 36px;
|
||||||
primary: true; icon: FontAwesomeIcons.arrow-right-long;
|
primary: true; icon: FontAwesomeIcons.arrow-right-long;
|
||||||
text: root.connection-pending || root.lan-connecting ? GalleryStrings.tr("正在连接…", "Connecting…", "Подключение…") : UiStrings.connect;
|
text: root.connection-pending || root.lan-connecting ? GalleryStrings.texts[56] : UiStrings.connect;
|
||||||
enabled: !root.connection-pending && !root.lan-connecting;
|
enabled: !root.connection-pending && !root.lan-connecting;
|
||||||
clicked => { root.submit-connection(); }
|
clicked => { root.submit-connection(); }
|
||||||
}
|
}
|
||||||
@@ -1524,9 +1529,24 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recent connections: previews, presence badges, and separate primary/overflow actions.
|
// Recent connections: previews, presence badges, and separate primary/overflow actions.
|
||||||
recent-panel := Rectangle {
|
recent-panel := TouchArea {
|
||||||
y: 260px;
|
y: 260px;
|
||||||
height: parent.height - self.y;
|
height: parent.height - self.y;
|
||||||
|
// This observer survives model resets that destroy hovered cards
|
||||||
|
// before their has-hover changed handler can dismiss the details.
|
||||||
|
private property <bool> preview-hovered: root.window-active && self.has-hover
|
||||||
|
&& root.recent-tooltip-index >= 0
|
||||||
|
&& root.recent-tooltip-index < root.recent-connections.length
|
||||||
|
&& root.recent-connections[root.recent-tooltip-index].remote-id == root.recent-tooltip-id
|
||||||
|
&& self.mouse-x >= max(recent-list.x + recent-scroll.x,
|
||||||
|
recent-list.x + recent-scroll.x + recent-scroll.viewport-x + root.recent-tooltip-card-x)
|
||||||
|
&& self.mouse-x < min(recent-list.x + recent-scroll.x + recent-scroll.width,
|
||||||
|
recent-list.x + recent-scroll.x + recent-scroll.viewport-x + root.recent-tooltip-card-x + root.recent-preview-width)
|
||||||
|
&& self.mouse-y >= recent-list.y + recent-scroll.y
|
||||||
|
&& self.mouse-y < recent-list.y + recent-scroll.y + root.recent-preview-height;
|
||||||
|
changed preview-hovered => {
|
||||||
|
if !self.preview-hovered { root.recent-tooltip-visible = false; }
|
||||||
|
}
|
||||||
|
|
||||||
Text { x: 20px; y: 0px; width: recent-group-filter.x - self.x - 12px; height: 32px; text: UiStrings.recent-connections; font-size: 15px; font-weight: 600; color: WorkbenchStyle.text; vertical-alignment: center; }
|
Text { x: 20px; y: 0px; width: recent-group-filter.x - self.x - 12px; height: 32px; text: UiStrings.recent-connections; font-size: 15px; font-weight: 600; color: WorkbenchStyle.text; vertical-alignment: center; }
|
||||||
recent-group-filter := GroupFilter {
|
recent-group-filter := GroupFilter {
|
||||||
@@ -1564,19 +1584,19 @@ export component MainWindow inherits Window {
|
|||||||
Text {
|
Text {
|
||||||
x: 84px; y: 36px; width: parent.width - 104px; height: 24px;
|
x: 84px; y: 36px; width: parent.width - 104px; height: 24px;
|
||||||
text: root.lan-mode
|
text: root.lan-mode
|
||||||
? GalleryStrings.tr("暂无近期 IP 连接", "No recent IP connections", "Нет недавних IP-подключений")
|
? GalleryStrings.texts[88]
|
||||||
: root.recent-group-index == 0
|
: root.recent-group-index == 0
|
||||||
? GalleryStrings.tr("暂无近期连接", "No recent connections", "Нет недавних подключений")
|
? GalleryStrings.texts[89]
|
||||||
: GalleryStrings.tr("该分组暂无近期连接", "No recent connections in this group", "В этой группе нет недавних подключений");
|
: GalleryStrings.texts[90];
|
||||||
color: WorkbenchStyle.text; font-size: 14px; font-weight: 600;
|
color: WorkbenchStyle.text; font-size: 14px; font-weight: 600;
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
x: 84px; y: 68px; width: parent.width - 104px; height: 20px;
|
x: 84px; y: 68px; width: parent.width - 104px; height: 20px;
|
||||||
text: root.lan-mode
|
text: root.lan-mode
|
||||||
? GalleryStrings.tr("通过 IP 连接过的设备会显示在这里。", "Devices you connect to by IP will appear here.", "Здесь появятся устройства, подключённые по IP.")
|
? GalleryStrings.texts[91]
|
||||||
: root.recent-group-index == 0
|
: root.recent-group-index == 0
|
||||||
? GalleryStrings.tr("连接过的设备会显示在这里,方便下次快速访问。", "Devices you connect to will appear here for quick access.", "Устройства появятся здесь после первого подключения.")
|
? GalleryStrings.texts[92]
|
||||||
: GalleryStrings.tr("切换设备分组,查看其他连接记录。", "Choose another group to see your other connections.", "Выберите другую группу для просмотра подключений.");
|
: GalleryStrings.texts[93];
|
||||||
color: WorkbenchStyle.muted; font-size: 12px;
|
color: WorkbenchStyle.muted; font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1722,7 +1742,7 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
clicked => { root.copy-feedback = CopyFeedback.idle; root.recent-connect(connection.remote-id); }
|
clicked => { root.copy-feedback = CopyFeedback.idle; root.recent-connect(connection.remote-id); }
|
||||||
function show-details() {
|
function show-details() {
|
||||||
if !self.has-hover { return; }
|
if !root.window-active || !self.has-hover { return; }
|
||||||
// Leave the menu trigger and its popup free of device details.
|
// Leave the menu trigger and its popup free of device details.
|
||||||
if card-menu.menu-open || (self.mouse-x >= card-menu.x - 4px
|
if card-menu.menu-open || (self.mouse-x >= card-menu.x - 4px
|
||||||
&& self.mouse-y <= card-menu.y + card-menu.height + 4px) {
|
&& self.mouse-y <= card-menu.y + card-menu.height + 4px) {
|
||||||
@@ -1730,6 +1750,8 @@ export component MainWindow inherits Window {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
root.recent-tooltip-name = connection.display-name;
|
root.recent-tooltip-name = connection.display-name;
|
||||||
|
root.recent-tooltip-index = index;
|
||||||
|
root.recent-tooltip-card-x = connection-card.x;
|
||||||
root.recent-tooltip-host = connection.host-name;
|
root.recent-tooltip-host = connection.host-name;
|
||||||
root.recent-tooltip-id = connection.remote-id;
|
root.recent-tooltip-id = connection.remote-id;
|
||||||
root.recent-tooltip-groups = self.group-names;
|
root.recent-tooltip-groups = self.group-names;
|
||||||
@@ -1760,12 +1782,12 @@ export component MainWindow inherits Window {
|
|||||||
compact-trigger: true;
|
compact-trigger: true;
|
||||||
compact-menu: true;
|
compact-menu: true;
|
||||||
enabled: !root.gallery-mode;
|
enabled: !root.gallery-mode;
|
||||||
text: GalleryStrings.tr("更多操作", "More actions", "Другие действия");
|
text: GalleryStrings.texts[94];
|
||||||
menu-width: root.compact-language ? 116px : root.language-index == 2 ? 156px : 128px;
|
menu-width: root.compact-language ? 116px : 156px;
|
||||||
items: [
|
items: [
|
||||||
{label: GalleryStrings.tr("修改名称", "Rename", "Переименовать"), enabled: true},
|
{label: GalleryStrings.texts[35], enabled: true},
|
||||||
{label: GalleryStrings.tr("修改分组", "Edit groups", "Изменить группы"), enabled: !connection.lan},
|
{label: GalleryStrings.texts[30], enabled: !connection.lan},
|
||||||
{label: GalleryStrings.tr("删除记录", "Delete record", "Удалить запись"), enabled: true},
|
{label: GalleryStrings.texts[95], enabled: true},
|
||||||
];
|
];
|
||||||
item-icons: [FontAwesomeIcons.pen, FontAwesomeIcons.tags, FontAwesomeIcons.trash-can];
|
item-icons: [FontAwesomeIcons.pen, FontAwesomeIcons.tags, FontAwesomeIcons.trash-can];
|
||||||
destructive-index: 2;
|
destructive-index: 2;
|
||||||
@@ -1792,7 +1814,7 @@ export component MainWindow inherits Window {
|
|||||||
primary: !connection.connected;
|
primary: !connection.connected;
|
||||||
destructive: connection.connected;
|
destructive: connection.connected;
|
||||||
icon: connection.connected ? FontAwesomeIcons.xmark : FontAwesomeIcons.arrow-right-long;
|
icon: connection.connected ? FontAwesomeIcons.xmark : FontAwesomeIcons.arrow-right-long;
|
||||||
tooltip: connection.connected ? GalleryStrings.tr("断开连接", "Disconnect", "Отключить") : UiStrings.connect;
|
tooltip: connection.connected ? GalleryStrings.texts[96] : UiStrings.connect;
|
||||||
tooltip-above: true;
|
tooltip-above: true;
|
||||||
clicked => {
|
clicked => {
|
||||||
root.copy-feedback = CopyFeedback.idle;
|
root.copy-feedback = CopyFeedback.idle;
|
||||||
@@ -1882,7 +1904,7 @@ export component MainWindow inherits Window {
|
|||||||
height: parent.height;
|
height: parent.height;
|
||||||
width: parent.width - 160px;
|
width: parent.width - 160px;
|
||||||
overflow: elide;
|
overflow: elide;
|
||||||
text: root.lan-mode ? (root.lan-message != "" ? LanStrings.error-message(root.lan-message) : root.lan-listener-error != "" ? (root.lan-listener-address == "" ? "" : root.lan-listener-address + " · ") + LanStrings.error-message(root.lan-listener-error) : root.lan-listening ? GalleryStrings.tr("IP 连接已启用", "IP connections enabled", "Подключения по IP включены") : root.lan-password-pending ? GalleryStrings.tr("IP 连接准备中…", "Preparing IP connections…", "Подготовка подключений по IP…") : GalleryStrings.tr("IP 连接未就绪", "IP connections not ready", "Подключения по IP не готовы"))
|
text: root.lan-mode ? (root.lan-message != "" ? LanStrings.error-message(root.lan-message) : root.lan-listener-error != "" ? (root.lan-listener-address == "" ? "" : root.lan-listener-address + " · ") + LanStrings.error-message(root.lan-listener-error) : root.lan-listening ? GalleryStrings.texts[97] : root.lan-password-pending ? GalleryStrings.texts[98] : GalleryStrings.texts[99])
|
||||||
: root.signal-tls-error ? UiStrings.tls-error
|
: root.signal-tls-error ? UiStrings.tls-error
|
||||||
: root.signal-connected ? UiStrings.signal-connected
|
: root.signal-connected ? UiStrings.signal-connected
|
||||||
: root.signal-reconnecting ? UiStrings.signal-reconnecting
|
: root.signal-reconnecting ? UiStrings.signal-reconnecting
|
||||||
@@ -1920,7 +1942,7 @@ export component MainWindow inherits Window {
|
|||||||
|
|
||||||
// Shared device details sit outside the clipped recent list and wall rail,
|
// Shared device details sit outside the clipped recent list and wall rail,
|
||||||
// below the modal layer (z >= 39).
|
// below the modal layer (z >= 39).
|
||||||
if (root.gallery-mode ? gallery.hovered-device.id != "" : root.recent-tooltip-visible)
|
if (root.gallery-mode ? gallery.hovered-device.id != "" : root.recent-tooltip-visible && recent-panel.preview-hovered)
|
||||||
&& !root.connection-dialog-open && !root.lan-open: device-tip := FlatSurface {
|
&& !root.connection-dialog-open && !root.lan-open: device-tip := FlatSurface {
|
||||||
private property <string> device-name: root.gallery-mode ? gallery.hovered-device.name : root.recent-tooltip-name;
|
private property <string> device-name: root.gallery-mode ? gallery.hovered-device.name : root.recent-tooltip-name;
|
||||||
private property <string> host-name: root.gallery-mode ? gallery.hovered-device.host-name : root.recent-tooltip-host;
|
private property <string> host-name: root.gallery-mode ? gallery.hovered-device.host-name : root.recent-tooltip-host;
|
||||||
@@ -1967,7 +1989,7 @@ export component MainWindow inherits Window {
|
|||||||
tooltip-status-text := Text {
|
tooltip-status-text := Text {
|
||||||
x: 7px; width: parent.width - 14px; height: parent.height;
|
x: 7px; width: parent.width - 14px; height: parent.height;
|
||||||
text: device-tip.online ? UiStrings.online : device-tip.presence-known ? UiStrings.offline
|
text: device-tip.online ? UiStrings.online : device-tip.presence-known ? UiStrings.offline
|
||||||
: GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
: GalleryStrings.texts[100];
|
||||||
color: device-tip.status-color; font-size: 10px; font-weight: 600;
|
color: device-tip.status-color; font-size: 10px; font-weight: 600;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
@@ -2011,14 +2033,14 @@ export component MainWindow inherits Window {
|
|||||||
tooltip-group-label := Text {
|
tooltip-group-label := Text {
|
||||||
x: 12px; y: tooltip-groups.y;
|
x: 12px; y: tooltip-groups.y;
|
||||||
width: device-tip.label-width; height: 16px;
|
width: device-tip.label-width; height: 16px;
|
||||||
text: GalleryStrings.tr("分组", "Groups", "Группы");
|
text: GalleryStrings.texts[101];
|
||||||
color: WorkbenchStyle.muted; font-size: 10px;
|
color: WorkbenchStyle.muted; font-size: 10px;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
tooltip-groups := Text {
|
tooltip-groups := Text {
|
||||||
x: 20px + device-tip.label-width; y: tooltip-id-label.y + tooltip-id-label.height + 4px;
|
x: 20px + device-tip.label-width; y: tooltip-id-label.y + tooltip-id-label.height + 4px;
|
||||||
width: parent.width - self.x - 12px; height: max(16px, self.preferred-height);
|
width: parent.width - self.x - 12px; height: max(16px, self.preferred-height);
|
||||||
text: device-tip.group-names == "" ? GalleryStrings.tr("未分组", "Ungrouped", "Без группы") : device-tip.group-names;
|
text: device-tip.group-names == "" ? GalleryStrings.texts[31] : device-tip.group-names;
|
||||||
color: device-tip.group-names == "" ? WorkbenchStyle.muted : WorkbenchStyle.accent;
|
color: device-tip.group-names == "" ? WorkbenchStyle.muted : WorkbenchStyle.accent;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
wrap: word-wrap; horizontal-alignment: right; vertical-alignment: center;
|
wrap: word-wrap; horizontal-alignment: right; vertical-alignment: center;
|
||||||
@@ -2218,8 +2240,8 @@ export component MainWindow inherits Window {
|
|||||||
error: root.copy-feedback == CopyFeedback.failed;
|
error: root.copy-feedback == CopyFeedback.failed;
|
||||||
icon: self.error ? FontAwesomeIcons.xmark : FontAwesomeIcons.check;
|
icon: self.error ? FontAwesomeIcons.xmark : FontAwesomeIcons.check;
|
||||||
text: self.error
|
text: self.error
|
||||||
? GalleryStrings.tr("复制失败,请重试", "Couldn’t copy. Try again", "Не удалось скопировать. Повторите попытку")
|
? GalleryStrings.texts[102]
|
||||||
: root.lan-mode ? GalleryStrings.tr("IP 和端口已复制", "IP and port copied", "IP и порт скопированы") : UiStrings.copied;
|
: root.lan-mode ? GalleryStrings.texts[103] : UiStrings.copied;
|
||||||
z: 30;
|
z: 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2330,7 +2352,7 @@ export component MainWindow inherits Window {
|
|||||||
height: 140px;
|
height: 140px;
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
padding: 20px; spacing: 18px;
|
padding: 20px; spacing: 18px;
|
||||||
DialogPromptText { prompt: root.delete-from-gallery ? (root.gallery-group-index > 0 ? GalleryStrings.tr("从当前分组移除此设备?", "Remove from this group?", "Убрать из этой группы?") : GalleryStrings.tr("从桌面墙移除此设备?", "Remove from DeskHub?", "Убрать из DeskHub?")) : UiStrings.confirm-delete; }
|
DialogPromptText { prompt: root.delete-from-gallery ? (root.gallery-group-index > 0 ? GalleryStrings.texts[104] : GalleryStrings.texts[105]) : UiStrings.confirm-delete; }
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
alignment: center; spacing: 8px;
|
alignment: center; spacing: 8px;
|
||||||
PrimaryButton { text: UiStrings.ok; clicked => { if root.delete-from-gallery { root.gallery-action("remove", root.delete-remote-id); } else { root.recent-delete(root.delete-remote-id); } root.delete-open = false; } }
|
PrimaryButton { text: UiStrings.ok; clicked => { if root.delete-from-gallery { root.gallery-action("remove", root.delete-remote-id); } else { root.recent-delete(root.delete-remote-id); } root.delete-open = false; } }
|
||||||
@@ -2383,7 +2405,7 @@ export component MainWindow inherits Window {
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
spacing: 16px;
|
spacing: 16px;
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("确认断开连接?", "Disconnect this session?", "Отключить этот сеанс?");
|
text: GalleryStrings.texts[106];
|
||||||
color: WorkbenchStyle.text;
|
color: WorkbenchStyle.text;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -2405,7 +2427,7 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("当前远程控制会话将结束。", "The current remote session will end.", "Текущий сеанс удалённого управления будет завершён.");
|
text: GalleryStrings.texts[107];
|
||||||
color: WorkbenchStyle.muted;
|
color: WorkbenchStyle.muted;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
wrap: word-wrap;
|
wrap: word-wrap;
|
||||||
@@ -2418,7 +2440,7 @@ export component MainWindow inherits Window {
|
|||||||
clicked => { root.dismiss-disconnect(); }
|
clicked => { root.dismiss-disconnect(); }
|
||||||
}
|
}
|
||||||
disconnect-confirm := WorkbenchButton {
|
disconnect-confirm := WorkbenchButton {
|
||||||
text: GalleryStrings.tr("断开连接", "Disconnect", "Отключить");
|
text: GalleryStrings.texts[96];
|
||||||
destructive: true;
|
destructive: true;
|
||||||
clicked => {
|
clicked => {
|
||||||
let remote-id = root.disconnect-remote-id;
|
let remote-id = root.disconnect-remote-id;
|
||||||
@@ -2546,9 +2568,9 @@ export component MainWindow inherits Window {
|
|||||||
spacing: 12px;
|
spacing: 12px;
|
||||||
for permission[index] in [
|
for permission[index] in [
|
||||||
{label: UiStrings.accessibility-permission, granted: root.accessibility-granted,
|
{label: UiStrings.accessibility-permission, granted: root.accessibility-granted,
|
||||||
description: GalleryStrings.tr("允许远程键盘和鼠标控制", "Allow remote keyboard and mouse input", "Разрешить удалённый ввод с клавиатуры и мыши")},
|
description: GalleryStrings.texts[108]},
|
||||||
{label: UiStrings.screen-recording-permission, granted: root.screen-recording-granted,
|
{label: UiStrings.screen-recording-permission, granted: root.screen-recording-granted,
|
||||||
description: GalleryStrings.tr("允许共享此 Mac 的屏幕画面", "Allow sharing this Mac’s screen", "Разрешить показ экрана этого Mac")},
|
description: GalleryStrings.texts[109]},
|
||||||
]: WorkbenchCard {
|
]: WorkbenchCard {
|
||||||
height: max(80px, permission-labels.preferred-height + 32px);
|
height: max(80px, permission-labels.preferred-height + 32px);
|
||||||
permission-labels := VerticalLayout {
|
permission-labels := VerticalLayout {
|
||||||
@@ -2569,7 +2591,7 @@ export component MainWindow inherits Window {
|
|||||||
height: 36px;
|
height: 36px;
|
||||||
permission-action-metrics := Text {
|
permission-action-metrics := Text {
|
||||||
visible: false; font-size: 13px; font-weight: 600;
|
visible: false; font-size: 13px; font-weight: 600;
|
||||||
text: GalleryStrings.tr("前往设置", "Open settings", "Настройки");
|
text: GalleryStrings.texts[110];
|
||||||
}
|
}
|
||||||
if !permission.granted: WorkbenchButton {
|
if !permission.granted: WorkbenchButton {
|
||||||
width: parent.width; height: parent.height; primary: true;
|
width: parent.width; height: parent.height; primary: true;
|
||||||
@@ -2588,7 +2610,7 @@ export component MainWindow inherits Window {
|
|||||||
vertical-alignment: center; accessible-role: none;
|
vertical-alignment: center; accessible-role: none;
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("已授权", "Granted", "Разрешено");
|
text: GalleryStrings.texts[111];
|
||||||
font-size: 12px; color: WorkbenchStyle.success; vertical-alignment: center;
|
font-size: 12px; color: WorkbenchStyle.success; vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2599,9 +2621,7 @@ export component MainWindow inherits Window {
|
|||||||
spacing: 12px;
|
spacing: 12px;
|
||||||
SurfaceSeparator { height: 1px; outline-color: UiSurface.separator; }
|
SurfaceSeparator { height: 1px; outline-color: UiSurface.separator; }
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("请在系统设置中允许 CrossDesk 访问。如系统提示重新打开应用,请按提示操作。",
|
text: GalleryStrings.texts[112];
|
||||||
"Allow CrossDesk access in System Settings. Reopen the app if macOS asks you to.",
|
|
||||||
"Разрешите доступ для CrossDesk в Системных настройках. Перезапустите приложение, если macOS попросит об этом.");
|
|
||||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2643,7 +2663,7 @@ export component MainWindow inherits Window {
|
|||||||
background: WorkbenchStyle.accent; border-radius: UiRadius.control;
|
background: WorkbenchStyle.accent; border-radius: UiRadius.control;
|
||||||
animate x { duration: 180ms; easing: ease-in-out; }
|
animate x { duration: 180ms; easing: ease-in-out; }
|
||||||
}
|
}
|
||||||
for label[index] in [GalleryStrings.tr("常规", "General", "Общие"), GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub")]: Rectangle {
|
for label[index] in [GalleryStrings.texts[113], GalleryStrings.texts[54]]: Rectangle {
|
||||||
x: index == 1 ? parent.width / 2 + 1px : 2px;
|
x: index == 1 ? parent.width / 2 + 1px : 2px;
|
||||||
y: 2px; width: (parent.width - 6px) / 2; height: parent.height - 4px;
|
y: 2px; width: (parent.width - 6px) / 2; height: parent.height - 4px;
|
||||||
accessible-role: button; accessible-label: label;
|
accessible-role: button; accessible-label: label;
|
||||||
@@ -2681,14 +2701,14 @@ export component MainWindow inherits Window {
|
|||||||
padding-right: settings-dialog.scrollbar-gutter; padding-bottom: 4px;
|
padding-right: settings-dialog.scrollbar-gutter; padding-bottom: 4px;
|
||||||
spacing: 14px;
|
spacing: 14px;
|
||||||
SettingsGroup {
|
SettingsGroup {
|
||||||
title: GalleryStrings.tr("界面", "Interface", "Интерфейс"); rows: 1;
|
title: GalleryStrings.texts[114]; rows: 1;
|
||||||
SettingsRow {
|
SettingsRow {
|
||||||
label: UiStrings.language; divider: false;
|
label: UiStrings.language; divider: false;
|
||||||
SettingsSelect { x: parent.width - 116px; y: 4px; width: 104px; model: ["中文", "English", "Русский"]; current-index <=> root.language-index; }
|
SettingsSelect { x: parent.width - 192px; y: 4px; width: 180px; model: root.language-options; current-index <=> root.language-index; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsGroup {
|
SettingsGroup {
|
||||||
title: GalleryStrings.tr("视频", "Video", "Видео");
|
title: GalleryStrings.texts[115];
|
||||||
rows: root.capture-method-visible ? 2 : 1;
|
rows: root.capture-method-visible ? 2 : 1;
|
||||||
if root.capture-method-visible: SettingsRow {
|
if root.capture-method-visible: SettingsRow {
|
||||||
label: UiStrings.capture-method; enabled: !root.settings-session-active;
|
label: UiStrings.capture-method; enabled: !root.settings-session-active;
|
||||||
@@ -2700,9 +2720,9 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsGroup {
|
SettingsGroup {
|
||||||
title: GalleryStrings.tr("网络连接", "Network", "Сеть"); rows: 3;
|
title: GalleryStrings.texts[116]; rows: 3;
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
label: GalleryStrings.tr("启用 IP 连接", "Enable IP connections", "Подключение по IP");
|
label: GalleryStrings.texts[117];
|
||||||
checked <=> root.ip-direct-enabled-draft;
|
checked <=> root.ip-direct-enabled-draft;
|
||||||
}
|
}
|
||||||
SettingsToggleRow { label: UiStrings.force-relay; checked <=> root.force-relay-enabled; enabled: !root.settings-session-active; }
|
SettingsToggleRow { label: UiStrings.force-relay; checked <=> root.force-relay-enabled; enabled: !root.settings-session-active; }
|
||||||
@@ -2713,7 +2733,7 @@ export component MainWindow inherits Window {
|
|||||||
self-host-edit := SettingsButton {
|
self-host-edit := SettingsButton {
|
||||||
x: parent.width - self.width - 60px; y: 4px;
|
x: parent.width - self.width - 60px; y: 4px;
|
||||||
width: self-host-edit-metrics.preferred-width + 24px; height: 28px;
|
width: self-host-edit-metrics.preferred-width + 24px; height: 28px;
|
||||||
text: GalleryStrings.tr("编辑", "Edit", "Изменить");
|
text: GalleryStrings.texts[118];
|
||||||
enabled: !root.settings-session-active;
|
enabled: !root.settings-session-active;
|
||||||
clicked => { root.self-host-settings-open = true; }
|
clicked => { root.self-host-settings-open = true; }
|
||||||
self-host-edit-metrics := Text {
|
self-host-edit-metrics := Text {
|
||||||
@@ -2724,20 +2744,21 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("关闭 IP 连接将隐藏模式按钮,并断开 IP 连接会话。", "Disabling IP connections hides the mode button and ends IP sessions.", "Отключение подключений по IP скроет кнопку режима и завершит IP-сеансы.");
|
text: GalleryStrings.texts[119];
|
||||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
SettingsGroup {
|
SettingsGroup {
|
||||||
title: GalleryStrings.tr("隐私保护", "Privacy", "Приватность"); rows: 2;
|
title: GalleryStrings.texts[120]; rows: 3;
|
||||||
SettingsToggleRow { label: UiStrings.privacy-on-connect; checked <=> root.privacy-on-connect-enabled; enabled: root.privacy-setting-available; }
|
SettingsToggleRow { label: UiStrings.privacy-on-connect; checked <=> root.privacy-on-connect-enabled; enabled: root.privacy-setting-available; }
|
||||||
|
SettingsToggleRow { label: UiStrings.privacy-local-unlock; checked <=> root.privacy-local-unlock-allowed; enabled: root.privacy-setting-available; }
|
||||||
SettingsToggleRow { label: UiStrings.lock-on-disconnect; checked <=> root.lock-on-disconnect-enabled; enabled: root.lock-on-disconnect-available; divider: false; }
|
SettingsToggleRow { label: UiStrings.lock-on-disconnect; checked <=> root.lock-on-disconnect-enabled; enabled: root.lock-on-disconnect-available; divider: false; }
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: GalleryStrings.tr("最后一个远程控制端断开后,自动锁定本机。", "Lock this device when the last remote controller disconnects.", "Блокировать это устройство после отключения последнего управляющего клиента.");
|
text: GalleryStrings.texts[121];
|
||||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
SettingsGroup {
|
SettingsGroup {
|
||||||
title: GalleryStrings.tr("本地存储", "Local storage", "Локальное хранение"); rows: 2;
|
title: GalleryStrings.texts[122]; rows: 2;
|
||||||
SettingsToggleRow { label: UiStrings.save-remote-previews; checked <=> root.save-remote-previews; }
|
SettingsToggleRow { label: UiStrings.save-remote-previews; checked <=> root.save-remote-previews; }
|
||||||
SettingsRow {
|
SettingsRow {
|
||||||
label: UiStrings.file-save-path; enabled: !root.settings-session-active; divider: false;
|
label: UiStrings.file-save-path; enabled: !root.settings-session-active; divider: false;
|
||||||
@@ -2745,7 +2766,7 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsGroup {
|
SettingsGroup {
|
||||||
title: GalleryStrings.tr("启动与后台", "Startup & background", "Запуск и фоновая работа"); rows: 2;
|
title: GalleryStrings.texts[123]; rows: 2;
|
||||||
if !root.autostart-system-managed: SettingsToggleRow { label: UiStrings.autostart; checked <=> root.autostart-enabled; enabled: !root.settings-session-active; }
|
if !root.autostart-system-managed: SettingsToggleRow { label: UiStrings.autostart; checked <=> root.autostart-enabled; enabled: !root.settings-session-active; }
|
||||||
if root.autostart-system-managed: SettingsToggleRow {
|
if root.autostart-system-managed: SettingsToggleRow {
|
||||||
label: UiStrings.autostart;
|
label: UiStrings.autostart;
|
||||||
@@ -2781,12 +2802,12 @@ export component MainWindow inherits Window {
|
|||||||
width: parent.width - 2 * settings-dialog.content-inset; height: 32px; spacing: 10px;
|
width: parent.width - 2 * settings-dialog.content-inset; height: 32px; spacing: 10px;
|
||||||
Rectangle { horizontal-stretch: 1; }
|
Rectangle { horizontal-stretch: 1; }
|
||||||
SettingsButton {
|
SettingsButton {
|
||||||
width: 80px; text: UiStrings.cancel;
|
width: max(80px, self.label-width); text: UiStrings.cancel;
|
||||||
clicked => { root.cancel-settings(); root.settings-open = false; root.self-host-settings-open = false; }
|
clicked => { root.cancel-settings(); root.settings-open = false; root.self-host-settings-open = false; }
|
||||||
}
|
}
|
||||||
SettingsButton {
|
SettingsButton {
|
||||||
width: 92px; primary: true;
|
width: max(92px, self.label-width); primary: true;
|
||||||
text: GalleryStrings.tr("保存", "Save", "Сохранить");
|
text: GalleryStrings.texts[34];
|
||||||
clicked => { if root.save-settings() { root.settings-open = false; root.self-host-settings-open = false; } }
|
clicked => { if root.save-settings() { root.settings-open = false; root.self-host-settings-open = false; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2804,7 +2825,7 @@ export component MainWindow inherits Window {
|
|||||||
TouchArea { clicked => { focus-sink.focus(); } }
|
TouchArea { clicked => { focus-sink.focus(); } }
|
||||||
Text {
|
Text {
|
||||||
x: 24px; y: 18px; width: parent.width - 48px; height: 26px;
|
x: 24px; y: 18px; width: parent.width - 48px; height: 26px;
|
||||||
text: GalleryStrings.tr("设置开机自启动", "Set up auto start", "Настройка автозапуска");
|
text: GalleryStrings.texts[124];
|
||||||
font-size: 18px; font-weight: 600; color: WorkbenchStyle.text;
|
font-size: 18px; font-weight: 600; color: WorkbenchStyle.text;
|
||||||
}
|
}
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -2816,25 +2837,20 @@ export component MainWindow inherits Window {
|
|||||||
width: parent.width; padding: 0px; spacing: 12px;
|
width: parent.width; padding: 0px; spacing: 12px;
|
||||||
Text { text: root.autostart-status-text; color: WorkbenchStyle.accent; font-size: 13px; wrap: word-wrap; }
|
Text { text: root.autostart-status-text; color: WorkbenchStyle.accent; font-size: 13px; wrap: word-wrap; }
|
||||||
Text {
|
Text {
|
||||||
text: root.autostart-status == AutostartStatus.unsupported ? GalleryStrings.tr(
|
text: root.autostart-status == AutostartStatus.unsupported ? GalleryStrings.texts[125] :
|
||||||
"当前设备不支持开机自启动。", "This device does not support auto start.", "Это устройство не поддерживает автозапуск.") :
|
root.autostart-status == AutostartStatus.failed ? GalleryStrings.texts[126] :
|
||||||
root.autostart-status == AutostartStatus.failed ? GalleryStrings.tr(
|
GalleryStrings.texts[127];
|
||||||
"无法读取系统状态。请刷新重试,或在系统设置中核对。", "Could not read the system status. Refresh to retry, or check System Settings.", "Не удалось прочитать состояние. Повторите попытку или проверьте настройки системы.") :
|
|
||||||
GalleryStrings.tr("请在系统设置中添加或移除本应用,返回后会自动刷新状态。", "Add or remove this app in system Settings. The status refreshes when you return.", "Добавьте или удалите приложение в настройках системы. После возвращения статус обновится.");
|
|
||||||
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
if root.autostart-status != AutostartStatus.unsupported: Text {
|
if root.autostart-status != AutostartStatus.unsupported: Text {
|
||||||
text: GalleryStrings.tr(
|
text: GalleryStrings.texts[128];
|
||||||
"设置 → 应用和元服务 → 应用启动管理\n\n开启:点击“添加应用”,选择 CrossDesk,然后点击“完成”。\n\n关闭:在自启动列表中找到本应用,点击“移除”。",
|
|
||||||
"Settings → Apps & services → App startup management\n\nEnable: choose Add app, select CrossDesk, then choose Done.\n\nDisable: find this app in the startup list and choose Remove.",
|
|
||||||
"Настройки → Приложения и сервисы → Управление запуском\n\nВключить: добавьте CrossDesk в список и подтвердите выбор.\n\nВыключить: удалите приложение из списка автозапуска.");
|
|
||||||
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsButton {
|
SettingsButton {
|
||||||
x: 24px; y: parent.height - 48px; width: 112px;
|
x: 24px; y: parent.height - 48px; width: 112px;
|
||||||
text: GalleryStrings.tr("刷新状态", "Refresh status", "Обновить статус");
|
text: GalleryStrings.texts[129];
|
||||||
enabled: root.autostart-status != AutostartStatus.checking;
|
enabled: root.autostart-status != AutostartStatus.checking;
|
||||||
clicked => { root.refresh-autostart-status(); }
|
clicked => { root.refresh-autostart-status(); }
|
||||||
}
|
}
|
||||||
@@ -2879,7 +2895,7 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
SurfaceSeparator { x: 18px; y: parent.height - 66px; width: parent.width - 36px; height: 1px; outline-color: UiSurface.separator; }
|
SurfaceSeparator { x: 18px; y: parent.height - 66px; width: parent.width - 36px; height: 1px; outline-color: UiSurface.separator; }
|
||||||
SettingsButton { x: parent.width - 114px; y: parent.height - 50px; width: 92px; primary: true; text: UiStrings.ok; clicked => { if root.save-self-hosted-settings() { root.self-host-settings-open = false; } } }
|
SettingsButton { x: parent.width - 114px; y: parent.height - 50px; width: 92px; primary: true; text: UiStrings.ok; clicked => { if root.save-self-hosted-settings() { root.self-host-settings-open = false; } } }
|
||||||
SettingsButton { x: parent.width - 204px; y: parent.height - 50px; width: 80px; text: UiStrings.cancel; clicked => { root.cancel-self-hosted-settings(); root.self-host-settings-open = false; } }
|
SettingsButton { x: parent.width - 124px - self.width; y: parent.height - 50px; width: max(80px, self.label-width); text: UiStrings.cancel; clicked => { root.cancel-self-hosted-settings(); root.self-host-settings-open = false; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export component SettingsButton inherits FlatSurface {
|
|||||||
in property <bool> quiet: false;
|
in property <bool> quiet: false;
|
||||||
in property <bool> align-left: false;
|
in property <bool> align-left: false;
|
||||||
in property <bool> enabled: true;
|
in property <bool> enabled: true;
|
||||||
|
out property <length> label-width: button-label.preferred-width + 2 * root.content-padding + (root.has-icon ? 24px : 0px);
|
||||||
private property <bool> has-icon: root.icon.width > 0;
|
private property <bool> has-icon: root.icon.width > 0;
|
||||||
private property <length> content-padding: root.has-icon ? 8px : 12px;
|
private property <length> content-padding: root.has-icon ? 8px : 12px;
|
||||||
callback clicked();
|
callback clicked();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { SurfaceSeparator, FlatSurface, UiSurface } from "ui_surface.slint";
|
|||||||
import { UiRadius } from "ui_radius.slint";
|
import { UiRadius } from "ui_radius.slint";
|
||||||
import { ComboBox, ScrollView } from "std-widgets.slint";
|
import { ComboBox, ScrollView } from "std-widgets.slint";
|
||||||
import { RemoteCursor } from "remote_cursor.slint";
|
import { RemoteCursor } from "remote_cursor.slint";
|
||||||
import { FontAwesomeIcons, IconButton, PrimaryButton, SecondaryButton, DialogSurface, ImGuiFontStyle, ControlStyle, ButtonStyle } from "common.slint";
|
import { FontAwesomeIcons, IconButton, PrimaryButton, SecondaryButton, DialogSurface, ImGuiFontStyle, ControlStyle, ButtonStyle, ScrollingText } from "common.slint";
|
||||||
|
|
||||||
export struct StreamTab {
|
export struct StreamTab {
|
||||||
remote-id: string,
|
remote-id: string,
|
||||||
@@ -47,6 +47,10 @@ export global StreamStrings {
|
|||||||
|
|
||||||
in-out property <string> select-display: "Select Display";
|
in-out property <string> select-display: "Select Display";
|
||||||
in-out property <string> send-shortcut: "Send Shortcut";
|
in-out property <string> send-shortcut: "Send Shortcut";
|
||||||
|
in-out property <string> privacy-unlocked-title: "Privacy screen unlocked";
|
||||||
|
in-out property <string> privacy-unlocked-message: "The remote privacy screen was unlocked using the local shortcut. Lock it again to restore privacy and disable local shortcut unlocking.";
|
||||||
|
in-out property <string> privacy-unlock-confirm: "Confirm";
|
||||||
|
in-out property <string> privacy-unlock-lock: "Lock";
|
||||||
in-out property <string> privacy-enable: "Enable privacy screen";
|
in-out property <string> privacy-enable: "Enable privacy screen";
|
||||||
in-out property <string> privacy-disable: "Turn privacy off / recover";
|
in-out property <string> privacy-disable: "Turn privacy off / recover";
|
||||||
in-out property <string> control-mouse: "Control Mouse";
|
in-out property <string> control-mouse: "Control Mouse";
|
||||||
@@ -98,6 +102,7 @@ component ControlBarButton inherits Rectangle {
|
|||||||
in property <bool> show-logo: false;
|
in property <bool> show-logo: false;
|
||||||
in property <bool> logo-pressed: false;
|
in property <bool> logo-pressed: false;
|
||||||
in property <bool> logo-dragging: false;
|
in property <bool> logo-dragging: false;
|
||||||
|
in property <bool> hand-cursor-supported: true;
|
||||||
in property <float> reveal-progress: 1;
|
in property <float> reveal-progress: 1;
|
||||||
in property <string> badge: "";
|
in property <string> badge: "";
|
||||||
in property <string> tooltip: "";
|
in property <string> tooltip: "";
|
||||||
@@ -223,7 +228,7 @@ component ControlBarButton inherits Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
touch := TouchArea {
|
touch := TouchArea {
|
||||||
mouse-cursor: !root.action-enabled ? default : root.show-logo ? (root.logo-pressed ? grabbing : grab) : pointer;
|
mouse-cursor: root.action-enabled && root.hand-cursor-supported ? pointer : default;
|
||||||
scroll-event(event) => { accept }
|
scroll-event(event) => { accept }
|
||||||
pointer-event(event) => {
|
pointer-event(event) => {
|
||||||
root.pointer-input(event.button, event.kind, self.mouse-x, self.mouse-y);
|
root.pointer-input(event.button, event.kind, self.mouse-x, self.mouse-y);
|
||||||
@@ -281,6 +286,7 @@ export component StreamWindow inherits Window {
|
|||||||
default-font-size: ImGuiFontStyle.base;
|
default-font-size: ImGuiFontStyle.base;
|
||||||
|
|
||||||
in property <bool> custom-titlebar: false;
|
in property <bool> custom-titlebar: false;
|
||||||
|
in property <bool> control-hand-cursor-supported: true;
|
||||||
in property <bool> native-video-enabled: false;
|
in property <bool> native-video-enabled: false;
|
||||||
// Export the actual input surface in logical pixels. Native rendering and
|
// Export the actual input surface in logical pixels. Native rendering and
|
||||||
// input mapping must follow the same layout, including title and tab bars.
|
// input mapping must follow the same layout, including title and tab bars.
|
||||||
@@ -296,10 +302,16 @@ export component StreamWindow inherits Window {
|
|||||||
in property <string> status-text: "";
|
in property <string> status-text: "";
|
||||||
in property <string> receiving-text: "";
|
in property <string> receiving-text: "";
|
||||||
in property <bool> remote-update-visible: false;
|
in property <bool> remote-update-visible: false;
|
||||||
|
in property <bool> privacy-unlock-visible: false;
|
||||||
|
in property <bool> privacy-can-lock: false;
|
||||||
|
out property <bool> modal-visible: root.remote-update-visible || root.privacy-unlock-visible;
|
||||||
|
callback dismiss-privacy-unlock;
|
||||||
|
callback lock-privacy;
|
||||||
callback dismiss-remote-update;
|
callback dismiss-remote-update;
|
||||||
in property <[string]> displays;
|
in property <[string]> displays;
|
||||||
in-out property <int> selected-display: 0;
|
in-out property <int> selected-display: 0;
|
||||||
in property <bool> mouse-control-enabled: true;
|
in property <bool> mouse-control-enabled: true;
|
||||||
|
in property <bool> privacy-visible: true;
|
||||||
in property <bool> privacy-can-toggle: false;
|
in property <bool> privacy-can-toggle: false;
|
||||||
in property <bool> privacy-active: false;
|
in property <bool> privacy-active: false;
|
||||||
in property <bool> privacy-off: false;
|
in property <bool> privacy-off: false;
|
||||||
@@ -334,10 +346,8 @@ export component StreamWindow inherits Window {
|
|||||||
callback reorder-tab(int, length, length);
|
callback reorder-tab(int, length, length);
|
||||||
callback close-tab(string);
|
callback close-tab(string);
|
||||||
callback switch-display(int);
|
callback switch-display(int);
|
||||||
in property <bool> secure-attention-supported: true;
|
in property <bool> secure-attention-supported: false;
|
||||||
private property <[string]> secure-shortcut-items: ["Ctrl+Alt+Del", "Win+L"];
|
private property <[string]> shortcut-items: ["Ctrl+Alt+Del", "Win+L"];
|
||||||
private property <[string]> ordinary-shortcut-items: ["Win+L"];
|
|
||||||
private property <[string]> shortcut-items: root.secure-attention-supported ? root.secure-shortcut-items : root.ordinary-shortcut-items;
|
|
||||||
private property <length> shortcut-menu-height: root.shortcut-items.length * 30px + 16px;
|
private property <length> shortcut-menu-height: root.shortcut-items.length * 30px + 16px;
|
||||||
callback send-shortcut(string);
|
callback send-shortcut(string);
|
||||||
callback toggle-mouse-control;
|
callback toggle-mouse-control;
|
||||||
@@ -363,6 +373,8 @@ export component StreamWindow inherits Window {
|
|||||||
private property <bool> control-visual-expanded: false;
|
private property <bool> control-visual-expanded: false;
|
||||||
private property <length> control-toolbar-height: 38px;
|
private property <length> control-toolbar-height: 38px;
|
||||||
private property <length> control-button-step: 32px;
|
private property <length> control-button-step: 32px;
|
||||||
|
private property <length> control-shortcut-width: root.secure-attention-supported ? root.control-button-step : 0px;
|
||||||
|
private property <length> control-privacy-width: root.privacy-visible ? root.control-button-step : 0px;
|
||||||
private property <length> control-disconnect-gap: 8px;
|
private property <length> control-disconnect-gap: 8px;
|
||||||
private property <length> control-buttons-x: root.control-docked-left ? 43px : 7px;
|
private property <length> control-buttons-x: root.control-docked-left ? 43px : 7px;
|
||||||
private property <length> control-panel-gap: 8px;
|
private property <length> control-panel-gap: 8px;
|
||||||
@@ -414,6 +426,9 @@ export component StreamWindow inherits Window {
|
|||||||
root.close-control-menus();
|
root.close-control-menus();
|
||||||
root.cancel-remote-pointer();
|
root.cancel-remote-pointer();
|
||||||
}
|
}
|
||||||
|
changed secure-attention-supported => {
|
||||||
|
if !root.secure-attention-supported { root.shortcut-menu-open = false; }
|
||||||
|
}
|
||||||
changed control-expanded => {
|
changed control-expanded => {
|
||||||
control-reveal-timer.running = false;
|
control-reveal-timer.running = false;
|
||||||
control-collapse-timer.running = false;
|
control-collapse-timer.running = false;
|
||||||
@@ -487,8 +502,8 @@ export component StreamWindow inherits Window {
|
|||||||
changed mouse-control-enabled => {
|
changed mouse-control-enabled => {
|
||||||
if !root.mouse-control-enabled { root.cancel-remote-pointer(); }
|
if !root.mouse-control-enabled { root.cancel-remote-pointer(); }
|
||||||
}
|
}
|
||||||
changed remote-update-visible => {
|
changed modal-visible => {
|
||||||
if root.remote-update-visible {
|
if root.modal-visible {
|
||||||
root.close-control-menus();
|
root.close-control-menus();
|
||||||
root.control-drag-pending = false;
|
root.control-drag-pending = false;
|
||||||
root.end-control-drag();
|
root.end-control-drag();
|
||||||
@@ -498,7 +513,7 @@ export component StreamWindow inherits Window {
|
|||||||
// adapter and aborts the process; defer focus to the event loop.
|
// adapter and aborts the process; defer focus to the event loop.
|
||||||
input-focus-timer.running = true;
|
input-focus-timer.running = true;
|
||||||
input-focus-timer.restart();
|
input-focus-timer.restart();
|
||||||
root.keyboard-focus-changed(!root.remote-update-visible);
|
root.keyboard-focus-changed(!root.modal-visible);
|
||||||
}
|
}
|
||||||
input-focus-timer := Timer {
|
input-focus-timer := Timer {
|
||||||
interval: 1ms;
|
interval: 1ms;
|
||||||
@@ -515,9 +530,9 @@ export component StreamWindow inherits Window {
|
|||||||
if logo-button.has-hover { return logo-button.tooltip-info; }
|
if logo-button.has-hover { return logo-button.tooltip-info; }
|
||||||
if root.control-expanded && root.control-content-visible {
|
if root.control-expanded && root.control-content-visible {
|
||||||
if display-button.has-hover { return display-button.tooltip-info; }
|
if display-button.has-hover { return display-button.tooltip-info; }
|
||||||
if shortcut-button.has-hover { return shortcut-button.tooltip-info; }
|
if root.secure-attention-supported && shortcut-button.has-hover { return shortcut-button.tooltip-info; }
|
||||||
if mouse-button.has-hover { return mouse-button.tooltip-info; }
|
if mouse-button.has-hover { return mouse-button.tooltip-info; }
|
||||||
if privacy-button.has-hover { return privacy-button.tooltip-info; }
|
if root.privacy-visible && privacy-button.has-hover { return privacy-button.tooltip-info; }
|
||||||
if video-button.has-hover { return video-button.tooltip-info; }
|
if video-button.has-hover { return video-button.tooltip-info; }
|
||||||
if audio-button.has-hover { return audio-button.tooltip-info; }
|
if audio-button.has-hover { return audio-button.tooltip-info; }
|
||||||
if file-button.has-hover { return file-button.tooltip-info; }
|
if file-button.has-hover { return file-button.tooltip-info; }
|
||||||
@@ -613,7 +628,7 @@ export component StreamWindow inherits Window {
|
|||||||
root.set-remote-button(button, false);
|
root.set-remote-button(button, false);
|
||||||
root.pointer-input(button, kind, x, y);
|
root.pointer-input(button, kind, x, y);
|
||||||
}
|
}
|
||||||
} else if root.window-active && root.mouse-control-enabled && !root.remote-update-visible {
|
} else if root.window-active && root.mouse-control-enabled && !root.modal-visible {
|
||||||
let remote-target = root.remote-pointer-active
|
let remote-target = root.remote-pointer-active
|
||||||
|| (!root.control-menu-open && !root.is-local-control-area(x, y));
|
|| (!root.control-menu-open && !root.is-local-control-area(x, y));
|
||||||
if kind == PointerEventKind.down && remote-target
|
if kind == PointerEventKind.down && remote-target
|
||||||
@@ -649,7 +664,7 @@ export component StreamWindow inherits Window {
|
|||||||
// so a captured drag can never also toggle the toolbar.
|
// so a captured drag can never also toggle the toolbar.
|
||||||
let toggle-expanded = kind == PointerEventKind.up && root.control-drag-pending
|
let toggle-expanded = kind == PointerEventKind.up && root.control-drag-pending
|
||||||
&& !root.control-dragging
|
&& !root.control-dragging
|
||||||
&& root.window-active && !root.remote-update-visible
|
&& root.window-active && !root.modal-visible
|
||||||
&& x >= control.x + logo-button.x && x < control.x + logo-button.x + logo-button.width
|
&& x >= control.x + logo-button.x && x < control.x + logo-button.x + logo-button.width
|
||||||
&& y >= control.y + logo-button.y && y < control.y + logo-button.y + logo-button.height;
|
&& y >= control.y + logo-button.y && y < control.y + logo-button.y + logo-button.height;
|
||||||
root.control-drag-pending = false;
|
root.control-drag-pending = false;
|
||||||
@@ -698,7 +713,7 @@ export component StreamWindow inherits Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is-local-control-area(pointer-x, pointer-y) => {
|
is-local-control-area(pointer-x, pointer-y) => {
|
||||||
if root.remote-update-visible { return true; }
|
if root.modal-visible { return true; }
|
||||||
let over-control = pointer-x >= root.control-surface-x
|
let over-control = pointer-x >= root.control-surface-x
|
||||||
&& pointer-x <= root.control-surface-x + root.control-surface-width
|
&& pointer-x <= root.control-surface-x + root.control-surface-width
|
||||||
&& pointer-y >= control.y
|
&& pointer-y >= control.y
|
||||||
@@ -713,7 +728,7 @@ export component StreamWindow inherits Window {
|
|||||||
&& pointer-y >= root.display-menu-y
|
&& pointer-y >= root.display-menu-y
|
||||||
&& pointer-y <= root.display-menu-y + root.display-menu-height;
|
&& pointer-y <= root.display-menu-y + root.display-menu-height;
|
||||||
let shortcut-menu-x = control.x + shortcut-button.x;
|
let shortcut-menu-x = control.x + shortcut-button.x;
|
||||||
let over-shortcut-menu = root.shortcut-menu-open
|
let over-shortcut-menu = root.secure-attention-supported && root.shortcut-menu-open
|
||||||
&& pointer-x >= shortcut-menu-x
|
&& pointer-x >= shortcut-menu-x
|
||||||
&& pointer-x <= shortcut-menu-x + 180px
|
&& pointer-x <= shortcut-menu-x + 180px
|
||||||
&& pointer-y >= root.shortcut-menu-y
|
&& pointer-y >= root.shortcut-menu-y
|
||||||
@@ -1011,11 +1026,11 @@ export component StreamWindow inherits Window {
|
|||||||
|
|
||||||
drop-zone := DropArea {
|
drop-zone := DropArea {
|
||||||
can-drop(event) => {
|
can-drop(event) => {
|
||||||
return !root.remote-update-visible && root.can-drop-file(event.data)
|
return !root.modal-visible && root.can-drop-file(event.data)
|
||||||
? DragAction.copy : DragAction.none;
|
? DragAction.copy : DragAction.none;
|
||||||
}
|
}
|
||||||
dropped(event) => {
|
dropped(event) => {
|
||||||
if root.remote-update-visible { return DragAction.none; }
|
if root.modal-visible { return DragAction.none; }
|
||||||
root.file-dropped(event.data);
|
root.file-dropped(event.data);
|
||||||
return DragAction.copy;
|
return DragAction.copy;
|
||||||
}
|
}
|
||||||
@@ -1026,16 +1041,16 @@ export component StreamWindow inherits Window {
|
|||||||
focus-gained => { root.keyboard-focus-changed(true); }
|
focus-gained => { root.keyboard-focus-changed(true); }
|
||||||
focus-lost => { root.keyboard-focus-changed(false); }
|
focus-lost => { root.keyboard-focus-changed(false); }
|
||||||
key-pressed(event) => {
|
key-pressed(event) => {
|
||||||
if !root.remote-update-visible {
|
if !root.modal-visible {
|
||||||
root.key-input(event.text, true, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta);
|
root.key-input(event.text, true, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta);
|
||||||
}
|
}
|
||||||
accept
|
accept
|
||||||
}
|
}
|
||||||
key-released(event) => {
|
key-released(event) => {
|
||||||
if root.remote-update-visible {
|
if root.modal-visible {
|
||||||
// Dismiss on release so the same key cannot reach the peer.
|
// Dismiss on release so the same key cannot reach the peer.
|
||||||
if event.text == Key.Escape || event.text == Key.Return {
|
if event.text == Key.Escape || event.text == Key.Return {
|
||||||
root.dismiss-remote-update();
|
if root.privacy-unlock-visible { root.dismiss-privacy-unlock(); } else { root.dismiss-remote-update(); }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
root.key-input(event.text, false, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta);
|
root.key-input(event.text, false, event.modifiers.control, event.modifiers.alt, event.modifiers.shift, event.modifiers.meta);
|
||||||
@@ -1060,7 +1075,8 @@ export component StreamWindow inherits Window {
|
|||||||
// The logo also retains a stable owner if another button is used.
|
// The logo also retains a stable owner if another button is used.
|
||||||
if root.remote-pointer-active || root.control-drag-pending: TouchArea {
|
if root.remote-pointer-active || root.control-drag-pending: TouchArea {
|
||||||
z: 15;
|
z: 15;
|
||||||
mouse-cursor: root.control-drag-pending ? MouseCursor.grabbing
|
mouse-cursor: root.control-drag-pending
|
||||||
|
? (root.control-hand-cursor-supported ? MouseCursor.pointer : MouseCursor.default)
|
||||||
: root.remote-cursor-active ? RemoteCursor.shape(root.remote-cursor-shape)
|
: root.remote-cursor-active ? RemoteCursor.shape(root.remote-cursor-shape)
|
||||||
: MouseCursor.default;
|
: MouseCursor.default;
|
||||||
pointer-event(event) => {
|
pointer-event(event) => {
|
||||||
@@ -1083,7 +1099,7 @@ export component StreamWindow inherits Window {
|
|||||||
control := Rectangle {
|
control := Rectangle {
|
||||||
x: root.control-dragging ? root.control-drag-x : root.control-docked-left ? 0px : parent.width - self.width;
|
x: root.control-dragging ? root.control-drag-x : root.control-docked-left ? 0px : parent.width - self.width;
|
||||||
y: min(max(0px, root.control-pos-y), parent.height - self.height);
|
y: min(max(0px, root.control-pos-y), parent.height - self.height);
|
||||||
width: root.control-visual-expanded ? min(80px + 9 * root.control-button-step + root.control-disconnect-gap, parent.width) : root.control-toolbar-height;
|
width: root.control-visual-expanded ? min(80px + 7 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width + root.control-disconnect-gap, parent.width) : root.control-toolbar-height;
|
||||||
height: root.control-toolbar-height;
|
height: root.control-toolbar-height;
|
||||||
z: 10;
|
z: 10;
|
||||||
|
|
||||||
@@ -1128,8 +1144,8 @@ export component StreamWindow inherits Window {
|
|||||||
}
|
}
|
||||||
shortcut-button := ControlBarButton {
|
shortcut-button := ControlBarButton {
|
||||||
reveal-progress: root.control-content-progress;
|
reveal-progress: root.control-content-progress;
|
||||||
visible: root.control-content-visible;
|
visible: root.control-content-visible && root.secure-attention-supported;
|
||||||
action-enabled: root.control-expanded;
|
action-enabled: root.control-expanded && root.secure-attention-supported;
|
||||||
x: root.control-buttons-x + root.control-button-step;
|
x: root.control-buttons-x + root.control-button-step;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
icon: FontAwesomeIcons.keyboard; tooltip: StreamStrings.send-shortcut;
|
icon: FontAwesomeIcons.keyboard; tooltip: StreamStrings.send-shortcut;
|
||||||
@@ -1147,7 +1163,7 @@ export component StreamWindow inherits Window {
|
|||||||
reveal-progress: root.control-content-progress;
|
reveal-progress: root.control-content-progress;
|
||||||
visible: root.control-content-visible;
|
visible: root.control-content-visible;
|
||||||
action-enabled: root.control-expanded;
|
action-enabled: root.control-expanded;
|
||||||
x: root.control-buttons-x + 2 * root.control-button-step;
|
x: root.control-buttons-x + root.control-button-step + root.control-shortcut-width;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
icon: FontAwesomeIcons.computer-mouse; slashed: !root.mouse-control-enabled;
|
icon: FontAwesomeIcons.computer-mouse; slashed: !root.mouse-control-enabled;
|
||||||
tooltip: root.mouse-control-enabled ? StreamStrings.release-mouse : StreamStrings.control-mouse;
|
tooltip: root.mouse-control-enabled ? StreamStrings.release-mouse : StreamStrings.control-mouse;
|
||||||
@@ -1157,7 +1173,7 @@ export component StreamWindow inherits Window {
|
|||||||
reveal-progress: root.control-content-progress;
|
reveal-progress: root.control-content-progress;
|
||||||
visible: root.control-content-visible;
|
visible: root.control-content-visible;
|
||||||
action-enabled: root.control-expanded;
|
action-enabled: root.control-expanded;
|
||||||
x: root.control-buttons-x + 4 * root.control-button-step;
|
x: root.control-buttons-x + 2 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
icon: FontAwesomeIcons.volume-high; slashed: !root.audio-enabled;
|
icon: FontAwesomeIcons.volume-high; slashed: !root.audio-enabled;
|
||||||
tooltip: root.audio-enabled ? StreamStrings.mute : StreamStrings.audio;
|
tooltip: root.audio-enabled ? StreamStrings.mute : StreamStrings.audio;
|
||||||
@@ -1167,7 +1183,7 @@ export component StreamWindow inherits Window {
|
|||||||
reveal-progress: root.control-content-progress;
|
reveal-progress: root.control-content-progress;
|
||||||
visible: root.control-content-visible;
|
visible: root.control-content-visible;
|
||||||
action-enabled: root.control-expanded;
|
action-enabled: root.control-expanded;
|
||||||
x: root.control-buttons-x + 5 * root.control-button-step;
|
x: root.control-buttons-x + 3 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
icon: FontAwesomeIcons.folder-open; tooltip: StreamStrings.select-file;
|
icon: FontAwesomeIcons.folder-open; tooltip: StreamStrings.select-file;
|
||||||
clicked => { root.close-control-menus(); root.select-file(); }
|
clicked => { root.close-control-menus(); root.select-file(); }
|
||||||
@@ -1176,7 +1192,7 @@ export component StreamWindow inherits Window {
|
|||||||
reveal-progress: root.control-content-progress;
|
reveal-progress: root.control-content-progress;
|
||||||
visible: root.control-content-visible;
|
visible: root.control-content-visible;
|
||||||
action-enabled: root.control-expanded;
|
action-enabled: root.control-expanded;
|
||||||
x: root.control-buttons-x + 6 * root.control-button-step;
|
x: root.control-buttons-x + 4 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
icon: "\u{f1de}"; selected: root.video-menu-open;
|
icon: "\u{f1de}"; selected: root.video-menu-open;
|
||||||
tooltip: StreamStrings.video-settings;
|
tooltip: StreamStrings.video-settings;
|
||||||
@@ -1191,7 +1207,7 @@ export component StreamWindow inherits Window {
|
|||||||
reveal-progress: root.control-content-progress;
|
reveal-progress: root.control-content-progress;
|
||||||
visible: root.control-content-visible;
|
visible: root.control-content-visible;
|
||||||
action-enabled: root.control-expanded;
|
action-enabled: root.control-expanded;
|
||||||
x: root.control-buttons-x + 7 * root.control-button-step;
|
x: root.control-buttons-x + 5 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
icon: FontAwesomeIcons.signal; selected: root.stats-visible;
|
icon: FontAwesomeIcons.signal; selected: root.stats-visible;
|
||||||
tooltip: root.stats-visible ? StreamStrings.hide-stats : StreamStrings.show-stats;
|
tooltip: root.stats-visible ? StreamStrings.hide-stats : StreamStrings.show-stats;
|
||||||
@@ -1201,7 +1217,7 @@ export component StreamWindow inherits Window {
|
|||||||
reveal-progress: root.control-content-progress;
|
reveal-progress: root.control-content-progress;
|
||||||
visible: root.control-content-visible;
|
visible: root.control-content-visible;
|
||||||
action-enabled: root.control-expanded;
|
action-enabled: root.control-expanded;
|
||||||
x: root.control-buttons-x + 8 * root.control-button-step;
|
x: root.control-buttons-x + 6 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
icon: root.fullscreen-enabled ? FontAwesomeIcons.compress : FontAwesomeIcons.expand;
|
icon: root.fullscreen-enabled ? FontAwesomeIcons.compress : FontAwesomeIcons.expand;
|
||||||
tooltip: root.fullscreen-enabled ? StreamStrings.exit-fullscreen : StreamStrings.fullscreen;
|
tooltip: root.fullscreen-enabled ? StreamStrings.exit-fullscreen : StreamStrings.fullscreen;
|
||||||
@@ -1217,7 +1233,7 @@ export component StreamWindow inherits Window {
|
|||||||
reveal-progress: root.control-content-progress;
|
reveal-progress: root.control-content-progress;
|
||||||
visible: root.control-content-visible;
|
visible: root.control-content-visible;
|
||||||
action-enabled: root.control-expanded;
|
action-enabled: root.control-expanded;
|
||||||
x: root.control-buttons-x + 9 * root.control-button-step + root.control-disconnect-gap;
|
x: root.control-buttons-x + 7 * root.control-button-step + root.control-shortcut-width + root.control-privacy-width + root.control-disconnect-gap;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
icon: FontAwesomeIcons.xmark; tooltip: StreamStrings.disconnect;
|
icon: FontAwesomeIcons.xmark; tooltip: StreamStrings.disconnect;
|
||||||
danger: true;
|
danger: true;
|
||||||
@@ -1236,6 +1252,7 @@ export component StreamWindow inherits Window {
|
|||||||
width: root.control-toolbar-height;
|
width: root.control-toolbar-height;
|
||||||
height: root.control-toolbar-height;
|
height: root.control-toolbar-height;
|
||||||
show-logo: true;
|
show-logo: true;
|
||||||
|
hand-cursor-supported: root.control-hand-cursor-supported;
|
||||||
logo-pressed: root.control-drag-pending;
|
logo-pressed: root.control-drag-pending;
|
||||||
logo-dragging: self.logo-pressed && root.control-dragging;
|
logo-dragging: self.logo-pressed && root.control-dragging;
|
||||||
tooltip: root.control-expanded ? StreamStrings.collapse-control : StreamStrings.expand-control;
|
tooltip: root.control-expanded ? StreamStrings.collapse-control : StreamStrings.expand-control;
|
||||||
@@ -1252,13 +1269,13 @@ export component StreamWindow inherits Window {
|
|||||||
|
|
||||||
privacy-button := ControlBarButton {
|
privacy-button := ControlBarButton {
|
||||||
reveal-progress: root.control-content-progress;
|
reveal-progress: root.control-content-progress;
|
||||||
visible: root.control-content-visible;
|
visible: root.control-content-visible && root.privacy-visible;
|
||||||
x: root.control-buttons-x + 3 * root.control-button-step;
|
x: root.control-buttons-x + 2 * root.control-button-step + root.control-shortcut-width;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
icon: FontAwesomeIcons.user-secret;
|
icon: FontAwesomeIcons.user-secret;
|
||||||
slashed: root.privacy-off && !root.privacy-unsupported;
|
slashed: root.privacy-off && !root.privacy-unsupported;
|
||||||
muted: root.privacy-unsupported;
|
muted: root.privacy-unsupported;
|
||||||
action-enabled: root.control-expanded && root.privacy-can-toggle;
|
action-enabled: root.control-expanded && root.privacy-visible && root.privacy-can-toggle;
|
||||||
tooltip: root.privacy-status-text
|
tooltip: root.privacy-status-text
|
||||||
+ (root.privacy-can-toggle ? "\n" + (root.privacy-active
|
+ (root.privacy-can-toggle ? "\n" + (root.privacy-active
|
||||||
? StreamStrings.privacy-disable : StreamStrings.privacy-enable) : "");
|
? StreamStrings.privacy-disable : StreamStrings.privacy-enable) : "");
|
||||||
@@ -1454,13 +1471,12 @@ export component StreamWindow inherits Window {
|
|||||||
color: #667085; font-size: ImGuiFontStyle.small;
|
color: #667085; font-size: ImGuiFontStyle.small;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
Text {
|
ScrollingText {
|
||||||
x: codec-label-width-probe.preferred-width + 8px; y: 0px;
|
x: codec-label-width-probe.preferred-width + 8px; y: 0px;
|
||||||
width: parent.width - self.x; height: parent.height;
|
width: parent.width - self.x; height: parent.height;
|
||||||
text: codec.value; color: #344054;
|
text: codec.value; text-color: #344054;
|
||||||
font-size: ImGuiFontStyle.body; font-weight: 500;
|
font-size: ImGuiFontStyle.body; font-weight: 500;
|
||||||
horizontal-alignment: right;
|
align-right: true;
|
||||||
vertical-alignment: center; overflow: elide;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1696,7 +1712,7 @@ export component StreamWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.shortcut-menu-open: StreamControlSurface {
|
if root.secure-attention-supported && root.shortcut-menu-open: StreamControlSurface {
|
||||||
x: control.x + shortcut-button.x; y: root.shortcut-menu-y;
|
x: control.x + shortcut-button.x; y: root.shortcut-menu-y;
|
||||||
width: 180px; height: root.shortcut-menu-height; z: 11;
|
width: 180px; height: root.shortcut-menu-height; z: 11;
|
||||||
TouchArea { mouse-cursor: default; scroll-event(event) => { accept } }
|
TouchArea { mouse-cursor: default; scroll-event(event) => { accept } }
|
||||||
@@ -1740,7 +1756,7 @@ export component StreamWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.remote-update-visible: Rectangle {
|
if root.modal-visible: Rectangle {
|
||||||
width: parent.width; height: parent.height;
|
width: parent.width; height: parent.height;
|
||||||
background: #0c162c52;
|
background: #0c162c52;
|
||||||
z: 13;
|
z: 13;
|
||||||
@@ -1749,8 +1765,43 @@ export component StreamWindow inherits Window {
|
|||||||
scroll-event(event) => { accept }
|
scroll-event(event) => { accept }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if root.privacy-unlock-visible: DialogSurface {
|
||||||
|
x: (parent.width - self.width) / 2;
|
||||||
|
y: max(16px, (parent.height - self.height) / 2);
|
||||||
|
width: min(460px, parent.width - 32px);
|
||||||
|
height: unlock-message.y + unlock-message.height + 84px;
|
||||||
|
outline-color: UiSurface.edge;
|
||||||
|
border-radius: UiRadius.surface;
|
||||||
|
z: 14;
|
||||||
|
TouchArea { scroll-event(event) => { accept } }
|
||||||
|
unlock-title := Text {
|
||||||
|
x: 24px; y: 24px; width: parent.width - 48px;
|
||||||
|
height: max(28px, self.preferred-height);
|
||||||
|
text: StreamStrings.privacy-unlocked-title;
|
||||||
|
color: #1b2433; font-size: 20px; font-weight: 600; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
unlock-message := Text {
|
||||||
|
x: 24px; y: unlock-title.y + unlock-title.height + 8px;
|
||||||
|
width: parent.width - 48px; height: max(22px, self.preferred-height);
|
||||||
|
text: StreamStrings.privacy-unlocked-message;
|
||||||
|
color: #667085; font-size: 15px; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
SecondaryButton {
|
||||||
|
x: parent.width - 244px; y: parent.height - 60px;
|
||||||
|
width: 104px; height: 36px;
|
||||||
|
text: StreamStrings.privacy-unlock-confirm;
|
||||||
|
clicked => { root.dismiss-privacy-unlock(); }
|
||||||
|
}
|
||||||
|
PrimaryButton {
|
||||||
|
x: parent.width - 128px; y: parent.height - 60px;
|
||||||
|
width: 104px; height: 36px;
|
||||||
|
text: StreamStrings.privacy-unlock-lock;
|
||||||
|
enabled: root.privacy-can-lock;
|
||||||
|
clicked => { root.lock-privacy(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
remote-update-banner := DialogSurface {
|
remote-update-banner := DialogSurface {
|
||||||
visible: root.remote-update-visible;
|
visible: root.remote-update-visible && !root.privacy-unlock-visible;
|
||||||
x: (parent.width - self.width) / 2;
|
x: (parent.width - self.width) / 2;
|
||||||
y: min(max(64px, parent.height * 0.18), max(16px, parent.height - self.height - 16px));
|
y: min(max(64px, parent.height * 0.18), max(16px, parent.height - self.height - 16px));
|
||||||
width: min(400px, parent.width - 48px);
|
width: min(400px, parent.width - 48px);
|
||||||
|
|||||||
@@ -40,7 +40,21 @@ inline int ApplyMainWindowStrings(
|
|||||||
int language_index) {
|
int language_index) {
|
||||||
const int language =
|
const int language =
|
||||||
localization::detail::ClampLanguageIndex(language_index);
|
localization::detail::ClampLanguageIndex(language_index);
|
||||||
window->global<ui::GalleryStrings>().set_language(language);
|
auto& gallery = window->global<ui::GalleryStrings>();
|
||||||
|
gallery.set_language(language);
|
||||||
|
const auto& retry = localization::Literal("请在 {0} 秒后重试", language);
|
||||||
|
const auto split = retry.find("{0}");
|
||||||
|
gallery.set_retry_prefix(Text(retry.substr(0, split)));
|
||||||
|
gallery.set_retry_suffix(Text(split == std::string::npos ? "" : retry.substr(split + 3)));
|
||||||
|
std::vector<slint::SharedString> literals;
|
||||||
|
for (const auto* key : localization::detail::kSlintLiterals)
|
||||||
|
literals.push_back(Text(localization::Literal(key, language)));
|
||||||
|
gallery.set_texts(std::make_shared<slint::VectorModel<slint::SharedString>>(literals));
|
||||||
|
std::vector<slint::SharedString> languages;
|
||||||
|
for (const auto& option : localization::GetSupportedLanguages())
|
||||||
|
languages.push_back(Text(option.display_name));
|
||||||
|
languages.push_back(Text(localization::Literal("跟随系统", language)));
|
||||||
|
window->set_language_options(std::make_shared<slint::VectorModel<slint::SharedString>>(languages));
|
||||||
auto& strings = window->global<ui::UiStrings>();
|
auto& strings = window->global<ui::UiStrings>();
|
||||||
strings.set_window_minimize(Text(localization::window_minimize[language]));
|
strings.set_window_minimize(Text(localization::window_minimize[language]));
|
||||||
strings.set_window_close(Text(localization::window_close[language]));
|
strings.set_window_close(Text(localization::window_close[language]));
|
||||||
@@ -105,6 +119,7 @@ inline int ApplyMainWindowStrings(
|
|||||||
strings.set_daemon(Text(localization::enable_daemon[language]));
|
strings.set_daemon(Text(localization::enable_daemon[language]));
|
||||||
strings.set_save_remote_previews(Text(localization::save_remote_previews[language]));
|
strings.set_save_remote_previews(Text(localization::save_remote_previews[language]));
|
||||||
strings.set_unknown_platform(Text(localization::unknown_platform[language]));
|
strings.set_unknown_platform(Text(localization::unknown_platform[language]));
|
||||||
|
strings.set_privacy_local_unlock(Text(localization::privacy_local_unlock[language]));
|
||||||
strings.set_privacy_on_connect(Text(localization::privacy_on_connect[language]));
|
strings.set_privacy_on_connect(Text(localization::privacy_on_connect[language]));
|
||||||
strings.set_lock_on_disconnect(Text(localization::lock_on_disconnect[language]));
|
strings.set_lock_on_disconnect(Text(localization::lock_on_disconnect[language]));
|
||||||
strings.set_file_save_path(
|
strings.set_file_save_path(
|
||||||
@@ -206,6 +221,10 @@ inline int ApplyStreamWindowStrings(
|
|||||||
strings.set_remote_update_dismiss(Text(localization::remote_update_dismiss[language]));
|
strings.set_remote_update_dismiss(Text(localization::remote_update_dismiss[language]));
|
||||||
strings.set_select_display(Text(localization::select_display[language]));
|
strings.set_select_display(Text(localization::select_display[language]));
|
||||||
strings.set_send_shortcut(Text(localization::send_shortcut[language]));
|
strings.set_send_shortcut(Text(localization::send_shortcut[language]));
|
||||||
|
strings.set_privacy_unlocked_title(Text(localization::privacy_unlocked_title[language]));
|
||||||
|
strings.set_privacy_unlocked_message(Text(localization::privacy_unlocked_message[language]));
|
||||||
|
strings.set_privacy_unlock_confirm(Text(localization::privacy_unlock_confirm[language]));
|
||||||
|
strings.set_privacy_unlock_lock(Text(localization::privacy_unlock_lock[language]));
|
||||||
strings.set_privacy_enable(Text(localization::privacy_enable[language]));
|
strings.set_privacy_enable(Text(localization::privacy_enable[language]));
|
||||||
strings.set_privacy_disable(Text(localization::privacy_disable[language]));
|
strings.set_privacy_disable(Text(localization::privacy_disable[language]));
|
||||||
strings.set_control_mouse(Text(localization::control_mouse[language]));
|
strings.set_control_mouse(Text(localization::control_mouse[language]));
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ void GuiRuntime::ResetRemoteServiceStatus(RemoteSession& props) {
|
|||||||
std::lock_guard lock(props.privacy_status_mutex_);
|
std::lock_guard lock(props.privacy_status_mutex_);
|
||||||
props.privacy_status_ = {};
|
props.privacy_status_ = {};
|
||||||
props.privacy_status_received_ = false;
|
props.privacy_status_received_ = false;
|
||||||
|
props.privacy_desktop_available_ = true;
|
||||||
props.privacy_command_pending_ = false;
|
props.privacy_command_pending_ = false;
|
||||||
props.privacy_status_tick_ = 0;
|
props.privacy_status_tick_ = 0;
|
||||||
}
|
}
|
||||||
@@ -17,6 +18,12 @@ void GuiRuntime::ResetRemoteServiceStatus(RemoteSession& props) {
|
|||||||
|
|
||||||
void GuiRuntime::ApplyRemoteServiceStatus(RemoteSession& props,
|
void GuiRuntime::ApplyRemoteServiceStatus(RemoteSession& props,
|
||||||
const ServiceStatus& status) {
|
const ServiceStatus& status) {
|
||||||
|
{
|
||||||
|
std::lock_guard lock(props.privacy_status_mutex_);
|
||||||
|
const std::string stage = status.interactive_stage;
|
||||||
|
props.privacy_desktop_available_ = stage != "lock-screen" &&
|
||||||
|
stage != "credential-ui" && stage != "secure-desktop";
|
||||||
|
}
|
||||||
props.remote_service_status_received_ = true;
|
props.remote_service_status_received_ = true;
|
||||||
props.remote_service_available_ = status.available;
|
props.remote_service_available_ = status.available;
|
||||||
props.remote_interactive_stage_ = status.interactive_stage;
|
props.remote_interactive_stage_ = status.interactive_stage;
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ std::optional<bool> Boolean(const std::string& value) {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
const char* OnOff(bool value) { return value ? "on" : "off"; }
|
const char* OnOff(bool value) { return value ? "on" : "off"; }
|
||||||
const char* languages[] = {"zh-CN", "en-US", "ru-RU"};
|
const char* languages[] = {"zh-CN", "en-US", "ru-RU", "zh-Hant", "ja", "ko", "de", "fr", "es", "pt-BR", "system"};
|
||||||
const char* turn_modes[] = {"off", "auto", "udp", "tcp"};
|
const char* turn_modes[] = {"off", "auto", "udp", "tcp"};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::vector<ConsoleSetting> GetConsoleSettings(const ConfigCenter& config) {
|
std::vector<ConsoleSetting> GetConsoleSettings(const ConfigCenter& config) {
|
||||||
const auto language = std::clamp(static_cast<int>(config.GetLanguage()), 0, 2);
|
const auto language = std::clamp(static_cast<int>(config.GetLanguage()), 0, 10);
|
||||||
const auto turn = std::clamp(static_cast<int>(config.GetTurnMode()), 0, 3);
|
const auto turn = std::clamp(static_cast<int>(config.GetTurnMode()), 0, 3);
|
||||||
return {
|
return {
|
||||||
{"language", "console_setting_language", languages[language], "zh-CN / en-US / ru-RU"},
|
{"language", "console_setting_language", languages[language], "system / zh-CN / en-US / ru-RU / zh-Hant / ja / ko / de / fr / es / pt-BR"},
|
||||||
{"codec", "console_setting_codec", config.GetVideoEncodeFormat() == ConfigCenter::VIDEO_ENCODE_FORMAT::AV1 ? "av1" : "h264", "h264 / av1"},
|
{"codec", "console_setting_codec", config.GetVideoEncodeFormat() == ConfigCenter::VIDEO_ENCODE_FORMAT::AV1 ? "av1" : "h264", "h264 / av1"},
|
||||||
{"turn", "console_setting_turn", turn_modes[turn], "off / auto / udp / tcp"},
|
{"turn", "console_setting_turn", turn_modes[turn], "off / auto / udp / tcp"},
|
||||||
{"self_hosted", "console_setting_self_hosted", OnOff(config.IsSelfHosted()), "on / off"},
|
{"self_hosted", "console_setting_self_hosted", OnOff(config.IsSelfHosted()), "on / off"},
|
||||||
@@ -70,6 +70,10 @@ SettingResult ApplyConsoleSetting(ConfigCenter& config, const std::string& key,
|
|||||||
int language = value == "zh-cn" || value == "zh" || value == "0" ? 0
|
int language = value == "zh-cn" || value == "zh" || value == "0" ? 0
|
||||||
: value == "en-us" || value == "en" || value == "1" ? 1
|
: value == "en-us" || value == "en" || value == "1" ? 1
|
||||||
: value == "ru-ru" || value == "ru" || value == "2" ? 2 : -1;
|
: value == "ru-ru" || value == "ru" || value == "2" ? 2 : -1;
|
||||||
|
if (language < 0) {
|
||||||
|
for (int i = 0; i <= 10; ++i)
|
||||||
|
if (value == Lower(languages[i]) || value == std::to_string(i)) language = i;
|
||||||
|
}
|
||||||
if (language < 0) return result;
|
if (language < 0) return result;
|
||||||
canonical = languages[language];
|
canonical = languages[language];
|
||||||
save = [&config, language] { return config.SetLanguage(static_cast<ConfigCenter::LANGUAGE>(language)); };
|
save = [&config, language] { return config.SetLanguage(static_cast<ConfigCenter::LANGUAGE>(language)); };
|
||||||
|
|||||||
@@ -63,10 +63,12 @@ class LinuxPrivacyBackend final : public PrivacyBackend {
|
|||||||
: "Checking Linux privacy support"};
|
: "Checking Linux privacy support"};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Enable(bool block_input, const PrivacyScreenText&,
|
bool Enable(bool block_input, const PrivacyScreenText& text,
|
||||||
std::string& error) override {
|
std::string& error) override {
|
||||||
failure_.clear();
|
failure_.clear();
|
||||||
emergency_ = false;
|
emergency_ = false;
|
||||||
|
UpdateText(text);
|
||||||
|
if (!failure_.empty()) { error = failure_; return false; }
|
||||||
if (!Send(block_input ? LinuxPrivacyCommand::enable_blocking
|
if (!Send(block_input ? LinuxPrivacyCommand::enable_blocking
|
||||||
: LinuxPrivacyCommand::enable)) {
|
: LinuxPrivacyCommand::enable)) {
|
||||||
error = failure_;
|
error = failure_;
|
||||||
@@ -80,6 +82,11 @@ class LinuxPrivacyBackend final : public PrivacyBackend {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateText(const PrivacyScreenText& text) override {
|
||||||
|
if (socket_ >= 0 && !Send(text.allow_local_unlock ? LinuxPrivacyCommand::allow_local_unlock
|
||||||
|
: LinuxPrivacyCommand::forbid_local_unlock))
|
||||||
|
failure_ = "Cannot update the local privacy unlock setting";
|
||||||
|
}
|
||||||
void Recover() override {
|
void Recover() override {
|
||||||
ReadReplies();
|
ReadReplies();
|
||||||
if (recovered_) {
|
if (recovered_) {
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ class NativePrivacy {
|
|||||||
"blocking is unavailable");
|
"blocking is unavailable");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetLocalUnlockAllowed(bool allowed) { allow_local_unlock_ = allowed; }
|
||||||
|
|
||||||
void Enable(bool block_input) {
|
void Enable(bool block_input) {
|
||||||
++reply_.generation;
|
++reply_.generation;
|
||||||
reply_.failure[0] = '\0';
|
reply_.failure[0] = '\0';
|
||||||
@@ -570,7 +572,7 @@ class NativePrivacy {
|
|||||||
shift |= symbol == XK_Shift_L || symbol == XK_Shift_R;
|
shift |= symbol == XK_Shift_L || symbol == XK_Shift_R;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (down && ctrl && alt && shift &&
|
if (allow_local_unlock_ && down && ctrl && alt && shift &&
|
||||||
XkbKeycodeToKeysym(display_, keycode, 0, 0) == XK_F12) {
|
XkbKeycodeToKeysym(display_, keycode, 0, 0) == XK_F12) {
|
||||||
reply_.emergency = true;
|
reply_.emergency = true;
|
||||||
Recover();
|
Recover();
|
||||||
@@ -624,6 +626,7 @@ class NativePrivacy {
|
|||||||
std::vector<Window> hidden_roots_;
|
std::vector<Window> hidden_roots_;
|
||||||
std::map<int, std::array<bool, 256>> keys_;
|
std::map<int, std::array<bool, 256>> keys_;
|
||||||
LinuxPrivacyReply reply_;
|
LinuxPrivacyReply reply_;
|
||||||
|
bool allow_local_unlock_ = true;
|
||||||
Clock::time_point checked_{};
|
Clock::time_point checked_{};
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -676,6 +679,12 @@ int RunLinuxPrivacyGuard() {
|
|||||||
case LinuxPrivacyCommand::recover:
|
case LinuxPrivacyCommand::recover:
|
||||||
privacy.Recover();
|
privacy.Recover();
|
||||||
break;
|
break;
|
||||||
|
case LinuxPrivacyCommand::allow_local_unlock:
|
||||||
|
privacy.SetLocalUnlockAllowed(true);
|
||||||
|
break;
|
||||||
|
case LinuxPrivacyCommand::forbid_local_unlock:
|
||||||
|
privacy.SetLocalUnlockAllowed(false);
|
||||||
|
break;
|
||||||
case LinuxPrivacyCommand::heartbeat:
|
case LinuxPrivacyCommand::heartbeat:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ enum class LinuxPrivacyCommand : uint8_t {
|
|||||||
enable_blocking,
|
enable_blocking,
|
||||||
recover,
|
recover,
|
||||||
heartbeat,
|
heartbeat,
|
||||||
|
allow_local_unlock,
|
||||||
|
forbid_local_unlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LinuxPrivacyReply {
|
struct LinuxPrivacyReply {
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ class MacPrivacyBackend final : public PrivacyBackend {
|
|||||||
const auto caps = Query();
|
const auto caps = Query();
|
||||||
if (!caps.overlay) { error = caps.reason; return false; }
|
if (!caps.overlay) { error = caps.reason; return false; }
|
||||||
block_input_ = block_input;
|
block_input_ = block_input;
|
||||||
|
allow_local_unlock_ = text.allow_local_unlock;
|
||||||
emergency_ = false;
|
emergency_ = false;
|
||||||
tap_failed_ = false;
|
tap_failed_ = false;
|
||||||
CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) |
|
CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) |
|
||||||
@@ -239,7 +240,8 @@ class MacPrivacyBackend final : public PrivacyBackend {
|
|||||||
m.frame = screen.frame;
|
m.frame = screen.frame;
|
||||||
NSView* content = CreateMacPrivacyCoverView(
|
NSView* content = CreateMacPrivacyCoverView(
|
||||||
NSMakeRect(0, 0, m.frame.size.width, m.frame.size.height),
|
NSMakeRect(0, 0, m.frame.size.width, m.frame.size.height),
|
||||||
[NSString stringWithUTF8String:text.unlock_hint.c_str()]);
|
[NSString stringWithUTF8String:(text.allow_local_unlock ? text.unlock_hint : text.locked_hint).c_str()],
|
||||||
|
text.allow_local_unlock);
|
||||||
if (!content) {
|
if (!content) {
|
||||||
state->failure = "Could not load the CrossDesk privacy screen logo";
|
state->failure = "Could not load the CrossDesk privacy screen logo";
|
||||||
break;
|
break;
|
||||||
@@ -260,6 +262,17 @@ class MacPrivacyBackend final : public PrivacyBackend {
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
void UpdateText(const PrivacyScreenText& text) override {
|
||||||
|
allow_local_unlock_ = text.allow_local_unlock;
|
||||||
|
auto state = state_;
|
||||||
|
OnMain([state, text] {
|
||||||
|
std::lock_guard lock(state->mutex);
|
||||||
|
for (const auto& monitor : state->monitors)
|
||||||
|
UpdateMacPrivacyCoverView(monitor.cover.contentView,
|
||||||
|
[NSString stringWithUTF8String:(text.allow_local_unlock ? text.unlock_hint : text.locked_hint).c_str()],
|
||||||
|
text.allow_local_unlock);
|
||||||
|
});
|
||||||
|
}
|
||||||
void Recover() override {
|
void Recover() override {
|
||||||
if (tap_) { CGEventTapEnable(tap_, false); CFMachPortInvalidate(tap_); }
|
if (tap_) { CGEventTapEnable(tap_, false); CFMachPortInvalidate(tap_); }
|
||||||
if (tap_source_) {
|
if (tap_source_) {
|
||||||
@@ -359,7 +372,7 @@ class MacPrivacyBackend final : public PrivacyBackend {
|
|||||||
CGEventGetIntegerValueField(event, kCGEventSourceUnixProcessID) == getpid()) return event;
|
CGEventGetIntegerValueField(event, kCGEventSourceUnixProcessID) == getpid()) return event;
|
||||||
const auto flags = CGEventGetFlags(event);
|
const auto flags = CGEventGetFlags(event);
|
||||||
const auto chord = kCGEventFlagMaskControl | kCGEventFlagMaskAlternate | kCGEventFlagMaskShift;
|
const auto chord = kCGEventFlagMaskControl | kCGEventFlagMaskAlternate | kCGEventFlagMaskShift;
|
||||||
if (type == kCGEventKeyDown && (flags & chord) == chord &&
|
if (self->allow_local_unlock_ && type == kCGEventKeyDown && (flags & chord) == chord &&
|
||||||
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode) == 53) {
|
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode) == 53) {
|
||||||
self->emergency_ = true;
|
self->emergency_ = true;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -372,6 +385,7 @@ class MacPrivacyBackend final : public PrivacyBackend {
|
|||||||
CFMachPortRef tap_ = nullptr;
|
CFMachPortRef tap_ = nullptr;
|
||||||
CFRunLoopSourceRef tap_source_ = nullptr;
|
CFRunLoopSourceRef tap_source_ = nullptr;
|
||||||
bool block_input_ = false;
|
bool block_input_ = false;
|
||||||
|
bool allow_local_unlock_ = true;
|
||||||
bool emergency_ = false;
|
bool emergency_ = false;
|
||||||
bool tap_failed_ = false;
|
bool tap_failed_ = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
// Returns nil if the CrossDesk icon resource cannot be loaded.
|
// Returns nil if the CrossDesk icon resource cannot be loaded.
|
||||||
NSView* CreateMacPrivacyCoverView(NSRect frame, NSString* unlock_hint);
|
NSView* CreateMacPrivacyCoverView(NSRect frame, NSString* hint, bool allow_local_unlock = true);
|
||||||
|
void UpdateMacPrivacyCoverView(NSView* view, NSString* hint, bool allow_local_unlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -52,6 +52,7 @@ CGFloat TextHeight(NSAttributedString* text, CGFloat width) {
|
|||||||
@interface CrossDeskPrivacyCoverView : NSView
|
@interface CrossDeskPrivacyCoverView : NSView
|
||||||
@property(nonatomic, strong) NSImage* logo;
|
@property(nonatomic, strong) NSImage* logo;
|
||||||
@property(nonatomic, copy) NSString* unlockHint;
|
@property(nonatomic, copy) NSString* unlockHint;
|
||||||
|
@property(nonatomic) BOOL allowLocalUnlock;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation CrossDeskPrivacyCoverView
|
@implementation CrossDeskPrivacyCoverView
|
||||||
@@ -77,7 +78,7 @@ CGFloat TextHeight(NSAttributedString* text, CGFloat width) {
|
|||||||
const CGFloat logo_size = std::ceil(brand.size.width);
|
const CGFloat logo_size = std::ceil(brand.size.width);
|
||||||
const CGFloat brand_height = TextHeight(brand, logo_size);
|
const CGFloat brand_height = TextHeight(brand, logo_size);
|
||||||
const CGFloat hint_height = TextHeight(hint, text_width);
|
const CGFloat hint_height = TextHeight(hint, text_width);
|
||||||
const CGFloat shortcut_height = TextHeight(shortcut, text_width);
|
const CGFloat shortcut_height = self.allowLocalUnlock ? TextHeight(shortcut, text_width) : 0;
|
||||||
const CGFloat bottom = height - margin;
|
const CGFloat bottom = height - margin;
|
||||||
const CGFloat logo_top = bottom - brand_height - 6 * scale - logo_size;
|
const CGFloat logo_top = bottom - brand_height - 6 * scale - logo_size;
|
||||||
[self.logo drawInRect:NSMakeRect(margin, logo_top, logo_size, logo_size)
|
[self.logo drawInRect:NSMakeRect(margin, logo_top, logo_size, logo_size)
|
||||||
@@ -86,19 +87,26 @@ CGFloat TextHeight(NSAttributedString* text, CGFloat width) {
|
|||||||
[brand drawWithRect:NSMakeRect(margin, bottom - brand_height, logo_size, brand_height)
|
[brand drawWithRect:NSMakeRect(margin, bottom - brand_height, logo_size, brand_height)
|
||||||
options:kTextOptions];
|
options:kTextOptions];
|
||||||
const CGFloat text_left = margin + logo_size + 24 * scale;
|
const CGFloat text_left = margin + logo_size + 24 * scale;
|
||||||
[hint drawWithRect:NSMakeRect(text_left, bottom - shortcut_height - 12 * scale - hint_height,
|
[hint drawWithRect:NSMakeRect(text_left, bottom - shortcut_height - (self.allowLocalUnlock ? 12 * scale : 0) - hint_height,
|
||||||
text_width, hint_height) options:kTextOptions];
|
text_width, hint_height) options:kTextOptions];
|
||||||
[shortcut drawWithRect:NSMakeRect(text_left, bottom - shortcut_height,
|
if (self.allowLocalUnlock) [shortcut drawWithRect:NSMakeRect(text_left, bottom - shortcut_height,
|
||||||
text_width, shortcut_height) options:kTextOptions];
|
text_width, shortcut_height) options:kTextOptions];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
NSView* CreateMacPrivacyCoverView(NSRect frame, NSString* unlock_hint) {
|
void UpdateMacPrivacyCoverView(NSView* view, NSString* hint, bool allow_local_unlock) {
|
||||||
|
auto* cover = static_cast<CrossDeskPrivacyCoverView*>(view);
|
||||||
|
cover.unlockHint = hint;
|
||||||
|
cover.allowLocalUnlock = allow_local_unlock;
|
||||||
|
cover.needsDisplay = YES;
|
||||||
|
}
|
||||||
|
NSView* CreateMacPrivacyCoverView(NSRect frame, NSString* unlock_hint, bool allow_local_unlock) {
|
||||||
NSImage* logo = LoadPrivacyLogo();
|
NSImage* logo = LoadPrivacyLogo();
|
||||||
if (!logo) return nil;
|
if (!logo) return nil;
|
||||||
auto* view = [[CrossDeskPrivacyCoverView alloc] initWithFrame:frame];
|
auto* view = [[CrossDeskPrivacyCoverView alloc] initWithFrame:frame];
|
||||||
view.logo = logo;
|
view.logo = logo;
|
||||||
|
view.allowLocalUnlock = allow_local_unlock;
|
||||||
view.unlockHint = unlock_hint ?: @"To turn off the privacy screen, press";
|
view.unlockHint = unlock_hint ?: @"To turn off the privacy screen, press";
|
||||||
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||||
return view;
|
return view;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "windows_input_injector.h"
|
#include "windows_input_injector.h"
|
||||||
#include "windows_input_marker.h"
|
#include "windows_input_marker.h"
|
||||||
#include "windows_key_metadata.h"
|
#include "windows_key_metadata.h"
|
||||||
|
#include "windows_text_input.h"
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -393,52 +394,17 @@ int PlatformKeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int PlatformKeyboardCapturer::SendTextCommand(const std::string& text) {
|
int PlatformKeyboardCapturer::SendTextCommand(const std::string& text) {
|
||||||
if (text.empty() || text.size() > kMaxTextInputBytes ||
|
|
||||||
text.find('\0') != std::string::npos) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Validate the entire UTF-8 commit before injecting any characters.
|
|
||||||
const int byte_count = static_cast<int>(text.size());
|
|
||||||
const int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
|
||||||
text.data(), byte_count, nullptr, 0);
|
|
||||||
if (length == 0) return -1;
|
|
||||||
std::wstring characters(length, L'\0');
|
|
||||||
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.data(),
|
|
||||||
byte_count, characters.data(), length) != length) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<INPUT> inputs;
|
std::vector<INPUT> inputs;
|
||||||
inputs.reserve(characters.size() * 2);
|
if (!BuildWindowsTextInputs(text, inputs)) return -1;
|
||||||
for (size_t index = 0; index < characters.size(); ++index) {
|
|
||||||
const wchar_t character = characters[index];
|
|
||||||
INPUT input{};
|
|
||||||
input.type = INPUT_KEYBOARD;
|
|
||||||
input.ki.dwExtraInfo = static_cast<ULONG_PTR>(kInjectedKeyboardInputMarker);
|
|
||||||
if (character == L'\r' || character == L'\n' || character == L'\t') {
|
|
||||||
input.ki.wVk = character == L'\t' ? VK_TAB : VK_RETURN;
|
|
||||||
if (character == L'\r' && index + 1 < characters.size() &&
|
|
||||||
characters[index + 1] == L'\n') {
|
|
||||||
++index;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// VK_PACKET carries UTF-16 units, including both halves of emoji.
|
|
||||||
input.ki.wScan = static_cast<WORD>(character);
|
|
||||||
input.ki.dwFlags = KEYEVENTF_UNICODE;
|
|
||||||
}
|
|
||||||
inputs.push_back(input);
|
|
||||||
input.ki.dwFlags |= KEYEVENTF_KEYUP;
|
|
||||||
inputs.push_back(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
// One batch keeps surrogate pairs and key releases together with the text.
|
// One batch keeps surrogate pairs and key releases together with the text.
|
||||||
const UINT count = static_cast<UINT>(inputs.size());
|
const UINT count = static_cast<UINT>(inputs.size());
|
||||||
const UINT sent = SendInputOnUserDesktop(inputs.data(), count);
|
const UINT sent = SendWindowsTextInputs(
|
||||||
|
inputs, [](INPUT* batch, UINT size) {
|
||||||
|
return SendInputOnUserDesktop(batch, size);
|
||||||
|
});
|
||||||
if (sent != count) {
|
if (sent != count) {
|
||||||
const DWORD error = GetLastError();
|
const DWORD error = GetLastError();
|
||||||
if (sent < count && sent % 2 != 0) {
|
|
||||||
SendInputOnUserDesktop(inputs[sent]); // Best-effort release.
|
|
||||||
}
|
|
||||||
LOG_WARN(
|
LOG_WARN(
|
||||||
"SendInput failed for committed text, sent={}, expected={}, err={}",
|
"SendInput failed for committed text, sent={}, expected={}, err={}",
|
||||||
sent, count, error);
|
sent, count, error);
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-10-26
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _WINDOWS_TEXT_INPUT_H_
|
||||||
|
#define _WINDOWS_TEXT_INPUT_H_
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "remote_action.h"
|
||||||
|
#include "windows_input_marker.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
inline bool BuildWindowsTextInputs(const std::string& text,
|
||||||
|
std::vector<INPUT>& inputs) {
|
||||||
|
inputs.clear();
|
||||||
|
if (text.empty() || text.size() > kMaxTextInputBytes ||
|
||||||
|
text.find('\0') != std::string::npos) return false;
|
||||||
|
// Validate the entire commit before injecting any characters.
|
||||||
|
const int bytes = static_cast<int>(text.size());
|
||||||
|
const int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||||
|
text.data(), bytes, nullptr, 0);
|
||||||
|
if (length == 0) return false;
|
||||||
|
std::wstring characters(length, L'\0');
|
||||||
|
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.data(), bytes,
|
||||||
|
characters.data(), length) != length) return false;
|
||||||
|
|
||||||
|
inputs.reserve(characters.size() * 2);
|
||||||
|
for (size_t index = 0; index < characters.size(); ++index) {
|
||||||
|
const wchar_t character = characters[index];
|
||||||
|
INPUT input{};
|
||||||
|
input.type = INPUT_KEYBOARD;
|
||||||
|
input.ki.dwExtraInfo = static_cast<ULONG_PTR>(kInjectedKeyboardInputMarker);
|
||||||
|
if (character == L'\r' || character == L'\n' || character == L'\t') {
|
||||||
|
input.ki.wVk = character == L'\t' ? VK_TAB : VK_RETURN;
|
||||||
|
if (character == L'\r' && index + 1 < characters.size() &&
|
||||||
|
characters[index + 1] == L'\n') ++index;
|
||||||
|
} else {
|
||||||
|
input.ki.wScan = static_cast<WORD>(character);
|
||||||
|
input.ki.dwFlags = KEYEVENTF_UNICODE;
|
||||||
|
}
|
||||||
|
inputs.push_back(input);
|
||||||
|
input.ki.dwFlags |= KEYEVENTF_KEYUP;
|
||||||
|
inputs.push_back(input);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A partial batch must never be replayed. Release its final key if necessary,
|
||||||
|
// retaining the original count and error for the desktop-routing decision.
|
||||||
|
template <typename Sender>
|
||||||
|
UINT SendWindowsTextInputs(std::vector<INPUT>& inputs, Sender send) {
|
||||||
|
const UINT count = static_cast<UINT>(inputs.size());
|
||||||
|
SetLastError(ERROR_SUCCESS);
|
||||||
|
const UINT sent = send(inputs.data(), count);
|
||||||
|
if (sent == count) return sent;
|
||||||
|
const DWORD error = GetLastError();
|
||||||
|
if (sent < count && sent % 2 != 0) send(&inputs[sent], 1);
|
||||||
|
SetLastError(error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE);
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -190,6 +190,10 @@ class Windows {
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
if (message == WM_CLOSE) return 0;
|
if (message == WM_CLOSE) return 0;
|
||||||
|
if (message == kPrivacyTextChanged) {
|
||||||
|
InvalidateRect(hwnd, nullptr, TRUE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if ((message == WM_DISPLAYCHANGE || message == WM_DPICHANGED) && self) {
|
if ((message == WM_DISPLAYCHANGE || message == WM_DPICHANGED) && self) {
|
||||||
self->changed = true;
|
self->changed = true;
|
||||||
InvalidateRect(hwnd, nullptr, TRUE);
|
InvalidateRect(hwnd, nullptr, TRUE);
|
||||||
@@ -200,8 +204,12 @@ class Windows {
|
|||||||
HDC dc = BeginPaint(hwnd, &paint);
|
HDC dc = BeginPaint(hwnd, &paint);
|
||||||
RECT rect{};
|
RECT rect{};
|
||||||
GetClientRect(hwnd, &rect);
|
GetClientRect(hwnd, &rect);
|
||||||
|
const bool allow_unlock = self &&
|
||||||
|
InterlockedCompareExchange(&self->shared_->allow_local_unlock, 0, 0);
|
||||||
if (self && !DrawPrivacyCover(dc, rect, PrivacyWindowDpi(hwnd), module,
|
if (self && !DrawPrivacyCover(dc, rect, PrivacyWindowDpi(hwnd), module,
|
||||||
self->shared_->unlock_hint)) {
|
allow_unlock ? self->shared_->unlock_hint
|
||||||
|
: self->shared_->locked_hint,
|
||||||
|
allow_unlock)) {
|
||||||
self->paint_failed_ = true;
|
self->paint_failed_ = true;
|
||||||
self->Fail(L"Paint privacy logo and unlock instructions");
|
self->Fail(L"Paint privacy logo and unlock instructions");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ bool Select(HDC dc, HGDIOBJ object) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
||||||
const wchar_t* unlock_hint) {
|
const wchar_t* unlock_hint, bool allow_local_unlock) {
|
||||||
if (!FillRect(dc, &rect, static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH))))
|
if (!FillRect(dc, &rect, static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH))))
|
||||||
return false;
|
return false;
|
||||||
const int width = rect.right - rect.left;
|
const int width = rect.right - rect.left;
|
||||||
@@ -68,6 +68,7 @@ bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
|||||||
}};
|
}};
|
||||||
constexpr UINT flags = DT_LEFT | DT_WORDBREAK | DT_NOPREFIX;
|
constexpr UINT flags = DT_LEFT | DT_WORDBREAK | DT_NOPREFIX;
|
||||||
for (size_t i = 0; i < lines.size(); ++i) {
|
for (size_t i = 0; i < lines.size(); ++i) {
|
||||||
|
if (i == 1 && !allow_local_unlock) continue;
|
||||||
auto& line = lines[i];
|
auto& line = lines[i];
|
||||||
resources.fonts[i] =
|
resources.fonts[i] =
|
||||||
CreateFontW(-px(line.size), 0, 0, 0, line.weight, FALSE, FALSE, FALSE,
|
CreateFontW(-px(line.size), 0, 0, 0, line.weight, FALSE, FALSE, FALSE,
|
||||||
@@ -81,7 +82,7 @@ bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
|||||||
}
|
}
|
||||||
const int content_height =
|
const int content_height =
|
||||||
static_cast<int>(lines[0].bounds.bottom + lines[1].bounds.bottom) +
|
static_cast<int>(lines[0].bounds.bottom + lines[1].bounds.bottom) +
|
||||||
px(12);
|
(allow_local_unlock ? px(12) : 0);
|
||||||
// Match the icon width to the name; each side keeps its own height.
|
// Match the icon width to the name; each side keeps its own height.
|
||||||
const int brand_gap = px(6);
|
const int brand_gap = px(6);
|
||||||
const int brand_width = static_cast<int>(lines[kBrandLine].bounds.right);
|
const int brand_width = static_cast<int>(lines[kBrandLine].bounds.right);
|
||||||
@@ -110,6 +111,7 @@ bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
|||||||
DT_CENTER | DT_SINGLELINE | DT_NOPREFIX))
|
DT_CENTER | DT_SINGLELINE | DT_NOPREFIX))
|
||||||
return false;
|
return false;
|
||||||
for (size_t i = 0; i < kInstructionLines; ++i) {
|
for (size_t i = 0; i < kInstructionLines; ++i) {
|
||||||
|
if (i == 1 && !allow_local_unlock) continue;
|
||||||
const auto& line = lines[i];
|
const auto& line = lines[i];
|
||||||
if (!Select(dc, resources.fonts[i])) return false;
|
if (!Select(dc, resources.fonts[i])) return false;
|
||||||
RECT bounds{left, top, left + text_width, top + line.bounds.bottom};
|
RECT bounds{left, top, left + text_width, top + line.bounds.bottom};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace crossdesk {
|
|||||||
|
|
||||||
// Paints the local, opaque cover. The caller owns the HWND and its DPI context.
|
// Paints the local, opaque cover. The caller owns the HWND and its DPI context.
|
||||||
bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
bool DrawPrivacyCover(HDC dc, const RECT& rect, UINT dpi, HINSTANCE module,
|
||||||
const wchar_t* unlock_hint);
|
const wchar_t* unlock_hint, bool allow_local_unlock = true);
|
||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ class WindowsPrivacyBackend final : public PrivacyBackend {
|
|||||||
}
|
}
|
||||||
if (!Initialize(error)) return false;
|
if (!Initialize(error)) return false;
|
||||||
emergency_ = false;
|
emergency_ = false;
|
||||||
|
allow_local_unlock_ = text.allow_local_unlock;
|
||||||
health_error_.clear();
|
health_error_.clear();
|
||||||
if (!RegisterHotKey(sink_, kHotkeyId,
|
if (!RegisterHotKey(sink_, kHotkeyId,
|
||||||
MOD_CONTROL | MOD_ALT | MOD_SHIFT | MOD_NOREPEAT,
|
MOD_CONTROL | MOD_ALT | MOD_SHIFT | MOD_NOREPEAT,
|
||||||
@@ -149,6 +150,10 @@ class WindowsPrivacyBackend final : public PrivacyBackend {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateText(const PrivacyScreenText& text) override {
|
||||||
|
allow_local_unlock_ = text.allow_local_unlock;
|
||||||
|
band_guard_.SetLocalUnlockAllowed(text.allow_local_unlock);
|
||||||
|
}
|
||||||
void Recover() override {
|
void Recover() override {
|
||||||
for (auto& hook : window_events_) {
|
for (auto& hook : window_events_) {
|
||||||
if (UnhookWinEvent(hook))
|
if (UnhookWinEvent(hook))
|
||||||
@@ -475,7 +480,8 @@ class WindowsPrivacyBackend final : public PrivacyBackend {
|
|||||||
auto self = reinterpret_cast<WindowsPrivacyBackend*>(
|
auto self = reinterpret_cast<WindowsPrivacyBackend*>(
|
||||||
GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||||
if (msg == WM_HOTKEY || msg == kEmergency) {
|
if (msg == WM_HOTKEY || msg == kEmergency) {
|
||||||
if (self && hwnd == self->sink_) self->emergency_ = true;
|
if (self && hwnd == self->sink_ && self->allow_local_unlock_)
|
||||||
|
self->emergency_ = true;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (msg == WM_MOUSEACTIVATE) return MA_NOACTIVATE;
|
if (msg == WM_MOUSEACTIVATE) return MA_NOACTIVATE;
|
||||||
@@ -522,6 +528,7 @@ class WindowsPrivacyBackend final : public PrivacyBackend {
|
|||||||
std::unique_ptr<ScopedWindowsPhysicalCoordinates> dpi_scope_;
|
std::unique_ptr<ScopedWindowsPhysicalCoordinates> dpi_scope_;
|
||||||
bool registered_ = false;
|
bool registered_ = false;
|
||||||
bool hotkey_ = false;
|
bool hotkey_ = false;
|
||||||
|
bool allow_local_unlock_ = true;
|
||||||
bool emergency_ = false;
|
bool emergency_ = false;
|
||||||
bool layout_changed_ = false;
|
bool layout_changed_ = false;
|
||||||
bool topology_pending_ = false;
|
bool topology_pending_ = false;
|
||||||
|
|||||||
@@ -153,6 +153,11 @@ bool PrivacyBandGuard::Start(const PrivacyScreenText& text,
|
|||||||
text.unlock_hint.c_str(), -1, shared_->unlock_hint,
|
text.unlock_hint.c_str(), -1, shared_->unlock_hint,
|
||||||
static_cast<int>(std::size(shared_->unlock_hint))))
|
static_cast<int>(std::size(shared_->unlock_hint))))
|
||||||
return Failed("Encode privacy screen text", error);
|
return Failed("Encode privacy screen text", error);
|
||||||
|
if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||||
|
text.locked_hint.c_str(), -1, shared_->locked_hint,
|
||||||
|
static_cast<int>(std::size(shared_->locked_hint))))
|
||||||
|
return Failed("Encode locked privacy screen text", error);
|
||||||
|
shared_->allow_local_unlock = text.allow_local_unlock;
|
||||||
shared_->parent_pid = GetCurrentProcessId();
|
shared_->parent_pid = GetCurrentProcessId();
|
||||||
HANDLE handles[] = {parent.value, stop_, ready_, mapping_};
|
HANDLE handles[] = {parent.value, stop_, ready_, mapping_};
|
||||||
std::wstring bootstrap;
|
std::wstring bootstrap;
|
||||||
@@ -299,6 +304,19 @@ bool PrivacyBandGuard::Windows(std::vector<PrivacyBandWindow>& windows,
|
|||||||
return retry();
|
return retry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrivacyBandGuard::SetLocalUnlockAllowed(bool allowed) {
|
||||||
|
const LONG value = allowed ? 1 : 0;
|
||||||
|
if (!shared_ ||
|
||||||
|
InterlockedExchange(&shared_->allow_local_unlock, value) == value)
|
||||||
|
return;
|
||||||
|
std::vector<PrivacyBandWindow> windows;
|
||||||
|
std::string error;
|
||||||
|
if (Windows(windows, error))
|
||||||
|
for (const auto& window : windows)
|
||||||
|
PostMessageW(reinterpret_cast<HWND>(static_cast<uintptr_t>(window.hwnd)),
|
||||||
|
kPrivacyTextChanged, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
bool PrivacyBandGuard::Stop(std::string& error) {
|
bool PrivacyBandGuard::Stop(std::string& error) {
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
if (process_) {
|
if (process_) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class PrivacyBandGuard {
|
|||||||
~PrivacyBandGuard();
|
~PrivacyBandGuard();
|
||||||
static bool Available(std::string& error);
|
static bool Available(std::string& error);
|
||||||
bool Start(const PrivacyScreenText& text, std::string& error);
|
bool Start(const PrivacyScreenText& text, std::string& error);
|
||||||
|
void SetLocalUnlockAllowed(bool allowed);
|
||||||
bool Stop(std::string& error);
|
bool Stop(std::string& error);
|
||||||
bool Healthy(std::string& error);
|
bool Healthy(std::string& error);
|
||||||
bool Windows(std::vector<PrivacyBandWindow>& windows, std::string& error);
|
bool Windows(std::vector<PrivacyBandWindow>& windows, std::string& error);
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
constexpr uint32_t kPrivacyBandMagic = 0x43534231;
|
constexpr uint32_t kPrivacyBandMagic = 0x43534231;
|
||||||
constexpr uint32_t kPrivacyBandVersion = 3;
|
constexpr uint32_t kPrivacyBandVersion = 4;
|
||||||
|
constexpr UINT kPrivacyTextChanged = WM_APP + 72;
|
||||||
constexpr DWORD kPrivacyWindowBand = 18; // ZBID_ABOVELOCK_UX, current desktop.
|
constexpr DWORD kPrivacyWindowBand = 18; // ZBID_ABOVELOCK_UX, current desktop.
|
||||||
constexpr size_t kPrivacyMaxMonitors = 64;
|
constexpr size_t kPrivacyMaxMonitors = 64;
|
||||||
constexpr wchar_t kPrivacyBandBootstrap[] = L"CROSSDESK_PRIVACY_BAND_BOOTSTRAP";
|
constexpr wchar_t kPrivacyBandBootstrap[] = L"CROSSDESK_PRIVACY_BAND_BOOTSTRAP";
|
||||||
@@ -40,6 +41,8 @@ struct PrivacyBandShared {
|
|||||||
alignas(8) volatile LONG64 heartbeat = 0;
|
alignas(8) volatile LONG64 heartbeat = 0;
|
||||||
wchar_t operation[128]{};
|
wchar_t operation[128]{};
|
||||||
wchar_t unlock_hint[128]{};
|
wchar_t unlock_hint[128]{};
|
||||||
|
wchar_t locked_hint[128]{};
|
||||||
|
volatile LONG allow_local_unlock = 1;
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
PrivacyBandWindow windows[kPrivacyMaxMonitors]{};
|
PrivacyBandWindow windows[kPrivacyMaxMonitors]{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-10-26
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SECURE_TEXT_INPUT_H_
|
||||||
|
#define _SECURE_TEXT_INPUT_H_
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "remote_action.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
inline constexpr char kSecureTextInputCommandPrefix[] = "text:";
|
||||||
|
// JSON can escape each text byte as six ASCII bytes. Leave room for metadata.
|
||||||
|
inline constexpr size_t kSecureInputCommandBufferBytes =
|
||||||
|
6 * kMaxTextInputBytes + 1024;
|
||||||
|
|
||||||
|
struct SecureTextInputRequest {
|
||||||
|
std::string text;
|
||||||
|
std::string stage;
|
||||||
|
std::string desktop;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::string BuildSecureTextInputCommand(
|
||||||
|
const std::string& text, const std::string& stage = {},
|
||||||
|
const std::string& desktop = {}) {
|
||||||
|
return kSecureTextInputCommandPrefix +
|
||||||
|
nlohmann::json{{"text", text}, {"stage", stage},
|
||||||
|
{"desktop", desktop}}.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ParseSecureTextInputCommand(const std::string& command,
|
||||||
|
SecureTextInputRequest& request) {
|
||||||
|
request = {};
|
||||||
|
if (command.size() > kSecureInputCommandBufferBytes ||
|
||||||
|
command.rfind(kSecureTextInputCommandPrefix, 0) != 0) return false;
|
||||||
|
const auto json = nlohmann::json::parse(
|
||||||
|
command.substr(sizeof(kSecureTextInputCommandPrefix) - 1), nullptr, false);
|
||||||
|
if (!json.is_object() || !json.contains("text") ||
|
||||||
|
!json["text"].is_string() || !json.contains("stage") ||
|
||||||
|
!json["stage"].is_string() || !json.contains("desktop") ||
|
||||||
|
!json["desktop"].is_string()) return false;
|
||||||
|
const auto& text = json["text"].get_ref<const std::string&>();
|
||||||
|
const auto& stage = json["stage"].get_ref<const std::string&>();
|
||||||
|
const auto& desktop = json["desktop"].get_ref<const std::string&>();
|
||||||
|
if (text.empty() || text.size() > kMaxTextInputBytes ||
|
||||||
|
text.find('\0') != std::string::npos || stage.size() > 64 ||
|
||||||
|
desktop.size() > 256 || stage.find('\0') != std::string::npos ||
|
||||||
|
desktop.find('\0') != std::string::npos) return false;
|
||||||
|
request = {text, stage, desktop};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "path_manager.h"
|
#include "path_manager.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "session_helper_shared.h"
|
#include "session_helper_shared.h"
|
||||||
|
#include "secure_text_input.h"
|
||||||
#include "unattended_config.h"
|
#include "unattended_config.h"
|
||||||
#include "unattended_policy.h"
|
#include "unattended_policy.h"
|
||||||
#include "rdp_session_handoff.h"
|
#include "rdp_session_handoff.h"
|
||||||
@@ -1421,7 +1422,7 @@ void CrossDeskServiceHost::IpcServerLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char buffer[1024] = {0};
|
char buffer[kSecureInputCommandBufferBytes] = {0};
|
||||||
DWORD bytes_read = 0;
|
DWORD bytes_read = 0;
|
||||||
DWORD transfer_error = ERROR_SUCCESS;
|
DWORD transfer_error = ERROR_SUCCESS;
|
||||||
if (pipe_deadline_detail::Transfer(
|
if (pipe_deadline_detail::Transfer(
|
||||||
@@ -2235,6 +2236,14 @@ void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type,
|
|||||||
std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command,
|
std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command,
|
||||||
DWORD client_session_id,
|
DWORD client_session_id,
|
||||||
HANDLE client_pipe) {
|
HANDLE client_pipe) {
|
||||||
|
// Password text is case/whitespace sensitive. Parse before normalizing the
|
||||||
|
// legacy command verbs, and never accept the caller's desktop selection.
|
||||||
|
if (command.rfind(kSecureTextInputCommandPrefix, 0) == 0) {
|
||||||
|
SecureTextInputRequest request;
|
||||||
|
if (!ParseSecureTextInputCommand(command, request))
|
||||||
|
return BuildErrorJson("invalid_text_input");
|
||||||
|
return SendSecureDesktopTextInput(client_session_id, request.text);
|
||||||
|
}
|
||||||
std::string normalized = ToLower(Trim(command));
|
std::string normalized = ToLower(Trim(command));
|
||||||
if (normalized == kRdpHandoffCommand) {
|
if (normalized == kRdpHandoffCommand) {
|
||||||
bool observed_rdp = false;
|
bool observed_rdp = false;
|
||||||
@@ -2618,6 +2627,16 @@ std::string CrossDeskServiceHost::SendSecureDesktopMouseInput(
|
|||||||
1000);
|
1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string CrossDeskServiceHost::SendSecureDesktopTextInput(
|
||||||
|
DWORD client_session_id, const std::string& text) {
|
||||||
|
SecureInputTarget target;
|
||||||
|
const auto error = ResolveSecureInputTarget(client_session_id, target);
|
||||||
|
if (!error.empty()) return error;
|
||||||
|
return QueryNamedPipeMessage(
|
||||||
|
GetCrossDeskSecureInputHelperPipeName(target.session_id),
|
||||||
|
BuildSecureTextInputCommand(text, target.stage, target.desktop), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
bool InstallCrossDeskService(const std::wstring& binary_path) {
|
bool InstallCrossDeskService(const std::wstring& binary_path) {
|
||||||
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||||
if (manager == nullptr) {
|
if (manager == nullptr) {
|
||||||
@@ -2892,4 +2911,9 @@ std::string SendCrossDeskSecureDesktopMouseInput(int x, int y, int wheel,
|
|||||||
BuildSecureDesktopMouseIpcCommand(x, y, wheel, flag), timeout_ms);
|
BuildSecureDesktopMouseIpcCommand(x, y, wheel, flag), timeout_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string SendCrossDeskSecureDesktopTextInput(const std::string& text,
|
||||||
|
DWORD timeout_ms) {
|
||||||
|
return QueryDesktopInput(BuildSecureTextInputCommand(text), timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ class CrossDeskServiceHost {
|
|||||||
bool extended = false);
|
bool extended = false);
|
||||||
std::string SendSecureDesktopMouseInput(DWORD client_session_id,
|
std::string SendSecureDesktopMouseInput(DWORD client_session_id,
|
||||||
int x, int y, int wheel, int flag);
|
int x, int y, int wheel, int flag);
|
||||||
|
std::string SendSecureDesktopTextInput(DWORD client_session_id,
|
||||||
|
const std::string& text);
|
||||||
|
|
||||||
static void WINAPI ServiceMain(DWORD argc, LPWSTR* argv);
|
static void WINAPI ServiceMain(DWORD argc, LPWSTR* argv);
|
||||||
static BOOL WINAPI ConsoleControlHandler(DWORD control_type);
|
static BOOL WINAPI ConsoleControlHandler(DWORD control_type);
|
||||||
@@ -197,6 +199,8 @@ std::string SendCrossDeskSecureDesktopKeyInput(int key_code, bool is_down,
|
|||||||
std::string SendCrossDeskSecureDesktopMouseInput(int x, int y, int wheel,
|
std::string SendCrossDeskSecureDesktopMouseInput(int x, int y, int wheel,
|
||||||
int flag,
|
int flag,
|
||||||
DWORD timeout_ms = 1000);
|
DWORD timeout_ms = 1000);
|
||||||
|
std::string SendCrossDeskSecureDesktopTextInput(const std::string& text,
|
||||||
|
DWORD timeout_ms = 1000);
|
||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "platform/path_backend.h"
|
#include "platform/path_backend.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "session_helper_shared.h"
|
#include "session_helper_shared.h"
|
||||||
|
#include "secure_text_input.h"
|
||||||
#include "secure_desktop_dxgi_capture.h"
|
#include "secure_desktop_dxgi_capture.h"
|
||||||
#include "secure_desktop_frame_pacer.h"
|
#include "secure_desktop_frame_pacer.h"
|
||||||
#include "secure_desktop_nv12_cache.h"
|
#include "secure_desktop_nv12_cache.h"
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
#include "windows_cursor_state.h"
|
#include "windows_cursor_state.h"
|
||||||
#include "cursor_draw.h"
|
#include "cursor_draw.h"
|
||||||
#include "windows_input_marker.h"
|
#include "windows_input_marker.h"
|
||||||
|
#include "windows_text_input.h"
|
||||||
#include "windows_key_metadata.h"
|
#include "windows_key_metadata.h"
|
||||||
#include "../windows_thread_dpi.h"
|
#include "../windows_thread_dpi.h"
|
||||||
|
|
||||||
@@ -948,6 +950,31 @@ InputInjectionResult InjectKeyboardInput(
|
|||||||
return BuildInputSuccess(std::move(desktop_switch));
|
return BuildInputSuccess(std::move(desktop_switch));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InputInjectionResult InjectTextInput(
|
||||||
|
const crossdesk::SecureTextInputRequest& request) {
|
||||||
|
std::vector<INPUT> inputs;
|
||||||
|
if (!crossdesk::BuildWindowsTextInputs(request.text, inputs)) {
|
||||||
|
return BuildInputFailure("invalid_text_input", ERROR_INVALID_PARAMETER,
|
||||||
|
BuildDesktopSwitchDetails(request.stage,
|
||||||
|
request.desktop));
|
||||||
|
}
|
||||||
|
ScopedInteractiveDesktop desktop;
|
||||||
|
DesktopSwitchDetails desktop_switch;
|
||||||
|
if (!EnsureThreadInteractiveDesktopForStage(request.stage, request.desktop,
|
||||||
|
&desktop, &desktop_switch)) {
|
||||||
|
return BuildInputFailure("switch_interactive_desktop_failed", GetLastError(),
|
||||||
|
desktop_switch);
|
||||||
|
}
|
||||||
|
const UINT sent = crossdesk::SendWindowsTextInputs(
|
||||||
|
inputs, [](INPUT* batch, UINT count) {
|
||||||
|
return SendInput(count, batch, sizeof(INPUT));
|
||||||
|
});
|
||||||
|
if (sent != inputs.size()) {
|
||||||
|
return BuildInputFailure("send_input_failed", GetLastError(), desktop_switch);
|
||||||
|
}
|
||||||
|
return BuildInputSuccess(std::move(desktop_switch));
|
||||||
|
}
|
||||||
|
|
||||||
void ParseInteractionTail(const std::string& tail,
|
void ParseInteractionTail(const std::string& tail,
|
||||||
std::string* interactive_stage_out,
|
std::string* interactive_stage_out,
|
||||||
std::string* interactive_desktop_out) {
|
std::string* interactive_desktop_out) {
|
||||||
@@ -1833,6 +1860,21 @@ std::vector<uint8_t> StartSecureDesktopSharedCapture(
|
|||||||
std::vector<uint8_t> HandleSecureInputHelperCommand(
|
std::vector<uint8_t> HandleSecureInputHelperCommand(
|
||||||
const std::string& command,
|
const std::string& command,
|
||||||
SecureSharedCaptureState* capture_state) {
|
SecureSharedCaptureState* capture_state) {
|
||||||
|
if (command.rfind(crossdesk::kSecureTextInputCommandPrefix, 0) == 0) {
|
||||||
|
crossdesk::SecureTextInputRequest request;
|
||||||
|
if (!crossdesk::ParseSecureTextInputCommand(command, request))
|
||||||
|
return BuildTextResponseBytes(BuildErrorJson("invalid_text_input"));
|
||||||
|
const auto result = InjectTextInput(request);
|
||||||
|
if (!result.ok) {
|
||||||
|
// Text may contain a login password. Never echo it in logs or replies.
|
||||||
|
LOG_WARN("Secure input helper text injection failed: error={}, code={}, "
|
||||||
|
"stage={}, target={}, current={}",
|
||||||
|
result.error, result.error_code, result.desktop.stage,
|
||||||
|
result.desktop.target_desktop, result.desktop.current_desktop);
|
||||||
|
return BuildTextResponseBytes(BuildInputFailureJson(result).dump());
|
||||||
|
}
|
||||||
|
return BuildTextResponseBytes("{\"ok\":true,\"injected\":\"text\"}");
|
||||||
|
}
|
||||||
if (command == "ping") {
|
if (command == "ping") {
|
||||||
return BuildTextResponseBytes("{\"ok\":true,\"reply\":\"pong\"}");
|
return BuildTextResponseBytes("{\"ok\":true,\"reply\":\"pong\"}");
|
||||||
}
|
}
|
||||||
@@ -1922,7 +1964,7 @@ std::vector<uint8_t> HandleSecureInputHelperCommand(
|
|||||||
void HandleSecureInputHelperPipeClient(
|
void HandleSecureInputHelperPipeClient(
|
||||||
HANDLE pipe, HANDLE event_handle,
|
HANDLE pipe, HANDLE event_handle,
|
||||||
std::shared_ptr<SecureSharedCaptureState> capture_state) {
|
std::shared_ptr<SecureSharedCaptureState> capture_state) {
|
||||||
char buffer[1024] = {0};
|
char buffer[crossdesk::kSecureInputCommandBufferBytes] = {0};
|
||||||
DWORD bytes_read = 0;
|
DWORD bytes_read = 0;
|
||||||
DWORD error = ERROR_SUCCESS;
|
DWORD error = ERROR_SUCCESS;
|
||||||
if (crossdesk::pipe_deadline_detail::Transfer(
|
if (crossdesk::pipe_deadline_detail::Transfer(
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ namespace crossdesk {
|
|||||||
|
|
||||||
struct PrivacyScreenText {
|
struct PrivacyScreenText {
|
||||||
std::string unlock_hint = "To turn off the privacy screen, press";
|
std::string unlock_hint = "To turn off the privacy screen, press";
|
||||||
|
std::string locked_hint = "Privacy screen is on. Local unlocking is disabled.";
|
||||||
|
bool allow_local_unlock = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PrivacyCapabilities {
|
struct PrivacyCapabilities {
|
||||||
@@ -38,6 +40,8 @@ class PrivacyBackend {
|
|||||||
virtual PrivacyCapabilities Query() = 0;
|
virtual PrivacyCapabilities Query() = 0;
|
||||||
virtual bool Enable(bool block_input, const PrivacyScreenText& text,
|
virtual bool Enable(bool block_input, const PrivacyScreenText& text,
|
||||||
std::string& error) = 0;
|
std::string& error) = 0;
|
||||||
|
// Update a live cover and its local shortcut policy without exposing the desktop.
|
||||||
|
virtual void UpdateText(const PrivacyScreenText& text) {}
|
||||||
virtual void Recover() = 0;
|
virtual void Recover() = 0;
|
||||||
virtual bool IsRecovered() const = 0;
|
virtual bool IsRecovered() const = 0;
|
||||||
virtual bool RecoveryPending() const { return false; }
|
virtual bool RecoveryPending() const { return false; }
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ PrivacyStatus PrivacyController::Snapshot() const {
|
|||||||
}
|
}
|
||||||
void PrivacyController::SetText(const PrivacyScreenText& text) {
|
void PrivacyController::SetText(const PrivacyScreenText& text) {
|
||||||
std::lock_guard lock(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
|
if (text_.unlock_hint == text.unlock_hint && text_.locked_hint == text.locked_hint &&
|
||||||
|
text_.allow_local_unlock == text.allow_local_unlock) return;
|
||||||
text_ = text;
|
text_ = text;
|
||||||
|
text_pending_ = true;
|
||||||
|
WakeLocked();
|
||||||
}
|
}
|
||||||
void PrivacyController::SetState(PrivacyState state, const std::string& reason) {
|
void PrivacyController::SetState(PrivacyState state, const std::string& reason) {
|
||||||
status_.state = state;
|
status_.state = state;
|
||||||
@@ -54,9 +58,28 @@ void PrivacyController::SetState(PrivacyState state, const std::string& reason)
|
|||||||
LOG_INFO("Privacy: state={}, overlay={}, input_blocked={}, reason={}",
|
LOG_INFO("Privacy: state={}, overlay={}, input_blocked={}, reason={}",
|
||||||
static_cast<int>(state), status_.overlay_active, status_.input_blocked, reason);
|
static_cast<int>(state), status_.overlay_active, status_.input_blocked, reason);
|
||||||
}
|
}
|
||||||
void PrivacyController::Enable(bool block_input) {
|
void PrivacyController::RejectCommand(const std::string& reason) {
|
||||||
std::lock_guard lock(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
if (quit_ || engaged_.load() || disable_pending_) return;
|
if (disable_pending_) {
|
||||||
|
failure_ = reason;
|
||||||
|
SetState(PrivacyState::failed, reason);
|
||||||
|
} else if (engaged_.load()) {
|
||||||
|
// A rejected command must not remove an already working cover.
|
||||||
|
SetState(status_.state, reason);
|
||||||
|
} else {
|
||||||
|
SetState(PrivacyState::failed, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void PrivacyController::Enable(bool block_input, bool wait_for_recovery) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
if (quit_) return;
|
||||||
|
if (engaged_.load() && !disable_pending_) {
|
||||||
|
// Another controller may have restored privacy before this lock arrived.
|
||||||
|
// Acknowledge it so this controller does not wait for a nonexistent transition.
|
||||||
|
if (wait_for_recovery) SetState(status_.state, status_.reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (disable_pending_ && !wait_for_recovery) return;
|
||||||
if (!status_.supported || (block_input && !status_.input_block_supported)) {
|
if (!status_.supported || (block_input && !status_.input_block_supported)) {
|
||||||
SetState(PrivacyState::unsupported, "Privacy screen is unavailable");
|
SetState(PrivacyState::unsupported, "Privacy screen is unavailable");
|
||||||
return;
|
return;
|
||||||
@@ -227,9 +250,17 @@ void PrivacyController::Run(Factory factory) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (text_pending_ && backend_) {
|
||||||
|
const auto text = text_;
|
||||||
|
text_pending_ = false;
|
||||||
|
lock.unlock();
|
||||||
|
backend_->UpdateText(text);
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
const auto health = backend_ ? backend_->Poll() : PrivacyHealth{};
|
const auto health = backend_ ? backend_->Poll() : PrivacyHealth{};
|
||||||
if (engaged_.load() && !disable_pending_) {
|
if (engaged_.load() && !disable_pending_) {
|
||||||
if (health.emergency_exit) {
|
if (health.emergency_exit && text_.allow_local_unlock) {
|
||||||
|
++status_.local_unlock_revision;
|
||||||
automatic_enable_ = false;
|
automatic_enable_ = false;
|
||||||
resume_after_unlock_ = false;
|
resume_after_unlock_ = false;
|
||||||
failure_.clear();
|
failure_.clear();
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class PrivacyController {
|
|||||||
PrivacyController& operator=(const PrivacyController&) = delete;
|
PrivacyController& operator=(const PrivacyController&) = delete;
|
||||||
PrivacyStatus Snapshot() const;
|
PrivacyStatus Snapshot() const;
|
||||||
void SetText(const PrivacyScreenText& text);
|
void SetText(const PrivacyScreenText& text);
|
||||||
void Enable(bool block_input);
|
void Enable(bool block_input, bool wait_for_recovery = false);
|
||||||
|
void RejectCommand(const std::string& reason);
|
||||||
void EnableOnConnection();
|
void EnableOnConnection();
|
||||||
// Preserve automatic session intent while Windows shows another desktop.
|
// Preserve automatic session intent while Windows shows another desktop.
|
||||||
void SuspendForDesktop();
|
void SuspendForDesktop();
|
||||||
@@ -50,6 +51,7 @@ class PrivacyController {
|
|||||||
std::unique_ptr<PrivacyBackend> backend_;
|
std::unique_ptr<PrivacyBackend> backend_;
|
||||||
PrivacyStatus status_{};
|
PrivacyStatus status_{};
|
||||||
PrivacyScreenText text_;
|
PrivacyScreenText text_;
|
||||||
|
bool text_pending_ = false;
|
||||||
std::atomic<bool> engaged_{false};
|
std::atomic<bool> engaged_{false};
|
||||||
bool quit_ = false;
|
bool quit_ = false;
|
||||||
bool enable_pending_ = false;
|
bool enable_pending_ = false;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "crossdesk_ui.h"
|
#include "crossdesk_ui.h"
|
||||||
#include "slint_render_test_platform.h"
|
#include "slint_render_test_platform.h"
|
||||||
#include "ui/model_update.h"
|
#include "ui/model_update.h"
|
||||||
|
#include "ui/ui_localization.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -15,11 +16,23 @@ bool Check(bool value, const char* message) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto CreateMainWindow() {
|
||||||
|
auto main = crossdesk::ui::MainWindow::create();
|
||||||
|
// Text and language options now come from the application. Load them before
|
||||||
|
// measuring or clicking layouts whose geometry depends on their labels.
|
||||||
|
crossdesk::ui_localization::ApplyMainWindowStrings(main, 0);
|
||||||
|
main->set_language_index(0);
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
bool VerifyStreamPopups(RenderTestPlatform& platform) {
|
bool VerifyStreamPopups(RenderTestPlatform& platform) {
|
||||||
auto stream = crossdesk::ui::StreamWindow::create();
|
auto stream = crossdesk::ui::StreamWindow::create();
|
||||||
auto& screen = *platform.adapter;
|
auto& screen = *platform.adapter;
|
||||||
stream->window().set_size(slint::LogicalSize({1280, 720}));
|
stream->window().set_size(slint::LogicalSize({1280, 720}));
|
||||||
stream->set_video_settings_enabled(true);
|
stream->set_video_settings_enabled(true);
|
||||||
|
// Exercise the Windows toolbar, including shortcuts and privacy controls.
|
||||||
|
stream->set_secure_attention_supported(true);
|
||||||
|
stream->set_privacy_visible(true);
|
||||||
stream->show();
|
stream->show();
|
||||||
const int top = int(stream->get_video_area_top());
|
const int top = int(stream->get_video_area_top());
|
||||||
auto displays = std::make_shared<slint::VectorModel<slint::SharedString>>();
|
auto displays = std::make_shared<slint::VectorModel<slint::SharedString>>();
|
||||||
@@ -63,28 +76,32 @@ bool VerifyStreamPopups(RenderTestPlatform& platform) {
|
|||||||
if (close_synchronously) stream->set_file_transfer_visible(false);
|
if (close_synchronously) stream->set_file_transfer_visible(false);
|
||||||
});
|
});
|
||||||
screen.Settle();
|
screen.Settle();
|
||||||
|
// New sessions start with only the logo. Reveal the toolbar through its
|
||||||
|
// normal gesture before testing the buttons to the right of the logo.
|
||||||
|
screen.Click(19, top + 19);
|
||||||
|
constexpr int display_x = 43, shortcut_x = 75, file_x = 203, video_x = 235;
|
||||||
for (int repeat = 0; repeat < 2; ++repeat) {
|
for (int repeat = 0; repeat < 2; ++repeat) {
|
||||||
screen.Click(21, top + 19);
|
screen.Click(display_x + 14, top + 19);
|
||||||
if (!Check(screen.Light(12, top + 128), "Display menu must fully open without pointer movement")) return false;
|
if (!Check(screen.Light(display_x + 5, top + 128), "Display menu must fully open without pointer movement")) return false;
|
||||||
screen.Click(60, top + 86, 4);
|
screen.Click(display_x + 53, top + 94, 4);
|
||||||
if (!Check(selected_display == 1 && screen.Black(12, top + 128),
|
if (!Check(selected_display == 1 && screen.Black(display_x + 5, top + 128),
|
||||||
"Display selection must survive live refresh and close the menu")) return false;
|
"Display selection must survive live refresh and close the menu")) return false;
|
||||||
screen.Click(53, top + 19);
|
screen.Click(shortcut_x + 14, top + 19);
|
||||||
if (!Check(screen.Light(44, top + 102), "Shortcut menu must open without pointer movement")) return false;
|
if (!Check(screen.Light(shortcut_x + 5, top + 102), "Shortcut menu must open without pointer movement")) return false;
|
||||||
screen.Click(70, top + 88, 4);
|
screen.Click(shortcut_x + 31, top + 94, 4);
|
||||||
if (!Check(shortcut == "Win+L" && screen.Black(44, top + 102),
|
if (!Check(shortcut == "Win+L" && screen.Black(shortcut_x + 5, top + 102),
|
||||||
"Shortcut selection must close the menu immediately")) return false;
|
"Shortcut selection must close the menu immediately")) return false;
|
||||||
screen.Click(213, top + 19);
|
screen.Click(video_x + 14, top + 19);
|
||||||
if (!Check(stream->get_video_menu_open() && screen.Light(203, top + 210),
|
if (!Check(stream->get_video_menu_open() && screen.Light(video_x + 4, top + 210),
|
||||||
"Video settings menu must fully open without pointer movement")) return false;
|
"Video settings menu must fully open without pointer movement")) return false;
|
||||||
screen.Click(240, top + 80, 4);
|
screen.Click(video_x + 41, top + 80, 4);
|
||||||
screen.Click(900, top + 300);
|
screen.Click(900, top + 300);
|
||||||
if (!Check(!stream->get_video_menu_open() && video_changes == repeat + 1,
|
if (!Check(!stream->get_video_menu_open() && video_changes == repeat + 1,
|
||||||
"Video selection and outside dismissal must respond immediately")) return false;
|
"Video selection and outside dismissal must respond immediately")) return false;
|
||||||
}
|
}
|
||||||
if (!Check(remote_presses == 0 && remote_releases == 0 && remote_scrolls == 0,
|
if (!Check(remote_presses == 0 && remote_releases == 0 && remote_scrolls == 0,
|
||||||
"Menu gestures must not be forwarded to the peer")) return false;
|
"Menu gestures must not be forwarded to the peer")) return false;
|
||||||
screen.Click(181, top + 19);
|
screen.Click(file_x + 14, top + 19);
|
||||||
const int panel_y = top + int(stream->get_video_area_height()) - 210;
|
const int panel_y = top + int(stream->get_video_area_height()) - 210;
|
||||||
if (!Check(file_visible && screen.Light(20, panel_y + 25),
|
if (!Check(file_visible && screen.Light(20, panel_y + 25),
|
||||||
"File progress must appear without pointer movement")) return false;
|
"File progress must appear without pointer movement")) return false;
|
||||||
@@ -110,7 +127,7 @@ bool VerifyStreamPopups(RenderTestPlatform& platform) {
|
|||||||
if (!Check(remote_presses == 0 && remote_releases == 0,
|
if (!Check(remote_presses == 0 && remote_releases == 0,
|
||||||
"Closing file progress must not send an orphan pointer release")) return false;
|
"Closing file progress must not send an orphan pointer release")) return false;
|
||||||
close_synchronously = true;
|
close_synchronously = true;
|
||||||
screen.Click(181, top + 19);
|
screen.Click(file_x + 14, top + 19);
|
||||||
screen.Click(430, panel_y + 16, 4);
|
screen.Click(430, panel_y + 16, 4);
|
||||||
if (!Check(!file_visible && screen.Black(20, panel_y + 25) && remote_releases == 0,
|
if (!Check(!file_visible && screen.Black(20, panel_y + 25) && remote_releases == 0,
|
||||||
"Synchronous file panel dismissal must consume its closing release")) return false;
|
"Synchronous file panel dismissal must consume its closing release")) return false;
|
||||||
@@ -120,7 +137,7 @@ bool VerifyStreamPopups(RenderTestPlatform& platform) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool VerifyWorkbenchPopups(RenderTestPlatform& platform) {
|
bool VerifyWorkbenchPopups(RenderTestPlatform& platform) {
|
||||||
auto main = crossdesk::ui::MainWindow::create();
|
auto main = CreateMainWindow();
|
||||||
auto& screen = *platform.adapter;
|
auto& screen = *platform.adapter;
|
||||||
const float width = main->get_minimum_window_width();
|
const float width = main->get_minimum_window_width();
|
||||||
const float height = main->get_minimum_window_height();
|
const float height = main->get_minimum_window_height();
|
||||||
@@ -190,7 +207,7 @@ bool VerifyWorkbenchPopups(RenderTestPlatform& platform) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool VerifyAutostartSettings(RenderTestPlatform& platform) {
|
bool VerifyAutostartSettings(RenderTestPlatform& platform) {
|
||||||
auto main = crossdesk::ui::MainWindow::create();
|
auto main = CreateMainWindow();
|
||||||
auto& screen = *platform.adapter;
|
auto& screen = *platform.adapter;
|
||||||
using Status = crossdesk::ui::AutostartStatus;
|
using Status = crossdesk::ui::AutostartStatus;
|
||||||
const float width = main->get_minimum_window_width();
|
const float width = main->get_minimum_window_width();
|
||||||
@@ -271,7 +288,7 @@ bool VerifyAutostartSettings(RenderTestPlatform& platform) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool VerifyGalleryPopups(RenderTestPlatform& platform) {
|
bool VerifyGalleryPopups(RenderTestPlatform& platform) {
|
||||||
auto wall = crossdesk::ui::MainWindow::create();
|
auto wall = CreateMainWindow();
|
||||||
auto& screen = *platform.adapter;
|
auto& screen = *platform.adapter;
|
||||||
wall->set_gallery_mode(true);
|
wall->set_gallery_mode(true);
|
||||||
wall->window().set_size(slint::LogicalSize({900, 600}));
|
wall->window().set_size(slint::LogicalSize({900, 600}));
|
||||||
@@ -296,7 +313,8 @@ bool VerifyGalleryPopups(RenderTestPlatform& platform) {
|
|||||||
screen.Click(160, 578);
|
screen.Click(160, 578);
|
||||||
if (!Check(wall->get_gallery_network_open() && screen.Region(75, 100, 740, 400) != baseline,
|
if (!Check(wall->get_gallery_network_open() && screen.Region(75, 100, 740, 400) != baseline,
|
||||||
"DeskHub network dialog must paint immediately during live refresh")) return false;
|
"DeskHub network dialog must paint immediately during live refresh")) return false;
|
||||||
if (repeat == 0) screen.Click(780, 102, 4);
|
// Eight rows fill the available height, placing the dialog 16 px from the top.
|
||||||
|
if (repeat == 0) screen.Click(780, 46, 4);
|
||||||
else screen.Key(slint::platform::key_codes::Escape);
|
else screen.Key(slint::platform::key_codes::Escape);
|
||||||
if (!Check(!wall->get_gallery_network_open() && screen.Region(75, 100, 740, 400) == baseline,
|
if (!Check(!wall->get_gallery_network_open() && screen.Region(75, 100, 740, 400) == baseline,
|
||||||
"DeskHub network dialog must close completely without pointer movement")) return false;
|
"DeskHub network dialog must close completely without pointer movement")) return false;
|
||||||
@@ -308,7 +326,7 @@ bool VerifyGalleryPopups(RenderTestPlatform& platform) {
|
|||||||
"DeskHub add menu must appear without pointer movement")) return false;
|
"DeskHub add menu must appear without pointer movement")) return false;
|
||||||
screen.Click(600, 55, 4);
|
screen.Click(600, 55, 4);
|
||||||
if (!Check(wall->get_gallery_adding(), "Add menu must open the device dialog")) return false;
|
if (!Check(wall->get_gallery_adding(), "Add menu must open the device dialog")) return false;
|
||||||
screen.Click(458, 421, 4);
|
screen.Click(422, 421, 4);
|
||||||
return Check(!wall->get_gallery_adding(), "Add dialog Cancel must respond immediately");
|
return Check(!wall->get_gallery_adding(), "Add dialog Cancel must respond immediately");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ int main() {
|
|||||||
auto& adapter = *backend->adapter;
|
auto& adapter = *backend->adapter;
|
||||||
stream->window().dispatch_resize_event(slint::LogicalSize({1280, 720}));
|
stream->window().dispatch_resize_event(slint::LogicalSize({1280, 720}));
|
||||||
stream->show();
|
stream->show();
|
||||||
|
stream->set_secure_attention_supported(false);
|
||||||
|
stream->set_privacy_visible(true);
|
||||||
auto rows = std::make_shared<slint::VectorModel<crossdesk::ui::NetworkStatsRow>>();
|
auto rows = std::make_shared<slint::VectorModel<crossdesk::ui::NetworkStatsRow>>();
|
||||||
stream->set_stats_rows(rows);
|
stream->set_stats_rows(rows);
|
||||||
std::vector<crossdesk::ui::NetworkStatsRow> stats(4);
|
std::vector<crossdesk::ui::NetworkStatsRow> stats(4);
|
||||||
@@ -75,6 +77,13 @@ int main() {
|
|||||||
};
|
};
|
||||||
settle();
|
settle();
|
||||||
const float top = stream->get_video_area_top();
|
const float top = stream->get_video_area_top();
|
||||||
|
// The initial control is only the logo. Wait for the toolbar reveal before
|
||||||
|
// clicking network status, just as a user does in a new session.
|
||||||
|
const slint::LogicalPosition logo({19, top + 19});
|
||||||
|
stream->window().dispatch_pointer_move_event(logo);
|
||||||
|
stream->window().dispatch_pointer_press_event(logo, slint::PointerEventButton::Left);
|
||||||
|
stream->window().dispatch_pointer_release_event(logo, slint::PointerEventButton::Left);
|
||||||
|
settle(20);
|
||||||
for (bool dock_right : {false, true}) {
|
for (bool dock_right : {false, true}) {
|
||||||
if (dock_right) {
|
if (dock_right) {
|
||||||
stream->invoke_begin_control_drag(5, 20);
|
stream->invoke_begin_control_drag(5, 20);
|
||||||
@@ -82,19 +91,26 @@ int main() {
|
|||||||
stream->invoke_end_control_drag();
|
stream->invoke_end_control_drag();
|
||||||
settle();
|
settle();
|
||||||
}
|
}
|
||||||
const int control_x = dock_right ? 916 : 0;
|
// With privacy visible and no Windows shortcuts, the toolbar is 344 px.
|
||||||
const slint::LogicalPosition button({float(control_x + (dock_right ? 278 : 245)), top + 19});
|
// Its logo moves to the trailing edge when docked on the right.
|
||||||
|
const int control_x = dock_right ? int(stream->get_video_area_width()) - 344 : 0;
|
||||||
|
const slint::LogicalPosition button({float(control_x + (dock_right ? 214 : 250)), top + 19});
|
||||||
stream->window().dispatch_pointer_move_event(button);
|
stream->window().dispatch_pointer_move_event(button);
|
||||||
settle(35); // Include the tooltip that appears before the click.
|
settle(35); // Include the tooltip that appears before the click.
|
||||||
for (bool expanded : {true, false, true, false}) {
|
for (bool expanded : {true, false, true, false}) {
|
||||||
stream->window().dispatch_pointer_press_event(button, slint::PointerEventButton::Left);
|
stream->window().dispatch_pointer_press_event(button, slint::PointerEventButton::Left);
|
||||||
stream->window().dispatch_pointer_release_event(button, slint::PointerEventButton::Left);
|
stream->window().dispatch_pointer_release_event(button, slint::PointerEventButton::Left);
|
||||||
settle(); // No pointer movement: animation must finish on its own.
|
settle(); // No pointer movement: animation must finish on its own.
|
||||||
|
if (stats_visible != expanded) {
|
||||||
|
std::cerr << "Network status button did not toggle the panel; dock_right="
|
||||||
|
<< dock_right << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
for (int y : {65, 195}) {
|
for (int y : {65, 195}) {
|
||||||
const auto pixel = adapter.pixels[(int(top) + y) * 1280 + control_x + 4];
|
const auto pixel = adapter.pixels[(int(top) + y) * 1280 + control_x + 4];
|
||||||
const bool painted = pixel.r >= 240 && pixel.g >= 240 && pixel.b >= 240;
|
const bool painted = pixel.r >= 240 && pixel.g >= 240 && pixel.b >= 240;
|
||||||
const bool cleared = pixel.r == 0 && pixel.g == 0 && pixel.b == 0;
|
const bool cleared = pixel.r == 0 && pixel.g == 0 && pixel.b == 0;
|
||||||
if (stats_visible != expanded || (expanded ? !painted : !cleared)) {
|
if (expanded ? !painted : !cleared) {
|
||||||
std::cerr << "Stats must fully " << (expanded ? "expand" : "collapse")
|
std::cerr << "Stats must fully " << (expanded ? "expand" : "collapse")
|
||||||
<< " without pointer movement; dock_right=" << dock_right
|
<< " without pointer movement; dock_right=" << dock_right
|
||||||
<< ", y=" << y << ", pixel=" << int(pixel.r) << ','
|
<< ", y=" << y << ", pixel=" << int(pixel.r) << ','
|
||||||
|
|||||||
@@ -779,7 +779,8 @@ bool VerifyWorkbenchActions() {
|
|||||||
if (window->get_settings_tab() != 0) return false;
|
if (window->get_settings_tab() != 0) return false;
|
||||||
window->set_gallery_settings_error("");
|
window->set_gallery_settings_error("");
|
||||||
window->set_settings_open(false);
|
window->set_settings_open(false);
|
||||||
click(60, 20);
|
// DeskHub follows the ID/IP mode switch when direct IP connections are enabled.
|
||||||
|
click(window->get_ip_direct_enabled() ? 204 : 60, 20);
|
||||||
if (!gallery_opened) return false;
|
if (!gallery_opened) return false;
|
||||||
crossdesk::ui::RecentConnection device{};
|
crossdesk::ui::RecentConnection device{};
|
||||||
device.remote_id = "987654321";
|
device.remote_id = "987654321";
|
||||||
@@ -850,24 +851,45 @@ bool VerifyWorkbenchActions() {
|
|||||||
bool VerifySystemShortcutMenu() {
|
bool VerifySystemShortcutMenu() {
|
||||||
auto stream = crossdesk::ui::StreamWindow::create();
|
auto stream = crossdesk::ui::StreamWindow::create();
|
||||||
stream->window().set_size(slint::LogicalSize(slint::Size<float>{1280, 720}));
|
stream->window().set_size(slint::LogicalSize(slint::Size<float>{1280, 720}));
|
||||||
|
stream->show();
|
||||||
std::vector<std::string> commands;
|
std::vector<std::string> commands;
|
||||||
stream->on_send_shortcut([&](slint::SharedString command) { commands.emplace_back(command); });
|
stream->on_send_shortcut([&](slint::SharedString command) { commands.emplace_back(command); });
|
||||||
const auto click = [&](float y) {
|
const auto settle = [] {
|
||||||
const slint::LogicalPosition point({55, stream->get_video_area_top() + y});
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||||
|
slint::platform::update_timers_and_animations();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto click = [&](float x, float y) {
|
||||||
|
const slint::LogicalPosition point({x, stream->get_video_area_top() + y});
|
||||||
stream->window().dispatch_pointer_move_event(point);
|
stream->window().dispatch_pointer_move_event(point);
|
||||||
stream->window().dispatch_pointer_press_event(point, slint::PointerEventButton::Left);
|
stream->window().dispatch_pointer_press_event(point, slint::PointerEventButton::Left);
|
||||||
stream->window().dispatch_pointer_release_event(point, slint::PointerEventButton::Left);
|
stream->window().dispatch_pointer_release_event(point, slint::PointerEventButton::Left);
|
||||||
};
|
};
|
||||||
click(20);
|
// The toolbar starts collapsed. Reveal it before targeting its buttons.
|
||||||
if (!stream->invoke_is_local_control_area(55, 90)) return false;
|
click(19, 19);
|
||||||
click(58);
|
settle();
|
||||||
stream->set_secure_attention_supported(false);
|
|
||||||
click(20);
|
|
||||||
if (stream->invoke_is_local_control_area(55, 90)) return false;
|
|
||||||
click(58);
|
|
||||||
stream->set_secure_attention_supported(true);
|
stream->set_secure_attention_supported(true);
|
||||||
click(20); click(88);
|
settle();
|
||||||
return commands == std::vector<std::string>{"Ctrl+Alt+Del", "Win+L", "Win+L"};
|
click(90, 19);
|
||||||
|
if (!stream->invoke_is_local_control_area(90, 90)) return false;
|
||||||
|
click(90, 68);
|
||||||
|
|
||||||
|
// Switching away from a Windows host hides the entire shortcut entry and
|
||||||
|
// closes an open menu; its former rows must no longer dispatch commands.
|
||||||
|
click(90, 19);
|
||||||
|
if (!stream->invoke_is_local_control_area(90, 90)) return false;
|
||||||
|
stream->set_secure_attention_supported(false);
|
||||||
|
settle();
|
||||||
|
if (stream->invoke_is_local_control_area(90, 90)) return false;
|
||||||
|
click(90, 98);
|
||||||
|
if (commands != std::vector<std::string>{"Ctrl+Alt+Del"}) return false;
|
||||||
|
|
||||||
|
stream->set_secure_attention_supported(true);
|
||||||
|
settle();
|
||||||
|
click(90, 19);
|
||||||
|
click(90, 98);
|
||||||
|
return commands == std::vector<std::string>{"Ctrl+Alt+Del", "Win+L"};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VerifyRemoteIdEditing() {
|
bool VerifyRemoteIdEditing() {
|
||||||
@@ -905,7 +927,7 @@ bool VerifyRemoteIdEditing() {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
if (!VerifySystemShortcutMenu()) {
|
if (!VerifySystemShortcutMenu()) {
|
||||||
std::cerr << "Shortcut menu must omit unsupported SAS and resize its input area\n";
|
std::cerr << "Shortcut menu must dispatch Windows shortcuts and hide for unsupported hosts\n";
|
||||||
return 13;
|
return 13;
|
||||||
}
|
}
|
||||||
if (!VerifyRemoteIdEditing()) {
|
if (!VerifyRemoteIdEditing()) {
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ function setup_targets()
|
|||||||
end
|
end
|
||||||
add_packages("slint")
|
add_packages("slint")
|
||||||
add_deps("crossdesk_ui")
|
add_deps("crossdesk_ui")
|
||||||
add_includedirs("apps/desktop/src/gui")
|
add_includedirs("apps/desktop/src/gui", "apps/desktop/src/gui/assets/localization")
|
||||||
add_files("apps/desktop/tests/slint_popup_render_test.cpp")
|
add_files("apps/desktop/tests/slint_popup_render_test.cpp")
|
||||||
add_rules("crossdesk.slint_runtime")
|
add_rules("crossdesk.slint_runtime")
|
||||||
|
|
||||||
@@ -664,6 +664,7 @@ function setup_targets()
|
|||||||
target("crossdesk_service")
|
target("crossdesk_service")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_packages("nlohmann_json")
|
add_packages("nlohmann_json")
|
||||||
|
add_includedirs("libs/wire/include")
|
||||||
add_deps("rd_log", "path_manager", "windows_unattended_config")
|
add_deps("rd_log", "path_manager", "windows_unattended_config")
|
||||||
add_links("Advapi32", "Wtsapi32", "Ole32", "Userenv")
|
add_links("Advapi32", "Wtsapi32", "Ole32", "Userenv")
|
||||||
add_files("apps/desktop/src/platform/windows/service/main.cpp",
|
add_files("apps/desktop/src/platform/windows/service/main.cpp",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ apps/harmony/.native/mobile-project/entry/build/default/outputs/default/entry-de
|
|||||||
| Android 功能 | 鸿蒙实现 |
|
| Android 功能 | 鸿蒙实现 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 前台自动登录、空闲后台停止、服务器隔离 | 独立 identity peer;只有主动远控才创建 controller peer;拒绝任何入站被控连接 |
|
| 前台自动登录、空闲后台停止、服务器隔离 | 独立 identity peer;只有主动远控才创建 controller peer;拒绝任何入站被控连接 |
|
||||||
|
| 电脑隐私屏 | 悬浮菜单直接开关,无详情页;按钮按电脑确认的状态显示“隐私屏已开启”或“隐私屏已关闭”,开启与关闭使用相同配色,正常切换不显示额外文字,仅在异常时显示提示;开启时与桌面端一致屏蔽电脑本地键鼠;5 秒状态失效、15 秒指令超时与跨会话隔离 |
|
||||||
| 首次隐私确认、撤回与离线阅读 | 同意前不发起身份、公告、在线状态或版本网络请求;撤回结束会话并清除预览 |
|
| 首次隐私确认、撤回与离线阅读 | 同意前不发起身份、公告、在线状态或版本网络请求;撤回结束会话并清除预览 |
|
||||||
| 加密身份、可选记住访问密码 | 系统 Asset Store,设备首次解锁后可读,禁止跨设备同步;普通 Preferences 不存密码 |
|
| 加密身份、可选记住访问密码 | 系统 Asset Store,设备首次解锁后可读,禁止跨设备同步;普通 Preferences 不存密码 |
|
||||||
| 最近连接、删除、在线状态 | 最近 20 台、在线优先、真实信令订阅、30 秒刷新和 60 秒过期;删除同步清理密码与预览 |
|
| 最近连接、删除、在线状态 | 最近 20 台、在线优先、真实信令订阅、30 秒刷新和 60 秒过期;删除同步清理密码与预览 |
|
||||||
|
|||||||
@@ -9,10 +9,12 @@
|
|||||||
|
|
||||||
两者仍为开发版。已完成的模拟器验证及未完成的真机验收见各端说明。
|
两者仍为开发版。已完成的模拟器验证及未完成的真机验收见各端说明。
|
||||||
|
|
||||||
手机控制端的“设置 → 屏幕方向 → 连接后自动横屏”默认开启并保存在本机。连接后先保持横屏,检测到手机实际横过来后恢复跟随设备方向;竖持、平放或斜持不会解除初始横屏。关闭后沿用自动旋转,平板和多窗口不受此选项影响。退出会话恢复首页方向,返回同一后台会话不重复锁定已解锁的方向;重力传感器不可用时沿用自动旋转。
|
手机控制端的“设置 → 屏幕方向 → 连接建立后锁定横屏”默认关闭并保存在本机。开启后,手机连接时进入并保持横屏;关闭时跟随设备方向。平板和多窗口不受此选项影响。退出会话恢复首页方向,返回后台会话沿用该会话的方向设置。已移除检测手机姿态后解除初始横屏的逻辑。
|
||||||
|
|
||||||
## 构建
|
## 构建
|
||||||
|
|
||||||
|
依赖仓库沿用 Windows/macOS/Linux 的 Xmake 策略:使用默认仓库或调用环境指定的 `XMAKE_GLOBALDIR` / `XMAKE_MAIN_REPO`,不固定或校验仓库提交。鸿蒙 PC 与移动端共用此规则,SDK、编译产物和依赖安装缓存仍按平台隔离。
|
||||||
|
|
||||||
在仓库根目录执行,当前构建脚本面向 macOS:
|
在仓库根目录执行,当前构建脚本面向 macOS:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { i18n } from '@kit.LocalizationKit';
|
||||||
import native from 'libslint_desktop.so';
|
import native from 'libslint_desktop.so';
|
||||||
import { TextInputHost } from './TextInputHost';
|
import { TextInputHost } from './TextInputHost';
|
||||||
import { TrayHost } from './TrayHost';
|
import { TrayHost } from './TrayHost';
|
||||||
@@ -128,7 +129,7 @@ export class DesktopHost {
|
|||||||
if (event.op === 'clipboardChanged') DesktopHost.clipboardChanged();
|
if (event.op === 'clipboardChanged') DesktopHost.clipboardChanged();
|
||||||
else DesktopHost.send(event);
|
else DesktopHost.send(event);
|
||||||
}, hostPermissions);
|
}, hostPermissions);
|
||||||
native.start(JSON.stringify({ filesDir: context.filesDir, cacheDir: context.cacheDir,
|
native.start(JSON.stringify({ language: i18n.System.getSystemLanguage(), filesDir: context.filesDir, cacheDir: context.cacheDir,
|
||||||
width: screen.width, height: screen.height, density: screen.densityPixels,
|
width: screen.width, height: screen.height, density: screen.densityPixels,
|
||||||
displayId: screen.id, displays: screens, name: 'Harmony PC',
|
displayId: screen.id, displays: screens, name: 'Harmony PC',
|
||||||
certificates: certificateManager.getCertificateStorePath({ certType: certificateManager.CertType.CA_CERT_SYSTEM }) }));
|
certificates: certificateManager.getCertificateStorePath({ certType: certificateManager.CertType.CA_CERT_SYSTEM }) }));
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user