mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-20 09:43:19 +03:00
Merge branch 'master' into modern-dialog
This commit is contained in:
14
.github/workflows/flutter-ci.yml
vendored
14
.github/workflows/flutter-ci.yml
vendored
@@ -18,7 +18,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
LLVM_VERSION: "15.0.6"
|
LLVM_VERSION: "15.0.6"
|
||||||
FLUTTER_VERSION: "3.7.0"
|
FLUTTER_VERSION: "3.7.5"
|
||||||
# vcpkg version: 2022.05.10
|
# vcpkg version: 2022.05.10
|
||||||
# for multiarch gcc compatibility
|
# for multiarch gcc compatibility
|
||||||
VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44"
|
VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44"
|
||||||
@@ -260,7 +260,7 @@ jobs:
|
|||||||
job:
|
job:
|
||||||
- {
|
- {
|
||||||
target: x86_64-unknown-linux-gnu,
|
target: x86_64-unknown-linux-gnu,
|
||||||
os: ubuntu-18.04,
|
os: ubuntu-20.04,
|
||||||
extra-build-args: "",
|
extra-build-args: "",
|
||||||
}
|
}
|
||||||
steps:
|
steps:
|
||||||
@@ -330,13 +330,13 @@ jobs:
|
|||||||
- {
|
- {
|
||||||
arch: x86_64,
|
arch: x86_64,
|
||||||
target: aarch64-linux-android,
|
target: aarch64-linux-android,
|
||||||
os: ubuntu-18.04,
|
os: ubuntu-20.04,
|
||||||
extra-build-features: "",
|
extra-build-features: "",
|
||||||
}
|
}
|
||||||
# - {
|
# - {
|
||||||
# arch: x86_64,
|
# arch: x86_64,
|
||||||
# target: armv7-linux-androideabi,
|
# target: armv7-linux-androideabi,
|
||||||
# os: ubuntu-18.04,
|
# os: ubuntu-20.04,
|
||||||
# extra-build-features: "",
|
# extra-build-features: "",
|
||||||
# }
|
# }
|
||||||
steps:
|
steps:
|
||||||
@@ -907,19 +907,19 @@ jobs:
|
|||||||
- {
|
- {
|
||||||
arch: x86_64,
|
arch: x86_64,
|
||||||
target: x86_64-unknown-linux-gnu,
|
target: x86_64-unknown-linux-gnu,
|
||||||
os: ubuntu-18.04,
|
os: ubuntu-20.04,
|
||||||
extra-build-features: "",
|
extra-build-features: "",
|
||||||
}
|
}
|
||||||
- {
|
- {
|
||||||
arch: x86_64,
|
arch: x86_64,
|
||||||
target: x86_64-unknown-linux-gnu,
|
target: x86_64-unknown-linux-gnu,
|
||||||
os: ubuntu-18.04,
|
os: ubuntu-20.04,
|
||||||
extra-build-features: "flatpak",
|
extra-build-features: "flatpak",
|
||||||
}
|
}
|
||||||
- {
|
- {
|
||||||
arch: x86_64,
|
arch: x86_64,
|
||||||
target: x86_64-unknown-linux-gnu,
|
target: x86_64-unknown-linux-gnu,
|
||||||
os: ubuntu-18.04,
|
os: ubuntu-20.04,
|
||||||
extra-build-features: "appimage",
|
extra-build-features: "appimage",
|
||||||
}
|
}
|
||||||
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
||||||
|
|||||||
6
.github/workflows/flutter-nightly.yml
vendored
6
.github/workflows/flutter-nightly.yml
vendored
@@ -732,7 +732,7 @@ jobs:
|
|||||||
x86_64)
|
x86_64)
|
||||||
# no need mock on x86_64
|
# no need mock on x86_64
|
||||||
export VCPKG_ROOT=/opt/artifacts/vcpkg
|
export VCPKG_ROOT=/opt/artifacts/vcpkg
|
||||||
cargo build --lib --features hwcodec,flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
|
cargo build --lib --features hwcodec,flutter,${{ matrix.job.extra-build-features }} --release
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -900,7 +900,7 @@ jobs:
|
|||||||
ln -s /usr/include /vcpkg/installed/arm64-linux/include
|
ln -s /usr/include /vcpkg/installed/arm64-linux/include
|
||||||
export VCPKG_ROOT=/vcpkg
|
export VCPKG_ROOT=/vcpkg
|
||||||
# disable hwcodec for compilation
|
# disable hwcodec for compilation
|
||||||
cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
|
cargo build --lib --features flutter,${{ matrix.job.extra-build-features }} --release
|
||||||
;;
|
;;
|
||||||
armv7)
|
armv7)
|
||||||
cp -r /opt/artifacts/vcpkg/installed/lib/* /usr/lib/arm-linux-gnueabihf/
|
cp -r /opt/artifacts/vcpkg/installed/lib/* /usr/lib/arm-linux-gnueabihf/
|
||||||
@@ -910,7 +910,7 @@ jobs:
|
|||||||
ln -s /usr/include /vcpkg/installed/arm-linux/include
|
ln -s /usr/include /vcpkg/installed/arm-linux/include
|
||||||
export VCPKG_ROOT=/vcpkg
|
export VCPKG_ROOT=/vcpkg
|
||||||
# disable hwcodec for compilation
|
# disable hwcodec for compilation
|
||||||
cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
|
cargo build --lib --features flutter,${{ matrix.job.extra-build-features }} --release
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4656,7 +4656,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "rdev"
|
name = "rdev"
|
||||||
version = "0.5.0-2"
|
version = "0.5.0-2"
|
||||||
source = "git+https://github.com/fufesou/rdev#5b9fb5e42117f44e0ce0fe7cf2bddf270c75f1dc"
|
source = "git+https://github.com/fufesou/rdev#25a99ce71ab42843ad253dd51e6a35e83e87a8a4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"core-foundation 0.9.3",
|
"core-foundation 0.9.3",
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ flutter_rust_bridge = "1.61.1"
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/simple_rc", "libs/portable"]
|
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/simple_rc", "libs/portable"]
|
||||||
|
exclude = ["vdi/host"]
|
||||||
|
|
||||||
[package.metadata.winres]
|
[package.metadata.winres]
|
||||||
LegalCopyright = "Copyright © 2022 Purslane, Inc."
|
LegalCopyright = "Copyright © 2022 Purslane, Inc."
|
||||||
|
|||||||
187
build.py
187
build.py
@@ -18,14 +18,11 @@ exe_path = 'target/release/' + hbb_name
|
|||||||
flutter_win_target_dir = 'flutter/build/windows/runner/Release/'
|
flutter_win_target_dir = 'flutter/build/windows/runner/Release/'
|
||||||
skip_cargo = False
|
skip_cargo = False
|
||||||
|
|
||||||
def custom_os_system(cmd):
|
def system2(cmd):
|
||||||
err = os._system(cmd)
|
err = os.system(cmd)
|
||||||
if err != 0:
|
if err != 0:
|
||||||
print(f"Error occurred when executing: {cmd}. Exiting.")
|
print(f"Error occurred when executing: {cmd}. Exiting.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
# replace prebuilt os.system
|
|
||||||
os._system = os.system
|
|
||||||
os.system = custom_os_system
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
with open("Cargo.toml", encoding="utf-8") as fh:
|
with open("Cargo.toml", encoding="utf-8") as fh:
|
||||||
@@ -144,8 +141,8 @@ def generate_build_script_for_docker():
|
|||||||
# build rustdesk
|
# build rustdesk
|
||||||
./build.py --flutter --hwcodec
|
./build.py --flutter --hwcodec
|
||||||
''')
|
''')
|
||||||
os.system("chmod +x /tmp/build.sh")
|
system2("chmod +x /tmp/build.sh")
|
||||||
os.system("bash /tmp/build.sh")
|
system2("bash /tmp/build.sh")
|
||||||
|
|
||||||
|
|
||||||
def download_extract_features(features, res_dir):
|
def download_extract_features(features, res_dir):
|
||||||
@@ -250,7 +247,7 @@ def get_features(args):
|
|||||||
|
|
||||||
def generate_control_file(version):
|
def generate_control_file(version):
|
||||||
control_file_path = "../res/DEBIAN/control"
|
control_file_path = "../res/DEBIAN/control"
|
||||||
os.system('/bin/rm -rf %s' % control_file_path)
|
system2('/bin/rm -rf %s' % control_file_path)
|
||||||
|
|
||||||
content = """Package: rustdesk
|
content = """Package: rustdesk
|
||||||
Version: %s
|
Version: %s
|
||||||
@@ -268,45 +265,45 @@ Description: A remote control software.
|
|||||||
|
|
||||||
def ffi_bindgen_function_refactor():
|
def ffi_bindgen_function_refactor():
|
||||||
# workaround ffigen
|
# workaround ffigen
|
||||||
os.system(
|
system2(
|
||||||
'sed -i "s/ffi.NativeFunction<ffi.Bool Function(DartPort/ffi.NativeFunction<ffi.Uint8 Function(DartPort/g" flutter/lib/generated_bridge.dart')
|
'sed -i "s/ffi.NativeFunction<ffi.Bool Function(DartPort/ffi.NativeFunction<ffi.Uint8 Function(DartPort/g" flutter/lib/generated_bridge.dart')
|
||||||
|
|
||||||
|
|
||||||
def build_flutter_deb(version, features):
|
def build_flutter_deb(version, features):
|
||||||
if not skip_cargo:
|
if not skip_cargo:
|
||||||
os.system(f'cargo build --features {features} --lib --release')
|
system2(f'cargo build --features {features} --lib --release')
|
||||||
ffi_bindgen_function_refactor()
|
ffi_bindgen_function_refactor()
|
||||||
os.chdir('flutter')
|
os.chdir('flutter')
|
||||||
os.system('flutter build linux --release')
|
system2('flutter build linux --release')
|
||||||
os.system('mkdir -p tmpdeb/usr/bin/')
|
system2('mkdir -p tmpdeb/usr/bin/')
|
||||||
os.system('mkdir -p tmpdeb/usr/lib/rustdesk')
|
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
|
||||||
os.system('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
os.system('mkdir -p tmpdeb/usr/share/applications/')
|
system2('mkdir -p tmpdeb/usr/share/applications/')
|
||||||
os.system('mkdir -p tmpdeb/usr/share/polkit-1/actions')
|
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
|
||||||
os.system('rm tmpdeb/usr/bin/rustdesk || true')
|
system2('rm tmpdeb/usr/bin/rustdesk || true')
|
||||||
os.system(
|
system2(
|
||||||
'cp -r build/linux/x64/release/bundle/* tmpdeb/usr/lib/rustdesk/')
|
'cp -r build/linux/x64/release/bundle/* tmpdeb/usr/lib/rustdesk/')
|
||||||
os.system(
|
system2(
|
||||||
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
os.system(
|
system2(
|
||||||
'cp ../res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png')
|
'cp ../res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png')
|
||||||
os.system(
|
system2(
|
||||||
'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
|
'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
|
||||||
os.system(
|
system2(
|
||||||
'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
|
'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
|
||||||
os.system(
|
system2(
|
||||||
'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/')
|
'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/')
|
||||||
os.system(
|
system2(
|
||||||
"echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")
|
"echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")
|
||||||
|
|
||||||
os.system('mkdir -p tmpdeb/DEBIAN')
|
system2('mkdir -p tmpdeb/DEBIAN')
|
||||||
generate_control_file(version)
|
generate_control_file(version)
|
||||||
os.system('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
|
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
|
||||||
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
|
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
|
||||||
os.system('dpkg-deb -b tmpdeb rustdesk.deb;')
|
system2('dpkg-deb -b tmpdeb rustdesk.deb;')
|
||||||
|
|
||||||
os.system('/bin/rm -rf tmpdeb/')
|
system2('/bin/rm -rf tmpdeb/')
|
||||||
os.system('/bin/rm -rf ../res/DEBIAN/control')
|
system2('/bin/rm -rf ../res/DEBIAN/control')
|
||||||
os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version)
|
os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version)
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
|
|
||||||
@@ -314,46 +311,43 @@ def build_flutter_deb(version, features):
|
|||||||
def build_flutter_dmg(version, features):
|
def build_flutter_dmg(version, features):
|
||||||
if not skip_cargo:
|
if not skip_cargo:
|
||||||
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
|
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
|
||||||
os.system(f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
|
system2(f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
|
||||||
# copy dylib
|
# copy dylib
|
||||||
os.system(
|
system2(
|
||||||
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
|
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
|
||||||
# ffi_bindgen_function_refactor()
|
|
||||||
# limitations from flutter rust bridge
|
|
||||||
os.system('sed -i "" "s/char \*\*rustdesk_core_main(int \*args_len);//" flutter/macos/Runner/bridge_generated.h')
|
|
||||||
os.chdir('flutter')
|
os.chdir('flutter')
|
||||||
os.system('flutter build macos --release')
|
system2('flutter build macos --release')
|
||||||
os.system(
|
system2(
|
||||||
"create-dmg rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app")
|
"create-dmg --volname \"RustDesk Installer\" --window-pos 200 120 --window-size 800 400 --icon-size 100 --app-drop-link 600 185 --icon RustDesk.app 200 190 --hide-extension RustDesk.app rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app")
|
||||||
os.rename("rustdesk.dmg", f"../rustdesk-{version}.dmg")
|
os.rename("rustdesk.dmg", f"../rustdesk-{version}.dmg")
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
|
|
||||||
|
|
||||||
def build_flutter_arch_manjaro(version, features):
|
def build_flutter_arch_manjaro(version, features):
|
||||||
if not skip_cargo:
|
if not skip_cargo:
|
||||||
os.system(f'cargo build --features {features} --lib --release')
|
system2(f'cargo build --features {features} --lib --release')
|
||||||
ffi_bindgen_function_refactor()
|
ffi_bindgen_function_refactor()
|
||||||
os.chdir('flutter')
|
os.chdir('flutter')
|
||||||
os.system('flutter build linux --release')
|
system2('flutter build linux --release')
|
||||||
os.system('strip build/linux/x64/release/bundle/lib/librustdesk.so')
|
system2('strip build/linux/x64/release/bundle/lib/librustdesk.so')
|
||||||
os.chdir('../res')
|
os.chdir('../res')
|
||||||
os.system('HBB=`pwd`/.. FLUTTER=1 makepkg -f')
|
system2('HBB=`pwd`/.. FLUTTER=1 makepkg -f')
|
||||||
|
|
||||||
|
|
||||||
def build_flutter_windows(version, features):
|
def build_flutter_windows(version, features):
|
||||||
if not skip_cargo:
|
if not skip_cargo:
|
||||||
os.system(f'cargo build --features {features} --lib --release')
|
system2(f'cargo build --features {features} --lib --release')
|
||||||
if not os.path.exists("target/release/librustdesk.dll"):
|
if not os.path.exists("target/release/librustdesk.dll"):
|
||||||
print("cargo build failed, please check rust source code.")
|
print("cargo build failed, please check rust source code.")
|
||||||
exit(-1)
|
exit(-1)
|
||||||
os.chdir('flutter')
|
os.chdir('flutter')
|
||||||
os.system('flutter build windows --release')
|
system2('flutter build windows --release')
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
shutil.copy2('target/release/deps/dylib_virtual_display.dll',
|
shutil.copy2('target/release/deps/dylib_virtual_display.dll',
|
||||||
flutter_win_target_dir)
|
flutter_win_target_dir)
|
||||||
os.chdir('libs/portable')
|
os.chdir('libs/portable')
|
||||||
os.system('pip3 install -r requirements.txt')
|
system2('pip3 install -r requirements.txt')
|
||||||
os.system(
|
system2(
|
||||||
f'python3 ./generate.py -f ../../{flutter_win_target_dir} -o . -e ../../{flutter_win_target_dir}/rustdesk.exe')
|
f'python3 ./generate.py -f ../../{flutter_win_target_dir} -o . -e ../../{flutter_win_target_dir}/rustdesk.exe')
|
||||||
os.chdir('../..')
|
os.chdir('../..')
|
||||||
if os.path.exists('./rustdesk_portable.exe'):
|
if os.path.exists('./rustdesk_portable.exe'):
|
||||||
@@ -374,22 +368,15 @@ def main():
|
|||||||
parser = make_parser()
|
parser = make_parser()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
shutil.copy2('Cargo.toml', 'Cargo.toml.bk')
|
|
||||||
shutil.copy2('src/main.rs', 'src/main.rs.bk')
|
|
||||||
if windows:
|
|
||||||
txt = open('src/main.rs', encoding='utf8').read()
|
|
||||||
with open('src/main.rs', 'wt', encoding='utf8') as fh:
|
|
||||||
fh.write(txt.replace(
|
|
||||||
'//#
|
||||||
|
einverstanden sind. In Git ist dies die Option `-s` f<>r `git commit`.
|
||||||
|
|
||||||
|
- Wenn Ihr Patch nicht begutachtet wird oder Sie eine bestimmte Person zur
|
||||||
|
Begutachtung ben<65>tigen, k<>nnen Sie einem Gutachter mit @ antworten und um eine
|
||||||
|
Begutachtung des Pull Requests oder einen Kommentar bitten. Sie k<>nnen auch
|
||||||
|
per [E-Mail](mailto:info@rustdesk.com) um eine Begutachtung bitten.
|
||||||
|
|
||||||
|
- F<>gen Sie Tests hinzu, die sich auf den behobenen Fehler oder die neue
|
||||||
|
Funktion beziehen.
|
||||||
|
|
||||||
|
Spezifische Git-Anweisungen finden Sie im [GitHub-Workflow](https://github.com/servo/servo/wiki/GitHub-workflow).
|
||||||
|
|
||||||
|
## Verhalten
|
||||||
|
|
||||||
|
https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md
|
||||||
|
|
||||||
|
## Kommunikation
|
||||||
|
|
||||||
|
RustDesk-Mitarbeiter arbeiten h<>ufig im [Discord](https://discord.gg/nDceKgxnkV).
|
||||||
14
docs/DEVCONTAINER-DE.md
Normal file
14
docs/DEVCONTAINER-DE.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
Nach dem Start von Dev-Container im Docker-Container wird ein Linux-Bin<69>rprogramm im Debug-Modus erstellt.
|
||||||
|
|
||||||
|
Derzeit bietet Dev-Container Linux- und Android-Builds sowohl im Debug- als auch im Release-Modus an.
|
||||||
|
|
||||||
|
Nachfolgend finden Sie eine Tabelle mit Befehlen, die im Stammverzeichnis des Projekts ausgef<65>hrt werden m<>ssen, um bestimmte Builds zu erstellen.
|
||||||
|
|
||||||
|
Kommando|Build-Typ|Modus
|
||||||
|
-|-|-|
|
||||||
|
`.devcontainer/build.sh --debug linux`|Linux|debug
|
||||||
|
`.devcontainer/build.sh --release linux`|Linux|release
|
||||||
|
`.devcontainer/build.sh --debug android`|android-arm64|debug
|
||||||
|
`.devcontainer/build.sh --release android`|android-arm64|release
|
||||||
|
|
||||||
@@ -17,9 +17,9 @@ RustDesk ist eine in Rust geschriebene Remote-Desktop-Software, die out of the b
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
RustDesk heißt jegliche Mitarbeit willkommen. Schau dir [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) an, wenn du Unterstützung beim Start brauchst.
|
RustDesk heißt jegliche Mitarbeit willkommen. Schau dir [CONTRIBUTING-DE.md](CONTRIBUTING-DE.md) an, wenn du Unterstützung beim Start brauchst.
|
||||||
|
|
||||||
[**Wie arbeitet RustDesk?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F)
|
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||||
|
|
||||||
[**Programm herunterladen**](https://github.com/rustdesk/rustdesk/releases)
|
[**Programm herunterladen**](https://github.com/rustdesk/rustdesk/releases)
|
||||||
|
|
||||||
@@ -41,6 +41,14 @@ Nachfolgend sind die Server gelistet, die du kostenlos nutzen kannst. Es kann se
|
|||||||
| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8 GB RAM |
|
| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8 GB RAM |
|
||||||
| Ukraine (Kiew) | dc.volia (2VM) | 2 vCPU / 4 GB RAM |
|
| Ukraine (Kiew) | dc.volia (2VM) | 2 vCPU / 4 GB RAM |
|
||||||
|
|
||||||
|
## Dev-Container
|
||||||
|
|
||||||
|
[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/rustdesk/rustdesk)
|
||||||
|
|
||||||
|
Wenn du VS Code und Docker bereits installiert hast, kannst du auf das Abzeichen oben klicken, um loszulegen. Wenn du darauf klickst, wird VS Code automatisch die Dev-Container-Erweiterung installieren, den Quellcode in ein Container-Volume klonen und einen Dev-Container für die Verwendung aufsetzen.
|
||||||
|
|
||||||
|
Weitere Informationen findest du in [DEVCONTAINER-DE.md](DEVCONTAINER-DE.md).
|
||||||
|
|
||||||
## Abhängigkeiten
|
## Abhängigkeiten
|
||||||
|
|
||||||
Desktop-Versionen verwenden [Sciter](https://sciter.com/) oder Flutter für die GUI, dieses Tutorial ist nur für Sciter.
|
Desktop-Versionen verwenden [Sciter](https://sciter.com/) oder Flutter für die GUI, dieses Tutorial ist nur für Sciter.
|
||||||
|
|||||||
@@ -11,22 +11,25 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<!--<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />-->
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="RustDesk"
|
android:label="RustDesk"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true">
|
||||||
android:requestLegacyExternalStorage="true">
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".BootReceiver"
|
android:name=".BootReceiver"
|
||||||
android:enabled="false"
|
android:enabled="true"
|
||||||
android:exported="false">
|
android:exported="true">
|
||||||
<intent-filter android:priority="1000">
|
<intent-filter android:priority="1000">
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
<!--ACTION_BOOT_COMPLETED for debug test on no root device-->
|
||||||
|
<action android:name="com.carriez.flutter_hbb.DEBUG_BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
@@ -53,8 +56,6 @@
|
|||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
@@ -62,6 +63,11 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".PermissionRequestTransparentActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:theme="@style/Transparent" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".MainService"
|
android:name=".MainService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
|||||||
@@ -1,18 +1,42 @@
|
|||||||
package com.carriez.flutter_hbb
|
package com.carriez.flutter_hbb
|
||||||
|
|
||||||
|
import android.Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||||
|
import android.Manifest.permission.SYSTEM_ALERT_WINDOW
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import com.hjq.permissions.XXPermissions
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
const val DEBUG_BOOT_COMPLETED = "com.carriez.flutter_hbb.DEBUG_BOOT_COMPLETED"
|
||||||
|
|
||||||
class BootReceiver : BroadcastReceiver() {
|
class BootReceiver : BroadcastReceiver() {
|
||||||
|
private val logTag = "tagBootReceiver"
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if ("android.intent.action.BOOT_COMPLETED" == intent.action){
|
Log.d(logTag, "onReceive ${intent.action}")
|
||||||
val it = Intent(context,MainService::class.java).apply {
|
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
if (Intent.ACTION_BOOT_COMPLETED == intent.action || DEBUG_BOOT_COMPLETED == intent.action) {
|
||||||
|
// check SharedPreferences config
|
||||||
|
val prefs = context.getSharedPreferences(KEY_SHARED_PREFERENCES, FlutterActivity.MODE_PRIVATE)
|
||||||
|
if (!prefs.getBoolean(KEY_START_ON_BOOT_OPT, false)) {
|
||||||
|
Log.d(logTag, "KEY_START_ON_BOOT_OPT is false")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
Toast.makeText(context, "RustDesk is Open", Toast.LENGTH_LONG).show();
|
// check pre-permission
|
||||||
|
if (!XXPermissions.isGranted(context, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, SYSTEM_ALERT_WINDOW)){
|
||||||
|
Log.d(logTag, "REQUEST_IGNORE_BATTERY_OPTIMIZATIONS or SYSTEM_ALERT_WINDOW is not granted")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val it = Intent(context, MainService::class.java).apply {
|
||||||
|
action = ACT_INIT_MEDIA_PROJECTION_AND_SERVICE
|
||||||
|
putExtra(EXT_INIT_FROM_BOOT, true)
|
||||||
|
}
|
||||||
|
Toast.makeText(context, "RustDesk is Open", Toast.LENGTH_LONG).show()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
context.startForegroundService(it)
|
context.startForegroundService(it)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,35 +7,29 @@ package com.carriez.flutter_hbb
|
|||||||
* Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG
|
* Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.media.projection.MediaProjectionManager
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.provider.Settings
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.annotation.RequiresApi
|
import com.hjq.permissions.XXPermissions
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
const val MEDIA_REQUEST_CODE = 42
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var flutterMethodChannel: MethodChannel
|
var flutterMethodChannel: MethodChannel? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val channelTag = "mChannel"
|
private val channelTag = "mChannel"
|
||||||
private val logTag = "mMainActivity"
|
private val logTag = "mMainActivity"
|
||||||
private var mediaProjectionResultIntent: Intent? = null
|
|
||||||
private var mainService: MainService? = null
|
private var mainService: MainService? = null
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
if (MainService.isReady) {
|
if (MainService.isReady) {
|
||||||
@@ -46,169 +40,32 @@ class MainActivity : FlutterActivity() {
|
|||||||
flutterMethodChannel = MethodChannel(
|
flutterMethodChannel = MethodChannel(
|
||||||
flutterEngine.dartExecutor.binaryMessenger,
|
flutterEngine.dartExecutor.binaryMessenger,
|
||||||
channelTag
|
channelTag
|
||||||
).apply {
|
|
||||||
// make sure result is set, otherwise flutter will await forever
|
|
||||||
setMethodCallHandler { call, result ->
|
|
||||||
when (call.method) {
|
|
||||||
"init_service" -> {
|
|
||||||
Intent(activity, MainService::class.java).also {
|
|
||||||
bindService(it, serviceConnection, Context.BIND_AUTO_CREATE)
|
|
||||||
}
|
|
||||||
if (MainService.isReady) {
|
|
||||||
result.success(false)
|
|
||||||
return@setMethodCallHandler
|
|
||||||
}
|
|
||||||
getMediaProjection()
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
"start_capture" -> {
|
|
||||||
mainService?.let {
|
|
||||||
result.success(it.startCapture())
|
|
||||||
} ?: let {
|
|
||||||
result.success(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"stop_service" -> {
|
|
||||||
Log.d(logTag, "Stop service")
|
|
||||||
mainService?.let {
|
|
||||||
it.destroy()
|
|
||||||
result.success(true)
|
|
||||||
} ?: let {
|
|
||||||
result.success(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"check_permission" -> {
|
|
||||||
if (call.arguments is String) {
|
|
||||||
result.success(checkPermission(context, call.arguments as String))
|
|
||||||
} else {
|
|
||||||
result.success(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"request_permission" -> {
|
|
||||||
if (call.arguments is String) {
|
|
||||||
requestPermission(context, call.arguments as String)
|
|
||||||
result.success(true)
|
|
||||||
} else {
|
|
||||||
result.success(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"check_video_permission" -> {
|
|
||||||
mainService?.let {
|
|
||||||
result.success(it.checkMediaPermission())
|
|
||||||
} ?: let {
|
|
||||||
result.success(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"check_service" -> {
|
|
||||||
flutterMethodChannel.invokeMethod(
|
|
||||||
"on_state_changed",
|
|
||||||
mapOf("name" to "input", "value" to InputService.isOpen.toString())
|
|
||||||
)
|
)
|
||||||
flutterMethodChannel.invokeMethod(
|
initFlutterChannel(flutterMethodChannel!!)
|
||||||
"on_state_changed",
|
|
||||||
mapOf("name" to "media", "value" to MainService.isReady.toString())
|
|
||||||
)
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
"init_input" -> {
|
|
||||||
initInput()
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
"stop_input" -> {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
InputService.ctx?.disableSelf()
|
|
||||||
}
|
|
||||||
InputService.ctx = null
|
|
||||||
flutterMethodChannel.invokeMethod(
|
|
||||||
"on_state_changed",
|
|
||||||
mapOf("name" to "input", "value" to InputService.isOpen.toString())
|
|
||||||
)
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
"cancel_notification" -> {
|
|
||||||
try {
|
|
||||||
val id = call.arguments as Int
|
|
||||||
mainService?.cancelNotification(id)
|
|
||||||
} finally {
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"enable_soft_keyboard" -> {
|
|
||||||
// https://blog.csdn.net/hanye2020/article/details/105553780
|
|
||||||
try {
|
|
||||||
if (call.arguments as Boolean) {
|
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
|
|
||||||
} else {
|
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
result.error("-1", "No such method", null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getMediaProjection() {
|
|
||||||
val mMediaProjectionManager =
|
|
||||||
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
|
||||||
val mIntent = mMediaProjectionManager.createScreenCaptureIntent()
|
|
||||||
startActivityForResult(mIntent, MEDIA_REQUEST_CODE)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initService() {
|
|
||||||
if (mediaProjectionResultIntent == null) {
|
|
||||||
Log.w(logTag, "initService fail,mediaProjectionResultIntent is null")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Log.d(logTag, "Init service")
|
|
||||||
val serviceIntent = Intent(this, MainService::class.java)
|
|
||||||
serviceIntent.action = INIT_SERVICE
|
|
||||||
serviceIntent.putExtra(EXTRA_MP_DATA, mediaProjectionResultIntent)
|
|
||||||
|
|
||||||
launchMainService(serviceIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun launchMainService(intent: Intent) {
|
|
||||||
// TEST api < O
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
startForegroundService(intent)
|
|
||||||
} else {
|
|
||||||
startService(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initInput() {
|
|
||||||
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
|
|
||||||
if (intent.resolveActivity(packageManager) != null) {
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
val inputPer = InputService.isOpen
|
val inputPer = InputService.isOpen
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
flutterMethodChannel.invokeMethod(
|
flutterMethodChannel?.invokeMethod(
|
||||||
"on_state_changed",
|
"on_state_changed",
|
||||||
mapOf("name" to "input", "value" to inputPer.toString())
|
mapOf("name" to "input", "value" to inputPer.toString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun requestMediaProjection() {
|
||||||
|
val intent = Intent(this, PermissionRequestTransparentActivity::class.java).apply {
|
||||||
|
action = ACT_REQUEST_MEDIA_PROJECTION
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
if (requestCode == MEDIA_REQUEST_CODE) {
|
if (requestCode == REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION && resultCode == RES_FAILED) {
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
flutterMethodChannel?.invokeMethod("on_media_projection_canceled", null)
|
||||||
mediaProjectionResultIntent = data
|
|
||||||
initService()
|
|
||||||
} else {
|
|
||||||
flutterMethodChannel.invokeMethod("on_media_projection_canceled", null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,4 +89,138 @@ class MainActivity : FlutterActivity() {
|
|||||||
mainService = null
|
mainService = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initFlutterChannel(flutterMethodChannel: MethodChannel) {
|
||||||
|
flutterMethodChannel.setMethodCallHandler { call, result ->
|
||||||
|
// make sure result will be invoked, otherwise flutter will await forever
|
||||||
|
when (call.method) {
|
||||||
|
"init_service" -> {
|
||||||
|
Intent(activity, MainService::class.java).also {
|
||||||
|
bindService(it, serviceConnection, Context.BIND_AUTO_CREATE)
|
||||||
|
}
|
||||||
|
if (MainService.isReady) {
|
||||||
|
result.success(false)
|
||||||
|
return@setMethodCallHandler
|
||||||
|
}
|
||||||
|
requestMediaProjection()
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
"start_capture" -> {
|
||||||
|
mainService?.let {
|
||||||
|
result.success(it.startCapture())
|
||||||
|
} ?: let {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"stop_service" -> {
|
||||||
|
Log.d(logTag, "Stop service")
|
||||||
|
mainService?.let {
|
||||||
|
it.destroy()
|
||||||
|
result.success(true)
|
||||||
|
} ?: let {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"check_permission" -> {
|
||||||
|
if (call.arguments is String) {
|
||||||
|
result.success(XXPermissions.isGranted(context, call.arguments as String))
|
||||||
|
} else {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"request_permission" -> {
|
||||||
|
if (call.arguments is String) {
|
||||||
|
requestPermission(context, call.arguments as String)
|
||||||
|
result.success(true)
|
||||||
|
} else {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
START_ACTION -> {
|
||||||
|
if (call.arguments is String) {
|
||||||
|
startAction(context, call.arguments as String)
|
||||||
|
result.success(true)
|
||||||
|
} else {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"check_video_permission" -> {
|
||||||
|
mainService?.let {
|
||||||
|
result.success(it.checkMediaPermission())
|
||||||
|
} ?: let {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"check_service" -> {
|
||||||
|
Companion.flutterMethodChannel?.invokeMethod(
|
||||||
|
"on_state_changed",
|
||||||
|
mapOf("name" to "input", "value" to InputService.isOpen.toString())
|
||||||
|
)
|
||||||
|
Companion.flutterMethodChannel?.invokeMethod(
|
||||||
|
"on_state_changed",
|
||||||
|
mapOf("name" to "media", "value" to MainService.isReady.toString())
|
||||||
|
)
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
"stop_input" -> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
InputService.ctx?.disableSelf()
|
||||||
|
}
|
||||||
|
InputService.ctx = null
|
||||||
|
Companion.flutterMethodChannel?.invokeMethod(
|
||||||
|
"on_state_changed",
|
||||||
|
mapOf("name" to "input", "value" to InputService.isOpen.toString())
|
||||||
|
)
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
"cancel_notification" -> {
|
||||||
|
if (call.arguments is Int) {
|
||||||
|
val id = call.arguments as Int
|
||||||
|
mainService?.cancelNotification(id)
|
||||||
|
} else {
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"enable_soft_keyboard" -> {
|
||||||
|
// https://blog.csdn.net/hanye2020/article/details/105553780
|
||||||
|
if (call.arguments as Boolean) {
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
|
||||||
|
} else {
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
|
||||||
|
}
|
||||||
|
result.success(true)
|
||||||
|
|
||||||
|
}
|
||||||
|
GET_START_ON_BOOT_OPT -> {
|
||||||
|
val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
|
||||||
|
result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false))
|
||||||
|
}
|
||||||
|
SET_START_ON_BOOT_OPT -> {
|
||||||
|
if (call.arguments is Boolean) {
|
||||||
|
val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
|
||||||
|
val edit = prefs.edit()
|
||||||
|
edit.putBoolean(KEY_START_ON_BOOT_OPT, call.arguments as Boolean)
|
||||||
|
edit.apply()
|
||||||
|
result.success(true)
|
||||||
|
} else {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SYNC_APP_DIR_CONFIG_PATH -> {
|
||||||
|
if (call.arguments is String) {
|
||||||
|
val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
|
||||||
|
val edit = prefs.edit()
|
||||||
|
edit.putString(KEY_APP_DIR_CONFIG_PATH, call.arguments as String)
|
||||||
|
edit.apply()
|
||||||
|
result.success(true)
|
||||||
|
} else {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
result.error("-1", "No such method", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import androidx.annotation.RequiresApi
|
|||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
@@ -43,10 +44,6 @@ import java.nio.ByteBuffer
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
const val EXTRA_MP_DATA = "mp_intent"
|
|
||||||
const val INIT_SERVICE = "init_service"
|
|
||||||
const val ACTION_LOGIN_REQ_NOTIFY = "ACTION_LOGIN_REQ_NOTIFY"
|
|
||||||
const val EXTRA_LOGIN_REQ_NOTIFY = "EXTRA_LOGIN_REQ_NOTIFY"
|
|
||||||
|
|
||||||
const val DEFAULT_NOTIFY_TITLE = "RustDesk"
|
const val DEFAULT_NOTIFY_TITLE = "RustDesk"
|
||||||
const val DEFAULT_NOTIFY_TEXT = "Service is running"
|
const val DEFAULT_NOTIFY_TEXT = "Service is running"
|
||||||
@@ -147,7 +144,11 @@ class MainService : Service() {
|
|||||||
|
|
||||||
// jvm call rust
|
// jvm call rust
|
||||||
private external fun init(ctx: Context)
|
private external fun init(ctx: Context)
|
||||||
private external fun startServer()
|
|
||||||
|
/// When app start on boot, app_dir will not be passed from flutter
|
||||||
|
/// so pass a app_dir here to rust server
|
||||||
|
private external fun startServer(app_dir: String)
|
||||||
|
private external fun startService()
|
||||||
private external fun onVideoFrameUpdate(buf: ByteBuffer)
|
private external fun onVideoFrameUpdate(buf: ByteBuffer)
|
||||||
private external fun onAudioFrameUpdate(buf: ByteBuffer)
|
private external fun onAudioFrameUpdate(buf: ByteBuffer)
|
||||||
private external fun translateLocale(localeName: String, input: String): String
|
private external fun translateLocale(localeName: String, input: String): String
|
||||||
@@ -195,6 +196,7 @@ class MainService : Service() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
Log.d(logTag,"MainService onCreate")
|
||||||
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
|
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
|
||||||
start()
|
start()
|
||||||
serviceLooper = looper
|
serviceLooper = looper
|
||||||
@@ -202,7 +204,13 @@ class MainService : Service() {
|
|||||||
}
|
}
|
||||||
updateScreenInfo(resources.configuration.orientation)
|
updateScreenInfo(resources.configuration.orientation)
|
||||||
initNotification()
|
initNotification()
|
||||||
startServer()
|
|
||||||
|
// keep the config dir same with flutter
|
||||||
|
val prefs = applicationContext.getSharedPreferences(KEY_SHARED_PREFERENCES, FlutterActivity.MODE_PRIVATE)
|
||||||
|
val configPath = prefs.getString(KEY_APP_DIR_CONFIG_PATH, "") ?: ""
|
||||||
|
startServer(configPath)
|
||||||
|
|
||||||
|
createForegroundNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@@ -279,17 +287,25 @@ class MainService : Service() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
Log.d("whichService", "this service: ${Thread.currentThread()}")
|
Log.d("whichService", "this service: ${Thread.currentThread()}")
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
if (intent?.action == INIT_SERVICE) {
|
if (intent?.action == ACT_INIT_MEDIA_PROJECTION_AND_SERVICE) {
|
||||||
Log.d(logTag, "service starting:${startId}:${Thread.currentThread()}")
|
|
||||||
createForegroundNotification()
|
createForegroundNotification()
|
||||||
val mMediaProjectionManager =
|
|
||||||
|
if (intent.getBooleanExtra(EXT_INIT_FROM_BOOT, false)) {
|
||||||
|
startService()
|
||||||
|
}
|
||||||
|
Log.d(logTag, "service starting: ${startId}:${Thread.currentThread()}")
|
||||||
|
val mediaProjectionManager =
|
||||||
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||||||
intent.getParcelableExtra<Intent>(EXTRA_MP_DATA)?.let {
|
|
||||||
|
intent.getParcelableExtra<Intent>(EXT_MEDIA_PROJECTION_RES_INTENT)?.let {
|
||||||
mediaProjection =
|
mediaProjection =
|
||||||
mMediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it)
|
mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it)
|
||||||
checkMediaPermission()
|
checkMediaPermission()
|
||||||
init(this)
|
init(this)
|
||||||
_isReady = true
|
_isReady = true
|
||||||
|
} ?: let {
|
||||||
|
Log.d(logTag, "getParcelableExtra intent null, invoke requestMediaProjection")
|
||||||
|
requestMediaProjection()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return START_NOT_STICKY // don't use sticky (auto restart), the new service (from auto restart) will lose control
|
return START_NOT_STICKY // don't use sticky (auto restart), the new service (from auto restart) will lose control
|
||||||
@@ -300,6 +316,14 @@ class MainService : Service() {
|
|||||||
updateScreenInfo(newConfig.orientation)
|
updateScreenInfo(newConfig.orientation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun requestMediaProjection() {
|
||||||
|
val intent = Intent(this, PermissionRequestTransparentActivity::class.java).apply {
|
||||||
|
action = ACT_REQUEST_MEDIA_PROJECTION
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("WrongConstant")
|
@SuppressLint("WrongConstant")
|
||||||
private fun createSurface(): Surface? {
|
private fun createSurface(): Surface? {
|
||||||
return if (useVP9) {
|
return if (useVP9) {
|
||||||
@@ -400,13 +424,13 @@ class MainService : Service() {
|
|||||||
|
|
||||||
fun checkMediaPermission(): Boolean {
|
fun checkMediaPermission(): Boolean {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
MainActivity.flutterMethodChannel.invokeMethod(
|
MainActivity.flutterMethodChannel?.invokeMethod(
|
||||||
"on_state_changed",
|
"on_state_changed",
|
||||||
mapOf("name" to "media", "value" to isReady.toString())
|
mapOf("name" to "media", "value" to isReady.toString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
MainActivity.flutterMethodChannel.invokeMethod(
|
MainActivity.flutterMethodChannel?.invokeMethod(
|
||||||
"on_state_changed",
|
"on_state_changed",
|
||||||
mapOf("name" to "input", "value" to InputService.isOpen.toString())
|
mapOf("name" to "input", "value" to InputService.isOpen.toString())
|
||||||
)
|
)
|
||||||
@@ -653,8 +677,8 @@ class MainService : Service() {
|
|||||||
@SuppressLint("UnspecifiedImmutableFlag")
|
@SuppressLint("UnspecifiedImmutableFlag")
|
||||||
private fun genLoginRequestPendingIntent(res: Boolean): PendingIntent {
|
private fun genLoginRequestPendingIntent(res: Boolean): PendingIntent {
|
||||||
val intent = Intent(this, MainService::class.java).apply {
|
val intent = Intent(this, MainService::class.java).apply {
|
||||||
action = ACTION_LOGIN_REQ_NOTIFY
|
action = ACT_LOGIN_REQ_NOTIFY
|
||||||
putExtra(EXTRA_LOGIN_REQ_NOTIFY, res)
|
putExtra(EXT_LOGIN_REQ_NOTIFY, res)
|
||||||
}
|
}
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
PendingIntent.getService(this, 111, intent, FLAG_IMMUTABLE)
|
PendingIntent.getService(this, 111, intent, FLAG_IMMUTABLE)
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.carriez.flutter_hbb
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.media.projection.MediaProjectionManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
class PermissionRequestTransparentActivity: Activity() {
|
||||||
|
private val logTag = "permissionRequest"
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
Log.d(logTag, "onCreate PermissionRequestTransparentActivity: intent.action: ${intent.action}")
|
||||||
|
|
||||||
|
when (intent.action) {
|
||||||
|
ACT_REQUEST_MEDIA_PROJECTION -> {
|
||||||
|
val mediaProjectionManager =
|
||||||
|
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||||||
|
val intent = mediaProjectionManager.createScreenCaptureIntent()
|
||||||
|
startActivityForResult(intent, REQ_REQUEST_MEDIA_PROJECTION)
|
||||||
|
}
|
||||||
|
else -> finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
if (requestCode == REQ_REQUEST_MEDIA_PROJECTION) {
|
||||||
|
if (resultCode == RESULT_OK && data != null) {
|
||||||
|
launchService(data)
|
||||||
|
} else {
|
||||||
|
setResult(RES_FAILED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchService(mediaProjectionResultIntent: Intent) {
|
||||||
|
Log.d(logTag, "Launch MainService")
|
||||||
|
val serviceIntent = Intent(this, MainService::class.java)
|
||||||
|
serviceIntent.action = ACT_INIT_MEDIA_PROJECTION_AND_SERVICE
|
||||||
|
serviceIntent.putExtra(EXT_MEDIA_PROJECTION_RES_INTENT, mediaProjectionResultIntent)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
startForegroundService(serviceIntent)
|
||||||
|
} else {
|
||||||
|
startService(serviceIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.carriez.flutter_hbb
|
package com.carriez.flutter_hbb
|
||||||
|
|
||||||
|
import android.Manifest.permission.*
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -12,8 +13,8 @@ import android.os.Build
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
|
import android.provider.Settings
|
||||||
import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
import android.provider.Settings.*
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat.getSystemService
|
import androidx.core.content.ContextCompat.getSystemService
|
||||||
import com.hjq.permissions.Permission
|
import com.hjq.permissions.Permission
|
||||||
@@ -22,6 +23,31 @@ import java.nio.ByteBuffer
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
// intent action, extra
|
||||||
|
const val ACT_REQUEST_MEDIA_PROJECTION = "REQUEST_MEDIA_PROJECTION"
|
||||||
|
const val ACT_INIT_MEDIA_PROJECTION_AND_SERVICE = "INIT_MEDIA_PROJECTION_AND_SERVICE"
|
||||||
|
const val ACT_LOGIN_REQ_NOTIFY = "LOGIN_REQ_NOTIFY"
|
||||||
|
const val EXT_INIT_FROM_BOOT = "EXT_INIT_FROM_BOOT"
|
||||||
|
const val EXT_MEDIA_PROJECTION_RES_INTENT = "MEDIA_PROJECTION_RES_INTENT"
|
||||||
|
const val EXT_LOGIN_REQ_NOTIFY = "LOGIN_REQ_NOTIFY"
|
||||||
|
|
||||||
|
// Activity requestCode
|
||||||
|
const val REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION = 101
|
||||||
|
const val REQ_REQUEST_MEDIA_PROJECTION = 201
|
||||||
|
|
||||||
|
// Activity responseCode
|
||||||
|
const val RES_FAILED = -100
|
||||||
|
|
||||||
|
// Flutter channel
|
||||||
|
const val START_ACTION = "start_action"
|
||||||
|
const val GET_START_ON_BOOT_OPT = "get_start_on_boot_opt"
|
||||||
|
const val SET_START_ON_BOOT_OPT = "set_start_on_boot_opt"
|
||||||
|
const val SYNC_APP_DIR_CONFIG_PATH = "sync_app_dir"
|
||||||
|
|
||||||
|
const val KEY_SHARED_PREFERENCES = "KEY_SHARED_PREFERENCES"
|
||||||
|
const val KEY_START_ON_BOOT_OPT = "KEY_START_ON_BOOT_OPT"
|
||||||
|
const val KEY_APP_DIR_CONFIG_PATH = "KEY_APP_DIR_CONFIG_PATH"
|
||||||
|
|
||||||
@SuppressLint("ConstantLocale")
|
@SuppressLint("ConstantLocale")
|
||||||
val LOCAL_NAME = Locale.getDefault().toString()
|
val LOCAL_NAME = Locale.getDefault().toString()
|
||||||
val SCREEN_INFO = Info(0, 0, 1, 200)
|
val SCREEN_INFO = Info(0, 0, 1, 200)
|
||||||
@@ -30,61 +56,13 @@ data class Info(
|
|||||||
var width: Int, var height: Int, var scale: Int, var dpi: Int
|
var width: Int, var height: Int, var scale: Int, var dpi: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
fun testVP9Support(): Boolean {
|
|
||||||
return true
|
|
||||||
val res = MediaCodecList(MediaCodecList.ALL_CODECS)
|
|
||||||
.findEncoderForFormat(
|
|
||||||
MediaFormat.createVideoFormat(
|
|
||||||
MediaFormat.MIMETYPE_VIDEO_VP9,
|
|
||||||
SCREEN_INFO.width,
|
|
||||||
SCREEN_INFO.width
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return res != null
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
|
||||||
fun requestPermission(context: Context, type: String) {
|
fun requestPermission(context: Context, type: String) {
|
||||||
val permission = when (type) {
|
|
||||||
"ignore_battery_optimizations" -> {
|
|
||||||
try {
|
|
||||||
context.startActivity(Intent(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
|
||||||
data = Uri.parse("package:" + context.packageName)
|
|
||||||
})
|
|
||||||
} catch (e:Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
"application_details_settings" -> {
|
|
||||||
try {
|
|
||||||
context.startActivity(Intent().apply {
|
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
action = "android.settings.APPLICATION_DETAILS_SETTINGS"
|
|
||||||
data = Uri.parse("package:" + context.packageName)
|
|
||||||
})
|
|
||||||
} catch (e:Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
"audio" -> {
|
|
||||||
Permission.RECORD_AUDIO
|
|
||||||
}
|
|
||||||
"file" -> {
|
|
||||||
Permission.MANAGE_EXTERNAL_STORAGE
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
XXPermissions.with(context)
|
XXPermissions.with(context)
|
||||||
.permission(permission)
|
.permission(type)
|
||||||
.request { _, all ->
|
.request { _, all ->
|
||||||
if (all) {
|
if (all) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
MainActivity.flutterMethodChannel.invokeMethod(
|
MainActivity.flutterMethodChannel?.invokeMethod(
|
||||||
"on_android_permission_result",
|
"on_android_permission_result",
|
||||||
mapOf("type" to type, "result" to all)
|
mapOf("type" to type, "result" to all)
|
||||||
)
|
)
|
||||||
@@ -93,24 +71,18 @@ fun requestPermission(context: Context, type: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
fun startAction(context: Context, action: String) {
|
||||||
fun checkPermission(context: Context, type: String): Boolean {
|
try {
|
||||||
val permission = when (type) {
|
context.startActivity(Intent(action).apply {
|
||||||
"ignore_battery_optimizations" -> {
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
val pw = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
// don't pass package name when launch ACTION_ACCESSIBILITY_SETTINGS
|
||||||
return pw.isIgnoringBatteryOptimizations(context.packageName)
|
if (ACTION_ACCESSIBILITY_SETTINGS != action) {
|
||||||
|
data = Uri.parse("package:" + context.packageName)
|
||||||
}
|
}
|
||||||
"audio" -> {
|
})
|
||||||
Permission.RECORD_AUDIO
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
"file" -> {
|
|
||||||
Permission.MANAGE_EXTERNAL_STORAGE
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return XXPermissions.isGranted(context, permission)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AudioReader(val bufSize: Int, private val maxFrames: Int) {
|
class AudioReader(val bufSize: Int, private val maxFrames: Int) {
|
||||||
|
|||||||
@@ -15,4 +15,12 @@
|
|||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
|
<style name="Transparent" parent="Theme.AppCompat.NoActionBar">
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:windowIsFloating">true</item>
|
||||||
|
<item name="android:backgroundDimEnabled">false</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -109,27 +109,32 @@ class IconFont {
|
|||||||
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||||
const ColorThemeExtension({
|
const ColorThemeExtension({
|
||||||
required this.border,
|
required this.border,
|
||||||
|
required this.border2,
|
||||||
required this.highlight,
|
required this.highlight,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Color? border;
|
final Color? border;
|
||||||
|
final Color? border2;
|
||||||
final Color? highlight;
|
final Color? highlight;
|
||||||
|
|
||||||
static const light = ColorThemeExtension(
|
static const light = ColorThemeExtension(
|
||||||
border: Color(0xFFCCCCCC),
|
border: Color(0xFFCCCCCC),
|
||||||
|
border2: Color(0xFFBBBBBB),
|
||||||
highlight: Color(0xFFE5E5E5),
|
highlight: Color(0xFFE5E5E5),
|
||||||
);
|
);
|
||||||
|
|
||||||
static const dark = ColorThemeExtension(
|
static const dark = ColorThemeExtension(
|
||||||
border: Color(0xFF555555),
|
border: Color(0xFF555555),
|
||||||
|
border2: Color(0xFFE5E5E5),
|
||||||
highlight: Color(0xFF3F3F3F),
|
highlight: Color(0xFF3F3F3F),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ThemeExtension<ColorThemeExtension> copyWith(
|
ThemeExtension<ColorThemeExtension> copyWith(
|
||||||
{Color? border, Color? highlight}) {
|
{Color? border, Color? border2, Color? highlight}) {
|
||||||
return ColorThemeExtension(
|
return ColorThemeExtension(
|
||||||
border: border ?? this.border,
|
border: border ?? this.border,
|
||||||
|
border2: border2 ?? this.border2,
|
||||||
highlight: highlight ?? this.highlight,
|
highlight: highlight ?? this.highlight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -142,6 +147,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
|||||||
}
|
}
|
||||||
return ColorThemeExtension(
|
return ColorThemeExtension(
|
||||||
border: Color.lerp(border, other.border, t),
|
border: Color.lerp(border, other.border, t),
|
||||||
|
border2: Color.lerp(border2, other.border2, t),
|
||||||
highlight: Color.lerp(highlight, other.highlight, t),
|
highlight: Color.lerp(highlight, other.highlight, t),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -207,41 +213,33 @@ class MyTheme {
|
|||||||
splashFactory: isDesktop ? NoSplash.splashFactory : null,
|
splashFactory: isDesktop ? NoSplash.splashFactory : null,
|
||||||
textButtonTheme: isDesktop
|
textButtonTheme: isDesktop
|
||||||
? TextButtonThemeData(
|
? TextButtonThemeData(
|
||||||
style: ButtonStyle(
|
style: TextButton.styleFrom(
|
||||||
splashFactory: NoSplash.splashFactory,
|
splashFactory: NoSplash.splashFactory,
|
||||||
shape: MaterialStatePropertyAll<RoundedRectangleBorder>(
|
shape: RoundedRectangleBorder(
|
||||||
RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(18.0),
|
borderRadius: BorderRadius.circular(18.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
style: ButtonStyle(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: MaterialStatePropertyAll(
|
backgroundColor: MyTheme.accent,
|
||||||
MyTheme.accent,
|
shape: RoundedRectangleBorder(
|
||||||
),
|
|
||||||
shape: MaterialStatePropertyAll<RoundedRectangleBorder>(
|
|
||||||
RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||||
style: ButtonStyle(
|
style: OutlinedButton.styleFrom(
|
||||||
backgroundColor: MaterialStatePropertyAll(
|
backgroundColor: Color(
|
||||||
Color(0xFFEEEEEE),
|
0xFFEEEEEE,
|
||||||
),
|
),
|
||||||
foregroundColor: MaterialStatePropertyAll(Colors.black87),
|
foregroundColor: Colors.black87,
|
||||||
shape: MaterialStatePropertyAll<RoundedRectangleBorder>(
|
shape: RoundedRectangleBorder(
|
||||||
RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
checkboxTheme: const CheckboxThemeData(
|
checkboxTheme: const CheckboxThemeData(
|
||||||
splashRadius: 0,
|
splashRadius: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
@@ -306,49 +304,45 @@ class MyTheme {
|
|||||||
tabBarTheme: const TabBarTheme(
|
tabBarTheme: const TabBarTheme(
|
||||||
labelColor: Colors.white70,
|
labelColor: Colors.white70,
|
||||||
),
|
),
|
||||||
|
scrollbarTheme: ScrollbarThemeData(
|
||||||
|
thumbColor: MaterialStateProperty.all(Colors.grey[500]),
|
||||||
|
),
|
||||||
splashColor: Colors.transparent,
|
splashColor: Colors.transparent,
|
||||||
highlightColor: Colors.transparent,
|
highlightColor: Colors.transparent,
|
||||||
splashFactory: isDesktop ? NoSplash.splashFactory : null,
|
splashFactory: isDesktop ? NoSplash.splashFactory : null,
|
||||||
textButtonTheme: isDesktop
|
textButtonTheme: isDesktop
|
||||||
? TextButtonThemeData(
|
? TextButtonThemeData(
|
||||||
style: ButtonStyle(
|
style: TextButton.styleFrom(
|
||||||
splashFactory: NoSplash.splashFactory,
|
splashFactory: NoSplash.splashFactory,
|
||||||
shape: MaterialStatePropertyAll<RoundedRectangleBorder>(
|
disabledForegroundColor: Colors.white70,
|
||||||
RoundedRectangleBorder(
|
foregroundColor: Colors.white70,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(18.0),
|
borderRadius: BorderRadius.circular(18.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
style: ButtonStyle(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: MaterialStatePropertyAll(
|
backgroundColor: MyTheme.accent,
|
||||||
MyTheme.accent,
|
disabledForegroundColor: Colors.white70,
|
||||||
),
|
disabledBackgroundColor: Colors.white10,
|
||||||
shape: MaterialStatePropertyAll<RoundedRectangleBorder>(
|
shape: RoundedRectangleBorder(
|
||||||
RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||||
style: ButtonStyle(
|
style: OutlinedButton.styleFrom(
|
||||||
backgroundColor: MaterialStatePropertyAll(
|
backgroundColor: Color(0xFF24252B),
|
||||||
Color(0xFF24252B),
|
side: BorderSide(color: Colors.white12, width: 0.5),
|
||||||
),
|
disabledForegroundColor: Colors.white70,
|
||||||
side: MaterialStatePropertyAll(
|
foregroundColor: Colors.white70,
|
||||||
BorderSide(color: Colors.white12, width: 0.5),
|
shape: RoundedRectangleBorder(
|
||||||
),
|
|
||||||
foregroundColor: MaterialStatePropertyAll(Colors.white70),
|
|
||||||
shape: MaterialStatePropertyAll<RoundedRectangleBorder>(
|
|
||||||
RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
checkboxTheme: const CheckboxThemeData(
|
checkboxTheme: const CheckboxThemeData(
|
||||||
checkColor: MaterialStatePropertyAll(dark),
|
checkColor: MaterialStatePropertyAll(dark),
|
||||||
splashRadius: 0,
|
splashRadius: 0,
|
||||||
@@ -1045,21 +1039,14 @@ class AccessibilityListener extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PermissionManager {
|
class AndroidPermissionManager {
|
||||||
static Completer<bool>? _completer;
|
static Completer<bool>? _completer;
|
||||||
static Timer? _timer;
|
static Timer? _timer;
|
||||||
static var _current = "";
|
static var _current = "";
|
||||||
|
|
||||||
static final permissions = [
|
|
||||||
"audio",
|
|
||||||
"file",
|
|
||||||
"ignore_battery_optimizations",
|
|
||||||
"application_details_settings"
|
|
||||||
];
|
|
||||||
|
|
||||||
static bool isWaitingFile() {
|
static bool isWaitingFile() {
|
||||||
if (_completer != null) {
|
if (_completer != null) {
|
||||||
return !_completer!.isCompleted && _current == "file";
|
return !_completer!.isCompleted && _current == kManageExternalStorage;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1068,31 +1055,33 @@ class PermissionManager {
|
|||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
return Future.value(true);
|
return Future.value(true);
|
||||||
}
|
}
|
||||||
if (!permissions.contains(type)) {
|
|
||||||
return Future.error("Wrong permission!$type");
|
|
||||||
}
|
|
||||||
return gFFI.invokeMethod("check_permission", type);
|
return gFFI.invokeMethod("check_permission", type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// startActivity goto Android Setting's page to request permission manually by user
|
||||||
|
static void startAction(String action) {
|
||||||
|
gFFI.invokeMethod(AndroidChannel.kStartAction, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We use XXPermissions to request permissions,
|
||||||
|
/// for supported types, see https://github.com/getActivity/XXPermissions/blob/e46caea32a64ad7819df62d448fb1c825481cd28/library/src/main/java/com/hjq/permissions/Permission.java
|
||||||
static Future<bool> request(String type) {
|
static Future<bool> request(String type) {
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
return Future.value(true);
|
return Future.value(true);
|
||||||
}
|
}
|
||||||
if (!permissions.contains(type)) {
|
|
||||||
return Future.error("Wrong permission!$type");
|
|
||||||
}
|
|
||||||
|
|
||||||
gFFI.invokeMethod("request_permission", type);
|
gFFI.invokeMethod("request_permission", type);
|
||||||
if (type == "ignore_battery_optimizations") {
|
|
||||||
return Future.value(false);
|
// clear last task
|
||||||
|
if (_completer?.isCompleted == false) {
|
||||||
|
_completer?.complete(false);
|
||||||
}
|
}
|
||||||
|
_timer?.cancel();
|
||||||
|
|
||||||
_current = type;
|
_current = type;
|
||||||
_completer = Completer<bool>();
|
_completer = Completer<bool>();
|
||||||
gFFI.invokeMethod("request_permission", type);
|
|
||||||
|
|
||||||
// timeout
|
_timer = Timer(Duration(seconds: 120), () {
|
||||||
_timer?.cancel();
|
|
||||||
_timer = Timer(Duration(seconds: 60), () {
|
|
||||||
if (_completer == null) return;
|
if (_completer == null) return;
|
||||||
if (!_completer!.isCompleted) {
|
if (!_completer!.isCompleted) {
|
||||||
_completer!.complete(false);
|
_completer!.complete(false);
|
||||||
@@ -1622,8 +1611,8 @@ connect(BuildContext context, String id,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isFileTransfer) {
|
if (isFileTransfer) {
|
||||||
if (!await PermissionManager.check("file")) {
|
if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
|
||||||
if (!await PermissionManager.request("file")) {
|
if (!await AndroidPermissionManager.request(kManageExternalStorage)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
|
|
||||||
const double kDesktopRemoteTabBarHeight = 28.0;
|
const double kDesktopRemoteTabBarHeight = 28.0;
|
||||||
const int kMainWindowId = 0;
|
const int kMainWindowId = 0;
|
||||||
@@ -58,6 +59,12 @@ const double kDesktopFileTransferMaximumWidth = 300;
|
|||||||
const double kDesktopFileTransferRowHeight = 30.0;
|
const double kDesktopFileTransferRowHeight = 30.0;
|
||||||
const double kDesktopFileTransferHeaderHeight = 25.0;
|
const double kDesktopFileTransferHeaderHeight = 25.0;
|
||||||
|
|
||||||
|
EdgeInsets get kDragToResizeAreaPadding =>
|
||||||
|
!kUseCompatibleUiMode && Platform.isLinux
|
||||||
|
? stateGlobal.fullscreen || stateGlobal.maximize
|
||||||
|
? EdgeInsets.zero
|
||||||
|
: EdgeInsets.all(5.0)
|
||||||
|
: EdgeInsets.zero;
|
||||||
// https://en.wikipedia.org/wiki/Non-breaking_space
|
// https://en.wikipedia.org/wiki/Non-breaking_space
|
||||||
const int $nbsp = 0x00A0;
|
const int $nbsp = 0x00A0;
|
||||||
|
|
||||||
@@ -79,6 +86,7 @@ const kDefaultScrollAmountMultiplier = 5.0;
|
|||||||
const kDefaultScrollDuration = Duration(milliseconds: 50);
|
const kDefaultScrollDuration = Duration(milliseconds: 50);
|
||||||
const kDefaultMouseWheelThrottleDuration = Duration(milliseconds: 50);
|
const kDefaultMouseWheelThrottleDuration = Duration(milliseconds: 50);
|
||||||
const kFullScreenEdgeSize = 0.0;
|
const kFullScreenEdgeSize = 0.0;
|
||||||
|
const kMaximizeEdgeSize = 0.0;
|
||||||
var kWindowEdgeSize = Platform.isWindows ? 1.0 : 5.0;
|
var kWindowEdgeSize = Platform.isWindows ? 1.0 : 5.0;
|
||||||
const kWindowBorderWidth = 1.0;
|
const kWindowBorderWidth = 1.0;
|
||||||
const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0);
|
const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0);
|
||||||
@@ -129,6 +137,25 @@ const kRemoteAudioDualWay = 'dual-way';
|
|||||||
|
|
||||||
const kIgnoreDpi = true;
|
const kIgnoreDpi = true;
|
||||||
|
|
||||||
|
/// Android constants
|
||||||
|
const kActionApplicationDetailsSettings =
|
||||||
|
"android.settings.APPLICATION_DETAILS_SETTINGS";
|
||||||
|
const kActionAccessibilitySettings = "android.settings.ACCESSIBILITY_SETTINGS";
|
||||||
|
|
||||||
|
const kRecordAudio = "android.permission.RECORD_AUDIO";
|
||||||
|
const kManageExternalStorage = "android.permission.MANAGE_EXTERNAL_STORAGE";
|
||||||
|
const kRequestIgnoreBatteryOptimizations =
|
||||||
|
"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
|
||||||
|
const kSystemAlertWindow = "android.permission.SYSTEM_ALERT_WINDOW";
|
||||||
|
|
||||||
|
/// Android channel invoke type key
|
||||||
|
class AndroidChannel {
|
||||||
|
static final kStartAction = "start_action";
|
||||||
|
static final kGetStartOnBootOpt = "get_start_on_boot_opt";
|
||||||
|
static final kSetStartOnBootOpt = "set_start_on_boot_opt";
|
||||||
|
static final kSyncAppDirConfigPath = "sync_app_dir";
|
||||||
|
}
|
||||||
|
|
||||||
/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
|
/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
|
||||||
/// see [LogicalKeyboardKey.keyLabel]
|
/// see [LogicalKeyboardKey.keyLabel]
|
||||||
const Map<int, String> logicalKeyMap = <int, String>{
|
const Map<int, String> logicalKeyMap = <int, String>{
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
|||||||
import '../../common/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
import '../../common/widgets/login.dart';
|
import '../../common/widgets/login.dart';
|
||||||
|
|
||||||
const double _kTabWidth = 235;
|
const double _kTabWidth = 200;
|
||||||
const double _kTabHeight = 42;
|
const double _kTabHeight = 42;
|
||||||
const double _kCardFixedWidth = 540;
|
const double _kCardFixedWidth = 540;
|
||||||
const double _kCardLeftMargin = 15;
|
const double _kCardLeftMargin = 15;
|
||||||
@@ -538,6 +538,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
translate('Screen Share'),
|
translate('Screen Share'),
|
||||||
translate('Deny remote access'),
|
translate('Deny remote access'),
|
||||||
],
|
],
|
||||||
|
enabled: enabled,
|
||||||
initialKey: initialKey,
|
initialKey: initialKey,
|
||||||
onChanged: (mode) async {
|
onChanged: (mode) async {
|
||||||
String modeValue;
|
String modeValue;
|
||||||
@@ -667,6 +668,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
|
|
||||||
return _Card(title: 'Password', children: [
|
return _Card(title: 'Password', children: [
|
||||||
_ComboBox(
|
_ComboBox(
|
||||||
|
enabled: !locked,
|
||||||
keys: modeKeys,
|
keys: modeKeys,
|
||||||
values: modeValues,
|
values: modeValues,
|
||||||
initialKey: modeInitialKey,
|
initialKey: modeInitialKey,
|
||||||
@@ -1722,7 +1724,6 @@ class _ComboBox extends StatelessWidget {
|
|||||||
required this.values,
|
required this.values,
|
||||||
required this.initialKey,
|
required this.initialKey,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
// ignore: unused_element
|
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@@ -1735,7 +1736,12 @@ class _ComboBox extends StatelessWidget {
|
|||||||
var ref = values[index].obs;
|
var ref = values[index].obs;
|
||||||
current = keys[index];
|
current = keys[index];
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(border: Border.all(color: MyTheme.border)),
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: enabled
|
||||||
|
? MyTheme.color(context).border2 ?? MyTheme.border
|
||||||
|
: MyTheme.border,
|
||||||
|
)),
|
||||||
height: 30,
|
height: 30,
|
||||||
child: Obx(() => DropdownButton<String>(
|
child: Obx(() => DropdownButton<String>(
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
@@ -1744,6 +1750,10 @@ class _ComboBox extends StatelessWidget {
|
|||||||
underline: Container(
|
underline: Container(
|
||||||
height: 25,
|
height: 25,
|
||||||
),
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
color: enabled
|
||||||
|
? Theme.of(context).textTheme.titleMedium?.color
|
||||||
|
: _disabledTextColor(context, enabled)),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.expand_more_sharp,
|
Icons.expand_more_sharp,
|
||||||
size: 20,
|
size: 20,
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
|||||||
isClose: false,
|
isClose: false,
|
||||||
),
|
),
|
||||||
)));
|
)));
|
||||||
return Platform.isMacOS
|
return Platform.isMacOS || kUseCompatibleUiMode
|
||||||
? tabWidget
|
? tabWidget
|
||||||
: Obx(
|
: Obx(
|
||||||
() => DragToResizeArea(
|
() => DragToResizeArea(
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
|||||||
labelGetter: DesktopTab.labelGetterAlias,
|
labelGetter: DesktopTab.labelGetterAlias,
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
return Platform.isMacOS
|
return Platform.isMacOS || kUseCompatibleUiMode
|
||||||
? tabWidget
|
? tabWidget
|
||||||
: SubWindowDragToResizeArea(
|
: SubWindowDragToResizeArea(
|
||||||
child: tabWidget,
|
child: tabWidget,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
@@ -13,7 +15,51 @@ class InstallPage extends StatefulWidget {
|
|||||||
State<InstallPage> createState() => _InstallPageState();
|
State<InstallPage> createState() => _InstallPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InstallPageState extends State<InstallPage> with WindowListener {
|
class _InstallPageState extends State<InstallPage> {
|
||||||
|
final tabController = DesktopTabController(tabType: DesktopTabType.main);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
Get.put<DesktopTabController>(tabController);
|
||||||
|
const lable = "install";
|
||||||
|
tabController.add(TabInfo(
|
||||||
|
key: lable,
|
||||||
|
label: lable,
|
||||||
|
closable: false,
|
||||||
|
page: _InstallPageBody(
|
||||||
|
key: const ValueKey(lable),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
Get.delete<DesktopTabController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DragToResizeArea(
|
||||||
|
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
||||||
|
child: Container(
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
|
body: DesktopTab(controller: tabController)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InstallPageBody extends StatefulWidget {
|
||||||
|
const _InstallPageBody({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_InstallPageBody> createState() => _InstallPageBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InstallPageBodyState extends State<_InstallPageBody>
|
||||||
|
with WindowListener {
|
||||||
late final TextEditingController controller;
|
late final TextEditingController controller;
|
||||||
final RxBool startmenu = true.obs;
|
final RxBool startmenu = true.obs;
|
||||||
final RxBool desktopicon = true.obs;
|
final RxBool desktopicon = true.obs;
|
||||||
@@ -46,15 +92,19 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
|||||||
final double em = 13;
|
final double em = 13;
|
||||||
final btnFontSize = 0.9 * em;
|
final btnFontSize = 0.9 * em;
|
||||||
final double button_radius = 6;
|
final double button_radius = 6;
|
||||||
|
final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark;
|
||||||
final buttonStyle = OutlinedButton.styleFrom(
|
final buttonStyle = OutlinedButton.styleFrom(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(button_radius)),
|
borderRadius: BorderRadius.all(Radius.circular(button_radius)),
|
||||||
));
|
));
|
||||||
final inputBorder = OutlineInputBorder(
|
final inputBorder = OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.zero,
|
borderRadius: BorderRadius.zero,
|
||||||
borderSide: BorderSide(color: Colors.black12));
|
borderSide:
|
||||||
|
BorderSide(color: isDarkTheme ? Colors.white70 : Colors.black12));
|
||||||
|
final textColor = isDarkTheme ? null : Colors.black87;
|
||||||
|
final dividerColor = isDarkTheme ? Colors.white70 : Colors.black87;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: null,
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -91,8 +141,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
|||||||
style: buttonStyle,
|
style: buttonStyle,
|
||||||
child: Text(translate('Change Path'),
|
child: Text(translate('Change Path'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black87,
|
color: textColor, fontSize: btnFontSize)))
|
||||||
fontSize: btnFontSize)))
|
|
||||||
.marginOnly(left: em))
|
.marginOnly(left: em))
|
||||||
],
|
],
|
||||||
).marginSymmetric(vertical: 2 * em),
|
).marginSymmetric(vertical: 2 * em),
|
||||||
@@ -127,8 +176,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
|||||||
)).marginOnly(top: 2 * em),
|
)).marginOnly(top: 2 * em),
|
||||||
Row(children: [Text(translate('agreement_tip'))])
|
Row(children: [Text(translate('agreement_tip'))])
|
||||||
.marginOnly(top: em),
|
.marginOnly(top: em),
|
||||||
Divider(color: Colors.black87)
|
Divider(color: dividerColor).marginSymmetric(vertical: 0.5 * em),
|
||||||
.marginSymmetric(vertical: 0.5 * em),
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -143,8 +191,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
|||||||
style: buttonStyle,
|
style: buttonStyle,
|
||||||
child: Text(translate('Cancel'),
|
child: Text(translate('Cancel'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black87,
|
color: textColor, fontSize: btnFontSize)))
|
||||||
fontSize: btnFontSize)))
|
|
||||||
.marginOnly(right: 2 * em)),
|
.marginOnly(right: 2 * em)),
|
||||||
Obx(() => ElevatedButton(
|
Obx(() => ElevatedButton(
|
||||||
onPressed: btnEnabled.value ? install : null,
|
onPressed: btnEnabled.value ? install : null,
|
||||||
@@ -167,8 +214,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
|
|||||||
style: buttonStyle,
|
style: buttonStyle,
|
||||||
child: Text(translate('Run without install'),
|
child: Text(translate('Run without install'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black87,
|
color: textColor, fontSize: btnFontSize)))
|
||||||
fontSize: btnFontSize)))
|
|
||||||
.marginOnly(left: 2 * em)),
|
.marginOnly(left: 2 * em)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -107,12 +107,14 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
|||||||
labelGetter: DesktopTab.labelGetterAlias,
|
labelGetter: DesktopTab.labelGetterAlias,
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
return Platform.isMacOS
|
return Platform.isMacOS || kUseCompatibleUiMode
|
||||||
? tabWidget
|
? tabWidget
|
||||||
: SubWindowDragToResizeArea(
|
: Obx(
|
||||||
|
() => SubWindowDragToResizeArea(
|
||||||
child: tabWidget,
|
child: tabWidget,
|
||||||
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
||||||
windowId: stateGlobal.windowId,
|
windowId: stateGlobal.windowId,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -205,11 +205,13 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return Platform.isMacOS
|
return Platform.isMacOS || kUseCompatibleUiMode
|
||||||
? tabWidget
|
? tabWidget
|
||||||
: Obx(() => SubWindowDragToResizeArea(
|
: Obx(() => SubWindowDragToResizeArea(
|
||||||
key: contentKey,
|
key: contentKey,
|
||||||
child: tabWidget,
|
child: tabWidget,
|
||||||
|
// Specially configured for a better resize area and remote control.
|
||||||
|
childPadding: kDragToResizeAreaPadding,
|
||||||
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
|
||||||
windowId: stateGlobal.windowId,
|
windowId: stateGlobal.windowId,
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/remote_tab_page.dart';
|
import 'package:flutter_hbb/desktop/pages/remote_tab_page.dart';
|
||||||
@@ -26,6 +28,9 @@ class DesktopRemoteScreen extends StatelessWidget {
|
|||||||
ChangeNotifierProvider.value(value: gFFI.canvasModel),
|
ChangeNotifierProvider.value(value: gFFI.canvasModel),
|
||||||
],
|
],
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
// Set transparent background for padding the resize area out of the flutter view.
|
||||||
|
// This allows the wallpaper goes through our resize area. (Linux only now).
|
||||||
|
backgroundColor: Platform.isLinux ? Colors.transparent : null,
|
||||||
body: ConnectionTabPage(
|
body: ConnectionTabPage(
|
||||||
params: params,
|
params: params,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -942,6 +942,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
disableClipboard(),
|
disableClipboard(),
|
||||||
lockAfterSessionEnd(),
|
lockAfterSessionEnd(),
|
||||||
privacyMode(),
|
privacyMode(),
|
||||||
|
swapKey(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,12 +976,13 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
|
|
||||||
final canvasModel = widget.ffi.canvasModel;
|
final canvasModel = widget.ffi.canvasModel;
|
||||||
final width = (canvasModel.getDisplayWidth() * canvasModel.scale +
|
final width = (canvasModel.getDisplayWidth() * canvasModel.scale +
|
||||||
canvasModel.windowBorderWidth * 2) *
|
CanvasModel.leftToEdge +
|
||||||
|
CanvasModel.rightToEdge) *
|
||||||
scale +
|
scale +
|
||||||
magicWidth;
|
magicWidth;
|
||||||
final height = (canvasModel.getDisplayHeight() * canvasModel.scale +
|
final height = (canvasModel.getDisplayHeight() * canvasModel.scale +
|
||||||
canvasModel.tabBarHeight +
|
CanvasModel.topToEdge +
|
||||||
canvasModel.windowBorderWidth * 2) *
|
CanvasModel.bottomToEdge) *
|
||||||
scale +
|
scale +
|
||||||
magicHeight;
|
magicHeight;
|
||||||
double left = wndRect.left + (wndRect.width - width) / 2;
|
double left = wndRect.left + (wndRect.width - width) / 2;
|
||||||
@@ -1049,10 +1051,10 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
final canvasModel = widget.ffi.canvasModel;
|
final canvasModel = widget.ffi.canvasModel;
|
||||||
final displayWidth = canvasModel.getDisplayWidth();
|
final displayWidth = canvasModel.getDisplayWidth();
|
||||||
final displayHeight = canvasModel.getDisplayHeight();
|
final displayHeight = canvasModel.getDisplayHeight();
|
||||||
final requiredWidth = displayWidth +
|
final requiredWidth =
|
||||||
(canvasModel.tabBarHeight + canvasModel.windowBorderWidth * 2);
|
CanvasModel.leftToEdge + displayWidth + CanvasModel.rightToEdge;
|
||||||
final requiredHeight = displayHeight +
|
final requiredHeight =
|
||||||
(canvasModel.tabBarHeight + canvasModel.windowBorderWidth * 2);
|
CanvasModel.topToEdge + displayHeight + CanvasModel.bottomToEdge;
|
||||||
return selfWidth > (requiredWidth * scale) &&
|
return selfWidth > (requiredWidth * scale) &&
|
||||||
selfHeight > (requiredHeight * scale);
|
selfHeight > (requiredHeight * scale);
|
||||||
}
|
}
|
||||||
@@ -1549,6 +1551,23 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
child: Text(translate('Privacy mode')));
|
child: Text(translate('Privacy mode')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
swapKey() {
|
||||||
|
final visible = perms['keyboard'] != false &&
|
||||||
|
((Platform.isMacOS && pi.platform != kPeerPlatformMacOS) ||
|
||||||
|
(!Platform.isMacOS && pi.platform == kPeerPlatformMacOS));
|
||||||
|
if (!visible) return Offstage();
|
||||||
|
final option = 'allow_swap_key';
|
||||||
|
final value = bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
|
||||||
|
return _CheckboxMenuButton(
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
bind.sessionToggleOption(id: widget.id, value: option);
|
||||||
|
},
|
||||||
|
ffi: widget.ffi,
|
||||||
|
child: Text(translate('Swap control-command key')));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _KeyboardMenu extends StatelessWidget {
|
class _KeyboardMenu extends StatelessWidget {
|
||||||
@@ -1564,9 +1583,8 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Do not check permission here?
|
var ffiModel = Provider.of<FfiModel>(context);
|
||||||
// var ffiModel = Provider.of<FfiModel>(context);
|
if (ffiModel.permissions['keyboard'] == false) return Offstage();
|
||||||
// if (ffiModel.permissions['keyboard'] == false) return Offstage();
|
|
||||||
if (stateGlobal.grabKeyboard) {
|
if (stateGlobal.grabKeyboard) {
|
||||||
if (bind.sessionIsKeyboardModeSupported(id: id, mode: _kKeyMapMode)) {
|
if (bind.sessionIsKeyboardModeSupported(id: id, mode: _kKeyMapMode)) {
|
||||||
bind.sessionSetKeyboardMode(id: id, value: _kKeyMapMode);
|
bind.sessionSetKeyboardMode(id: id, value: _kKeyMapMode);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class DesktopScrollWrapper extends StatelessWidget {
|
|||||||
return ImprovedScrolling(
|
return ImprovedScrolling(
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
enableCustomMouseWheelScrolling: true,
|
enableCustomMouseWheelScrolling: true,
|
||||||
|
// enableKeyboardScrolling: true, // strange behavior on mac
|
||||||
customMouseWheelScrollConfig: CustomMouseWheelScrollConfig(
|
customMouseWheelScrollConfig: CustomMouseWheelScrollConfig(
|
||||||
scrollDuration: kDefaultScrollDuration,
|
scrollDuration: kDefaultScrollDuration,
|
||||||
scrollCurve: Curves.linearToEaseOut,
|
scrollCurve: Curves.linearToEaseOut,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ enum DesktopTabType {
|
|||||||
remoteScreen,
|
remoteScreen,
|
||||||
fileTransfer,
|
fileTransfer,
|
||||||
portForward,
|
portForward,
|
||||||
|
install,
|
||||||
}
|
}
|
||||||
|
|
||||||
class DesktopTabState {
|
class DesktopTabState {
|
||||||
@@ -249,8 +250,9 @@ class DesktopTab extends StatelessWidget {
|
|||||||
this.unSelectedTabBackgroundColor,
|
this.unSelectedTabBackgroundColor,
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
tabType = controller.tabType;
|
tabType = controller.tabType;
|
||||||
isMainWindow =
|
isMainWindow = tabType == DesktopTabType.main ||
|
||||||
tabType == DesktopTabType.main || tabType == DesktopTabType.cm;
|
tabType == DesktopTabType.cm ||
|
||||||
|
tabType == DesktopTabType.install;
|
||||||
}
|
}
|
||||||
|
|
||||||
static RxString labelGetterAlias(String peerId) {
|
static RxString labelGetterAlias(String peerId) {
|
||||||
@@ -361,7 +363,8 @@ class DesktopTab extends StatelessWidget {
|
|||||||
/// - hide single item when only has one item (home) on [DesktopTabPage].
|
/// - hide single item when only has one item (home) on [DesktopTabPage].
|
||||||
bool isHideSingleItem() {
|
bool isHideSingleItem() {
|
||||||
return state.value.tabs.length == 1 &&
|
return state.value.tabs.length == 1 &&
|
||||||
controller.tabType == DesktopTabType.main;
|
(controller.tabType == DesktopTabType.main ||
|
||||||
|
controller.tabType == DesktopTabType.install);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBar() {
|
Widget _buildBar() {
|
||||||
@@ -523,12 +526,18 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setMaximize(bool maximize) {
|
||||||
|
stateGlobal.setMaximize(maximize);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onWindowMaximize() {
|
void onWindowMaximize() {
|
||||||
// catch maximize from system
|
// catch maximize from system
|
||||||
if (!widget.isMaximized.value) {
|
if (!widget.isMaximized.value) {
|
||||||
widget.isMaximized.value = true;
|
widget.isMaximized.value = true;
|
||||||
}
|
}
|
||||||
|
_setMaximize(true);
|
||||||
super.onWindowMaximize();
|
super.onWindowMaximize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,6 +547,7 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
if (widget.isMaximized.value) {
|
if (widget.isMaximized.value) {
|
||||||
widget.isMaximized.value = false;
|
widget.isMaximized.value = false;
|
||||||
}
|
}
|
||||||
|
_setMaximize(false);
|
||||||
super.onWindowUnmaximize();
|
super.onWindowUnmaximize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -752,7 +762,8 @@ class _ListView extends StatelessWidget {
|
|||||||
/// - hide single item when only has one item (home) on [DesktopTabPage].
|
/// - hide single item when only has one item (home) on [DesktopTabPage].
|
||||||
bool isHideSingleItem() {
|
bool isHideSingleItem() {
|
||||||
return state.value.tabs.length == 1 &&
|
return state.value.tabs.length == 1 &&
|
||||||
controller.tabType == DesktopTabType.main;
|
controller.tabType == DesktopTabType.main ||
|
||||||
|
controller.tabType == DesktopTabType.install;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ void runMainApp(bool startService) async {
|
|||||||
void runMobileApp() async {
|
void runMobileApp() async {
|
||||||
await initEnv(kAppTypeMain);
|
await initEnv(kAppTypeMain);
|
||||||
if (isAndroid) androidChannelInit();
|
if (isAndroid) androidChannelInit();
|
||||||
|
platformFFI.syncAndroidServiceAppDirConfigPath();
|
||||||
runApp(App());
|
runApp(App());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,17 +292,20 @@ void _runApp(
|
|||||||
void runInstallPage() async {
|
void runInstallPage() async {
|
||||||
await windowManager.ensureInitialized();
|
await windowManager.ensureInitialized();
|
||||||
await initEnv(kAppTypeMain);
|
await initEnv(kAppTypeMain);
|
||||||
_runApp('', const InstallPage(), ThemeMode.light);
|
_runApp('', const InstallPage(), MyTheme.currentThemeMode());
|
||||||
windowManager.waitUntilReadyToShow(
|
WindowOptions windowOptions =
|
||||||
WindowOptions(size: Size(800, 600), center: true), () async {
|
getHiddenTitleBarWindowOptions(size: Size(800, 600), center: true);
|
||||||
|
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
windowManager.show();
|
windowManager.show();
|
||||||
windowManager.focus();
|
windowManager.focus();
|
||||||
windowManager.setOpacity(1);
|
windowManager.setOpacity(1);
|
||||||
windowManager.setAlignment(Alignment.center); // ensure
|
windowManager.setAlignment(Alignment.center); // ensure
|
||||||
|
windowManager.setTitle(getWindowName());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
|
WindowOptions getHiddenTitleBarWindowOptions(
|
||||||
|
{Size? size, bool center = false}) {
|
||||||
var defaultTitleBarStyle = TitleBarStyle.hidden;
|
var defaultTitleBarStyle = TitleBarStyle.hidden;
|
||||||
// we do not hide titlebar on win7 because of the frame overflow.
|
// we do not hide titlebar on win7 because of the frame overflow.
|
||||||
if (kUseCompatibleUiMode) {
|
if (kUseCompatibleUiMode) {
|
||||||
@@ -309,7 +313,7 @@ WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
|
|||||||
}
|
}
|
||||||
return WindowOptions(
|
return WindowOptions(
|
||||||
size: size,
|
size: size,
|
||||||
center: false,
|
center: center,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
skipTaskbar: false,
|
skipTaskbar: false,
|
||||||
titleBarStyle: defaultTitleBarStyle,
|
titleBarStyle: defaultTitleBarStyle,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
|
import '../../consts.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../../models/server_model.dart';
|
import '../../models/server_model.dart';
|
||||||
import 'home_page.dart';
|
import 'home_page.dart';
|
||||||
@@ -40,14 +41,14 @@ class ServerPage extends StatefulWidget implements PageShape {
|
|||||||
value: "setTemporaryPasswordLength",
|
value: "setTemporaryPasswordLength",
|
||||||
enabled:
|
enabled:
|
||||||
gFFI.serverModel.verificationMethod != kUsePermanentPassword,
|
gFFI.serverModel.verificationMethod != kUsePermanentPassword,
|
||||||
child: Text(translate("Set temporary password length")),
|
child: Text(translate("One-time password length")),
|
||||||
),
|
),
|
||||||
const PopupMenuDivider(),
|
const PopupMenuDivider(),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 0.0),
|
padding: const EdgeInsets.symmetric(horizontal: 0.0),
|
||||||
value: kUseTemporaryPassword,
|
value: kUseTemporaryPassword,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text(translate("Use temporary password")),
|
title: Text(translate("Use one-time password")),
|
||||||
trailing: Icon(
|
trailing: Icon(
|
||||||
Icons.check,
|
Icons.check,
|
||||||
color: gFFI.serverModel.verificationMethod ==
|
color: gFFI.serverModel.verificationMethod ==
|
||||||
@@ -150,10 +151,11 @@ class _ServerPageState extends State<ServerPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void checkService() async {
|
void checkService() async {
|
||||||
gFFI.invokeMethod("check_service"); // jvm
|
gFFI.invokeMethod("check_service");
|
||||||
// for Android 10/11,MANAGE_EXTERNAL_STORAGE permission from a system setting page
|
// for Android 10/11, request MANAGE_EXTERNAL_STORAGE permission from system setting page
|
||||||
if (PermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) {
|
if (AndroidPermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) {
|
||||||
PermissionManager.complete("file", await PermissionManager.check("file"));
|
AndroidPermissionManager.complete(kManageExternalStorage,
|
||||||
|
await AndroidPermissionManager.check(kManageExternalStorage));
|
||||||
debugPrint("file permission finished");
|
debugPrint("file permission finished");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,7 +569,7 @@ void androidChannelInit() {
|
|||||||
{
|
{
|
||||||
var type = arguments["type"] as String;
|
var type = arguments["type"] as String;
|
||||||
var result = arguments["result"] as bool;
|
var result = arguments["result"] as bool;
|
||||||
PermissionManager.complete(type, result);
|
AndroidPermissionManager.complete(type, result);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "on_media_projection_canceled":
|
case "on_media_projection_canceled":
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
import '../../common/widgets/login.dart';
|
import '../../common/widgets/login.dart';
|
||||||
|
import '../../consts.dart';
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../widgets/dialog.dart';
|
import '../widgets/dialog.dart';
|
||||||
@@ -31,8 +32,11 @@ class SettingsPage extends StatefulWidget implements PageShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = 'https://rustdesk.com/';
|
const url = 'https://rustdesk.com/';
|
||||||
|
|
||||||
|
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||||
final _hasIgnoreBattery = androidVersion >= 26;
|
final _hasIgnoreBattery = androidVersion >= 26;
|
||||||
var _ignoreBatteryOpt = false;
|
var _ignoreBatteryOpt = false;
|
||||||
|
var _enableStartOnBoot = false;
|
||||||
var _enableAbr = false;
|
var _enableAbr = false;
|
||||||
var _denyLANDiscovery = false;
|
var _denyLANDiscovery = false;
|
||||||
var _onlyWhiteList = false;
|
var _onlyWhiteList = false;
|
||||||
@@ -42,7 +46,6 @@ var _autoRecordIncomingSession = false;
|
|||||||
var _localIP = "";
|
var _localIP = "";
|
||||||
var _directAccessPort = "";
|
var _directAccessPort = "";
|
||||||
|
|
||||||
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -50,11 +53,34 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
() async {
|
() async {
|
||||||
var update = false;
|
var update = false;
|
||||||
|
|
||||||
if (_hasIgnoreBattery) {
|
if (_hasIgnoreBattery) {
|
||||||
update = await updateIgnoreBatteryStatus();
|
if (await checkAndUpdateIgnoreBatteryStatus()) {
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final enableAbrRes = await bind.mainGetOption(key: "enable-abr") != "N";
|
if (await checkAndUpdateStartOnBoot()) {
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW
|
||||||
|
var enableStartOnBoot =
|
||||||
|
await gFFI.invokeMethod(AndroidChannel.kGetStartOnBootOpt);
|
||||||
|
if (enableStartOnBoot) {
|
||||||
|
if (!await canStartOnBoot()) {
|
||||||
|
enableStartOnBoot = false;
|
||||||
|
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableStartOnBoot != _enableStartOnBoot) {
|
||||||
|
update = true;
|
||||||
|
_enableStartOnBoot = enableStartOnBoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
final enableAbrRes = option2bool(
|
||||||
|
"enable-abr", await bind.mainGetOption(key: "enable-abr"));
|
||||||
if (enableAbrRes != _enableAbr) {
|
if (enableAbrRes != _enableAbr) {
|
||||||
update = true;
|
update = true;
|
||||||
_enableAbr = enableAbrRes;
|
_enableAbr = enableAbrRes;
|
||||||
@@ -125,15 +151,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
if (state == AppLifecycleState.resumed) {
|
if (state == AppLifecycleState.resumed) {
|
||||||
() async {
|
() async {
|
||||||
if (await updateIgnoreBatteryStatus()) {
|
final ibs = await checkAndUpdateIgnoreBatteryStatus();
|
||||||
|
final sob = await checkAndUpdateStartOnBoot();
|
||||||
|
if (ibs || sob) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updateIgnoreBatteryStatus() async {
|
Future<bool> checkAndUpdateIgnoreBatteryStatus() async {
|
||||||
final res = await PermissionManager.check("ignore_battery_optimizations");
|
final res = await AndroidPermissionManager.check(
|
||||||
|
kRequestIgnoreBatteryOptimizations);
|
||||||
if (_ignoreBatteryOpt != res) {
|
if (_ignoreBatteryOpt != res) {
|
||||||
_ignoreBatteryOpt = res;
|
_ignoreBatteryOpt = res;
|
||||||
return true;
|
return true;
|
||||||
@@ -142,6 +171,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> checkAndUpdateStartOnBoot() async {
|
||||||
|
if (!await canStartOnBoot() && _enableStartOnBoot) {
|
||||||
|
_enableStartOnBoot = false;
|
||||||
|
debugPrint(
|
||||||
|
"checkAndUpdateStartOnBoot and set _enableStartOnBoot -> false");
|
||||||
|
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, false);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Provider.of<FfiModel>(context);
|
Provider.of<FfiModel>(context);
|
||||||
@@ -265,7 +306,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
]),
|
]),
|
||||||
onToggle: (v) async {
|
onToggle: (v) async {
|
||||||
if (v) {
|
if (v) {
|
||||||
PermissionManager.request("ignore_battery_optimizations");
|
await AndroidPermissionManager.request(
|
||||||
|
kRequestIgnoreBatteryOptimizations);
|
||||||
} else {
|
} else {
|
||||||
final res = await gFFI.dialogManager
|
final res = await gFFI.dialogManager
|
||||||
.show<bool>((setState, close) => CustomAlertDialog(
|
.show<bool>((setState, close) => CustomAlertDialog(
|
||||||
@@ -282,11 +324,44 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
],
|
],
|
||||||
));
|
));
|
||||||
if (res == true) {
|
if (res == true) {
|
||||||
PermissionManager.request("application_details_settings");
|
AndroidPermissionManager.startAction(
|
||||||
|
kActionApplicationDetailsSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
enhancementsTiles.add(SettingsTile.switchTile(
|
||||||
|
initialValue: _enableStartOnBoot,
|
||||||
|
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
|
Text("${translate('Start on Boot')} (beta)"),
|
||||||
|
Text(
|
||||||
|
'* ${translate('Start the screen sharing service on boot, requires special permissions')}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall),
|
||||||
|
]),
|
||||||
|
onToggle: (toValue) async {
|
||||||
|
if (toValue) {
|
||||||
|
// 1. request kIgnoreBatteryOptimizations
|
||||||
|
if (!await AndroidPermissionManager.check(
|
||||||
|
kRequestIgnoreBatteryOptimizations)) {
|
||||||
|
if (!await AndroidPermissionManager.request(
|
||||||
|
kRequestIgnoreBatteryOptimizations)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. request kSystemAlertWindow
|
||||||
|
if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
|
||||||
|
if (!await AndroidPermissionManager.request(kSystemAlertWindow)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Optional) 3. request input permission
|
||||||
|
}
|
||||||
|
setState(() => _enableStartOnBoot = toValue);
|
||||||
|
|
||||||
|
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
|
||||||
|
}));
|
||||||
|
|
||||||
return SettingsList(
|
return SettingsList(
|
||||||
sections: [
|
sections: [
|
||||||
@@ -387,6 +462,17 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> canStartOnBoot() async {
|
||||||
|
// start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW
|
||||||
|
if (_hasIgnoreBattery && !_ignoreBatteryOpt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void showServerSettings(OverlayDialogManager dialogManager) async {
|
void showServerSettings(OverlayDialogManager dialogManager) async {
|
||||||
|
|||||||
@@ -458,10 +458,8 @@ class InputModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
evt['type'] = type;
|
evt['type'] = type;
|
||||||
if (isDesktop) {
|
y -= CanvasModel.topToEdge;
|
||||||
y = y - stateGlobal.tabBarHeight - stateGlobal.windowBorderWidth.value;
|
x -= CanvasModel.leftToEdge;
|
||||||
x -= stateGlobal.windowBorderWidth.value;
|
|
||||||
}
|
|
||||||
final canvasModel = parent.target!.canvasModel;
|
final canvasModel = parent.target!.canvasModel;
|
||||||
final nearThr = 3;
|
final nearThr = 3;
|
||||||
var nearRight = (canvasModel.size.width - x) < nearThr;
|
var nearRight = (canvasModel.size.width - x) < nearThr;
|
||||||
@@ -503,8 +501,21 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
x += d.x;
|
x += d.x;
|
||||||
y += d.y;
|
y += d.y;
|
||||||
|
var evtX = 0;
|
||||||
|
var evtY = 0;
|
||||||
|
try {
|
||||||
|
evtX = x.round();
|
||||||
|
evtY = y.round();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrintStack(
|
||||||
|
label: 'canvasModel.scale value ${canvasModel.scale}, $e');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (x < d.x || y < d.y || x > (d.x + d.width) || y > (d.y + d.height)) {
|
if (evtX < d.x ||
|
||||||
|
evtY < d.y ||
|
||||||
|
evtX > (d.x + d.width) ||
|
||||||
|
evtY > (d.y + d.height)) {
|
||||||
// If left mouse up, no early return.
|
// If left mouse up, no early return.
|
||||||
if (evt['buttons'] != kPrimaryMouseButton || type != 'up') {
|
if (evt['buttons'] != kPrimaryMouseButton || type != 'up') {
|
||||||
return;
|
return;
|
||||||
@@ -512,12 +523,12 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type != '') {
|
if (type != '') {
|
||||||
x = 0;
|
evtX = 0;
|
||||||
y = 0;
|
evtY = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
evt['x'] = '${x.round()}';
|
evt['x'] = '$evtX';
|
||||||
evt['y'] = '${y.round()}';
|
evt['y'] = '$evtY';
|
||||||
var buttons = '';
|
var buttons = '';
|
||||||
switch (evt['buttons']) {
|
switch (evt['buttons']) {
|
||||||
case kPrimaryMouseButton:
|
case kPrimaryMouseButton:
|
||||||
|
|||||||
@@ -617,13 +617,28 @@ class ViewStyle {
|
|||||||
final int displayWidth;
|
final int displayWidth;
|
||||||
final int displayHeight;
|
final int displayHeight;
|
||||||
ViewStyle({
|
ViewStyle({
|
||||||
this.style = '',
|
required this.style,
|
||||||
this.width = 0.0,
|
required this.width,
|
||||||
this.height = 0.0,
|
required this.height,
|
||||||
this.displayWidth = 0,
|
required this.displayWidth,
|
||||||
this.displayHeight = 0,
|
required this.displayHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static defaultViewStyle() {
|
||||||
|
final desktop = (isDesktop || isWebDesktop);
|
||||||
|
final w =
|
||||||
|
desktop ? kDesktopDefaultDisplayWidth : kMobileDefaultDisplayWidth;
|
||||||
|
final h =
|
||||||
|
desktop ? kDesktopDefaultDisplayHeight : kMobileDefaultDisplayHeight;
|
||||||
|
return ViewStyle(
|
||||||
|
style: '',
|
||||||
|
width: w.toDouble(),
|
||||||
|
height: h.toDouble(),
|
||||||
|
displayWidth: w,
|
||||||
|
displayHeight: h,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static int _double2Int(double v) => (v * 100).round().toInt();
|
static int _double2Int(double v) => (v * 100).round().toInt();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -652,10 +667,15 @@ class ViewStyle {
|
|||||||
double get scale {
|
double get scale {
|
||||||
double s = 1.0;
|
double s = 1.0;
|
||||||
if (style == kRemoteViewStyleAdaptive) {
|
if (style == kRemoteViewStyleAdaptive) {
|
||||||
|
if (width != 0 &&
|
||||||
|
height != 0 &&
|
||||||
|
displayWidth != 0 &&
|
||||||
|
displayHeight != 0) {
|
||||||
final s1 = width / displayWidth;
|
final s1 = width / displayWidth;
|
||||||
final s2 = height / displayHeight;
|
final s2 = height / displayHeight;
|
||||||
s = s1 < s2 ? s1 : s2;
|
s = s1 < s2 ? s1 : s2;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -680,7 +700,7 @@ class CanvasModel with ChangeNotifier {
|
|||||||
// scroll offset y percent
|
// scroll offset y percent
|
||||||
double _scrollY = 0.0;
|
double _scrollY = 0.0;
|
||||||
ScrollStyle _scrollStyle = ScrollStyle.scrollauto;
|
ScrollStyle _scrollStyle = ScrollStyle.scrollauto;
|
||||||
ViewStyle _lastViewStyle = ViewStyle();
|
ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle();
|
||||||
|
|
||||||
final _imageOverflow = false.obs;
|
final _imageOverflow = false.obs;
|
||||||
|
|
||||||
@@ -707,12 +727,25 @@ class CanvasModel with ChangeNotifier {
|
|||||||
double get scrollX => _scrollX;
|
double get scrollX => _scrollX;
|
||||||
double get scrollY => _scrollY;
|
double get scrollY => _scrollY;
|
||||||
|
|
||||||
|
static double get leftToEdge => (isDesktop || isWebDesktop)
|
||||||
|
? windowBorderWidth + kDragToResizeAreaPadding.left
|
||||||
|
: 0;
|
||||||
|
static double get rightToEdge => (isDesktop || isWebDesktop)
|
||||||
|
? windowBorderWidth + kDragToResizeAreaPadding.right
|
||||||
|
: 0;
|
||||||
|
static double get topToEdge => (isDesktop || isWebDesktop)
|
||||||
|
? tabBarHeight + windowBorderWidth + kDragToResizeAreaPadding.top
|
||||||
|
: 0;
|
||||||
|
static double get bottomToEdge => (isDesktop || isWebDesktop)
|
||||||
|
? windowBorderWidth + kDragToResizeAreaPadding.bottom
|
||||||
|
: 0;
|
||||||
|
|
||||||
updateViewStyle() async {
|
updateViewStyle() async {
|
||||||
Size getSize() {
|
Size getSize() {
|
||||||
final size = MediaQueryData.fromWindow(ui.window).size;
|
final size = MediaQueryData.fromWindow(ui.window).size;
|
||||||
// If minimized, w or h may be negative here.
|
// If minimized, w or h may be negative here.
|
||||||
double w = size.width - windowBorderWidth * 2;
|
double w = size.width - leftToEdge - rightToEdge;
|
||||||
double h = size.height - tabBarHeight - windowBorderWidth * 2;
|
double h = size.height - topToEdge - bottomToEdge;
|
||||||
return Size(w < 0 ? 0 : w, h < 0 ? 0 : h);
|
return Size(w < 0 ? 0 : w, h < 0 ? 0 : h);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,10 +819,14 @@ class CanvasModel with ChangeNotifier {
|
|||||||
return parent.target?.ffiModel.display.height ?? defaultHeight;
|
return parent.target?.ffiModel.display.height ?? defaultHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
double get windowBorderWidth => stateGlobal.windowBorderWidth.value;
|
static double get windowBorderWidth => stateGlobal.windowBorderWidth.value;
|
||||||
double get tabBarHeight => stateGlobal.tabBarHeight;
|
static double get tabBarHeight => stateGlobal.tabBarHeight;
|
||||||
|
|
||||||
moveDesktopMouse(double x, double y) {
|
moveDesktopMouse(double x, double y) {
|
||||||
|
if (size.width == 0 || size.height == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// On mobile platforms, move the canvas with the cursor.
|
// On mobile platforms, move the canvas with the cursor.
|
||||||
final dw = getDisplayWidth() * _scale;
|
final dw = getDisplayWidth() * _scale;
|
||||||
final dh = getDisplayHeight() * _scale;
|
final dh = getDisplayHeight() * _scale;
|
||||||
@@ -803,7 +840,9 @@ class CanvasModel with ChangeNotifier {
|
|||||||
dyOffset = (y - dh * (y / size.height) - _y).toInt();
|
dyOffset = (y - dh * (y / size.height) - _y).toInt();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Unhandled Exception: Unsupported operation: Infinity or NaN toInt
|
debugPrintStack(
|
||||||
|
label:
|
||||||
|
'(x,y) ($x,$y), (_x,_y) ($_x,$_y), _scale $_scale, display size (${getDisplayWidth()},${getDisplayHeight()}), size $size, , $e');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ class PlatformFFI {
|
|||||||
F5Dart? _session_next_rgba;
|
F5Dart? _session_next_rgba;
|
||||||
F6Dart? _session_register_texture;
|
F6Dart? _session_register_texture;
|
||||||
|
|
||||||
|
|
||||||
static get localeName => Platform.localeName;
|
static get localeName => Platform.localeName;
|
||||||
|
|
||||||
static get isMain => instance._appType == kAppTypeMain;
|
static get isMain => instance._appType == kAppTypeMain;
|
||||||
@@ -162,7 +161,8 @@ class PlatformFFI {
|
|||||||
dylib.lookupFunction<F4, F4Dart>("session_get_rgba_size");
|
dylib.lookupFunction<F4, F4Dart>("session_get_rgba_size");
|
||||||
_session_next_rgba =
|
_session_next_rgba =
|
||||||
dylib.lookupFunction<F5, F5Dart>("session_next_rgba");
|
dylib.lookupFunction<F5, F5Dart>("session_next_rgba");
|
||||||
_session_register_texture = dylib.lookupFunction<F6, F6Dart>("session_register_texture");
|
_session_register_texture =
|
||||||
|
dylib.lookupFunction<F6, F6Dart>("session_register_texture");
|
||||||
try {
|
try {
|
||||||
// SYSTEM user failed
|
// SYSTEM user failed
|
||||||
_dir = (await getApplicationDocumentsDirectory()).path;
|
_dir = (await getApplicationDocumentsDirectory()).path;
|
||||||
@@ -301,4 +301,8 @@ class PlatformFFI {
|
|||||||
if (!isAndroid) return Future<bool>(() => false);
|
if (!isAndroid) return Future<bool>(() => false);
|
||||||
return await _toAndroidChannel.invokeMethod(method, arguments);
|
return await _toAndroidChannel.invokeMethod(method, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void syncAndroidServiceAppDirConfigPath() {
|
||||||
|
invokeMethod(AndroidChannel.kSyncAppDirConfigPath, _dir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/main.dart';
|
import 'package:flutter_hbb/main.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -154,7 +155,8 @@ class ServerModel with ChangeNotifier {
|
|||||||
/// file true by default (if permission on)
|
/// file true by default (if permission on)
|
||||||
checkAndroidPermission() async {
|
checkAndroidPermission() async {
|
||||||
// audio
|
// audio
|
||||||
if (androidVersion < 30 || !await PermissionManager.check("audio")) {
|
if (androidVersion < 30 ||
|
||||||
|
!await AndroidPermissionManager.check(kRecordAudio)) {
|
||||||
_audioOk = false;
|
_audioOk = false;
|
||||||
bind.mainSetOption(key: "enable-audio", value: "N");
|
bind.mainSetOption(key: "enable-audio", value: "N");
|
||||||
} else {
|
} else {
|
||||||
@@ -163,7 +165,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// file
|
// file
|
||||||
if (!await PermissionManager.check("file")) {
|
if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
|
||||||
_fileOk = false;
|
_fileOk = false;
|
||||||
bind.mainSetOption(key: "enable-file-transfer", value: "N");
|
bind.mainSetOption(key: "enable-file-transfer", value: "N");
|
||||||
} else {
|
} else {
|
||||||
@@ -229,10 +231,10 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleAudio() async {
|
toggleAudio() async {
|
||||||
if (!_audioOk && !await PermissionManager.check("audio")) {
|
if (!_audioOk && !await AndroidPermissionManager.check(kRecordAudio)) {
|
||||||
final res = await PermissionManager.request("audio");
|
final res = await AndroidPermissionManager.request(kRecordAudio);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
// TODO handle fail
|
showToast(translate('Failed'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,10 +245,12 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleFile() async {
|
toggleFile() async {
|
||||||
if (!_fileOk && !await PermissionManager.check("file")) {
|
if (!_fileOk &&
|
||||||
final res = await PermissionManager.request("file");
|
!await AndroidPermissionManager.check(kManageExternalStorage)) {
|
||||||
|
final res =
|
||||||
|
await AndroidPermissionManager.request(kManageExternalStorage);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
// TODO handle fail
|
showToast(translate('Failed'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,10 +348,6 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initInput() async {
|
|
||||||
await parent.target?.invokeMethod("init_input");
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> setPermanentPassword(String newPW) async {
|
Future<bool> setPermanentPassword(String newPW) async {
|
||||||
await bind.mainSetPermanentPassword(password: newPW);
|
await bind.mainSetPermanentPassword(password: newPW);
|
||||||
await Future.delayed(Duration(milliseconds: 500));
|
await Future.delayed(Duration(milliseconds: 500));
|
||||||
@@ -561,7 +561,8 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> closeAll() async {
|
Future<void> closeAll() async {
|
||||||
await Future.wait(_clients.map((client) => bind.cmCloseConnection(connId: client.id)));
|
await Future.wait(
|
||||||
|
_clients.map((client) => bind.cmCloseConnection(connId: client.id)));
|
||||||
_clients.clear();
|
_clients.clear();
|
||||||
tabController.state.value.tabs.clear();
|
tabController.state.value.tabs.clear();
|
||||||
}
|
}
|
||||||
@@ -684,7 +685,7 @@ String getLoginDialogTag(int id) {
|
|||||||
showInputWarnAlert(FFI ffi) {
|
showInputWarnAlert(FFI ffi) {
|
||||||
ffi.dialogManager.show((setState, close) {
|
ffi.dialogManager.show((setState, close) {
|
||||||
submit() {
|
submit() {
|
||||||
ffi.serverModel.initInput();
|
AndroidPermissionManager.startAction(kActionAccessibilitySettings);
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import '../consts.dart';
|
|||||||
class StateGlobal {
|
class StateGlobal {
|
||||||
int _windowId = -1;
|
int _windowId = -1;
|
||||||
bool _fullscreen = false;
|
bool _fullscreen = false;
|
||||||
|
bool _maximize = false;
|
||||||
bool grabKeyboard = false;
|
bool grabKeyboard = false;
|
||||||
final RxBool _showTabBar = true.obs;
|
final RxBool _showTabBar = true.obs;
|
||||||
|
final RxBool _showResizeEdge = true.obs;
|
||||||
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
|
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
|
||||||
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
||||||
final RxBool showRemoteMenuBar = false.obs;
|
final RxBool showRemoteMenuBar = false.obs;
|
||||||
@@ -18,12 +20,20 @@ class StateGlobal {
|
|||||||
|
|
||||||
int get windowId => _windowId;
|
int get windowId => _windowId;
|
||||||
bool get fullscreen => _fullscreen;
|
bool get fullscreen => _fullscreen;
|
||||||
|
bool get maximize => _maximize;
|
||||||
double get tabBarHeight => fullscreen ? 0 : kDesktopRemoteTabBarHeight;
|
double get tabBarHeight => fullscreen ? 0 : kDesktopRemoteTabBarHeight;
|
||||||
RxBool get showTabBar => _showTabBar;
|
RxBool get showTabBar => _showTabBar;
|
||||||
RxDouble get resizeEdgeSize => _resizeEdgeSize;
|
RxDouble get resizeEdgeSize => _resizeEdgeSize;
|
||||||
RxDouble get windowBorderWidth => _windowBorderWidth;
|
RxDouble get windowBorderWidth => _windowBorderWidth;
|
||||||
|
|
||||||
setWindowId(int id) => _windowId = id;
|
setWindowId(int id) => _windowId = id;
|
||||||
|
setMaximize(bool v) {
|
||||||
|
if (_maximize != v) {
|
||||||
|
_maximize = v;
|
||||||
|
_resizeEdgeSize.value =
|
||||||
|
_maximize ? kMaximizeEdgeSize : kWindowEdgeSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
setFullscreen(bool v) {
|
setFullscreen(bool v) {
|
||||||
if (_fullscreen != v) {
|
if (_fullscreen != v) {
|
||||||
_fullscreen = v;
|
_fullscreen = v;
|
||||||
|
|||||||
@@ -487,7 +487,7 @@
|
|||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
|||||||
@@ -325,8 +325,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: f37357ed98a10717576eb9ed8413e92b2ec5d13a
|
ref: "3e2655677c54f421f9e378680d8171b95a211e0f"
|
||||||
resolved-ref: f37357ed98a10717576eb9ed8413e92b2ec5d13a
|
resolved-ref: "3e2655677c54f421f9e378680d8171b95a211e0f"
|
||||||
url: "https://github.com/Kingtous/rustdesk_desktop_multi_window"
|
url: "https://github.com/Kingtous/rustdesk_desktop_multi_window"
|
||||||
source: git
|
source: git
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
@@ -1563,5 +1563,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.1"
|
version: "0.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.18.0 <4.0.0"
|
dart: ">=2.18.0 <3.0.0"
|
||||||
flutter: ">=3.3.0"
|
flutter: ">=3.3.0"
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ dependencies:
|
|||||||
desktop_multi_window:
|
desktop_multi_window:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
|
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
|
||||||
ref: f37357ed98a10717576eb9ed8413e92b2ec5d13a
|
ref: 3e2655677c54f421f9e378680d8171b95a211e0f
|
||||||
freezed_annotation: ^2.0.3
|
freezed_annotation: ^2.0.3
|
||||||
flutter_custom_cursor: ^0.0.4
|
flutter_custom_cursor: ^0.0.4
|
||||||
window_size:
|
window_size:
|
||||||
@@ -76,7 +76,7 @@ dependencies:
|
|||||||
file_picker: ^5.1.0
|
file_picker: ^5.1.0
|
||||||
flutter_svg: ^1.1.5
|
flutter_svg: ^1.1.5
|
||||||
flutter_improved_scrolling:
|
flutter_improved_scrolling:
|
||||||
# currently, we use flutter 3.0.5 for windows build, latest for other builds.
|
# currently, we use flutter 3.7.0+.
|
||||||
#
|
#
|
||||||
# for flutter 3.0.5, please use official version(just comment code below).
|
# for flutter 3.0.5, please use official version(just comment code below).
|
||||||
# if build rustdesk by flutter >=3.3, please use our custom pub below (uncomment code below).
|
# if build rustdesk by flutter >=3.3, please use our custom pub below (uncomment code below).
|
||||||
|
|||||||
5
libs/hbb_common/examples/config.rs
Normal file
5
libs/hbb_common/examples/config.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
extern crate hbb_common;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("{:?}", hbb_common::config::PeerConfig::load("455058072"));
|
||||||
|
}
|
||||||
@@ -110,10 +110,10 @@ macro_rules! serde_field_string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! serde_field_bool {
|
macro_rules! serde_field_bool {
|
||||||
($struct_name: ident, $field_name: literal, $func: ident) => {
|
($struct_name: ident, $field_name: literal, $func: ident, $default: literal) => {
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct $struct_name {
|
pub struct $struct_name {
|
||||||
#[serde(rename = $field_name)]
|
#[serde(default = $default, rename = $field_name)]
|
||||||
pub v: bool,
|
pub v: bool,
|
||||||
}
|
}
|
||||||
impl Default for $struct_name {
|
impl Default for $struct_name {
|
||||||
@@ -217,6 +217,8 @@ pub struct PeerConfig {
|
|||||||
pub lock_after_session_end: LockAfterSessionEnd,
|
pub lock_after_session_end: LockAfterSessionEnd,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub privacy_mode: PrivacyMode,
|
pub privacy_mode: PrivacyMode,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub allow_swap_key: AllowSwapKey,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub port_forwards: Vec<(i32, String, i32)>,
|
pub port_forwards: Vec<(i32, String, i32)>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -1035,30 +1037,37 @@ impl PeerConfig {
|
|||||||
serde_field_bool!(
|
serde_field_bool!(
|
||||||
ShowRemoteCursor,
|
ShowRemoteCursor,
|
||||||
"show_remote_cursor",
|
"show_remote_cursor",
|
||||||
default_show_remote_cursor
|
default_show_remote_cursor,
|
||||||
|
"ShowRemoteCursor::default_show_remote_cursor"
|
||||||
);
|
);
|
||||||
serde_field_bool!(
|
serde_field_bool!(
|
||||||
ShowQualityMonitor,
|
ShowQualityMonitor,
|
||||||
"show_quality_monitor",
|
"show_quality_monitor",
|
||||||
default_show_quality_monitor
|
default_show_quality_monitor,
|
||||||
|
"ShowQualityMonitor::default_show_quality_monitor"
|
||||||
);
|
);
|
||||||
serde_field_bool!(DisableAudio, "disable_audio", default_disable_audio);
|
serde_field_bool!(DisableAudio, "disable_audio", default_disable_audio, "DisableAudio::default_disable_audio");
|
||||||
serde_field_bool!(
|
serde_field_bool!(
|
||||||
EnableFileTransfer,
|
EnableFileTransfer,
|
||||||
"enable_file_transfer",
|
"enable_file_transfer",
|
||||||
default_enable_file_transfer
|
default_enable_file_transfer,
|
||||||
|
"EnableFileTransfer::default_enable_file_transfer"
|
||||||
);
|
);
|
||||||
serde_field_bool!(
|
serde_field_bool!(
|
||||||
DisableClipboard,
|
DisableClipboard,
|
||||||
"disable_clipboard",
|
"disable_clipboard",
|
||||||
default_disable_clipboard
|
default_disable_clipboard,
|
||||||
|
"DisableClipboard::default_disable_clipboard"
|
||||||
);
|
);
|
||||||
serde_field_bool!(
|
serde_field_bool!(
|
||||||
LockAfterSessionEnd,
|
LockAfterSessionEnd,
|
||||||
"lock_after_session_end",
|
"lock_after_session_end",
|
||||||
default_lock_after_session_end
|
default_lock_after_session_end,
|
||||||
|
"LockAfterSessionEnd::default_lock_after_session_end"
|
||||||
);
|
);
|
||||||
serde_field_bool!(PrivacyMode, "privacy_mode", default_privacy_mode);
|
serde_field_bool!(PrivacyMode, "privacy_mode", default_privacy_mode, "PrivacyMode::default_privacy_mode");
|
||||||
|
|
||||||
|
serde_field_bool!(AllowSwapKey, "allow_swap_key", default_allow_swap_key, "AllowSwapKey::default_allow_swap_key");
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct LocalConfig {
|
pub struct LocalConfig {
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ def main():
|
|||||||
def expand():
|
def expand():
|
||||||
for fn in glob.glob('./src/lang/*'):
|
for fn in glob.glob('./src/lang/*'):
|
||||||
lang = os.path.basename(fn)[:-3]
|
lang = os.path.basename(fn)[:-3]
|
||||||
if lang in ['en','cn']: continue
|
if lang in ['en','template']: continue
|
||||||
print(lang)
|
print(lang)
|
||||||
dict = get_lang(lang)
|
dict = get_lang(lang)
|
||||||
fw = open("./src/lang/%s.rs"%lang, "wt", encoding='utf8')
|
fw = open("./src/lang/%s.rs"%lang, "wt", encoding='utf8')
|
||||||
for line in open('./src/lang/cn.rs', encoding='utf8'):
|
for line in open('./src/lang/template.rs', encoding='utf8'):
|
||||||
line_strip = line.strip()
|
line_strip = line.strip()
|
||||||
if line_strip.startswith('("'):
|
if line_strip.startswith('("'):
|
||||||
k, v = line_split(line_strip)
|
k, v = line_split(line_strip)
|
||||||
|
|||||||
@@ -1230,6 +1230,8 @@ impl LoginConfigHandler {
|
|||||||
option.block_input = BoolOption::No.into();
|
option.block_input = BoolOption::No.into();
|
||||||
} else if name == "show-quality-monitor" {
|
} else if name == "show-quality-monitor" {
|
||||||
config.show_quality_monitor.v = !config.show_quality_monitor.v;
|
config.show_quality_monitor.v = !config.show_quality_monitor.v;
|
||||||
|
} else if name == "allow_swap_key" {
|
||||||
|
config.allow_swap_key.v = !config.allow_swap_key.v;
|
||||||
} else {
|
} else {
|
||||||
let is_set = self
|
let is_set = self
|
||||||
.options
|
.options
|
||||||
@@ -1383,6 +1385,8 @@ impl LoginConfigHandler {
|
|||||||
self.config.disable_clipboard.v
|
self.config.disable_clipboard.v
|
||||||
} else if name == "show-quality-monitor" {
|
} else if name == "show-quality-monitor" {
|
||||||
self.config.show_quality_monitor.v
|
self.config.show_quality_monitor.v
|
||||||
|
} else if name == "allow_swap_key" {
|
||||||
|
self.config.allow_swap_key.v
|
||||||
} else {
|
} else {
|
||||||
!self.get_option(name).is_empty()
|
!self.get_option(name).is_empty()
|
||||||
}
|
}
|
||||||
@@ -1807,6 +1811,7 @@ pub fn send_mouse(
|
|||||||
if check_scroll_on_mac(mask, x, y) {
|
if check_scroll_on_mac(mask, x, y) {
|
||||||
mouse_event.modifiers.push(ControlKey::Scroll.into());
|
mouse_event.modifiers.push(ControlKey::Scroll.into());
|
||||||
}
|
}
|
||||||
|
interface.swap_modifier_mouse(&mut mouse_event);
|
||||||
msg_out.set_mouse_event(mouse_event);
|
msg_out.set_mouse_event(mouse_event);
|
||||||
interface.send(Data::Message(msg_out));
|
interface.send(Data::Message(msg_out));
|
||||||
}
|
}
|
||||||
@@ -2033,6 +2038,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
|
|||||||
fn is_force_relay(&self) -> bool {
|
fn is_force_relay(&self) -> bool {
|
||||||
self.get_login_config_handler().read().unwrap().force_relay
|
self.get_login_config_handler().read().unwrap().force_relay
|
||||||
}
|
}
|
||||||
|
fn swap_modifier_mouse(&self, _msg : &mut hbb_common::protos::message::MouseEvent) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data used by the client interface.
|
/// Data used by the client interface.
|
||||||
|
|||||||
@@ -1361,7 +1361,7 @@ pub fn send_url_scheme(_url: String) {
|
|||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
pub mod server_side {
|
pub mod server_side {
|
||||||
use hbb_common::log;
|
use hbb_common::{log, config};
|
||||||
use jni::{
|
use jni::{
|
||||||
objects::{JClass, JString},
|
objects::{JClass, JString},
|
||||||
sys::jstring,
|
sys::jstring,
|
||||||
@@ -1374,11 +1374,25 @@ pub mod server_side {
|
|||||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer(
|
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer(
|
||||||
env: JNIEnv,
|
env: JNIEnv,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
|
app_dir: JString,
|
||||||
) {
|
) {
|
||||||
log::debug!("startServer from java");
|
log::debug!("startServer from jvm");
|
||||||
|
if let Ok(app_dir) = env.get_string(app_dir) {
|
||||||
|
*config::APP_DIR.write().unwrap() = app_dir.into();
|
||||||
|
}
|
||||||
std::thread::spawn(move || start_server(true));
|
std::thread::spawn(move || start_server(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startService(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
) {
|
||||||
|
log::debug!("startService from jvm");
|
||||||
|
config::Config::set_option("stop-service".into(), "".into());
|
||||||
|
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
|
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
|
||||||
env: JNIEnv,
|
env: JNIEnv,
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"),
|
("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"),
|
||||||
("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"),
|
("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"),
|
||||||
("android_open_battery_optimizations_tip", ""),
|
("android_open_battery_optimizations_tip", ""),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Connexió no disponible"),
|
("Connection not allowed", "Connexió no disponible"),
|
||||||
("Legacy mode", "Mode heretat"),
|
("Legacy mode", "Mode heretat"),
|
||||||
("Map mode", "Mode mapa"),
|
("Map mode", "Mode mapa"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "保持 RustDesk 后台服务"),
|
("Keep RustDesk background service", "保持 RustDesk 后台服务"),
|
||||||
("Ignore Battery Optimizations", "忽略电池优化"),
|
("Ignore Battery Optimizations", "忽略电池优化"),
|
||||||
("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的 RustDesk 应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"),
|
("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的 RustDesk 应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"),
|
||||||
|
("Start on Boot", "开机自启动"),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", "开机自动启动屏幕共享服务,此功能需要一些特殊权限。"),
|
||||||
("Connection not allowed", "对方不允许连接"),
|
("Connection not allowed", "对方不允许连接"),
|
||||||
("Legacy mode", "传统模式"),
|
("Legacy mode", "传统模式"),
|
||||||
("Map mode", "1:1 传输"),
|
("Map mode", "1:1 传输"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", ""),
|
("Keep RustDesk background service", ""),
|
||||||
("Ignore Battery Optimizations", ""),
|
("Ignore Battery Optimizations", ""),
|
||||||
("android_open_battery_optimizations_tip", ""),
|
("android_open_battery_optimizations_tip", ""),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", ""),
|
("Connection not allowed", ""),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
("No transfers in progress", ""),
|
|
||||||
("Codec", ""),
|
("Codec", ""),
|
||||||
("Resolution", ""),
|
("Resolution", ""),
|
||||||
|
("No transfers in progress", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"),
|
("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"),
|
||||||
("Ignore Battery Optimizations", "Ignorer betteri optimeringer"),
|
("Ignore Battery Optimizations", "Ignorer betteri optimeringer"),
|
||||||
("android_open_battery_optimizations_tip", ""),
|
("android_open_battery_optimizations_tip", ""),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Forbindelse ikke tilladt"),
|
("Connection not allowed", "Forbindelse ikke tilladt"),
|
||||||
("Legacy mode", "Bagudkompatibilitetstilstand"),
|
("Legacy mode", "Bagudkompatibilitetstilstand"),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"),
|
("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"),
|
||||||
("Ignore Battery Optimizations", "Akkuoptimierung ignorieren"),
|
("Ignore Battery Optimizations", "Akkuoptimierung ignorieren"),
|
||||||
("android_open_battery_optimizations_tip", "Möchten Sie die Einstellungen zur Akkuoptimierung öffnen?"),
|
("android_open_battery_optimizations_tip", "Möchten Sie die Einstellungen zur Akkuoptimierung öffnen?"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Verbindung abgelehnt"),
|
("Connection not allowed", "Verbindung abgelehnt"),
|
||||||
("Legacy mode", "Kompatibilitätsmodus"),
|
("Legacy mode", "Kompatibilitätsmodus"),
|
||||||
("Map mode", "Kartenmodus"),
|
("Map mode", "Kartenmodus"),
|
||||||
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "Sprachanruf beenden"),
|
("Stop voice call", "Sprachanruf beenden"),
|
||||||
("relay_hint_tip", "Wenn eine direkte Verbindung nicht möglich ist, können Sie versuchen, eine Verbindung über einen Relay-Server herzustellen. \nWenn Sie eine Relay-Verbindung beim ersten Versuch herstellen möchten, können Sie das Suffix \"/r\" an die ID anhängen oder die Option \"Immer über Relay-Server verbinden\" auf der Gegenstelle auswählen."),
|
("relay_hint_tip", "Wenn eine direkte Verbindung nicht möglich ist, können Sie versuchen, eine Verbindung über einen Relay-Server herzustellen. \nWenn Sie eine Relay-Verbindung beim ersten Versuch herstellen möchten, können Sie das Suffix \"/r\" an die ID anhängen oder die Option \"Immer über Relay-Server verbinden\" auf der Gegenstelle auswählen."),
|
||||||
("Reconnect", "Erneut verbinden"),
|
("Reconnect", "Erneut verbinden"),
|
||||||
("Codec", ""),
|
("Codec", "Codec"),
|
||||||
("Resolution", ""),
|
("Resolution", "Auflösung"),
|
||||||
("No transfers in progress", ""),
|
("No transfers in progress", "Keine Übertragungen im Gange"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", ""),
|
("Keep RustDesk background service", ""),
|
||||||
("Ignore Battery Optimizations", ""),
|
("Ignore Battery Optimizations", ""),
|
||||||
("android_open_battery_optimizations_tip", ""),
|
("android_open_battery_optimizations_tip", ""),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", ""),
|
("Connection not allowed", ""),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
|
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
|
||||||
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
|
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
|
||||||
("android_open_battery_optimizations_tip", "Si deseas deshabilitar esta característica, por favor, ve a la página siguiente de ajustes, busca y entra en [Batería] y desmarca [Sin restricción]"),
|
("android_open_battery_optimizations_tip", "Si deseas deshabilitar esta característica, por favor, ve a la página siguiente de ajustes, busca y entra en [Batería] y desmarca [Sin restricción]"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Conexión no disponible"),
|
("Connection not allowed", "Conexión no disponible"),
|
||||||
("Legacy mode", "Modo heredado"),
|
("Legacy mode", "Modo heredado"),
|
||||||
("Map mode", "Modo mapa"),
|
("Map mode", "Modo mapa"),
|
||||||
@@ -456,6 +458,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Reconnect", "Reconectar"),
|
("Reconnect", "Reconectar"),
|
||||||
("Codec", "Códec"),
|
("Codec", "Códec"),
|
||||||
("Resolution", "Resolución"),
|
("Resolution", "Resolución"),
|
||||||
("No transfers in progress", ""),
|
("No transfers in progress", "No hay transferencias en curso"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"),
|
("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"),
|
||||||
("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"),
|
("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"),
|
||||||
("android_open_battery_optimizations_tip", "به صفحه تنظیمات بعدی بروید"),
|
("android_open_battery_optimizations_tip", "به صفحه تنظیمات بعدی بروید"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "اتصال مجاز نیست"),
|
("Connection not allowed", "اتصال مجاز نیست"),
|
||||||
("Legacy mode", "legacy حالت"),
|
("Legacy mode", "legacy حالت"),
|
||||||
("Map mode", "map حالت"),
|
("Map mode", "map حالت"),
|
||||||
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "توقف تماس صوتی"),
|
("Stop voice call", "توقف تماس صوتی"),
|
||||||
("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر میخواهید فوراً از سرور رله استفاده کنید، میتوانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"),
|
("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر میخواهید فوراً از سرور رله استفاده کنید، میتوانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"),
|
||||||
("Reconnect", "اتصال مجدد"),
|
("Reconnect", "اتصال مجدد"),
|
||||||
("No transfers in progress", ""),
|
("Codec", "کدک"),
|
||||||
("Codec", ""),
|
("Resolution", "وضوح"),
|
||||||
("Resolution", ""),
|
("No transfers in progress", "هیچ انتقالی در حال انجام نیست"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"),
|
("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"),
|
||||||
("Ignore Battery Optimizations", "Ignorer les optimisations batterie"),
|
("Ignore Battery Optimizations", "Ignorer les optimisations batterie"),
|
||||||
("android_open_battery_optimizations_tip", "Conseil android d'optimisation de batterie"),
|
("android_open_battery_optimizations_tip", "Conseil android d'optimisation de batterie"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Connexion non autorisée"),
|
("Connection not allowed", "Connexion non autorisée"),
|
||||||
("Legacy mode", "Mode hérité"),
|
("Legacy mode", "Mode hérité"),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"),
|
("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"),
|
||||||
("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"),
|
("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"),
|
||||||
("android_open_battery_optimizations_tip", "Θέλετε να ανοίξετε τις ρυθμίσεις βελτιστοποίησης μπαταρίας;"),
|
("android_open_battery_optimizations_tip", "Θέλετε να ανοίξετε τις ρυθμίσεις βελτιστοποίησης μπαταρίας;"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Η σύνδεση απορρίφθηκε"),
|
("Connection not allowed", "Η σύνδεση απορρίφθηκε"),
|
||||||
("Legacy mode", "Λειτουργία συμβατότητας"),
|
("Legacy mode", "Λειτουργία συμβατότητας"),
|
||||||
("Map mode", "Map mode"),
|
("Map mode", "Map mode"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "RustDesk futtatása a háttérben"),
|
("Keep RustDesk background service", "RustDesk futtatása a háttérben"),
|
||||||
("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívűl hagyása"),
|
("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívűl hagyása"),
|
||||||
("android_open_battery_optimizations_tip", "Ha le szeretné tiltani ezt a funkciót, lépjen a RustDesk alkalmazás beállítási oldalára, keresse meg az [Akkumulátorkímélő] lehetőséget és válassza a nincs korlátozás lehetőséget."),
|
("android_open_battery_optimizations_tip", "Ha le szeretné tiltani ezt a funkciót, lépjen a RustDesk alkalmazás beállítási oldalára, keresse meg az [Akkumulátorkímélő] lehetőséget és válassza a nincs korlátozás lehetőséget."),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "A csatlakozás nem engedélyezett"),
|
("Connection not allowed", "A csatlakozás nem engedélyezett"),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
|
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
|
||||||
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
|
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
|
||||||
("android_open_battery_optimizations_tip", ""),
|
("android_open_battery_optimizations_tip", ""),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Koneksi tidak dijinkan"),
|
("Connection not allowed", "Koneksi tidak dijinkan"),
|
||||||
("Legacy mode", "Mode lama"),
|
("Legacy mode", "Mode lama"),
|
||||||
("Map mode", "Mode peta"),
|
("Map mode", "Mode peta"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"),
|
("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"),
|
||||||
("Ignore Battery Optimizations", "Ignora le ottimizzazioni della batteria"),
|
("Ignore Battery Optimizations", "Ignora le ottimizzazioni della batteria"),
|
||||||
("android_open_battery_optimizations_tip", "Se si desidera disabilitare questa funzione, andare nelle impostazioni dell'applicazione RustDesk, aprire la sezione [Batteria] e deselezionare [Senza restrizioni]."),
|
("android_open_battery_optimizations_tip", "Se si desidera disabilitare questa funzione, andare nelle impostazioni dell'applicazione RustDesk, aprire la sezione [Batteria] e deselezionare [Senza restrizioni]."),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Connessione non consentita"),
|
("Connection not allowed", "Connessione non consentita"),
|
||||||
("Legacy mode", "Modalità legacy"),
|
("Legacy mode", "Modalità legacy"),
|
||||||
("Map mode", "Modalità mappa"),
|
("Map mode", "Modalità mappa"),
|
||||||
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "Interrompi la chiamata vocale"),
|
("Stop voice call", "Interrompi la chiamata vocale"),
|
||||||
("relay_hint_tip", "Se non è possibile connettersi direttamente, si può provare a farlo tramite relay.\nInoltre, se si desidera utilizzare il relay al primo tentativo, è possibile aggiungere il suffisso \"/r\" all'ID o selezionare l'opzione \"Collegati sempre tramite relay\" nella scheda peer."),
|
("relay_hint_tip", "Se non è possibile connettersi direttamente, si può provare a farlo tramite relay.\nInoltre, se si desidera utilizzare il relay al primo tentativo, è possibile aggiungere il suffisso \"/r\" all'ID o selezionare l'opzione \"Collegati sempre tramite relay\" nella scheda peer."),
|
||||||
("Reconnect", "Riconnetti"),
|
("Reconnect", "Riconnetti"),
|
||||||
("No transfers in progress", "Nessun trasferimento in corso"),
|
|
||||||
("Codec", "Codec"),
|
("Codec", "Codec"),
|
||||||
("Resolution", "Risoluzione"),
|
("Resolution", "Risoluzione"),
|
||||||
|
("No transfers in progress", "Nessun trasferimento in corso"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"),
|
("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"),
|
||||||
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
|
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
|
||||||
("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"),
|
("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "接続が許可されていません"),
|
("Connection not allowed", "接続が許可されていません"),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"),
|
("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"),
|
||||||
("Ignore Battery Optimizations", "배터리 최적화 무시하기"),
|
("Ignore Battery Optimizations", "배터리 최적화 무시하기"),
|
||||||
("android_open_battery_optimizations_tip", "해당 기능을 비활성화하려면 RustDesk 응용 프로그램 설정 페이지로 이동하여 [배터리]에서 [제한 없음] 선택을 해제하십시오."),
|
("android_open_battery_optimizations_tip", "해당 기능을 비활성화하려면 RustDesk 응용 프로그램 설정 페이지로 이동하여 [배터리]에서 [제한 없음] 선택을 해제하십시오."),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "연결이 허용되지 않음"),
|
("Connection not allowed", "연결이 허용되지 않음"),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"),
|
("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"),
|
||||||
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
|
("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"),
|
||||||
("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"),
|
("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Қосылу рұқсат етілмеген"),
|
("Connection not allowed", "Қосылу рұқсат етілмеген"),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "RustDesk achtergronddienst behouden"),
|
("Keep RustDesk background service", "RustDesk achtergronddienst behouden"),
|
||||||
("Ignore Battery Optimizations", "Negeer Batterij Optimalisaties"),
|
("Ignore Battery Optimizations", "Negeer Batterij Optimalisaties"),
|
||||||
("android_open_battery_optimizations_tip", "Ga naar de volgende pagina met instellingen"),
|
("android_open_battery_optimizations_tip", "Ga naar de volgende pagina met instellingen"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Verbinding niet toegestaan"),
|
("Connection not allowed", "Verbinding niet toegestaan"),
|
||||||
("Legacy mode", "Verouderde modus"),
|
("Legacy mode", "Verouderde modus"),
|
||||||
("Map mode", "Map mode"),
|
("Map mode", "Map mode"),
|
||||||
@@ -444,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Default View Style", "Standaard Weergave Stijl"),
|
("Default View Style", "Standaard Weergave Stijl"),
|
||||||
("Default Scroll Style", "Standaard Scroll Stijl"),
|
("Default Scroll Style", "Standaard Scroll Stijl"),
|
||||||
("Default Image Quality", "Standaard Beeldkwaliteit"),
|
("Default Image Quality", "Standaard Beeldkwaliteit"),
|
||||||
("Default Codec", "tandaard Codec"),
|
("Default Codec", "Standaard Codec"),
|
||||||
("Bitrate", "Bitrate"),
|
("Bitrate", "Bitrate"),
|
||||||
("FPS", "FPS"),
|
("FPS", "FPS"),
|
||||||
("Auto", "Auto"),
|
("Auto", "Auto"),
|
||||||
@@ -452,10 +454,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Voice call", "Spraakoproep"),
|
("Voice call", "Spraakoproep"),
|
||||||
("Text chat", "Tekst chat"),
|
("Text chat", "Tekst chat"),
|
||||||
("Stop voice call", "Stop spraakoproep"),
|
("Stop voice call", "Stop spraakoproep"),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", "Indien een directe verbinding niet mogelijk is, kunt u proberen verbinding te maken via een Relay Server. \nAls u bij de eerste poging een relaisverbinding tot stand wilt brengen, kunt u het achtervoegsel \"/r\" toevoegen aan het ID of de optie \"Altijd verbinden via relaisserver\" selecteren op de externe terminal."),
|
||||||
("Reconnect", ""),
|
("Reconnect", "Herverbinden"),
|
||||||
("Codec", ""),
|
("Codec", "Codec"),
|
||||||
("Resolution", ""),
|
("Resolution", "Resolutie"),
|
||||||
("No transfers in progress", ""),
|
("No transfers in progress", "Geen overdrachten in uitvoering"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Zachowaj usługę RustDesk w tle"),
|
("Keep RustDesk background service", "Zachowaj usługę RustDesk w tle"),
|
||||||
("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"),
|
("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"),
|
||||||
("android_open_battery_optimizations_tip", "Jeśli chcesz wyłączyć tę funkcję, przejdź do następnej strony ustawień aplikacji RustDesk, znajdź i wprowadź [Bateria], odznacz [Bez ograniczeń]"),
|
("android_open_battery_optimizations_tip", "Jeśli chcesz wyłączyć tę funkcję, przejdź do następnej strony ustawień aplikacji RustDesk, znajdź i wprowadź [Bateria], odznacz [Bez ograniczeń]"),
|
||||||
|
("Start on Boot", "Autostart"),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", "Uruchom usługę udostępniania ekranu podczas startu, wymaga specjalnych uprawnień"),
|
||||||
("Connection not allowed", "Połączenie niedozwolone"),
|
("Connection not allowed", "Połączenie niedozwolone"),
|
||||||
("Legacy mode", "Tryb kompatybilności wstecznej (legacy)"),
|
("Legacy mode", "Tryb kompatybilności wstecznej (legacy)"),
|
||||||
("Map mode", "Tryb mapowania"),
|
("Map mode", "Tryb mapowania"),
|
||||||
@@ -456,9 +458,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Reconnect", "Połącz ponownie"),
|
("Reconnect", "Połącz ponownie"),
|
||||||
("Codec", "Kodek"),
|
("Codec", "Kodek"),
|
||||||
("Resolution", "Rozdzielczość"),
|
("Resolution", "Rozdzielczość"),
|
||||||
("Use temporary password", "Użyj hasła tymczasowego"),
|
|
||||||
("Set temporary password length", "Ustaw długość hasła tymczasowego"),
|
|
||||||
("Key", "Klucz"),
|
("Key", "Klucz"),
|
||||||
("No transfers in progress", ""),
|
("No transfers in progress", "Brak transferów w toku"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Manter o serviço RustDesk em funcionamento"),
|
("Keep RustDesk background service", "Manter o serviço RustDesk em funcionamento"),
|
||||||
("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"),
|
("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"),
|
||||||
("android_open_battery_optimizations_tip", ""),
|
("android_open_battery_optimizations_tip", ""),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Ligação não autorizada"),
|
("Connection not allowed", "Ligação não autorizada"),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", ""),
|
("Stop voice call", ""),
|
||||||
("relay_hint_tip", ""),
|
("relay_hint_tip", ""),
|
||||||
("Reconnect", ""),
|
("Reconnect", ""),
|
||||||
("No transfers in progress", ""),
|
|
||||||
("Codec", ""),
|
("Codec", ""),
|
||||||
("Resolution", ""),
|
("Resolution", ""),
|
||||||
|
("No transfers in progress", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"),
|
("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"),
|
||||||
("Ignore Battery Optimizations", "Ignorar otimizações de bateria"),
|
("Ignore Battery Optimizations", "Ignorar otimizações de bateria"),
|
||||||
("android_open_battery_optimizations_tip", "Abrir otimizações de bateria"),
|
("android_open_battery_optimizations_tip", "Abrir otimizações de bateria"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Conexão não permitida"),
|
("Connection not allowed", "Conexão não permitida"),
|
||||||
("Legacy mode", "Modo legado"),
|
("Legacy mode", "Modo legado"),
|
||||||
("Map mode", "Modo mapa"),
|
("Map mode", "Modo mapa"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"),
|
("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"),
|
||||||
("Ignore Battery Optimizations", "Ignoră optimizările de baterie"),
|
("Ignore Battery Optimizations", "Ignoră optimizările de baterie"),
|
||||||
("android_open_battery_optimizations_tip", "Pentru dezactivarea acestei funcții, accesează setările aplicației RustDesk, deschide secțiunea [Baterie] și deselectează [Fără restricții]."),
|
("android_open_battery_optimizations_tip", "Pentru dezactivarea acestei funcții, accesează setările aplicației RustDesk, deschide secțiunea [Baterie] și deselectează [Fără restricții]."),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Conexiune neautoriztă"),
|
("Connection not allowed", "Conexiune neautoriztă"),
|
||||||
("Legacy mode", "Mod legacy"),
|
("Legacy mode", "Mod legacy"),
|
||||||
("Map mode", "Mod hartă"),
|
("Map mode", "Mod hartă"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Держать в фоне службу RustDesk"),
|
("Keep RustDesk background service", "Держать в фоне службу RustDesk"),
|
||||||
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
|
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
|
||||||
("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек"),
|
("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Подключение не разрешено"),
|
("Connection not allowed", "Подключение не разрешено"),
|
||||||
("Legacy mode", "Устаревший режим"),
|
("Legacy mode", "Устаревший режим"),
|
||||||
("Map mode", "Режим сопоставления"),
|
("Map mode", "Режим сопоставления"),
|
||||||
@@ -454,8 +456,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "Завершить голосовой вызов"),
|
("Stop voice call", "Завершить голосовой вызов"),
|
||||||
("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."),
|
("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."),
|
||||||
("Reconnect", "Переподключить"),
|
("Reconnect", "Переподключить"),
|
||||||
("Codec", ""),
|
("Codec", "Кодек"),
|
||||||
("Resolution", ""),
|
("Resolution", "Разрешение"),
|
||||||
("No transfers in progress", ""),
|
("No transfers in progress", "Передача не осуществляется"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", ""),
|
("Keep RustDesk background service", ""),
|
||||||
("Ignore Battery Optimizations", ""),
|
("Ignore Battery Optimizations", ""),
|
||||||
("android_open_battery_optimizations_tip", ""),
|
("android_open_battery_optimizations_tip", ""),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", ""),
|
("Connection not allowed", ""),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"),
|
("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"),
|
||||||
("Ignore Battery Optimizations", "Prezri optimizacije baterije"),
|
("Ignore Battery Optimizations", "Prezri optimizacije baterije"),
|
||||||
("android_open_battery_optimizations_tip", "Če želite izklopiti to možnost, pojdite v nastavitve aplikacije RustDesk, poiščite »Baterija« in izklopite »Neomejeno«"),
|
("android_open_battery_optimizations_tip", "Če želite izklopiti to možnost, pojdite v nastavitve aplikacije RustDesk, poiščite »Baterija« in izklopite »Neomejeno«"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Povezava ni dovoljena"),
|
("Connection not allowed", "Povezava ni dovoljena"),
|
||||||
("Legacy mode", "Stari način"),
|
("Legacy mode", "Stari način"),
|
||||||
("Map mode", "Način preslikave"),
|
("Map mode", "Način preslikave"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Mbaje shërbimin e sfondit të RustDesk"),
|
("Keep RustDesk background service", "Mbaje shërbimin e sfondit të RustDesk"),
|
||||||
("Ignore Battery Optimizations", "Injoro optimizimet e baterisë"),
|
("Ignore Battery Optimizations", "Injoro optimizimet e baterisë"),
|
||||||
("android_open_battery_optimizations_tip", "Nëse dëshironi ta çaktivizoni këtë veçori, ju lutemi shkoni te faqja tjetër e cilësimeve të aplikacionit RustDesk, gjeni dhe shtypni [Batteri], hiqni zgjedhjen [Te pakufizuara]"),
|
("android_open_battery_optimizations_tip", "Nëse dëshironi ta çaktivizoni këtë veçori, ju lutemi shkoni te faqja tjetër e cilësimeve të aplikacionit RustDesk, gjeni dhe shtypni [Batteri], hiqni zgjedhjen [Te pakufizuara]"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Lidhja nuk lejohet"),
|
("Connection not allowed", "Lidhja nuk lejohet"),
|
||||||
("Legacy mode", "Modaliteti i trashëgimisë"),
|
("Legacy mode", "Modaliteti i trashëgimisë"),
|
||||||
("Map mode", "Modaliteti i hartës"),
|
("Map mode", "Modaliteti i hartës"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Zadrži RustDesk kao pozadinski servis"),
|
("Keep RustDesk background service", "Zadrži RustDesk kao pozadinski servis"),
|
||||||
("Ignore Battery Optimizations", "Zanemari optimizacije baterije"),
|
("Ignore Battery Optimizations", "Zanemari optimizacije baterije"),
|
||||||
("android_open_battery_optimizations_tip", "Ako želite da onemogućite ovu funkciju, molimo idite na sledeću stranicu za podešavanje RustDesk aplikacije, pronađite i uđite u [Battery], isključite [Unrestricted]"),
|
("android_open_battery_optimizations_tip", "Ako želite da onemogućite ovu funkciju, molimo idite na sledeću stranicu za podešavanje RustDesk aplikacije, pronađite i uđite u [Battery], isključite [Unrestricted]"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Konekcija nije dozvoljena"),
|
("Connection not allowed", "Konekcija nije dozvoljena"),
|
||||||
("Legacy mode", "Zastareli mod"),
|
("Legacy mode", "Zastareli mod"),
|
||||||
("Map mode", "Mod mapiranja"),
|
("Map mode", "Mod mapiranja"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"),
|
("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"),
|
||||||
("Ignore Battery Optimizations", "Ignorera batterioptimering"),
|
("Ignore Battery Optimizations", "Ignorera batterioptimering"),
|
||||||
("android_open_battery_optimizations_tip", "Om du vill stänga av denna funktion, gå till nästa RustDesk programs inställningar, hitta [Batteri], Checka ur [Obegränsad]"),
|
("android_open_battery_optimizations_tip", "Om du vill stänga av denna funktion, gå till nästa RustDesk programs inställningar, hitta [Batteri], Checka ur [Obegränsad]"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Anslutning ej tillåten"),
|
("Connection not allowed", "Anslutning ej tillåten"),
|
||||||
("Legacy mode", "Legacy mode"),
|
("Legacy mode", "Legacy mode"),
|
||||||
("Map mode", "Kartläge"),
|
("Map mode", "Kartläge"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", ""),
|
("Keep RustDesk background service", ""),
|
||||||
("Ignore Battery Optimizations", ""),
|
("Ignore Battery Optimizations", ""),
|
||||||
("android_open_battery_optimizations_tip", ""),
|
("android_open_battery_optimizations_tip", ""),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", ""),
|
("Connection not allowed", ""),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"),
|
("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"),
|
||||||
("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"),
|
("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"),
|
||||||
("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"),
|
("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"),
|
("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"),
|
("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"),
|
||||||
("Ignore Battery Optimizations", "Pil Optimizasyonlarını Yoksay"),
|
("Ignore Battery Optimizations", "Pil Optimizasyonlarını Yoksay"),
|
||||||
("android_open_battery_optimizations_tip", ""),
|
("android_open_battery_optimizations_tip", ""),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "bağlantıya izin verilmedi"),
|
("Connection not allowed", "bağlantıya izin verilmedi"),
|
||||||
("Legacy mode", "Eski mod"),
|
("Legacy mode", "Eski mod"),
|
||||||
("Map mode", "Haritalama modu"),
|
("Map mode", "Haritalama modu"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "保持RustDesk後台服務"),
|
("Keep RustDesk background service", "保持RustDesk後台服務"),
|
||||||
("Ignore Battery Optimizations", "忽略電池優化"),
|
("Ignore Battery Optimizations", "忽略電池優化"),
|
||||||
("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的RustDesk應用設置頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
|
("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的RustDesk應用設置頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "對方不允許連接"),
|
("Connection not allowed", "對方不允許連接"),
|
||||||
("Legacy mode", "傳統模式"),
|
("Legacy mode", "傳統模式"),
|
||||||
("Map mode", "1:1傳輸"),
|
("Map mode", "1:1傳輸"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Зберегти фонову службу RustDesk"),
|
("Keep RustDesk background service", "Зберегти фонову службу RustDesk"),
|
||||||
("Ignore Battery Optimizations", "Ігнорувати оптимізацію батареї"),
|
("Ignore Battery Optimizations", "Ігнорувати оптимізацію батареї"),
|
||||||
("android_open_battery_optimizations_tip", "Перейдіть на наступну сторінку налаштувань"),
|
("android_open_battery_optimizations_tip", "Перейдіть на наступну сторінку налаштувань"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Підключення не дозволено"),
|
("Connection not allowed", "Підключення не дозволено"),
|
||||||
("Legacy mode", "Застарілий режим"),
|
("Legacy mode", "Застарілий режим"),
|
||||||
("Map mode", "Режим карти"),
|
("Map mode", "Режим карти"),
|
||||||
|
|||||||
@@ -312,6 +312,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Keep RustDesk background service", "Giữ dịch vụ nền RustDesk"),
|
("Keep RustDesk background service", "Giữ dịch vụ nền RustDesk"),
|
||||||
("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"),
|
("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"),
|
||||||
("android_open_battery_optimizations_tip", "Nếu bạn muốn tắt tính năng này, vui lòng chuyển đến trang cài đặt ứng dụng RustDesk tiếp theo, tìm và nhập [Pin], Bỏ chọn [Không hạn chế]"),
|
("android_open_battery_optimizations_tip", "Nếu bạn muốn tắt tính năng này, vui lòng chuyển đến trang cài đặt ứng dụng RustDesk tiếp theo, tìm và nhập [Pin], Bỏ chọn [Không hạn chế]"),
|
||||||
|
("Start on Boot", ""),
|
||||||
|
("Start the screen sharing service on boot, requires special permissions", ""),
|
||||||
("Connection not allowed", "Kết nối không đuợc phép"),
|
("Connection not allowed", "Kết nối không đuợc phép"),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Specify the Windows subsystem to eliminate console window.
|
#![cfg_attr(
|
||||||
// Requires Rust 1.18.
|
all(not(debug_assertions), target_os = "windows"),
|
||||||
//#![windows_subsystem = "windows"]
|
windows_subsystem = "windows"
|
||||||
|
)]
|
||||||
|
|
||||||
use librustdesk::*;
|
use librustdesk::*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#import <AVFoundation/AVFoundation.h>
|
#import <AVFoundation/AVFoundation.h>
|
||||||
#import <AppKit/AppKit.h>
|
#import <AppKit/AppKit.h>
|
||||||
#import <IOKit/hidsystem/IOHIDLib.h>
|
#import <IOKit/hidsystem/IOHIDLib.h>
|
||||||
|
#include <Security/Authorization.h>
|
||||||
|
#include <Security/AuthorizationTags.h>
|
||||||
|
|
||||||
|
|
||||||
// https://github.com/codebytere/node-mac-permissions/blob/main/permissions.mm
|
// https://github.com/codebytere/node-mac-permissions/blob/main/permissions.mm
|
||||||
|
|
||||||
@@ -35,6 +38,33 @@ extern "C" bool InputMonitoringAuthStatus(bool prompt) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" bool MacCheckAdminAuthorization() {
|
||||||
|
AuthorizationRef authRef;
|
||||||
|
OSStatus status;
|
||||||
|
|
||||||
|
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
|
||||||
|
kAuthorizationFlagDefaults, &authRef);
|
||||||
|
if (status != errAuthorizationSuccess) {
|
||||||
|
printf("Failed to create AuthorizationRef\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorizationItem authItem = {kAuthorizationRightExecute, 0, NULL, 0};
|
||||||
|
AuthorizationRights authRights = {1, &authItem};
|
||||||
|
AuthorizationFlags flags = kAuthorizationFlagDefaults |
|
||||||
|
kAuthorizationFlagInteractionAllowed |
|
||||||
|
kAuthorizationFlagPreAuthorize |
|
||||||
|
kAuthorizationFlagExtendRights;
|
||||||
|
status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
|
||||||
|
if (status != errAuthorizationSuccess) {
|
||||||
|
printf("Failed to authorize\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorizationFree(authRef, kAuthorizationFlagDefaults);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" float BackingScaleFactor() {
|
extern "C" float BackingScaleFactor() {
|
||||||
NSScreen* s = [NSScreen mainScreen];
|
NSScreen* s = [NSScreen mainScreen];
|
||||||
if (s) return [s backingScaleFactor];
|
if (s) return [s backingScaleFactor];
|
||||||
@@ -44,44 +74,11 @@ extern "C" float BackingScaleFactor() {
|
|||||||
// https://github.com/jhford/screenresolution/blob/master/cg_utils.c
|
// https://github.com/jhford/screenresolution/blob/master/cg_utils.c
|
||||||
// https://github.com/jdoupe/screenres/blob/master/setgetscreen.m
|
// https://github.com/jdoupe/screenres/blob/master/setgetscreen.m
|
||||||
|
|
||||||
extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) {
|
|
||||||
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
|
|
||||||
if (allModes == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*numModes = CFArrayGetCount(allModes);
|
|
||||||
CFRelease(allModes);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" bool MacGetModes(CGDirectDisplayID display, uint32_t *widths, uint32_t *heights, uint32_t max, uint32_t *numModes) {
|
|
||||||
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
|
|
||||||
if (allModes == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*numModes = CFArrayGetCount(allModes);
|
|
||||||
for (uint32_t i = 0; i < *numModes && i < max; i++) {
|
|
||||||
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
|
|
||||||
widths[i] = (uint32_t)CGDisplayModeGetWidth(mode);
|
|
||||||
heights[i] = (uint32_t)CGDisplayModeGetHeight(mode);
|
|
||||||
}
|
|
||||||
CFRelease(allModes);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" bool MacGetMode(CGDirectDisplayID display, uint32_t *width, uint32_t *height) {
|
|
||||||
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display);
|
|
||||||
if (mode == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*width = (uint32_t)CGDisplayModeGetWidth(mode);
|
|
||||||
*height = (uint32_t)CGDisplayModeGetHeight(mode);
|
|
||||||
CGDisplayModeRelease(mode);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t bitDepth(CGDisplayModeRef mode) {
|
size_t bitDepth(CGDisplayModeRef mode) {
|
||||||
size_t depth = 0;
|
size_t depth = 0;
|
||||||
|
// Deprecated, same display same bpp?
|
||||||
|
// https://stackoverflow.com/questions/8210824/how-to-avoid-cgdisplaymodecopypixelencoding-to-get-bpp
|
||||||
|
// https://github.com/libsdl-org/SDL/pull/6628
|
||||||
CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(mode);
|
CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(mode);
|
||||||
// my numerical representation for kIO16BitFloatPixels and kIO32bitFloatPixels
|
// my numerical representation for kIO16BitFloatPixels and kIO32bitFloatPixels
|
||||||
// are made up and possibly non-sensical
|
// are made up and possibly non-sensical
|
||||||
@@ -104,7 +101,56 @@ size_t bitDepth(CGDisplayModeRef mode) {
|
|||||||
return depth;
|
return depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) {
|
extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) {
|
||||||
|
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
|
||||||
|
if (allModes == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*numModes = CFArrayGetCount(allModes);
|
||||||
|
CFRelease(allModes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool MacGetModes(CGDirectDisplayID display, uint32_t *widths, uint32_t *heights, uint32_t max, uint32_t *numModes) {
|
||||||
|
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display);
|
||||||
|
if (currentMode == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
|
||||||
|
if (allModes == NULL) {
|
||||||
|
CGDisplayModeRelease(currentMode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint32_t allModeCount = CFArrayGetCount(allModes);
|
||||||
|
uint32_t realNum = 0;
|
||||||
|
for (uint32_t i = 0; i < allModeCount && realNum < max; i++) {
|
||||||
|
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
|
||||||
|
if (CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode) &&
|
||||||
|
bitDepth(currentMode) == bitDepth(mode)) {
|
||||||
|
widths[realNum] = (uint32_t)CGDisplayModeGetWidth(mode);
|
||||||
|
heights[realNum] = (uint32_t)CGDisplayModeGetHeight(mode);
|
||||||
|
realNum++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*numModes = realNum;
|
||||||
|
CGDisplayModeRelease(currentMode);
|
||||||
|
CFRelease(allModes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool MacGetMode(CGDirectDisplayID display, uint32_t *width, uint32_t *height) {
|
||||||
|
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display);
|
||||||
|
if (mode == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*width = (uint32_t)CGDisplayModeGetWidth(mode);
|
||||||
|
*height = (uint32_t)CGDisplayModeGetHeight(mode);
|
||||||
|
CGDisplayModeRelease(mode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) {
|
||||||
CGError rc;
|
CGError rc;
|
||||||
CGDisplayConfigRef config;
|
CGDisplayConfigRef config;
|
||||||
rc = CGBeginDisplayConfiguration(&config);
|
rc = CGBeginDisplayConfiguration(&config);
|
||||||
@@ -122,7 +168,6 @@ bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t height)
|
extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t height)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
@@ -140,8 +185,8 @@ extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t h
|
|||||||
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
|
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
|
||||||
if (width == CGDisplayModeGetWidth(mode) &&
|
if (width == CGDisplayModeGetWidth(mode) &&
|
||||||
height == CGDisplayModeGetHeight(mode) &&
|
height == CGDisplayModeGetHeight(mode) &&
|
||||||
bitDepth(currentMode) == bitDepth(mode) &&
|
CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode) &&
|
||||||
CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode)) {
|
bitDepth(currentMode) == bitDepth(mode)) {
|
||||||
ret = setDisplayToMode(display, mode);
|
ret = setDisplayToMode(display, mode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ extern "C" {
|
|||||||
static kAXTrustedCheckOptionPrompt: CFStringRef;
|
static kAXTrustedCheckOptionPrompt: CFStringRef;
|
||||||
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
|
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
|
||||||
fn InputMonitoringAuthStatus(_: BOOL) -> BOOL;
|
fn InputMonitoringAuthStatus(_: BOOL) -> BOOL;
|
||||||
|
fn MacCheckAdminAuthorization() -> BOOL;
|
||||||
fn MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL;
|
fn MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL;
|
||||||
fn MacGetModes(
|
fn MacGetModes(
|
||||||
display: u32,
|
display: u32,
|
||||||
@@ -665,3 +666,10 @@ pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn check_super_user_permission() -> ResultType<bool> {
|
||||||
|
unsafe {
|
||||||
|
Ok(MacCheckAdminAuthorization() == YES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -976,7 +976,7 @@ fn get_after_install(exe: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn install_me(options: &str, path: String, silent: bool, debug: bool) -> ResultType<()> {
|
pub fn install_me(options: &str, path: String, silent: bool, debug: bool) -> ResultType<()> {
|
||||||
let uninstall_str = get_uninstall();
|
let uninstall_str = get_uninstall(false);
|
||||||
let mut path = path.trim_end_matches('\\').to_owned();
|
let mut path = path.trim_end_matches('\\').to_owned();
|
||||||
let (subkey, _path, start_menu, exe) = get_default_install_info();
|
let (subkey, _path, start_menu, exe) = get_default_install_info();
|
||||||
let mut exe = exe;
|
let mut exe = exe;
|
||||||
@@ -1188,30 +1188,35 @@ pub fn run_after_install() -> ResultType<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_before_uninstall() -> ResultType<()> {
|
pub fn run_before_uninstall() -> ResultType<()> {
|
||||||
run_cmds(get_before_uninstall(), true, "before_install")
|
run_cmds(get_before_uninstall(true), true, "before_install")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_before_uninstall() -> String {
|
fn get_before_uninstall(kill_self: bool) -> String {
|
||||||
let app_name = crate::get_app_name();
|
let app_name = crate::get_app_name();
|
||||||
let ext = app_name.to_lowercase();
|
let ext = app_name.to_lowercase();
|
||||||
|
let filter = if kill_self {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
format!(" /FI \"PID ne {}\"", get_current_pid())
|
||||||
|
};
|
||||||
format!(
|
format!(
|
||||||
"
|
"
|
||||||
chcp 65001
|
chcp 65001
|
||||||
sc stop {app_name}
|
sc stop {app_name}
|
||||||
sc delete {app_name}
|
sc delete {app_name}
|
||||||
taskkill /F /IM {broker_exe}
|
taskkill /F /IM {broker_exe}
|
||||||
taskkill /F /IM {app_name}.exe /FI \"PID ne {cur_pid}\"
|
taskkill /F /IM {app_name}.exe{filter}
|
||||||
reg delete HKEY_CLASSES_ROOT\\.{ext} /f
|
reg delete HKEY_CLASSES_ROOT\\.{ext} /f
|
||||||
netsh advfirewall firewall delete rule name=\"{app_name} Service\"
|
netsh advfirewall firewall delete rule name=\"{app_name} Service\"
|
||||||
",
|
",
|
||||||
app_name = app_name,
|
app_name = app_name,
|
||||||
broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE,
|
broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE,
|
||||||
ext = ext,
|
ext = ext,
|
||||||
cur_pid = get_current_pid(),
|
filter = filter,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_uninstall() -> String {
|
fn get_uninstall(kill_self: bool) -> String {
|
||||||
let (subkey, path, start_menu, _) = get_install_info();
|
let (subkey, path, start_menu, _) = get_install_info();
|
||||||
format!(
|
format!(
|
||||||
"
|
"
|
||||||
@@ -1222,7 +1227,7 @@ fn get_uninstall() -> String {
|
|||||||
if exist \"%PUBLIC%\\Desktop\\{app_name}.lnk\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}.lnk\"
|
if exist \"%PUBLIC%\\Desktop\\{app_name}.lnk\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}.lnk\"
|
||||||
if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\"
|
if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\"
|
||||||
",
|
",
|
||||||
before_uninstall=get_before_uninstall(),
|
before_uninstall=get_before_uninstall(kill_self),
|
||||||
subkey=subkey,
|
subkey=subkey,
|
||||||
app_name = crate::get_app_name(),
|
app_name = crate::get_app_name(),
|
||||||
path = path,
|
path = path,
|
||||||
@@ -1231,11 +1236,20 @@ fn get_uninstall() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn uninstall_me() -> ResultType<()> {
|
pub fn uninstall_me() -> ResultType<()> {
|
||||||
run_cmds(get_uninstall(), true, "uninstall")
|
run_cmds(get_uninstall(true), true, "uninstall")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_cmds(cmds: String, ext: &str, tip: &str) -> ResultType<std::path::PathBuf> {
|
fn write_cmds(cmds: String, ext: &str, tip: &str) -> ResultType<std::path::PathBuf> {
|
||||||
let mut tmp = std::env::temp_dir();
|
let mut tmp = std::env::temp_dir();
|
||||||
|
// When dir contains these characters, the bat file will not execute in elevated mode.
|
||||||
|
if vec!["&", "@", "^"]
|
||||||
|
.drain(..)
|
||||||
|
.any(|s| tmp.to_string_lossy().to_string().contains(s))
|
||||||
|
{
|
||||||
|
if let Ok(dir) = user_accessible_folder() {
|
||||||
|
tmp = dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
tmp.push(format!("{}_{}.{}", crate::get_app_name(), tip, ext));
|
tmp.push(format!("{}_{}.{}", crate::get_app_name(), tip, ext));
|
||||||
let mut file = std::fs::File::create(&tmp)?;
|
let mut file = std::fs::File::create(&tmp)?;
|
||||||
// in case cmds mixed with \r\n and \n, make sure all ending with \r\n
|
// in case cmds mixed with \r\n and \n, make sure all ending with \r\n
|
||||||
@@ -1872,3 +1886,19 @@ pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn user_accessible_folder() -> ResultType<PathBuf> {
|
||||||
|
let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string());
|
||||||
|
let dir1 = PathBuf::from(format!("{}\\ProgramData", disk));
|
||||||
|
// NOTICE: "C:\Windows\Temp" requires permanent authorization.
|
||||||
|
let dir2 = PathBuf::from(format!("{}\\Windows\\Temp", disk));
|
||||||
|
let dir;
|
||||||
|
if dir1.exists() {
|
||||||
|
dir = dir1;
|
||||||
|
} else if dir2.exists() {
|
||||||
|
dir = dir2;
|
||||||
|
} else {
|
||||||
|
bail!("no vaild user accessible folder");
|
||||||
|
}
|
||||||
|
Ok(dir)
|
||||||
|
}
|
||||||
|
|||||||
@@ -117,17 +117,7 @@ impl SharedMemory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn flink(name: String) -> ResultType<String> {
|
fn flink(name: String) -> ResultType<String> {
|
||||||
let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string());
|
let mut dir = crate::platform::user_accessible_folder()?;
|
||||||
let dir1 = PathBuf::from(format!("{}\\ProgramData", disk));
|
|
||||||
let dir2 = PathBuf::from(format!("{}\\Windows\\Temp", disk));
|
|
||||||
let mut dir;
|
|
||||||
if dir1.exists() {
|
|
||||||
dir = dir1;
|
|
||||||
} else if dir2.exists() {
|
|
||||||
dir = dir2;
|
|
||||||
} else {
|
|
||||||
bail!("no vaild flink directory");
|
|
||||||
}
|
|
||||||
dir = dir.join(hbb_common::config::APP_NAME.read().unwrap().clone());
|
dir = dir.join(hbb_common::config::APP_NAME.read().unwrap().clone());
|
||||||
if !dir.exists() {
|
if !dir.exists() {
|
||||||
std::fs::create_dir(&dir)?;
|
std::fs::create_dir(&dir)?;
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ class Header: Reactor.Component {
|
|||||||
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
|
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
|
||||||
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
|
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
|
||||||
{keyboard_enabled && pi.platform == "Windows" ? <li #privacy-mode><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
|
{keyboard_enabled && pi.platform == "Windows" ? <li #privacy-mode><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
|
||||||
|
{keyboard_enabled && ((is_osx && pi.platform != "Mac OS") || (!is_osx && pi.platform == "Mac OS")) ? <li #allow_swap_key .toggle-option><span>{svg_checkmark}</span>{translate('Swap control-command key')}</li> : ""}
|
||||||
</menu>
|
</menu>
|
||||||
</popup>;
|
</popup>;
|
||||||
}
|
}
|
||||||
@@ -440,7 +441,7 @@ function toggleMenuState() {
|
|||||||
for (var el in $$(menu#keyboard-options>li)) {
|
for (var el in $$(menu#keyboard-options>li)) {
|
||||||
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
|
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
|
||||||
}
|
}
|
||||||
for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
|
for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end", "allow_swap_key"]) {
|
||||||
var el = self.select('#' + id);
|
var el = self.select('#' + id);
|
||||||
if (el) {
|
if (el) {
|
||||||
var value = handler.get_toggle_option(id);
|
var value = handler.get_toggle_option(id);
|
||||||
|
|||||||
@@ -707,10 +707,10 @@ pub fn is_root() -> bool {
|
|||||||
pub fn check_super_user_permission() -> bool {
|
pub fn check_super_user_permission() -> bool {
|
||||||
#[cfg(feature = "flatpak")]
|
#[cfg(feature = "flatpak")]
|
||||||
return true;
|
return true;
|
||||||
#[cfg(any(windows, target_os = "linux"))]
|
#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
|
||||||
return crate::platform::check_super_user_permission().unwrap_or(false);
|
return crate::platform::check_super_user_permission().unwrap_or(false);
|
||||||
#[cfg(not(any(windows, target_os = "linux")))]
|
#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
|
||||||
true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@@ -373,10 +373,87 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
return "".to_owned();
|
return "".to_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn swab_modifier_key(&self, msg: &mut KeyEvent) {
|
||||||
|
|
||||||
|
let allow_swap_key = self.get_toggle_option("allow_swap_key".to_string());
|
||||||
|
if allow_swap_key {
|
||||||
|
if let Some(key_event::Union::ControlKey(ck)) = msg.union {
|
||||||
|
let ck = ck.enum_value_or_default();
|
||||||
|
let ck = match ck {
|
||||||
|
ControlKey::Control => ControlKey::Meta,
|
||||||
|
ControlKey::Meta => ControlKey::Control,
|
||||||
|
ControlKey::RControl => ControlKey::Meta,
|
||||||
|
ControlKey::RWin => ControlKey::Control,
|
||||||
|
_ => ck,
|
||||||
|
};
|
||||||
|
msg.set_control_key(ck);
|
||||||
|
}
|
||||||
|
msg.modifiers = msg.modifiers.iter().map(|ck| {
|
||||||
|
let ck = ck.enum_value_or_default();
|
||||||
|
let ck = match ck {
|
||||||
|
ControlKey::Control => ControlKey::Meta,
|
||||||
|
ControlKey::Meta => ControlKey::Control,
|
||||||
|
ControlKey::RControl => ControlKey::Meta,
|
||||||
|
ControlKey::RWin => ControlKey::Control,
|
||||||
|
_ => ck,
|
||||||
|
};
|
||||||
|
hbb_common::protobuf::EnumOrUnknown::new(ck)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
|
||||||
|
let code = msg.chr();
|
||||||
|
if code != 0 {
|
||||||
|
let mut peer = self.peer_platform().to_lowercase();
|
||||||
|
peer.retain(|c| !c.is_whitespace());
|
||||||
|
|
||||||
|
let key = match peer.as_str() {
|
||||||
|
"windows" => {
|
||||||
|
let key = rdev::win_key_from_scancode(code);
|
||||||
|
let key = match key {
|
||||||
|
rdev::Key::ControlLeft => rdev::Key::MetaLeft,
|
||||||
|
rdev::Key::MetaLeft => rdev::Key::ControlLeft,
|
||||||
|
rdev::Key::ControlRight => rdev::Key::MetaLeft,
|
||||||
|
rdev::Key::MetaRight => rdev::Key::ControlLeft,
|
||||||
|
_ => key,
|
||||||
|
};
|
||||||
|
rdev::win_scancode_from_key(key).unwrap_or_default()
|
||||||
|
}
|
||||||
|
"macos" => {
|
||||||
|
let key = rdev::macos_key_from_code(code);
|
||||||
|
let key = match key {
|
||||||
|
rdev::Key::ControlLeft => rdev::Key::MetaLeft,
|
||||||
|
rdev::Key::MetaLeft => rdev::Key::ControlLeft,
|
||||||
|
rdev::Key::ControlRight => rdev::Key::MetaLeft,
|
||||||
|
rdev::Key::MetaRight => rdev::Key::ControlLeft,
|
||||||
|
_ => key,
|
||||||
|
};
|
||||||
|
rdev::macos_keycode_from_key(key).unwrap_or_default()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let key = rdev::linux_key_from_code(code);
|
||||||
|
let key = match key {
|
||||||
|
rdev::Key::ControlLeft => rdev::Key::MetaLeft,
|
||||||
|
rdev::Key::MetaLeft => rdev::Key::ControlLeft,
|
||||||
|
rdev::Key::ControlRight => rdev::Key::MetaLeft,
|
||||||
|
rdev::Key::MetaRight => rdev::Key::ControlLeft,
|
||||||
|
_ => key,
|
||||||
|
};
|
||||||
|
rdev::linux_keycode_from_key(key).unwrap_or_default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
msg.set_chr(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
pub fn send_key_event(&self, evt: &KeyEvent) {
|
pub fn send_key_event(&self, evt: &KeyEvent) {
|
||||||
// mode: legacy(0), map(1), translate(2), auto(3)
|
// mode: legacy(0), map(1), translate(2), auto(3)
|
||||||
|
|
||||||
|
let mut msg = evt.clone();
|
||||||
|
self.swab_modifier_key(&mut msg);
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
msg_out.set_key_event(evt.clone());
|
msg_out.set_key_event(msg);
|
||||||
self.send(Data::Message(msg_out));
|
self.send(Data::Message(msg_out));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,6 +1011,23 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
|||||||
handle_test_delay(t, peer).await;
|
handle_test_delay(t, peer).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn swap_modifier_mouse(&self, msg : &mut hbb_common::protos::message::MouseEvent) {
|
||||||
|
let allow_swap_key = self.get_toggle_option("allow_swap_key".to_string());
|
||||||
|
if allow_swap_key {
|
||||||
|
msg.modifiers = msg.modifiers.iter().map(|ck| {
|
||||||
|
let ck = ck.enum_value_or_default();
|
||||||
|
let ck = match ck {
|
||||||
|
ControlKey::Control => ControlKey::Meta,
|
||||||
|
ControlKey::Meta => ControlKey::Control,
|
||||||
|
ControlKey::RControl => ControlKey::Meta,
|
||||||
|
ControlKey::RWin => ControlKey::Control,
|
||||||
|
_ => ck,
|
||||||
|
};
|
||||||
|
hbb_common::protobuf::EnumOrUnknown::new(ck)
|
||||||
|
}).collect();
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InvokeUiSession> Session<T> {
|
impl<T: InvokeUiSession> Session<T> {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"dockerfile": "./Dockerfile",
|
"dockerfile": "./Dockerfile",
|
||||||
"context": "."
|
"context": "."
|
||||||
},
|
},
|
||||||
"workspaceMount": "source=${localWorkspaceFolder},target=/home/vscode/rustdesk,type=bind,consistency=cache",
|
"workspaceMount": "source=${localWorkspaceFolder}/../..,target=/home/vscode/rustdesk,type=bind,consistency=cache",
|
||||||
"workspaceFolder": "/home/vscode/rustdesk",
|
"workspaceFolder": "/home/vscode/rustdesk/vdi/host",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"tamasfe.even-better-toml",
|
"tamasfe.even-better-toml",
|
||||||
"serayuzgur.crates",
|
"serayuzgur.crates",
|
||||||
"mhutchie.git-graph",
|
"mhutchie.git-graph",
|
||||||
|
"formulahendry.terminal",
|
||||||
"eamodio.gitlens"
|
"eamodio.gitlens"
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|||||||
970
vdi/host/Cargo.lock
generated
970
vdi/host/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,6 @@ version = "0.1.0"
|
|||||||
authors = ["rustdesk <info@rustdesk.com>"]
|
authors = ["rustdesk <info@rustdesk.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
qemu-display = { git = "https://gitlab.com/marcandre.lureau/qemu-display" }
|
qemu-display = { git = "https://gitlab.com/marcandre.lureau/qemu-display" }
|
||||||
|
hbb_common = { path = "../../libs/hbb_common" }
|
||||||
|
|||||||
Reference in New Issue
Block a user