Merge branch 'master' into master

This commit is contained in:
yuluo
2024-04-23 03:03:45 +08:00
committed by GitHub
157 changed files with 6056 additions and 2087 deletions

View File

@@ -8,6 +8,7 @@ on:
env:
FLUTTER_VERSION: "3.16.9"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
jobs:
generate_bridge:
@@ -49,9 +50,9 @@ jobs:
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
toolchain: ${{ env.RUST_VERSION }}
targets: ${{ matrix.job.target }}
components: ''
components: "rustfmt"
- uses: Swatinem/rust-cache@v2
with:

View File

@@ -54,8 +54,3 @@ jobs:
- name: Run
shell: bash
run: |
cd /opt/build
#./update_mac_template.sh
#security default-keychain -s rustdesk.keychain
#security unlock-keychain -p ${{ secrets.MACOS_P12_PASSWORD }} rustdesk.keychain
./agent.sh

View File

@@ -71,12 +71,12 @@ jobs:
# - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
# - { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true }
# - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
# - { target: i686-pc-windows-msvc , os: windows-2019 }
# - { target: i686-pc-windows-msvc , os: windows-2022 }
# - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
# - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
# - { target: x86_64-apple-darwin , os: macos-10.15 }
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
# - { target: x86_64-pc-windows-msvc , os: windows-2019 }
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
# - { target: x86_64-pc-windows-msvc , os: windows-2022 }
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 }
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
steps:

View File

@@ -14,7 +14,7 @@ env:
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
CARGO_NDK_VERSION: "3.1.2"
LLVM_VERSION: "15.0.6"
FLUTTER_VERSION: "3.16.9"
FLUTTER_VERSION: "3.19.6"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
# for arm64 linux because official Dart SDK does not work
FLUTTER_ELINUX_VERSION: "3.16.9"
@@ -41,7 +41,7 @@ jobs:
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
with:
upload-artifact: ${{ inputs.upload-artifact }}
target: windows-2019
target: windows-2022
configuration: Release
platform: x64
target_version: Windows10
@@ -56,10 +56,10 @@ jobs:
fail-fast: false
matrix:
job:
# - { target: i686-pc-windows-msvc , os: windows-2019 }
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
- { target: x86_64-pc-windows-msvc, os: windows-2019, arch: x86_64 }
# - { target: aarch64-pc-windows-msvc, os: windows-2019, arch: aarch64 }
# - { target: i686-pc-windows-msvc , os: windows-2022 }
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
- { target: x86_64-pc-windows-msvc, os: windows-2022, arch: x86_64 }
# - { target: aarch64-pc-windows-msvc, os: windows-2022, arch: aarch64 }
steps:
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v6
@@ -83,14 +83,6 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Replace engine with rustdesk custom flutter engine
run: |
flutter doctor -v
flutter precache --windows
Invoke-WebRequest -Uri https://github.com/fufesou/flutter-engine/releases/download/bugfix-subwindow-crash-3.16.9-apply-pull-47787/windows-x64-release.zip -OutFile windows-x64-flutter-release.zip
Expand-Archive windows-x64-flutter-release.zip -DestinationPath .
mv -Force windows-x64-release/* C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@v1
with:
@@ -121,7 +113,13 @@ jobs:
shell: bash
- name: Build rustdesk
run: python3 .\build.py --portable --hwcodec --flutter --gpucodec --skip-portable-pack
run: |
Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip
Expand-Archive usbmmidd_v2.zip -DestinationPath .
python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack --virtual-display
Remove-Item -Path usbmmidd_v2\Win32 -Recurse
Remove-Item -Path "usbmmidd_v2\deviceinstaller64.exe", "usbmmidd_v2\deviceinstaller.exe", "usbmmidd_v2\usbmmidd.bat"
mv -Force .\usbmmidd_v2 ./flutter/build/windows/x64/runner/Release/
- name: find Runner.res
# Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res
@@ -170,6 +168,19 @@ jobs:
mkdir -p ./SignOutput
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.exe
- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@v2
- name: Build msi
if: env.UPLOAD_ARTIFACT == 'true'
run: |
pushd ./res/msi
python preprocess.py --arp -d ../../rustdesk
nuget restore msi.sln
msbuild msi.sln -p:Configuration=Release -p:Platform=x64 /p:TargetVersion=Windows10
mv ./Package/bin/x64/Release/en-us/Package.msi ../../SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-beta.msi
sha256sum ../../SignOutput/rustdesk-*.msi
- name: Sign rustdesk self-extracted file
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != ''
shell: bash
@@ -183,6 +194,7 @@ jobs:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
./SignOutput/rustdesk-*.msi
./SignOutput/rustdesk-*.exe
./rustdesk-*.tar.gz
@@ -196,10 +208,10 @@ jobs:
fail-fast: false
matrix:
job:
# - { target: i686-pc-windows-msvc , os: windows-2019 }
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
- { target: i686-pc-windows-msvc, os: windows-2019, arch: x86 }
# - { target: aarch64-pc-windows-msvc, os: windows-2019 }
# - { target: i686-pc-windows-msvc , os: windows-2022 }
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
- { target: i686-pc-windows-msvc, os: windows-2022, arch: x86 }
# - { target: aarch64-pc-windows-msvc, os: windows-2022 }
steps:
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v6
@@ -245,11 +257,15 @@ jobs:
python3 res/inline-sciter.py
# Patch sciter x86
sed -i 's/branch = "dyn"/branch = "dyn_x86"/g' ./Cargo.toml
cargo build --features inline,gpucodec --release --bins
cargo build --features inline,vram,hwcodec,virtual_display_driver --release --bins
mkdir -p ./Release
mv ./target/release/rustdesk.exe ./Release/rustdesk.exe
curl -LJ -o ./Release/sciter.dll https://github.com/c-smile/sciter-sdk/raw/master/bin.win/x32/sciter.dll
echo "output_folder=./Release" >> $GITHUB_OUTPUT
curl -LJ -o ./usbmmidd_v2.zip https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip
unzip usbmmidd_v2.zip
rm -rf ./usbmmidd_v2/x64 ./usbmmidd_v2/deviceinstaller.exe ./usbmmidd_v2/deviceinstaller64.exe ./usbmmidd_v2/usbmmidd.bat
mv ./usbmmidd_v2 ./Release || true
- name: find Runner.res
# Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res
@@ -325,8 +341,7 @@ jobs:
- name: Build rustdesk
run: |
# --hwcodec not supported on macos yet
./build.py --flutter
./build.py --flutter --hwcodec
- name: create unsigned dmg
if: env.UPLOAD_ARTIFACT == 'true'
@@ -480,8 +495,7 @@ jobs:
- name: Build rustdesk
run: |
# --hwcodec not supported on macos yet
./build.py --flutter ${{ matrix.job.extra-build-args }}
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
- name: create unsigned dmg
if: env.UPLOAD_ARTIFACT == 'true'
@@ -630,7 +644,7 @@ jobs:
- name: Build rustdesk lib
run: |
rustup target add ${{ matrix.job.target }}
cargo build --features flutter --release --target aarch64-apple-ios --lib
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
- name: Build rustdesk
shell: bash
@@ -1251,7 +1265,7 @@ jobs:
export DEFAULT_FEAT=linux_headless
fi
export CARGO_INCREMENTAL=0
cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }},$DEFAULT_FEAT --release
cargo build --lib --features flutter,flutter_texture_render,hwcodec,${{ matrix.job.extra-build-features }},$DEFAULT_FEAT --release
- name: Upload Artifacts
uses: actions/upload-artifact@master
@@ -1594,7 +1608,7 @@ jobs:
;;
esac
export CARGO_INCREMENTAL=0
python3 ./build.py --flutter --hwcodec --skip-cargo
python3 ./build.py --flutter --skip-cargo
# rpm package
echo -e "start packaging fedora package"
pushd /workspace

View File

@@ -18,7 +18,7 @@ jobs:
fail-fast: false
matrix:
job:
- { target: x86_64-pc-windows-msvc, os: windows-2019, arch: x86_64, date: 2023-08-04, ref: 72c198a1e94cc1e0242fce88f92b3f3caedcd0c3 }
- { target: x86_64-pc-windows-msvc, os: windows-2022, arch: x86_64, date: 2023-08-04, ref: 72c198a1e94cc1e0242fce88f92b3f3caedcd0c3 }
steps:
- name: Checkout source code
uses: actions/checkout@v4

View File

@@ -10,7 +10,7 @@ on:
description: 'Target'
required: true
type: string
default: 'windows-2019'
default: 'windows-2022'
configuration:
description: 'Configuration'
required: true

84
Cargo.lock generated
View File

@@ -115,17 +115,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "amf"
version = "0.1.0"
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
dependencies = [
"bindgen 0.59.2",
"cc",
"gpu_common",
"log",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
@@ -2716,36 +2705,6 @@ dependencies = [
"system-deps 6.1.2",
]
[[package]]
name = "gpu_common"
version = "0.1.0"
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
dependencies = [
"bindgen 0.59.2",
"cc",
"log",
"serde 1.0.190",
"serde_derive",
"serde_json 1.0.107",
]
[[package]]
name = "gpucodec"
version = "0.1.0"
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
dependencies = [
"amf",
"bindgen 0.59.2",
"cc",
"gpu_common",
"log",
"nv",
"serde 1.0.190",
"serde_derive",
"serde_json 1.0.107",
"vpl",
]
[[package]]
name = "gstreamer"
version = "0.16.7"
@@ -3140,8 +3099,8 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hwcodec"
version = "0.2.0"
source = "git+https://github.com/21pages/hwcodec?branch=stable#52e1da2aae86acec5f374bc065f5921945b55e7b"
version = "0.3.3"
source = "git+https://github.com/21pages/hwcodec#eeebf980d4eb41daaf05090b097d5a59d688d3d8"
dependencies = [
"bindgen 0.59.2",
"cc",
@@ -4224,17 +4183,6 @@ dependencies = [
"libc",
]
[[package]]
name = "nv"
version = "0.1.0"
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
dependencies = [
"bindgen 0.59.2",
"cc",
"gpu_common",
"log",
]
[[package]]
name = "objc"
version = "0.2.7"
@@ -4852,9 +4800,9 @@ dependencies = [
[[package]]
name = "protobuf"
version = "3.3.0"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190"
checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0"
dependencies = [
"bytes",
"once_cell",
@@ -4864,9 +4812,9 @@ dependencies = [
[[package]]
name = "protobuf-codegen"
version = "3.3.0"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e85514a216b1c73111d9032e26cc7a5ecb1bb3d4d9539e91fb72a4395060f78"
checksum = "32777b0b3f6538d9d2e012b3fad85c7e4b9244b5958d04a6415f4333782b7a77"
dependencies = [
"anyhow",
"once_cell",
@@ -4879,9 +4827,9 @@ dependencies = [
[[package]]
name = "protobuf-parse"
version = "3.3.0"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77d6fbd6697c9e531873e81cec565a85e226b99a0f10e1acc079be057fe2fcba"
checksum = "96cb37955261126624a25b5e6bda40ae34cf3989d52a783087ca6091b29b5642"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@@ -4895,9 +4843,9 @@ dependencies = [
[[package]]
name = "protobuf-support"
version = "3.3.0"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c"
checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7"
dependencies = [
"thiserror",
]
@@ -5856,7 +5804,6 @@ dependencies = [
"cfg-if 1.0.0",
"dbus",
"docopt",
"gpucodec",
"gstreamer",
"gstreamer-app",
"gstreamer-video",
@@ -7097,17 +7044,6 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "vpl"
version = "0.1.0"
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
dependencies = [
"bindgen 0.59.2",
"cc",
"gpu_common",
"log",
]
[[package]]
name = "waker-fn"
version = "1.1.1"

View File

@@ -28,7 +28,7 @@ use_dasp = ["dasp"]
flutter = ["flutter_rust_bridge"]
default = ["use_dasp"]
hwcodec = ["scrap/hwcodec"]
gpucodec = ["scrap/gpucodec"]
vram = ["scrap/vram"]
mediacodec = ["scrap/mediacodec"]
linux_headless = ["pam" ]
virtual_display_driver = ["virtual_display"]
@@ -99,7 +99,20 @@ system_shutdown = "4.0"
qrcode-generator = "4.1"
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["winuser", "wincrypt", "shellscalingapi", "pdh", "synchapi", "memoryapi", "shellapi"] }
winapi = { version = "0.3", features = [
"winuser",
"wincrypt",
"shellscalingapi",
"pdh",
"synchapi",
"memoryapi",
"shellapi",
"devguid",
"setupapi",
"cguid",
"cfgmgr32",
"ioapiset",
] }
winreg = "0.11"
windows-service = "0.6"
virtual_display = { path = "libs/virtual_display", optional = true }

View File

@@ -33,9 +33,9 @@ def get_arch() -> str:
def system2(cmd):
err = os.system(cmd)
if err != 0:
print(f"Error occurred when executing: {cmd}. Exiting.")
exit_code = os.system(cmd)
if exit_code != 0:
sys.stderr.write(f"Error occurred when executing: `{cmd}`. Exiting.\n")
sys.exit(-1)
@@ -118,9 +118,9 @@ def make_parser():
'' if windows or osx else ', need libva-dev, libvdpau-dev.')
)
parser.add_argument(
'--gpucodec',
'--vram',
action='store_true',
help='Enable feature gpucodec, only available on windows now.'
help='Enable feature vram, only available on windows now.'
)
parser.add_argument(
'--portable',
@@ -153,6 +153,12 @@ def make_parser():
action='store_true',
help='Skip packing, only flutter version + Windows supported'
)
parser.add_argument(
'--virtual-display',
action='store_true',
default=False,
help='Build rustdesk libs with the virtual display feature enabled'
)
parser.add_argument(
"--package",
type=str
@@ -282,8 +288,8 @@ def get_features(args):
features = ['inline'] if not args.flutter else []
if args.hwcodec:
features.append('hwcodec')
if args.gpucodec:
features.append('gpucodec')
if args.vram:
features.append('vram')
if args.flutter:
features.append('flutter')
features.append('flutter_texture_render')
@@ -293,6 +299,9 @@ def get_features(args):
features.append('appimage')
if args.unix_file_copy_paste:
features.append('unix-file-copy-paste')
if windows:
if args.virtual_display:
features.append('virtual_display_driver')
print("features:", features)
return features

View File

@@ -1,9 +1,11 @@
#[cfg(windows)]
fn build_windows() {
let file = "src/platform/windows.cc";
cc::Build::new().file(file).compile("windows");
let file2 = "src/platform/windows_delete_test_cert.cc";
cc::Build::new().file(file).file(file2).compile("windows");
println!("cargo:rustc-link-lib=WtsApi32");
println!("cargo:rerun-if-changed={}", file);
println!("cargo:rerun-if-changed={}", file2);
}
#[cfg(target_os = "macos")]

View File

@@ -1,27 +1,29 @@
<p align="center">
<img src="../res/logo-header.svg" alt="RustDesk - Phần mềm điểu khiển máy tính từ xa dành cho bạn"><br>
<a href="#free-public-servers">Máy chủ</a> •
<img src="../res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
<a href="#free-public-servers">Server</a> •
<a href="#raw-steps-to-build">Build</a> •
<a href="#how-to-build-with-docker">Docker</a> •
<a href="#file-structure">Cấu trúc tệp tin</a> •
<a href="#file-structure">Structure</a> •
<a href="#snapshot">Snapshot</a><br>
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
<b>Chúng tôi cần sự gíup đỡ của bạn để dịch trang README này, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> và <a href="https://github.com/rustdesk/doc.rustdesk.com">tài liệu</a> sang ngôn ngữ bản địa của bạn</b>
<b>Chúng tôi rất hoan nghênh sự hỗ trợ của bạn trong việc dịch trang README, trang giao diện người dùng của RustDesk - <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> và trang tài liệu của RustDesk - <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> sang Tiếng Việt</b>
</p>
Chat với chúng tôi qua: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
Hãy trao đổi với chúng tôi qua: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
Một phần mềm điểu khiển máy tính từ xa, đuợc lập trình bằng ngôn ngữ Rust. Hoạt động tức thì, không cần phải cài đặt. Bạn có toàn quyền điểu khiển với dữ liệu của bạn mà không cần phải lo lắng về sự bảo mật. Bạn có thể sử dụng máy chủ rendezvous/relay của chúng tôi, [tự cài đặt máy chủ](https://rustdesk.com/server), hay thậm chí [tự tạo máy chủ rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo).
RustDesk là một phần mềm điểu khiển máy tính từ xa mã nguồn mở, được viết bằng Rust. Nó hoạt động ngay sau khi cài đặt, không yêu cầu cấu hình phức tạp. Bạn có toàn quyền kiểm soát với dữ liệu của mình mà không cần phải lo lắng về vấn đề bảo mật. Bạn có thể sử dụng máy chủ rendezvous/relay của chúng tôi hoặc [tự cài đặt máy chủ của riêng mình](https://rustdesk.com/server) hay thậm chí [tự tạo máy chủ rendezvous/relay cho riêng bạn](https://github.com/rustdesk/rustdesk-server-demo).
![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png)
Mọi người đều đuợc chào đón để đóng góp vào RustDesk. Để bắt đầu, hãy đọc [`docs/CONTRIBUTING.md`](CONTRIBUTING.md).
**RustDesk** luôn hoan nghênh mọi đóng góp từ mọi người. Hãy xem tệp [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) để bắt đầu.
[**RustDesk hoạt động như thế nào?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F)
[**CÁC BẢN PHÂN PHÁT MÃ NHỊ PHÂN**](https://github.com/rustdesk/rustdesk/releases)
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases)
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/FAQreleases/tag/nightly)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
@@ -29,28 +31,25 @@ Mọi người đều đuợc chào đón để đóng góp vào RustDesk. Để
## Dependencies
Phiên bản cho máy tính sử dụng [sciter](https://sciter.com/) cho giao diện của phần mềm, vậy nên bn cần tự tải về thư viện sciter.
Phiên bản máy tính sử dụng __Flutter__ hoặc __Sciter__ (đã lỗi thời) cho giao diện người dùng (GUI). Hướng dẫn này chỉ áp dụng cho phiên bn Sciter, vì nó thân thiện và dễ bắt đầu hơn. Hãy kiểm tra [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) của chúng tôi để xây dựng phiên bản Flutter.
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
Vui lòng tự tải thư viện `Sciter` về máy theo hướng dẫn cho từng hệ điều hành.
Phiên bản cho điện thoại sử dụng Flutter. Chúng tôi sẽ chuyển sang sử dụng Flutter thay cho Sciter cho phiên bản máy tính.
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) | [Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) | [MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
## Cách để build
## Các bước build cơ bản
- Chuẩn bị môi trường phát triển Rust và môi trường build C++
- Chuẩn bị môi trường phát triển Rust và môi trường biên dịch C++
- Tải và cài [vcpkg](https://github.com/microsoft/vcpkg), và đặt biến môi trường `VCPKG_ROOT` sao cho đúng.
- Đối với Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
- Đối với Linux/MacOS: vcpkg install libvpx libyuv opus aom
- Tải và cài đặt [`vcpkg`](https://github.com/microsoft/vcpkg), và thiết lập biến môi trường `VCPKG_ROOT`.
- Windows: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static`
- Linux/MacOS: `vcpkg install libvpx libyuv opus aom`
- Chạy lệnh `cargo run`
## [Build](https://rustdesk.com/docs/en/dev/build/)
## Cách để build cho Linux
## Cách build cho Linux
### Ubuntu 18 (Debian 10)
@@ -70,7 +69,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
```
### Cách cài vcpkg
### Cách cài đặt `vcpkg`
```sh
git clone https://github.com/microsoft/vcpkg
@@ -82,7 +81,7 @@ export VCPKG_ROOT=$HOME/vcpkg
vcpkg/vcpkg install libvpx libyuv opus aom
```
### Cách sửa lỗi libvpx (Dành cho hệ điều hành Fedora)
### Cách sửa lỗi `libvpx` (Dành cho hệ điều hành Fedora)
```sh
cd vcpkg/buildtrees/libvpx/src
@@ -95,7 +94,7 @@ cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
cd
```
### Cách build
### Build
```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
@@ -108,9 +107,9 @@ mv libsciter-gtk.so target/debug
VCPKG_ROOT=$HOME/vcpkg cargo run
```
## Cách để build sử dụng Docker
## Cách build bằng Docker
Bắt đầu bằng cách sao chép repo này về máy tính và build cái Docker cointainer:
Bắt đầu bằng cách sao chép repo này về máy tính của bạn và tạo Docker container:
```sh
git clone https://github.com/rustdesk/rustdesk
@@ -118,37 +117,37 @@ cd rustdesk
docker build -t "rustdesk-builder" .
```
Rồi mỗi khi bạn chạy ứng dụng, thì hãy chạy lệnh này:
Sau đó, mỗi khi bạn chạy ứng dụng, thì hãy chạy dòng lệnh sau:
```sh
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
```
Chú ý: Lần build đầu tiên có thể sẽ mất lâu hơn truớc khi các dependecies đuợc lưu lại, nhng lần build sau sẽ nhanh hơn. Hơn nũa, nếu bạn cần cung cấp các cài đặt lệnh khác cho lệnh build, bạn có thể đặt những cài đặt lệnh này vào cuối lệnh ở phần `<OPTIONAL-ARGS>`. Ví dụ nếu bạn cần build phiên bản đuợc tối ưu hóa, bạn sẽ chạy lệnh trên cùng với cài đặt lệnh --release. Kết quả build sẽ được lưu trong thư mục target trên máy tính của bạn, và có thể chạy với lệnh:
Lưu ý rằng **lần build đầu tiên có thể mất thời gian hơn trước khi các dependencies được lưu vào bộ nhớ cache**, nhưng các lần build sau sẽ nhanh hơn. Ngoài ra, nếu bạn cần chỉ định các đối số khác cho lệnh build, bạn có thể thêm chúng vào cuối lệnh ở phần `<OPTIONAL-ARGS>`. Ví dụ, nếu bạn muốn build phiên bản tối ưu hóa, bạn sẽ chạy lệnh trên với tùy chọn `--release`. Kết quả biên dịch sẽ được lưu trong thư mục target trên máy tính của bạn, và có thể chạy với lệnh:
```sh
target/debug/rustdesk
```
Nếu bạn đang chạy bản build đuợc tối ưu hóa, thì bạn có thể chạy với lệnh:
Nếu bạn đang chạy bản build được tối ưu hóa, thì bạn có thể chạy với lệnh:
```sh
target/release/rustdesk
```
Hãy đảm bảo bạn đang chạy những lệnh này từ thu mục rễ của repo RustDesk, vì nếu không thì ứng dụng có thể sẽ không tìm đuợc những tệp tài nguyên cần thiết. Cũng như nhớ rằng những lệnh con của cargo như `install` hoặc `run` hiện chưa được hỗ trợ bởi phương pháp này vì chúng sẽ cài đặt hoặc chạy ứng dụng trong container thay vì trên máy tính của bạn.
Hãy đảm bảo rằng bạn đang chạy các lệnh này từ gốc của thư mục **RustDesk**, nếu không, ứng dụng có thể không thể tìm thấy các tệp tài nguyên cần thiết. Hãy lưu ý rằng các câu lệnh con khác của **cargo** như **install** hoặc **run** hiện không được hỗ trợ qua phương pháp này, vì chúng sẽ cài đặt hoặc chạy chương trình bên trong **container** thay vì trên máy tính của bạn.
## Cấu trúc tệp tin
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, cấu hình, tcp/udp wrapper, protobuf, fs functions để truyền file, và một số hàm tiện ích khác
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: để ghi lại màn hình
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: để điều khiển máy tính/con chuột trên những nền tảng khác nhau
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: ghi lại màn hình
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: điều khiển máy tính/chuột trên các nền tảng khác nhau
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: giao diện người dùng
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: các dịch vụ âm thanh, clipboard, đầu vào, video và các kết nối mạng
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: để bắt đầu kết nối với một peer
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Để liên lạc với [rustdesk-server](https://github.com/rustdesk/rustdesk-server), đợi cho kết nối trực tiếp (TCP hole punching) hoặc kết nối được relayed.
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: bắt đầu kết nối với một peer
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: giao tiếp với [rustdesk-server](https://github.com/rustdesk/rustdesk-server), đợi kết nối trực tiếp (TCP hole punching) hoặc kết nối được chuyển tiếp.
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: mã nguồn riêng cho mỗi nền tảng
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Mã Flutter dành cho điện thoại
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Mã Flutter dành máy tính và điện thoại
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Mã JavaScript dành cho giao diện trên web bằng Flutter
## Snapshot

View File

@@ -108,4 +108,3 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("$kotlin_version") } }
}
apply plugin: 'com.google.gms.google-services'

View File

@@ -1,40 +0,0 @@
{
"project_info": {
"project_number": "768133699366",
"firebase_url": "https://rustdesk.firebaseio.com",
"project_id": "rustdesk",
"storage_bucket": "rustdesk.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:768133699366:android:5fc9015370e344457993e7",
"android_client_info": {
"package_name": "com.carriez.flutter_hbb"
}
},
"oauth_client": [
{
"client_id": "768133699366-s9gdfsijefsd5g1nura4kmfne42lencn.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAPOsKcXjrAR-7Z148sYr_gdB_JQZkamTM"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "768133699366-s9gdfsijefsd5g1nura4kmfne42lencn.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@@ -1,5 +1,7 @@
package com.carriez.flutter_hbb
import ffi.FFI
/**
* Capture screen,get video and audio,send to rust.
* Dispatch notifications
@@ -64,10 +66,6 @@ const val AUDIO_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO
class MainService : Service() {
init {
System.loadLibrary("rustdesk")
}
@Keep
@RequiresApi(Build.VERSION_CODES.N)
fun rustPointerInput(kind: String, mask: Int, x: Int, y: Int) {
@@ -156,23 +154,9 @@ class MainService : Service() {
private val powerManager: PowerManager by lazy { applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager }
private val wakeLock: PowerManager.WakeLock by lazy { powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "rustdesk:wakelock")}
// jvm call rust
private external fun init(ctx: Context)
/// 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 onAudioFrameUpdate(buf: ByteBuffer)
private external fun translateLocale(localeName: String, input: String): String
private external fun refreshScreen()
private external fun setFrameRawEnable(name: String, value: Boolean)
// private external fun sendVp9(data: ByteArray)
private fun translate(input: String): String {
Log.d(logTag, "translate:$LOCAL_NAME")
return translateLocale(LOCAL_NAME, input)
return FFI.translateLocale(LOCAL_NAME, input)
}
companion object {
@@ -211,7 +195,7 @@ class MainService : Service() {
override fun onCreate() {
super.onCreate()
Log.d(logTag,"MainService onCreate")
init(this)
FFI.init(this)
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
@@ -223,7 +207,7 @@ class MainService : Service() {
// 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)
FFI.startServer(configPath, "")
createForegroundNotification()
}
@@ -278,7 +262,7 @@ class MainService : Service() {
SCREEN_INFO.dpi = dpi
if (isStart) {
stopCapture()
refreshScreen()
FFI.refreshScreen()
startCapture()
}
}
@@ -306,7 +290,7 @@ class MainService : Service() {
createForegroundNotification()
if (intent.getBooleanExtra(EXT_INIT_FROM_BOOT, false)) {
startService()
FFI.startService()
}
Log.d(logTag, "service starting: ${startId}:${Thread.currentThread()}")
val mediaProjectionManager =
@@ -354,12 +338,15 @@ class MainService : Service() {
).apply {
setOnImageAvailableListener({ imageReader: ImageReader ->
try {
if (!isStart) {
return@setOnImageAvailableListener
}
imageReader.acquireLatestImage().use { image ->
if (image == null) return@setOnImageAvailableListener
if (image == null || !isStart) return@setOnImageAvailableListener
val planes = image.planes
val buffer = planes[0].buffer
buffer.rewind()
onVideoFrameUpdate(buffer)
FFI.onVideoFrameUpdate(buffer)
}
} catch (ignored: java.lang.Exception) {
}
@@ -393,21 +380,24 @@ class MainService : Service() {
}
checkMediaPermission()
_isStart = true
setFrameRawEnable("video",true)
setFrameRawEnable("audio",true)
FFI.setFrameRawEnable("video",true)
FFI.setFrameRawEnable("audio",true)
return true
}
@Synchronized
fun stopCapture() {
Log.d(logTag, "Stop Capture")
setFrameRawEnable("video",false)
setFrameRawEnable("audio",false)
FFI.setFrameRawEnable("video",false)
FFI.setFrameRawEnable("audio",false)
_isStart = false
// release video
virtualDisplay?.release()
surface?.release()
imageReader?.close()
imageReader = null
// suface needs to be release after imageReader.close to imageReader access released surface
// https://github.com/rustdesk/rustdesk/issues/4118#issuecomment-1515666629
surface?.release()
videoEncoder?.let {
it.signalEndOfInputStream()
it.stop()
@@ -418,9 +408,6 @@ class MainService : Service() {
// release audio
audioRecordStat = false
audioRecorder?.release()
audioRecorder = null
minBufferSize = 0
}
fun destroy() {
@@ -428,8 +415,6 @@ class MainService : Service() {
_isReady = false
stopCapture()
imageReader?.close()
imageReader = null
mediaProjection = null
checkMediaPermission()
@@ -537,9 +522,13 @@ class MainService : Service() {
thread {
while (audioRecordStat) {
audioReader!!.readSync(audioRecorder!!)?.let {
onAudioFrameUpdate(it)
FFI.onAudioFrameUpdate(it)
}
}
// let's release here rather than onDestroy to avoid threading issue
audioRecorder?.release()
audioRecorder = null
minBufferSize = 0
Log.d(logTag, "Exit audio thread")
}
} catch (e: Exception) {

View File

@@ -0,0 +1,21 @@
// ffi.kt
package ffi
import android.content.Context
import java.nio.ByteBuffer
object FFI {
init {
System.loadLibrary("rustdesk")
}
external fun init(ctx: Context)
external fun startServer(app_dir: String, custom_client_config: String)
external fun startService()
external fun onVideoFrameUpdate(buf: ByteBuffer)
external fun onAudioFrameUpdate(buf: ByteBuffer)
external fun translateLocale(localeName: String, input: String): String
external fun refreshScreen()
external fun setFrameRawEnable(name: String, value: Boolean)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -3106,6 +3106,27 @@ Color? disabledTextColor(BuildContext context, bool enabled) {
: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6);
}
Widget loadPowered(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
launchUrl(Uri.parse('https://rustdesk.com'));
},
child: Opacity(
opacity: 0.5,
child: Text(
translate("powered_by_me"),
overflow: TextOverflow.clip,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(fontSize: 9, decoration: TextDecoration.underline),
)),
),
).marginOnly(top: 6);
}
// max 300 x 60
Widget loadLogo() {
return FutureBuilder<ByteData>(
@@ -3155,3 +3176,41 @@ bool isInHomePage() {
final controller = Get.find<DesktopTabController>();
return controller.state.value.selected == 0;
}
Widget buildPresetPasswordWarning() {
return FutureBuilder<bool>(
future: bind.isPresetPassword(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // Show a loading spinner while waiting for the Future to complete
} else if (snapshot.hasError) {
return Text(
'Error: ${snapshot.error}'); // Show an error message if the Future completed with an error
} else if (snapshot.hasData && snapshot.data == true) {
return Container(
color: Colors.yellow,
child: Column(
children: [
Align(
child: Text(
translate("Security Alert"),
style: TextStyle(
color: Colors.red,
fontSize: 20,
fontWeight: FontWeight.bold,
),
)).paddingOnly(bottom: 8),
Text(
translate("preset_password_warning"),
style: TextStyle(color: Colors.red),
)
],
).paddingAll(8),
); // Show a warning message if the Future completed with true
} else {
return SizedBox
.shrink(); // Show nothing if the Future completed with false or null
}
},
);
}

View File

@@ -137,11 +137,13 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget _createSwitchBar(BuildContext context) {
final model = Provider.of<PeerTabModel>(context);
return ListView(
var counter = -1;
return ReorderableListView(
buildDefaultDragHandles: false,
onReorder: model.reorder,
scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(),
children: model.visibleIndexs.map((t) {
children: model.visibleEnabledOrderedIndexs.map((t) {
final selected = model.currentTab == t;
final color = selected
? MyTheme.tabbar(context).selectedTextColor
@@ -155,43 +157,47 @@ class _PeerTabPageState extends State<PeerTabPage>
border: Border(
bottom: BorderSide(width: 2, color: color!),
));
return Obx(() => Tooltip(
preferBelow: false,
message: model.tabTooltip(t),
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
child: InkWell(
child: Container(
decoration: (hover.value
? (selected ? decoBorder : deco)
: (selected ? decoBorder : null)),
child: Icon(model.tabIcon(t), color: color)
.paddingSymmetric(horizontal: 4),
).paddingSymmetric(horizontal: 4),
onTap: () async {
await handleTabSelection(t);
await bind.setLocalFlutterOption(
k: 'peer-tab-index', v: t.toString());
},
onHover: (value) => hover.value = value,
),
));
counter += 1;
return ReorderableDragStartListener(
key: ValueKey(t),
index: counter,
child: Obx(() => Tooltip(
preferBelow: false,
message: model.tabTooltip(t),
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
child: InkWell(
child: Container(
decoration: (hover.value
? (selected ? decoBorder : deco)
: (selected ? decoBorder : null)),
child: Icon(model.tabIcon(t), color: color)
.paddingSymmetric(horizontal: 4),
).paddingSymmetric(horizontal: 4),
onTap: () async {
await handleTabSelection(t);
await bind.setLocalFlutterOption(
k: PeerTabModel.kPeerTabIndex, v: t.toString());
},
onHover: (value) => hover.value = value,
),
)));
}).toList());
}
Widget _createPeersView() {
final model = Provider.of<PeerTabModel>(context);
Widget child;
if (model.visibleIndexs.isEmpty) {
if (model.visibleEnabledOrderedIndexs.isEmpty) {
child = visibleContextMenuListener(Row(
children: [Expanded(child: InkWell())],
));
} else {
if (model.visibleIndexs.contains(model.currentTab)) {
if (model.visibleEnabledOrderedIndexs.contains(model.currentTab)) {
child = entries[model.currentTab].widget;
} else {
debugPrint("should not happen! currentTab not in visibleIndexs");
Future.delayed(Duration.zero, () {
model.setCurrentTab(model.indexs[0]);
model.setCurrentTab(model.visibleEnabledOrderedIndexs[0]);
});
child = entries[0].widget;
}
@@ -255,16 +261,17 @@ class _PeerTabPageState extends State<PeerTabPage>
void mobileShowTabVisibilityMenu() {
final model = gFFI.peerTabModel;
final items = List<PopupMenuItem>.empty(growable: true);
for (int i = 0; i < model.tabNames.length; i++) {
for (int i = 0; i < PeerTabModel.maxTabCount; i++) {
if (!model.isEnabled[i]) continue;
items.add(PopupMenuItem(
height: kMinInteractiveDimension * 0.8,
onTap: () => model.setTabVisible(i, !model.isVisible[i]),
onTap: () => model.setTabVisible(i, !model.isVisibleEnabled[i]),
child: Row(
children: [
Checkbox(
value: model.isVisible[i],
value: model.isVisibleEnabled[i],
onChanged: (_) {
model.setTabVisible(i, !model.isVisible[i]);
model.setTabVisible(i, !model.isVisibleEnabled[i]);
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
@@ -314,16 +321,17 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget visibleContextMenu(CancelFunc cancelFunc) {
final model = Provider.of<PeerTabModel>(context);
final menu = List<MenuEntrySwitch>.empty(growable: true);
for (int i = 0; i < model.tabNames.length; i++) {
menu.add(MenuEntrySwitch(
final menu = List<MenuEntrySwitchSync>.empty(growable: true);
for (int i = 0; i < model.orders.length; i++) {
int tabIndex = model.orders[i];
if (tabIndex < 0 || tabIndex >= PeerTabModel.maxTabCount) continue;
if (!model.isEnabled[tabIndex]) continue;
menu.add(MenuEntrySwitchSync(
switchType: SwitchType.scheckbox,
text: model.tabTooltip(i),
getter: () async {
return model.isVisible[i];
},
text: model.tabTooltip(tabIndex),
currentValue: model.isVisibleEnabled[tabIndex],
setter: (show) async {
model.setTabVisible(i, show);
model.setTabVisible(tabIndex, show);
cancelFunc();
}));
}
@@ -434,7 +442,7 @@ class _PeerTabPageState extends State<PeerTabPage>
model.setMultiSelectionMode(false);
showToast(translate('Successful'));
},
child: Icon(model.icons[PeerTabIndex.fav.index]),
child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]),
).marginOnly(left: isMobile ? 11 : 6),
);
}
@@ -455,7 +463,7 @@ class _PeerTabPageState extends State<PeerTabPage>
addPeersToAbDialog(peers);
model.setMultiSelectionMode(false);
},
child: Icon(model.icons[PeerTabIndex.ab.index]),
child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]),
).marginOnly(left: isMobile ? 11 : 6),
);
}
@@ -563,7 +571,7 @@ class _PeerTabPageState extends State<PeerTabPage>
final screenWidth = MediaQuery.of(context).size.width;
final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
final leftActionsSize =
(leftIconSize + (4 + 4) * 2) * model.visibleIndexs.length;
(leftIconSize + (4 + 4) * 2) * model.visibleEnabledOrderedIndexs.length;
final availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2;
final searchWidth = 120;
final otherActionWidth = 18 + 10;

View File

@@ -441,10 +441,17 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
child: Text(translate('Mute'))));
}
// file copy and paste
// If the version is less than 1.2.4, file copy and paste is supported on Windows only.
final isSupportIfPeer_1_2_3 = versionCmp(pi.version, '1.2.4') < 0 &&
isWindows &&
pi.platform == kPeerPlatformWindows;
// If the version is 1.2.4 or later, file copy and paste is supported when kPlatformAdditionsHasFileClipboard is set.
final isSupportIfPeer_1_2_4 = versionCmp(pi.version, '1.2.4') >= 0 &&
bind.mainHasFileClipboard() &&
pi.platformAdditions.containsKey(kPlatformAdditionsHasFileClipboard);
if (ffiModel.keyboard &&
perms['file'] != false &&
bind.mainHasFileClipboard() &&
pi.platformAdditions.containsKey(kPlatformAdditionsHasFileClipboard)) {
(isSupportIfPeer_1_2_3 || isSupportIfPeer_1_2_4)) {
final enabled = !ffiModel.viewOnly;
final option = 'enable-file-transfer';
final value =

View File

@@ -19,7 +19,9 @@ const kKeyTranslateMode = 'translate';
const String kPlatformAdditionsIsWayland = "is_wayland";
const String kPlatformAdditionsHeadless = "headless";
const String kPlatformAdditionsIsInstalled = "is_installed";
const String kPlatformAdditionsVirtualDisplays = "virtual_displays";
const String kPlatformAdditionsIddImpl = "idd_impl";
const String kPlatformAdditionsRustDeskVirtualDisplays = "rustdesk_virtual_displays";
const String kPlatformAdditionsAmyuniVirtualDisplays = "amyuni_virtual_displays";
const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
const String kPlatformAdditionsSupportedPrivacyModeImpl =
"supported_privacy_mode_impl";
@@ -121,12 +123,11 @@ double kNewWindowOffset = isWindows
? 30.0
: 50.0;
EdgeInsets get kDragToResizeAreaPadding =>
!kUseCompatibleUiMode && isLinux
? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value
? EdgeInsets.zero
: EdgeInsets.all(5.0)
: EdgeInsets.zero;
EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && isLinux
? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value
? EdgeInsets.zero
: EdgeInsets.all(5.0)
: EdgeInsets.zero;
// https://en.wikipedia.org/wiki/Non-breaking_space
const int $nbsp = 0x00A0;

View File

@@ -69,44 +69,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
);
}
Widget buildPresetPasswordWarning() {
return FutureBuilder<bool>(
future: bind.isPresetPassword(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // Show a loading spinner while waiting for the Future to complete
} else if (snapshot.hasError) {
return Text(
'Error: ${snapshot.error}'); // Show an error message if the Future completed with an error
} else if (snapshot.hasData && snapshot.data == true) {
return Container(
color: Colors.yellow,
child: Column(
children: [
Align(
child: Text(
translate("Security Alert"),
style: TextStyle(
color: Colors.red,
fontSize: 20,
fontWeight: FontWeight.bold,
),
)).paddingOnly(bottom: 8),
Text(
translate("preset_password_warning"),
style: TextStyle(color: Colors.red),
)
],
).paddingAll(8),
); // Show a warning message if the Future completed with true
} else {
return SizedBox
.shrink(); // Show nothing if the Future completed with false or null
}
},
);
}
Widget buildLeftPane(BuildContext context) {
final isIncomingOnly = bind.isIncomingOnly();
final isOutgoingOnly = bind.isOutgoingOnly();
@@ -115,22 +77,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
if (bind.isCustomClient())
Align(
alignment: Alignment.center,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
launchUrl(Uri.parse('https://rustdesk.com'));
},
child: Opacity(
opacity: 0.5,
child: Text(
translate("powered_by_me"),
overflow: TextOverflow.clip,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 9, decoration: TextDecoration.underline),
)),
),
).marginOnly(top: 6),
child: loadPowered(context),
),
Align(
alignment: Alignment.center,
@@ -298,19 +245,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
RxBool hover = false.obs;
return InkWell(
onTap: DesktopTabPage.onAddSetting,
child: Obx(
() => CircleAvatar(
radius: 15,
backgroundColor: hover.value
? Theme.of(context).scaffoldBackgroundColor
: Theme.of(context).colorScheme.background,
child: Tooltip(
message: translate('Settings'),
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value ? textColor : textColor?.withOpacity(0.5),
)),
child: Tooltip(
message: translate('Settings'),
child: Obx(
() => CircleAvatar(
radius: 15,
backgroundColor: hover.value
? Theme.of(context).scaffoldBackgroundColor
: Theme.of(context).colorScheme.background,
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value ? textColor : textColor?.withOpacity(0.5),
),
),
),
),
onHover: (value) => hover.value = value,
@@ -371,31 +319,33 @@ class _DesktopHomePageState extends State<DesktopHomePage>
),
AnimatedRotationWidget(
onPressed: () => bind.mainUpdateTemporaryPassword(),
child: Obx(() => RotatedBox(
quarterTurns: 2,
child: Tooltip(
message: translate('Refresh Password'),
child: Icon(
Icons.refresh,
color: refreshHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
)))),
child: Tooltip(
message: translate('Refresh Password'),
child: Obx(() => RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.refresh,
color: refreshHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
))),
),
onHover: (value) => refreshHover.value = value,
).marginOnly(right: 8, top: 4),
if (!bind.isDisableSettings())
InkWell(
child: Obx(
() => Tooltip(
message: translate('Change Password'),
child: Icon(
Icons.edit,
color: editHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
)).marginOnly(right: 8, top: 4),
child: Tooltip(
message: translate('Change Password'),
child: Obx(
() => Icon(
Icons.edit,
color: editHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
).marginOnly(right: 8, top: 4),
),
),
onTap: () => DesktopSettingPage.switch2page(0),
onHover: (value) => editHover.value = value,

View File

@@ -400,11 +400,20 @@ class _GeneralState extends State<_General> {
Widget hwcodec() {
final hwcodec = bind.mainHasHwcodec();
final gpucodec = bind.mainHasGpucodec();
final vram = bind.mainHasVram();
return Offstage(
offstage: !(hwcodec || gpucodec),
offstage: !(hwcodec || vram),
child: _Card(title: 'Hardware Codec', children: [
_OptionCheckBox(context, 'Enable hardware codec', 'enable-hwcodec')
_OptionCheckBox(
context,
'Enable hardware codec',
'enable-hwcodec',
update: () {
if (mainGetBoolOptionSync('enable-hwcodec')) {
bind.mainCheckHwcodec();
}
},
)
]),
);
}
@@ -835,6 +844,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
...directIp(context),
whitelist(),
...autoDisconnect(context),
if (bind.mainIsInstalled())
_OptionCheckBox(context, 'allow-only-conn-window-open-tip',
'allow-only-conn-window-open',
reverse: false, enabled: enabled),
]);
}

View File

@@ -568,6 +568,47 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
}
}
// Compatible with MenuEntrySwitch, it uses value instead of getter
class MenuEntrySwitchSync<T> extends MenuEntrySwitchBase<T> {
final SwitchSetter setter;
final RxBool _curOption = false.obs;
MenuEntrySwitchSync({
required SwitchType switchType,
required String text,
required bool currentValue,
required this.setter,
Rx<TextStyle>? textStyle,
EdgeInsets? padding,
dismissOnClicked = false,
RxBool? enabled,
dismissCallback,
}) : super(
switchType: switchType,
text: text,
textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked,
enabled: enabled,
dismissCallback: dismissCallback,
) {
_curOption.value = currentValue;
}
@override
RxBool get curOption => _curOption;
@override
setOption(bool? option) async {
if (option != null) {
await setter(option);
// Notice: no ensure with getter, best used on menus that are destroyed on click
if (_curOption.value != option) {
_curOption.value = option;
}
}
}
}
typedef Switch2Getter = RxBool Function();
typedef Switch2Setter = Future<void> Function(bool);

View File

@@ -636,7 +636,7 @@ class _MonitorMenu extends StatelessWidget {
menuStyle: MenuStyle(
padding:
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
menuChildren: [buildMonitorSubmenuWidget()]);
menuChildrenGetter: () => [buildMonitorSubmenuWidget()]);
}
Widget buildMultiMonitorMenu() {
@@ -843,17 +843,17 @@ class _ControlMenu extends StatelessWidget {
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
ffi: ffi,
menuChildren: toolbarControls(context, id, ffi).map((e) {
if (e.divider) {
return Divider();
} else {
return MenuButton(
child: e.child,
onPressed: e.onPressed,
ffi: ffi,
trailingIcon: e.trailingIcon);
}
}).toList());
menuChildrenGetter: () => toolbarControls(context, id, ffi).map((e) {
if (e.divider) {
return Divider();
} else {
return MenuButton(
child: e.child,
onPressed: e.onPressed,
ffi: ffi,
trailingIcon: e.trailingIcon);
}
}).toList());
}
}
@@ -1046,53 +1046,61 @@ class _DisplayMenuState extends State<_DisplayMenu> {
Widget build(BuildContext context) {
_screenAdjustor.updateScreen();
final menuChildren = <Widget>[
_screenAdjustor.adjustWindow(context),
viewStyle(),
scrollStyle(),
imageQuality(),
codec(),
_ResolutionsMenu(
id: widget.id,
ffi: widget.ffi,
screenAdjustor: _screenAdjustor,
),
// We may add this feature if it is needed and we have an EV certificate.
// _VirtualDisplayMenu(
// id: widget.id,
// ffi: widget.ffi,
// ),
Divider(),
toggles(),
];
// privacy mode
if (ffiModel.keyboard && pi.features.privacyMode) {
final privacyModeState = PrivacyModeState.find(id);
final privacyModeList =
toolbarPrivacyMode(privacyModeState, context, id, ffi);
if (privacyModeList.length == 1) {
menuChildren.add(CkbMenuButton(
value: privacyModeList[0].value,
onChanged: privacyModeList[0].onChanged,
child: privacyModeList[0].child,
ffi: ffi));
} else if (privacyModeList.length > 1) {
menuChildren.addAll([
Divider(),
_SubmenuButton(
ffi: widget.ffi,
child: Text(translate('Privacy mode')),
menuChildren: privacyModeList
.map((e) => CkbMenuButton(
value: e.value,
onChanged: e.onChanged,
child: e.child,
ffi: ffi))
.toList()),
]);
menuChildrenGetter() {
final menuChildren = <Widget>[
_screenAdjustor.adjustWindow(context),
viewStyle(),
scrollStyle(),
imageQuality(),
codec(),
_ResolutionsMenu(
id: widget.id,
ffi: widget.ffi,
screenAdjustor: _screenAdjustor,
),
if (pi.isRustDeskIdd)
_RustDeskVirtualDisplayMenu(
id: widget.id,
ffi: widget.ffi,
),
if (pi.isAmyuniIdd)
_AmyuniVirtualDisplayMenu(
id: widget.id,
ffi: widget.ffi,
),
Divider(),
toggles(),
];
// privacy mode
if (ffiModel.keyboard && pi.features.privacyMode) {
final privacyModeState = PrivacyModeState.find(id);
final privacyModeList =
toolbarPrivacyMode(privacyModeState, context, id, ffi);
if (privacyModeList.length == 1) {
menuChildren.add(CkbMenuButton(
value: privacyModeList[0].value,
onChanged: privacyModeList[0].onChanged,
child: privacyModeList[0].child,
ffi: ffi));
} else if (privacyModeList.length > 1) {
menuChildren.addAll([
Divider(),
_SubmenuButton(
ffi: widget.ffi,
child: Text(translate('Privacy mode')),
menuChildren: privacyModeList
.map((e) => CkbMenuButton(
value: e.value,
onChanged: e.onChanged,
child: e.child,
ffi: ffi))
.toList()),
]);
}
}
menuChildren.add(widget.pluginItem);
return menuChildren;
}
menuChildren.add(widget.pluginItem);
return _IconSubmenuButton(
tooltip: 'Display Settings',
@@ -1100,7 +1108,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
ffi: widget.ffi,
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
menuChildren: menuChildren,
menuChildrenGetter: menuChildrenGetter,
);
}
@@ -1537,21 +1545,23 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
}
}
class _VirtualDisplayMenu extends StatefulWidget {
class _RustDeskVirtualDisplayMenu extends StatefulWidget {
final String id;
final FFI ffi;
_VirtualDisplayMenu({
_RustDeskVirtualDisplayMenu({
Key? key,
required this.id,
required this.ffi,
}) : super(key: key);
@override
State<_VirtualDisplayMenu> createState() => _VirtualDisplayMenuState();
State<_RustDeskVirtualDisplayMenu> createState() =>
_RustDeskVirtualDisplayMenuState();
}
class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
class _RustDeskVirtualDisplayMenuState
extends State<_RustDeskVirtualDisplayMenu> {
@override
void initState() {
super.initState();
@@ -1566,7 +1576,7 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
return Offstage();
}
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
final virtualDisplays = widget.ffi.ffiModel.pi.RustDeskVirtualDisplays;
final privacyModeState = PrivacyModeState.find(widget.id);
final children = <Widget>[];
@@ -1608,6 +1618,82 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
}
}
class _AmyuniVirtualDisplayMenu extends StatefulWidget {
final String id;
final FFI ffi;
_AmyuniVirtualDisplayMenu({
Key? key,
required this.id,
required this.ffi,
}) : super(key: key);
@override
State<_AmyuniVirtualDisplayMenu> createState() =>
_AmiyuniVirtualDisplayMenuState();
}
class _AmiyuniVirtualDisplayMenuState extends State<_AmyuniVirtualDisplayMenu> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
if (widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) {
return Offstage();
}
if (!widget.ffi.ffiModel.pi.isInstalled) {
return Offstage();
}
final count = widget.ffi.ffiModel.pi.amyuniVirtualDisplayCount;
final privacyModeState = PrivacyModeState.find(widget.id);
final children = <Widget>[
Obx(() => Row(
children: [
TextButton(
onPressed: privacyModeState.isNotEmpty || count == 0
? null
: () => bind.sessionToggleVirtualDisplay(
sessionId: widget.ffi.sessionId, index: 0, on: false),
child: Icon(Icons.remove),
),
Text(count.toString()),
TextButton(
onPressed: privacyModeState.isNotEmpty || count == 4
? null
: () => bind.sessionToggleVirtualDisplay(
sessionId: widget.ffi.sessionId, index: 0, on: true),
child: Icon(Icons.add),
),
],
)),
Divider(),
Obx(() => MenuButton(
onPressed: privacyModeState.isNotEmpty || count == 0
? null
: () {
bind.sessionToggleVirtualDisplay(
sessionId: widget.ffi.sessionId,
index: kAllVirtualDisplay,
on: false);
},
ffi: widget.ffi,
child: Text(translate('Plug out all')),
)),
];
return _SubmenuButton(
ffi: widget.ffi,
menuChildren: children,
child: Text(translate("Virtual display")),
);
}
}
class _KeyboardMenu extends StatelessWidget {
final String id;
final FFI ffi;
@@ -1623,19 +1709,7 @@ class _KeyboardMenu extends StatelessWidget {
Widget build(BuildContext context) {
var ffiModel = Provider.of<FfiModel>(context);
if (!ffiModel.keyboard) return Offstage();
// If use flutter to grab keys, we can only use one mode.
// Map mode and Legacy mode, at least one of them is supported.
String? modeOnly;
if (isInputSourceFlutter) {
if (bind.sessionIsKeyboardModeSupported(
sessionId: ffi.sessionId, mode: kKeyMapMode)) {
modeOnly = kKeyMapMode;
} else if (bind.sessionIsKeyboardModeSupported(
sessionId: ffi.sessionId, mode: kKeyLegacyMode)) {
modeOnly = kKeyLegacyMode;
}
}
final toolbarToggles = toolbarKeyboardToggles(ffi)
toolbarToggles() => toolbarKeyboardToggles(ffi)
.map((e) => CkbMenuButton(
value: e.value, onChanged: e.onChanged, child: e.child, ffi: ffi))
.toList();
@@ -1645,18 +1719,18 @@ class _KeyboardMenu extends StatelessWidget {
ffi: ffi,
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
menuChildren: [
keyboardMode(modeOnly),
localKeyboardType(),
inputSource(),
Divider(),
viewMode(),
Divider(),
...toolbarToggles,
]);
menuChildrenGetter: () => [
keyboardMode(),
localKeyboardType(),
inputSource(),
Divider(),
viewMode(),
Divider(),
...toolbarToggles(),
]);
}
keyboardMode(String? modeOnly) {
keyboardMode() {
return futureBuilder(future: () async {
return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ??
kKeyLegacyMode;
@@ -1676,6 +1750,19 @@ class _KeyboardMenu extends StatelessWidget {
await ffi.inputModel.updateKeyboardMode();
}
// If use flutter to grab keys, we can only use one mode.
// Map mode and Legacy mode, at least one of them is supported.
String? modeOnly;
if (isInputSourceFlutter) {
if (bind.sessionIsKeyboardModeSupported(
sessionId: ffi.sessionId, mode: kKeyMapMode)) {
modeOnly = kKeyMapMode;
} else if (bind.sessionIsKeyboardModeSupported(
sessionId: ffi.sessionId, mode: kKeyLegacyMode)) {
modeOnly = kKeyLegacyMode;
}
}
for (InputModeMenu mode in modes) {
if (modeOnly != null && mode.key != modeOnly) {
continue;
@@ -1804,7 +1891,7 @@ class _ChatMenuState extends State<_ChatMenu> {
ffi: widget.ffi,
color: _ToolbarTheme.blueColor,
hoverColor: _ToolbarTheme.hoverBlueColor,
menuChildren: [textChat(), voiceCall()]);
menuChildrenGetter: () => [textChat(), voiceCall()]);
}
textChat() {
@@ -2008,7 +2095,7 @@ class _IconSubmenuButton extends StatefulWidget {
final Widget? icon;
final Color color;
final Color hoverColor;
final List<Widget> menuChildren;
final List<Widget> Function() menuChildrenGetter;
final MenuStyle? menuStyle;
final FFI ffi;
final double? width;
@@ -2020,7 +2107,7 @@ class _IconSubmenuButton extends StatefulWidget {
required this.tooltip,
required this.color,
required this.hoverColor,
required this.menuChildren,
required this.menuChildrenGetter,
required this.ffi,
this.menuStyle,
this.width,
@@ -2064,7 +2151,8 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
color: hover ? widget.hoverColor : widget.color,
),
child: icon))),
menuChildren: widget.menuChildren
menuChildren: widget
.menuChildrenGetter()
.map((e) => _buildPointerTrackWidget(e, widget.ffi))
.toList()));
return MenuBar(children: [

View File

@@ -865,6 +865,7 @@ class _ListView extends StatelessWidget {
label: labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label),
tabType: controller.tabType,
selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon,
closable: tab.closable,
@@ -896,6 +897,7 @@ class _Tab extends StatefulWidget {
final int index;
final String tabInfoKey;
final Rx<String> label;
final DesktopTabType tabType;
final IconData? selectedIcon;
final IconData? unselectedIcon;
final bool closable;
@@ -914,6 +916,7 @@ class _Tab extends StatefulWidget {
required this.index,
required this.tabInfoKey,
required this.label,
required this.tabType,
this.selectedIcon,
this.unselectedIcon,
this.tabBuilder,
@@ -953,7 +956,9 @@ class _TabState extends State<_Tab> with RestorationMixin {
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
child: Tooltip(
message: translate(widget.label.value),
message: widget.tabType == DesktopTabType.main
? ''
: translate(widget.label.value),
child: Text(
translate(widget.label.value),
textAlign: TextAlign.center,

View File

@@ -84,7 +84,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
slivers: [
SliverList(
delegate: SliverChildListDelegate([
_buildUpdateUI(),
if (!bind.isCustomClient()) _buildUpdateUI(),
_buildRemoteIDTextField(),
])),
SliverFillRemaining(

View File

@@ -4,6 +4,7 @@ import 'package:flutter_hbb/mobile/pages/settings_page.dart';
import 'package:get/get.dart';
import '../../common.dart';
import '../../common/widgets/chat_page.dart';
import '../../models/platform_model.dart';
import 'connection_page.dart';
abstract class PageShape extends Widget {
@@ -25,8 +26,9 @@ class HomePageState extends State<HomePage> {
var _selectedIndex = 0;
int get selectedIndex => _selectedIndex;
final List<PageShape> _pages = [];
int _chatPageTabIndex = -1;
bool get isChatPageCurrentTab => isAndroid
? _selectedIndex == 1
? _selectedIndex == _chatPageTabIndex
: false; // change this when ios have chat page
void refreshPages() {
@@ -43,8 +45,9 @@ class HomePageState extends State<HomePage> {
void initPages() {
_pages.clear();
_pages.add(ConnectionPage());
if (isAndroid) {
if (!bind.isIncomingOnly()) _pages.add(ConnectionPage());
if (isAndroid && !bind.isOutgoingOnly()) {
_chatPageTabIndex = _pages.length;
_pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]);
}
_pages.add(SettingsPage());
@@ -141,7 +144,7 @@ class HomePageState extends State<HomePage> {
],
);
}
return Text("RustDesk");
return Text(bind.mainGetAppNameSync());
}
}
@@ -154,7 +157,7 @@ class WebHomePage extends StatelessWidget {
// backgroundColor: MyTheme.grayBg,
appBar: AppBar(
centerTitle: true,
title: Text("RustDesk${isWeb ? " (Beta) " : ""}"),
title: Text(bind.mainGetAppNameSync()),
actions: connectionPage.appBarActions,
),
body: connectionPage,

View File

@@ -68,7 +68,9 @@ class _RemotePageState extends State<RemotePage> {
gFFI.dialogManager
.showLoading(translate('Connecting...'), onCancel: closeConnection);
});
WakelockPlus.enable();
if (!isWeb) {
WakelockPlus.enable();
}
_physicalFocusNode.requestFocus();
gFFI.inputModel.listenToMouse(true);
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
@@ -95,7 +97,9 @@ class _RemotePageState extends State<RemotePage> {
gFFI.dialogManager.dismissAll();
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: SystemUiOverlay.values);
await WakelockPlus.disable();
if (!isWeb) {
await WakelockPlus.disable();
}
await keyboardSubscription.cancel();
removeSharedStates(widget.id);
}

View File

@@ -158,6 +158,7 @@ class _ServerPageState extends State<ServerPage> {
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
buildPresetPasswordWarning(),
gFFI.serverModel.isStart
? ServerInfo()
: ServiceNotRunningNotification(),
@@ -226,7 +227,7 @@ class ScamWarningDialog extends StatefulWidget {
}
class ScamWarningDialogState extends State<ScamWarningDialog> {
int _countdown = 12;
int _countdown = bind.isCustomClient() ? 0 : 12;
bool show_warning = false;
late Timer _timer;
late ServerModel _serverModel;
@@ -383,7 +384,7 @@ class ScamWarningDialogState extends State<ScamWarningDialog> {
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
primary: Colors.blueAccent,
backgroundColor: Colors.blueAccent,
),
child: Text(
translate("Decline"),

View File

@@ -27,7 +27,7 @@ class SettingsPage extends StatefulWidget implements PageShape {
final icon = Icon(Icons.settings);
@override
final appBarActions = [ScanButton()];
final appBarActions = bind.isDisableSettings() ? [] : [ScanButton()];
@override
State<SettingsPage> createState() => _SettingsState();
@@ -218,6 +218,21 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
Provider.of<FfiModel>(context);
final outgoingOnly = bind.isOutgoingOnly();
final customClientSection = CustomSettingsSection(
child: Column(
children: [
if (bind.isCustomClient())
Align(
alignment: Alignment.center,
child: loadPowered(context),
),
Align(
alignment: Alignment.center,
child: loadLogo(),
)
],
));
final List<AbstractSettingsTile> enhancementsTiles = [];
final List<AbstractSettingsTile> shareScreenTiles = [
SettingsTile.switchTile(
@@ -448,33 +463,37 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
}));
return SettingsList(
final disabledSettings = bind.isDisableSettings();
final settings = SettingsList(
sections: [
SettingsSection(
title: Text(translate('Account')),
tiles: [
SettingsTile(
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
? translate('Login')
: '${translate('Logout')} (${gFFI.userModel.userName.value})')),
leading: Icon(Icons.person),
onPressed: (context) {
if (gFFI.userModel.userName.value.isEmpty) {
loginDialog();
} else {
logOutConfirmDialog();
}
},
),
],
),
customClientSection,
if (!bind.isDisableAccount())
SettingsSection(
title: Text(translate('Account')),
tiles: [
SettingsTile(
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
? translate('Login')
: '${translate('Logout')} (${gFFI.userModel.userName.value})')),
leading: Icon(Icons.person),
onPressed: (context) {
if (gFFI.userModel.userName.value.isEmpty) {
loginDialog();
} else {
logOutConfirmDialog();
}
},
),
],
),
SettingsSection(title: Text(translate("Settings")), tiles: [
SettingsTile(
title: Text(translate('ID/Relay Server')),
leading: Icon(Icons.cloud),
onPressed: (context) {
showServerSettings(gFFI.dialogManager);
}),
if (!disabledSettings)
SettingsTile(
title: Text(translate('ID/Relay Server')),
leading: Icon(Icons.cloud),
onPressed: (context) {
showServerSettings(gFFI.dialogManager);
}),
SettingsTile(
title: Text(translate('Language')),
leading: Icon(Icons.translate),
@@ -494,7 +513,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
},
)
]),
if (isAndroid)
if (isAndroid && !outgoingOnly)
SettingsSection(
title: Text(translate("Recording")),
tiles: [
@@ -523,13 +542,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
),
],
),
if (isAndroid)
if (isAndroid && !disabledSettings && !outgoingOnly)
SettingsSection(
title: Text(translate("Share Screen")),
tiles: shareScreenTiles,
),
defaultDisplaySection(),
if (isAndroid)
if (!bind.isIncomingOnly()) defaultDisplaySection(),
if (isAndroid && !disabledSettings && !outgoingOnly)
SettingsSection(
title: Text(translate("Enhancements")),
tiles: enhancementsTiles,
@@ -578,6 +597,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
),
],
);
return settings;
}
Future<bool> canStartOnBoot() async {

View File

@@ -10,7 +10,7 @@ import './platform_model.dart';
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart'
if (dart.library.html) 'package:flutter_hbb/web/texture_rgba_renderer.dart';
// Feature flutter_texture_render need to be enabled if feature gpucodec is enabled.
// Feature flutter_texture_render need to be enabled if feature vram is enabled.
final useTextureRender = !isWeb &&
(bind.mainHasPixelbufferTextureRender() || bind.mainHasGpuTextureRender());

View File

@@ -561,8 +561,12 @@ class FfiModel with ChangeNotifier {
showRelayHintDialog(sessionId, type, title, text, dialogManager, peerId);
} else if (text == 'Connected, waiting for image...') {
showConnectedWaitingForImage(dialogManager, sessionId, type, title, text);
} else if (title == 'Privacy mode') {
final hasRetry = evt['hasRetry'] == 'true';
showPrivacyFailedDialog(
sessionId, type, title, text, link, hasRetry, dialogManager);
} else {
var hasRetry = evt['hasRetry'] == 'true';
final hasRetry = evt['hasRetry'] == 'true';
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
}
}
@@ -657,6 +661,27 @@ class FfiModel with ChangeNotifier {
bind.sessionOnWaitingForImageDialogShow(sessionId: sessionId);
}
void showPrivacyFailedDialog(
SessionID sessionId,
String type,
String title,
String text,
String link,
bool hasRetry,
OverlayDialogManager dialogManager) {
if (text == 'no_need_privacy_mode_no_physical_displays_tip' ||
text == 'Enter privacy mode') {
// There are display changes on the remote side,
// which will cause some messages to refresh the canvas and dismiss dialogs.
// So we add a delay here to ensure the dialog is displayed.
Future.delayed(Duration(milliseconds: 3000), () {
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
});
} else {
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
}
}
_updateSessionWidthHeight(SessionID sessionId) {
if (_rect == null) return;
if (_rect!.width <= 0 || _rect!.height <= 0) {
@@ -690,6 +715,8 @@ class FfiModel with ChangeNotifier {
// Map clone is required here, otherwise "evt" may be changed by other threads through the reference.
// Because this function is asynchronous, there's an "await" in this function.
cachedPeerData.peerInfo = {...evt};
// Do not cache resolutions, because a new display connection have different resolutions.
cachedPeerData.peerInfo.remove('resolutions');
// Recent peer is updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
bind.mainLoadRecentPeers();
@@ -745,7 +772,9 @@ class FfiModel with ChangeNotifier {
}
Map<String, dynamic> features = json.decode(evt['features']);
_pi.features.privacyMode = features['privacy_mode'] == 1;
handleResolutions(peerId, evt["resolutions"]);
if (!isCache) {
handleResolutions(peerId, evt["resolutions"]);
}
parent.target?.elevationModel.onPeerInfo(_pi);
}
if (connType == ConnType.defaultConn) {
@@ -986,15 +1015,21 @@ class FfiModel with ChangeNotifier {
}
if (updateData.isEmpty) {
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
_pi.platformAdditions.remove(kPlatformAdditionsRustDeskVirtualDisplays);
_pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays);
} else {
try {
final updateJson = json.decode(updateData) as Map<String, dynamic>;
for (final key in updateJson.keys) {
_pi.platformAdditions[key] = updateJson[key];
}
if (!updateJson.containsKey(kPlatformAdditionsVirtualDisplays)) {
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
if (!updateJson
.containsKey(kPlatformAdditionsRustDeskVirtualDisplays)) {
_pi.platformAdditions
.remove(kPlatformAdditionsRustDeskVirtualDisplays);
}
if (!updateJson.containsKey(kPlatformAdditionsAmyuniVirtualDisplays)) {
_pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays);
}
} catch (e) {
debugPrint('Failed to decode platformAdditions $e');
@@ -2286,6 +2321,8 @@ class FFI {
}
await ffiModel.handleCachedPeerData(data, id);
await sessionRefreshVideo(sessionId, ffiModel.pi);
await bind.sessionRequestNewDisplayInitMsgs(
sessionId: sessionId, display: ffiModel.pi.currentDisplay);
});
isToNewWindowNotified.value = true;
}
@@ -2490,14 +2527,21 @@ class PeerInfo with ChangeNotifier {
bool get isInstalled =>
platform != kPeerPlatformWindows ||
platformAdditions[kPlatformAdditionsIsInstalled] == true;
List<int> get virtualDisplays => List<int>.from(
platformAdditions[kPlatformAdditionsVirtualDisplays] ?? []);
List<int> get RustDeskVirtualDisplays => List<int>.from(
platformAdditions[kPlatformAdditionsRustDeskVirtualDisplays] ?? []);
int get amyuniVirtualDisplayCount =>
platformAdditions[kPlatformAdditionsAmyuniVirtualDisplays] ?? 0;
bool get isSupportMultiDisplay =>
(isDesktop || isWebDesktop) && isSupportMultiUiSession;
bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false;
bool get isRustDeskIdd =>
platformAdditions[kPlatformAdditionsIddImpl] == 'rustdesk_idd';
bool get isAmyuniIdd =>
platformAdditions[kPlatformAdditionsIddImpl] == 'amyuni_idd';
Display? tryGetDisplay() {
if (displays.isEmpty) {
return null;

View File

@@ -198,7 +198,10 @@ class PlatformFFI {
await _ffiBind.mainDeviceId(id: id);
await _ffiBind.mainDeviceName(name: name);
await _ffiBind.mainSetHomeDir(home: _homeDir);
await _ffiBind.mainInit(appDir: _dir);
await _ffiBind.mainInit(
appDir: _dir,
customClientConfig: '',
);
} catch (e) {
debugPrintStack(label: 'initialize failed: $e');
}

View File

@@ -21,24 +21,43 @@ class PeerTabModel with ChangeNotifier {
WeakReference<FFI> parent;
int get currentTab => _currentTab;
int _currentTab = 0; // index in tabNames
List<String> tabNames = [
static const int maxTabCount = 5;
static const String kPeerTabIndex = 'peer-tab-index';
static const String kPeerTabOrder = 'peer-tab-order';
static const String kPeerTabVisible = 'peer-tab-visible';
static const List<String> tabNames = [
'Recent sessions',
'Favorites',
if (!isWeb) 'Discovered',
if (!(bind.isDisableAb() || bind.isDisableAccount())) 'Address book',
if (!bind.isDisableAccount()) 'Group',
'Discovered',
'Address book',
'Group',
];
final List<IconData> icons = [
static const List<IconData> icons = [
Icons.access_time_filled,
Icons.star,
if (!isWeb) Icons.explore,
if (!(bind.isDisableAb() || bind.isDisableAccount())) IconFont.addressBook,
if (!bind.isDisableAccount()) Icons.group,
Icons.explore,
IconFont.addressBook,
Icons.group,
];
final List<bool> _isVisible = List.filled(5, true, growable: false);
List<bool> get isVisible => _isVisible;
List<int> get indexs => List.generate(tabNames.length, (index) => index);
List<int> get visibleIndexs => indexs.where((e) => _isVisible[e]).toList();
List<bool> isEnabled = List.from([
true,
true,
!isWeb,
!(bind.isDisableAb() || bind.isDisableAccount()),
!bind.isDisableAccount(),
]);
final List<bool> _isVisible = List.filled(maxTabCount, true, growable: false);
List<bool> get isVisibleEnabled => () {
final list = _isVisible.toList();
for (int i = 0; i < maxTabCount; i++) {
list[i] = list[i] && isEnabled[i];
}
return list;
}();
final List<int> orders =
List.generate(maxTabCount, (index) => index, growable: false);
List<int> get visibleEnabledOrderedIndexs =>
orders.where((e) => isVisibleEnabled[e]).toList();
List<Peer> _selectedPeers = List.empty(growable: true);
List<Peer> get selectedPeers => _selectedPeers;
bool _multiSelectionMode = false;
@@ -53,7 +72,7 @@ class PeerTabModel with ChangeNotifier {
PeerTabModel(this.parent) {
// visible
try {
final option = bind.getLocalFlutterOption(k: 'peer-tab-visible');
final option = bind.getLocalFlutterOption(k: kPeerTabVisible);
if (option.isNotEmpty) {
List<dynamic> decodeList = jsonDecode(option);
if (decodeList.length == _isVisible.length) {
@@ -67,13 +86,37 @@ class PeerTabModel with ChangeNotifier {
} catch (e) {
debugPrint("failed to get peer tab visible list:$e");
}
// order
try {
final option = bind.getLocalFlutterOption(k: kPeerTabOrder);
if (option.isNotEmpty) {
List<dynamic> decodeList = jsonDecode(option);
if (decodeList.length == maxTabCount) {
var sortedList = decodeList.toList();
sortedList.sort();
bool valid = true;
for (int i = 0; i < maxTabCount; i++) {
if (sortedList[i] is! int || sortedList[i] != i) {
valid = false;
}
}
if (valid) {
for (int i = 0; i < orders.length; i++) {
orders[i] = decodeList[i];
}
}
}
}
} catch (e) {
debugPrint("failed to get peer tab order list: $e");
}
// init currentTab
_currentTab =
int.tryParse(bind.getLocalFlutterOption(k: 'peer-tab-index')) ?? 0;
if (_currentTab < 0 || _currentTab >= tabNames.length) {
int.tryParse(bind.getLocalFlutterOption(k: kPeerTabIndex)) ?? 0;
if (_currentTab < 0 || _currentTab >= maxTabCount) {
_currentTab = 0;
}
_trySetCurrentTabToFirstVisible();
_trySetCurrentTabToFirstVisibleEnabled();
}
setCurrentTab(int index) {
@@ -87,15 +130,13 @@ class PeerTabModel with ChangeNotifier {
if (index >= 0 && index < tabNames.length) {
return translate(tabNames[index]);
}
assert(false);
return index.toString();
}
IconData tabIcon(int index) {
if (index >= 0 && index < tabNames.length) {
if (index >= 0 && index < icons.length) {
return icons[index];
}
assert(false);
return Icons.help;
}
@@ -171,29 +212,54 @@ class PeerTabModel with ChangeNotifier {
}
setTabVisible(int index, bool visible) {
if (index >= 0 && index < _isVisible.length) {
if (index >= 0 && index < maxTabCount) {
if (_isVisible[index] != visible) {
_isVisible[index] = visible;
if (index == _currentTab && !visible) {
_trySetCurrentTabToFirstVisible();
} else if (visible && visibleIndexs.length == 1) {
_trySetCurrentTabToFirstVisibleEnabled();
} else if (visible && visibleEnabledOrderedIndexs.length == 1) {
_currentTab = index;
}
try {
bind.setLocalFlutterOption(
k: 'peer-tab-visible', v: jsonEncode(_isVisible));
k: kPeerTabVisible, v: jsonEncode(_isVisible));
} catch (_) {}
notifyListeners();
}
}
}
_trySetCurrentTabToFirstVisible() {
if (!_isVisible[_currentTab]) {
int firstVisible = _isVisible.indexWhere((e) => e);
if (firstVisible >= 0) {
_currentTab = firstVisible;
_trySetCurrentTabToFirstVisibleEnabled() {
if (!visibleEnabledOrderedIndexs.contains(_currentTab)) {
if (visibleEnabledOrderedIndexs.isNotEmpty) {
_currentTab = visibleEnabledOrderedIndexs.first;
}
}
}
reorder(int oldIndex, int newIndex) {
if (oldIndex < newIndex) {
newIndex -= 1;
}
if (oldIndex < 0 || oldIndex >= visibleEnabledOrderedIndexs.length) {
return;
}
if (newIndex < 0 || newIndex >= visibleEnabledOrderedIndexs.length) {
return;
}
final oldTabValue = visibleEnabledOrderedIndexs[oldIndex];
final newTabValue = visibleEnabledOrderedIndexs[newIndex];
int oldValueIndex = orders.indexOf(oldTabValue);
int newValueIndex = orders.indexOf(newTabValue);
final list = orders.toList();
if (oldIndex != -1 && newIndex != -1) {
list.removeAt(oldValueIndex);
list.insert(newValueIndex, oldTabValue);
for (int i = 0; i < list.length; i++) {
orders[i] = list[i];
}
bind.setLocalFlutterOption(k: kPeerTabOrder, v: jsonEncode(orders));
notifyListeners();
}
}
}

View File

@@ -1052,7 +1052,7 @@ class RustdeskImpl {
throw UnimplementedError();
}
bool mainHasGpucodec({dynamic hint}) {
bool mainHasVram({dynamic hint}) {
throw UnimplementedError();
}
@@ -1573,5 +1573,14 @@ class RustdeskImpl {
throw UnimplementedError();
}
Future<void> mainCheckHwcodec({dynamic hint}) {
throw UnimplementedError();
}
Future<void> sessionRequestNewDisplayInitMsgs(
{required UuidValue sessionId, required int display, dynamic hint}) {
throw UnimplementedError();
}
void dispose() {}
}

View File

@@ -210,7 +210,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,2 +1,2 @@
#!/usr/bin/env bash
cargo ndk --platform 21 --target armv7-linux-androideabi build --release --features flutter
cargo ndk --platform 21 --target armv7-linux-androideabi build --release --features flutter,hwcodec

View File

@@ -1,2 +1,2 @@
#!/usr/bin/env bash
cargo ndk --platform 21 --target aarch64-linux-android build --release --features flutter
cargo ndk --platform 21 --target aarch64-linux-android build --release --features flutter,hwcodec

View File

@@ -849,18 +849,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.11.0"
mime:
dependency: transitive
description:
@@ -921,10 +921,10 @@ packages:
dependency: "direct main"
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_parsing:
dependency: transitive
description:
@@ -1526,10 +1526,10 @@ packages:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
version: "0.4.2"
web_socket_channel:
dependency: transitive
description:

View File

@@ -8,7 +8,7 @@ edition = "2018"
[dependencies]
flexi_logger = { version = "0.27", features = ["async"] }
protobuf = { version = "3.3", features = ["with-bytes"] }
protobuf = { version = "3.4", features = ["with-bytes"] }
tokio = { version = "1.36", features = ["full"] }
tokio-util = { version = "0.7", features = ["full"] }
futures = "0.3"
@@ -44,6 +44,7 @@ thiserror = "1.0"
httparse = "1.5"
base64 = "0.22"
url = "2.2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
mac_address = "1.1"
machine-uid = { git = "https://github.com/21pages/machine-uid" }
@@ -59,7 +60,7 @@ quic = []
flatpak = []
[build-dependencies]
protobuf-codegen = { version = "3.3" }
protobuf-codegen = { version = "3.4" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["winuser", "synchapi", "pdh", "memoryapi"] }

View File

@@ -504,6 +504,11 @@ message Resolution {
int32 height = 2;
}
message DisplayResolution {
int32 display = 1;
Resolution resolution = 2;
}
message SupportedResolutions { repeated Resolution resolutions = 1; }
message SwitchDisplay {
@@ -596,7 +601,8 @@ message OptionMessage {
BoolOption disable_keyboard = 12;
// Position 13 is used for Resolution. Remove later.
// Resolution custom_resolution = 13;
BoolOption support_windows_specific_session = 14;
// BoolOption support_windows_specific_session = 14;
// starting from 15 please, do not use removed fields
}
message TestDelay {
@@ -716,6 +722,13 @@ message WindowsSessions {
uint32 current_sid = 2;
}
// Query messages from peer.
message MessageQuery {
// The SwitchDisplay message of the target display.
// If the target display is not found, the message will be ignored.
int32 switch_display = 1;
}
message Misc {
oneof union {
ChatMessage chat_message = 4;
@@ -736,6 +749,8 @@ message Misc {
bool portable_service_running = 20;
SwitchSidesRequest switch_sides_request = 21;
SwitchBack switch_back = 22;
// Deprecated since 1.2.4, use `change_display_resolution` (36) instead.
// But we must keep it for compatibility when peer version < 1.2.4.
Resolution change_resolution = 24;
PluginRequest plugin_request = 25;
PluginFailure plugin_failure = 26;
@@ -748,6 +763,8 @@ message Misc {
TogglePrivacyMode toggle_privacy_mode = 33;
SupportedEncoding supported_encoding = 34;
uint32 selected_sid = 35;
DisplayResolution change_display_resolution = 36;
MessageQuery message_query = 37;
}
}

View File

@@ -409,9 +409,7 @@ fn patch(path: PathBuf) -> PathBuf {
if let Ok(user) = crate::platform::linux::run_cmds_trim_newline("whoami") {
if user != "root" {
let cmd = format!("getent passwd '{}' | awk -F':' '{{print $6}}'", user);
if let Ok(output) =
crate::platform::linux::run_cmds_trim_newline(&cmd)
{
if let Ok(output) = crate::platform::linux::run_cmds_trim_newline(&cmd) {
return output.into();
}
return format!("/home/{user}").into();
@@ -505,7 +503,7 @@ impl Config {
fn store_<T: serde::Serialize>(config: &T, suffix: &str) {
let file = Self::file_(suffix);
if let Err(err) = store_path(file, config) {
log::error!("Failed to store config: {}", err);
log::error!("Failed to store {suffix} config: {err}");
}
}
@@ -1495,8 +1493,10 @@ impl LanPeers {
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct HwCodecConfig {
#[serde(default, deserialize_with = "deserialize_hashmap_string_string")]
pub options: HashMap<String, String>,
#[serde(default, deserialize_with = "deserialize_string")]
pub ram: String,
#[serde(default, deserialize_with = "deserialize_string")]
pub vram: String,
}
impl HwCodecConfig {
@@ -1511,25 +1511,17 @@ impl HwCodecConfig {
pub fn clear() {
HwCodecConfig::default().store();
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct GpucodecConfig {
#[serde(default, deserialize_with = "deserialize_string")]
pub available: String,
}
impl GpucodecConfig {
pub fn load() -> GpucodecConfig {
Config::load_::<GpucodecConfig>("_gpucodec")
pub fn clear_ram() {
let mut c = Self::load();
c.ram = Default::default();
c.store();
}
pub fn store(&self) {
Config::store_(self, "_gpucodec");
}
pub fn clear() {
GpucodecConfig::default().store();
pub fn clear_vram() {
let mut c = Self::load();
c.vram = Default::default();
c.store();
}
}
@@ -1569,6 +1561,7 @@ impl UserDefaultConfig {
}
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 0xFFF as f64),
"custom-fps" => self.get_double_string(key, 30.0, 5.0, 120.0),
"enable_file_transfer" => self.get_string(key, "Y", vec![""]),
_ => self
.get_after(key)
.map(|v| v.to_string())

View File

@@ -47,10 +47,13 @@ pub mod keyboard;
pub use dlopen;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use machine_uid;
pub use serde_derive;
pub use serde_json;
pub use sysinfo;
pub use toml;
pub use uuid;
pub use base64;
pub use thiserror;
#[cfg(feature = "quic")]
pub type Stream = quic::Connection;

View File

@@ -13,6 +13,8 @@ edition = "2018"
wayland = ["gstreamer", "gstreamer-app", "gstreamer-video", "dbus", "tracing"]
mediacodec = ["ndk"]
linux-pkg-config = ["dep:pkg-config"]
hwcodec = ["dep:hwcodec"]
vram = ["hwcodec/vram"]
[dependencies]
cfg-if = "1.0"
@@ -20,6 +22,7 @@ num_cpus = "1.15"
lazy_static = "1.4"
hbb_common = { path = "../hbb_common" }
webm = { git = "https://github.com/21pages/rust-webm" }
serde = {version="1.0", features=["derive"]}
[dependencies.winapi]
version = "0.3"
@@ -41,7 +44,6 @@ ndk-context = "0.1"
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
repng = "0.2"
docopt = "1.1"
serde = {version="1.0", features=["derive"]}
quest = "0.3"
[build-dependencies]
@@ -56,8 +58,8 @@ gstreamer = { version = "0.16", optional = true }
gstreamer-app = { version = "0.16", features = ["v1_10"], optional = true }
gstreamer-video = { version = "0.16", optional = true }
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
hwcodec = { git = "https://github.com/21pages/hwcodec", branch = "stable", optional = true }
[dependencies.hwcodec]
git = "https://github.com/21pages/hwcodec"
optional = true
[target.'cfg(target_os = "windows")'.dependencies]
gpucodec = { git = "https://github.com/21pages/gpucodec", optional = true }

View File

@@ -239,16 +239,16 @@ fn test_av1(
#[cfg(feature = "hwcodec")]
mod hw {
use hwcodec::ffmpeg::CodecInfo;
use hwcodec::ffmpeg_ram::CodecInfo;
use scrap::{
hwcodec::{HwDecoder, HwEncoder, HwEncoderConfig},
hwcodec::{HwRamDecoder, HwRamEncoder, HwRamEncoderConfig},
CodecFormat,
};
use super::*;
pub fn test(c: &mut Capturer, width: usize, height: usize, quality: Q, yuv_count: usize) {
let best = HwEncoder::best();
let best = HwRamEncoder::best();
let mut h264s = Vec::new();
let mut h265s = Vec::new();
if let Some(info) = best.h264 {
@@ -270,8 +270,8 @@ mod hw {
yuv_count: usize,
h26xs: &mut Vec<Vec<u8>>,
) {
let mut encoder = HwEncoder::new(
EncoderCfg::HW(HwEncoderConfig {
let mut encoder = HwRamEncoder::new(
EncoderCfg::HWRAM(HwRamEncoderConfig {
name: info.name.clone(),
width,
height,
@@ -321,7 +321,7 @@ mod hw {
}
fn test_decoder(format: CodecFormat, h26xs: &Vec<Vec<u8>>) {
let mut decoder = HwDecoder::new(format).unwrap();
let mut decoder = HwRamDecoder::new(format).unwrap();
let start = Instant::now();
let mut cnt = 0;
for h26x in h26xs {

View File

@@ -94,7 +94,7 @@ pub fn get_audio_raw<'a>() -> Option<&'a [u8]> {
}
#[no_mangle]
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onVideoFrameUpdate(
pub extern "system" fn Java_ffi_FFI_onVideoFrameUpdate(
env: JNIEnv,
_class: JClass,
buffer: JObject,
@@ -108,7 +108,7 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onVideoFrameUpd
}
#[no_mangle]
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onAudioFrameUpdate(
pub extern "system" fn Java_ffi_FFI_onAudioFrameUpdate(
env: JNIEnv,
_class: JClass,
buffer: JObject,
@@ -122,7 +122,7 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onAudioFrameUpd
}
#[no_mangle]
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_setFrameRawEnable(
pub extern "system" fn Java_ffi_FFI_setFrameRawEnable(
env: JNIEnv,
_class: JClass,
name: JString,
@@ -141,7 +141,7 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_setFrameRawEnab
}
#[no_mangle]
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_init(
pub extern "system" fn Java_ffi_FFI_init(
env: JNIEnv,
_class: JClass,
ctx: JObject,

View File

@@ -268,7 +268,7 @@ impl EncoderApi for AomEncoder {
self.yuvfmt.clone()
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn input_texture(&self) -> bool {
false
}

View File

@@ -5,12 +5,12 @@ use std::{
sync::{Arc, Mutex},
};
#[cfg(feature = "gpucodec")]
use crate::gpucodec::*;
#[cfg(feature = "hwcodec")]
use crate::hwcodec::*;
#[cfg(feature = "mediacodec")]
use crate::mediacodec::{MediaCodecDecoder, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT};
#[cfg(feature = "vram")]
use crate::vram::*;
use crate::{
aom::{self, AomDecoder, AomEncoder, AomEncoderConfig},
common::GoogleImage,
@@ -31,7 +31,7 @@ use hbb_common::{
tokio::time::Instant,
ResultType,
};
#[cfg(any(feature = "hwcodec", feature = "mediacodec", feature = "gpucodec"))]
#[cfg(any(feature = "hwcodec", feature = "mediacodec", feature = "vram"))]
use hbb_common::{config::Config2, lazy_static};
lazy_static::lazy_static! {
@@ -47,9 +47,9 @@ pub enum EncoderCfg {
VPX(VpxEncoderConfig),
AOM(AomEncoderConfig),
#[cfg(feature = "hwcodec")]
HW(HwEncoderConfig),
#[cfg(feature = "gpucodec")]
GPU(GpuEncoderConfig),
HWRAM(HwRamEncoderConfig),
#[cfg(feature = "vram")]
VRAM(VRamEncoderConfig),
}
pub trait EncoderApi {
@@ -61,7 +61,7 @@ pub trait EncoderApi {
fn yuvfmt(&self) -> EncodeYuvFormat;
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn input_texture(&self) -> bool;
fn set_quality(&mut self, quality: Quality) -> ResultType<()>;
@@ -94,13 +94,13 @@ pub struct Decoder {
vp9: Option<VpxDecoder>,
av1: Option<AomDecoder>,
#[cfg(feature = "hwcodec")]
h264_ram: Option<HwDecoder>,
h264_ram: Option<HwRamDecoder>,
#[cfg(feature = "hwcodec")]
h265_ram: Option<HwDecoder>,
#[cfg(feature = "gpucodec")]
h264_vram: Option<GpuDecoder>,
#[cfg(feature = "gpucodec")]
h265_vram: Option<GpuDecoder>,
h265_ram: Option<HwRamDecoder>,
#[cfg(feature = "vram")]
h264_vram: Option<VRamDecoder>,
#[cfg(feature = "vram")]
h265_vram: Option<VRamDecoder>,
#[cfg(feature = "mediacodec")]
h264_media_codec: MediaCodecDecoder,
#[cfg(feature = "mediacodec")]
@@ -131,25 +131,25 @@ impl Encoder {
}),
#[cfg(feature = "hwcodec")]
EncoderCfg::HW(_) => match HwEncoder::new(config, i444) {
EncoderCfg::HWRAM(_) => match HwRamEncoder::new(config, i444) {
Ok(hw) => Ok(Encoder {
codec: Box::new(hw),
}),
Err(e) => {
log::error!("new hw encoder failed: {e:?}, clear config");
hbb_common::config::HwCodecConfig::clear();
hbb_common::config::HwCodecConfig::clear_ram();
*ENCODE_CODEC_NAME.lock().unwrap() = CodecName::VP9;
Err(e)
}
},
#[cfg(feature = "gpucodec")]
EncoderCfg::GPU(_) => match GpuEncoder::new(config, i444) {
#[cfg(feature = "vram")]
EncoderCfg::VRAM(_) => match VRamEncoder::new(config, i444) {
Ok(tex) => Ok(Encoder {
codec: Box::new(tex),
}),
Err(e) => {
log::error!("new gpu encoder failed: {e:?}, clear config");
hbb_common::config::GpucodecConfig::clear();
log::error!("new vram encoder failed: {e:?}, clear config");
hbb_common::config::HwCodecConfig::clear_vram();
*ENCODE_CODEC_NAME.lock().unwrap() = CodecName::VP9;
Err(e)
}
@@ -186,19 +186,19 @@ impl Encoder {
let _all_support_h265_decoding =
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h265 > 0);
#[allow(unused_mut)]
let mut h264gpu_encoding = false;
let mut h264vram_encoding = false;
#[allow(unused_mut)]
let mut h265gpu_encoding = false;
#[cfg(feature = "gpucodec")]
if enable_gpucodec_option() {
let mut h265vram_encoding = false;
#[cfg(feature = "vram")]
if enable_vram_option() {
if _all_support_h264_decoding {
if GpuEncoder::available(CodecName::H264GPU).len() > 0 {
h264gpu_encoding = true;
if VRamEncoder::available(CodecName::H264VRAM).len() > 0 {
h264vram_encoding = true;
}
}
if _all_support_h265_decoding {
if GpuEncoder::available(CodecName::H265GPU).len() > 0 {
h265gpu_encoding = true;
if VRamEncoder::available(CodecName::H265VRAM).len() > 0 {
h265vram_encoding = true;
}
}
}
@@ -208,7 +208,7 @@ impl Encoder {
let mut h265hw_encoding = None;
#[cfg(feature = "hwcodec")]
if enable_hwcodec_option() {
let best = HwEncoder::best();
let best = HwRamEncoder::best();
if _all_support_h264_decoding {
h264hw_encoding = best.h264.map_or(None, |c| Some(c.name));
}
@@ -217,9 +217,9 @@ impl Encoder {
}
}
let h264_useable =
_all_support_h264_decoding && (h264gpu_encoding || h264hw_encoding.is_some());
_all_support_h264_decoding && (h264vram_encoding || h264hw_encoding.is_some());
let h265_useable =
_all_support_h265_decoding && (h265gpu_encoding || h265hw_encoding.is_some());
_all_support_h265_decoding && (h265vram_encoding || h265hw_encoding.is_some());
let mut name = ENCODE_CODEC_NAME.lock().unwrap();
let mut preference = PreferCodec::Auto;
let preferences: Vec<_> = decodings
@@ -239,7 +239,8 @@ impl Encoder {
#[allow(unused_mut)]
let mut auto_codec = CodecName::VP9;
if av1_useable {
// aom is very slow for x86 sciter version on windows x64
if av1_useable && !(cfg!(windows) && std::env::consts::ARCH == "x86") {
auto_codec = CodecName::AV1;
}
let mut system = System::new();
@@ -254,19 +255,19 @@ impl Encoder {
PreferCodec::VP9 => CodecName::VP9,
PreferCodec::AV1 => CodecName::AV1,
PreferCodec::H264 => {
if h264gpu_encoding {
CodecName::H264GPU
if h264vram_encoding {
CodecName::H264VRAM
} else if let Some(v) = h264hw_encoding {
CodecName::H264HW(v)
CodecName::H264RAM(v)
} else {
auto_codec
}
}
PreferCodec::H265 => {
if h265gpu_encoding {
CodecName::H265GPU
if h265vram_encoding {
CodecName::H265VRAM
} else if let Some(v) = h265hw_encoding {
CodecName::H265HW(v)
CodecName::H265RAM(v)
} else {
auto_codec
}
@@ -306,14 +307,14 @@ impl Encoder {
};
#[cfg(feature = "hwcodec")]
if enable_hwcodec_option() {
let best = HwEncoder::best();
let best = HwRamEncoder::best();
encoding.h264 |= best.h264.is_some();
encoding.h265 |= best.h265.is_some();
}
#[cfg(feature = "gpucodec")]
if enable_gpucodec_option() {
encoding.h264 |= GpuEncoder::available(CodecName::H264GPU).len() > 0;
encoding.h265 |= GpuEncoder::available(CodecName::H265GPU).len() > 0;
#[cfg(feature = "vram")]
if enable_vram_option() {
encoding.h264 |= VRamEncoder::available(CodecName::H264VRAM).len() > 0;
encoding.h265 |= VRamEncoder::available(CodecName::H265VRAM).len() > 0;
}
encoding
}
@@ -326,21 +327,21 @@ impl Encoder {
},
EncoderCfg::AOM(_) => CodecName::AV1,
#[cfg(feature = "hwcodec")]
EncoderCfg::HW(hw) => {
EncoderCfg::HWRAM(hw) => {
if hw.name.to_lowercase().contains("h264") {
CodecName::H264HW(hw.name.clone())
CodecName::H264RAM(hw.name.clone())
} else {
CodecName::H265HW(hw.name.clone())
CodecName::H265RAM(hw.name.clone())
}
}
#[cfg(feature = "gpucodec")]
EncoderCfg::GPU(gpu) => match gpu.feature.data_format {
gpucodec::gpu_common::DataFormat::H264 => CodecName::H264GPU,
gpucodec::gpu_common::DataFormat::H265 => CodecName::H265GPU,
#[cfg(feature = "vram")]
EncoderCfg::VRAM(vram) => match vram.feature.data_format {
hwcodec::common::DataFormat::H264 => CodecName::H264VRAM,
hwcodec::common::DataFormat::H265 => CodecName::H265VRAM,
_ => {
log::error!(
"should not reach here, gpucodec not support {:?}",
gpu.feature.data_format
"should not reach here, vram not support {:?}",
vram.feature.data_format
);
return;
}
@@ -365,9 +366,9 @@ impl Encoder {
},
EncoderCfg::AOM(_) => decodings.iter().all(|d| d.1.i444.av1),
#[cfg(feature = "hwcodec")]
EncoderCfg::HW(_) => false,
#[cfg(feature = "gpucodec")]
EncoderCfg::GPU(_) => false,
EncoderCfg::HWRAM(_) => false,
#[cfg(feature = "vram")]
EncoderCfg::VRAM(_) => false,
};
prefer_i444 && i444_useable && !decodings.is_empty()
}
@@ -398,19 +399,19 @@ impl Decoder {
..Default::default()
};
#[cfg(feature = "hwcodec")]
if enable_hwcodec_option() {
let best = HwDecoder::best();
{
let best = HwRamDecoder::best();
decoding.ability_h264 |= if best.h264.is_some() { 1 } else { 0 };
decoding.ability_h265 |= if best.h265.is_some() { 1 } else { 0 };
}
#[cfg(feature = "gpucodec")]
if enable_gpucodec_option() && _flutter {
decoding.ability_h264 |= if GpuDecoder::available(CodecFormat::H264, _luid).len() > 0 {
#[cfg(feature = "vram")]
if enable_vram_option() && _flutter {
decoding.ability_h264 |= if VRamDecoder::available(CodecFormat::H264, _luid).len() > 0 {
1
} else {
0
};
decoding.ability_h265 |= if GpuDecoder::available(CodecFormat::H265, _luid).len() > 0 {
decoding.ability_h265 |= if VRamDecoder::available(CodecFormat::H265, _luid).len() > 0 {
1
} else {
0
@@ -449,7 +450,7 @@ impl Decoder {
let (mut vp8, mut vp9, mut av1) = (None, None, None);
#[cfg(feature = "hwcodec")]
let (mut h264_ram, mut h265_ram) = (None, None);
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
let (mut h264_vram, mut h265_vram) = (None, None);
#[cfg(feature = "mediacodec")]
let (mut h264_media_codec, mut h265_media_codec) = (None, None);
@@ -482,17 +483,17 @@ impl Decoder {
valid = av1.is_some();
}
CodecFormat::H264 => {
#[cfg(feature = "gpucodec")]
if !valid && enable_gpucodec_option() && _luid.clone().unwrap_or_default() != 0 {
match GpuDecoder::new(format, _luid) {
#[cfg(feature = "vram")]
if !valid && enable_vram_option() && _luid.clone().unwrap_or_default() != 0 {
match VRamDecoder::new(format, _luid) {
Ok(v) => h264_vram = Some(v),
Err(e) => log::error!("create H264 vram decoder failed: {}", e),
}
valid = h264_vram.is_some();
}
#[cfg(feature = "hwcodec")]
if !valid && enable_hwcodec_option() {
match HwDecoder::new(format) {
if !valid {
match HwRamDecoder::new(format) {
Ok(v) => h264_ram = Some(v),
Err(e) => log::error!("create H264 ram decoder failed: {}", e),
}
@@ -508,17 +509,17 @@ impl Decoder {
}
}
CodecFormat::H265 => {
#[cfg(feature = "gpucodec")]
if !valid && enable_gpucodec_option() && _luid.clone().unwrap_or_default() != 0 {
match GpuDecoder::new(format, _luid) {
#[cfg(feature = "vram")]
if !valid && enable_vram_option() && _luid.clone().unwrap_or_default() != 0 {
match VRamDecoder::new(format, _luid) {
Ok(v) => h265_vram = Some(v),
Err(e) => log::error!("create H265 vram decoder failed: {}", e),
}
valid = h265_vram.is_some();
}
#[cfg(feature = "hwcodec")]
if !valid && enable_hwcodec_option() {
match HwDecoder::new(format) {
if !valid {
match HwRamDecoder::new(format) {
Ok(v) => h265_ram = Some(v),
Err(e) => log::error!("create H265 ram decoder failed: {}", e),
}
@@ -550,9 +551,9 @@ impl Decoder {
h264_ram,
#[cfg(feature = "hwcodec")]
h265_ram,
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
h264_vram,
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
h265_vram,
#[cfg(feature = "mediacodec")]
h264_media_codec,
@@ -604,31 +605,31 @@ impl Decoder {
bail!("av1 decoder not available");
}
}
#[cfg(any(feature = "hwcodec", feature = "gpucodec"))]
#[cfg(any(feature = "hwcodec", feature = "vram"))]
video_frame::Union::H264s(h264s) => {
*chroma = Some(Chroma::I420);
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
if let Some(decoder) = &mut self.h264_vram {
*_pixelbuffer = false;
return Decoder::handle_gpu_video_frame(decoder, h264s, _texture);
return Decoder::handle_vram_video_frame(decoder, h264s, _texture);
}
#[cfg(feature = "hwcodec")]
if let Some(decoder) = &mut self.h264_ram {
return Decoder::handle_hw_video_frame(decoder, h264s, rgb, &mut self.i420);
return Decoder::handle_hwram_video_frame(decoder, h264s, rgb, &mut self.i420);
}
Err(anyhow!("don't support h264!"))
}
#[cfg(any(feature = "hwcodec", feature = "gpucodec"))]
#[cfg(any(feature = "hwcodec", feature = "vram"))]
video_frame::Union::H265s(h265s) => {
*chroma = Some(Chroma::I420);
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
if let Some(decoder) = &mut self.h265_vram {
*_pixelbuffer = false;
return Decoder::handle_gpu_video_frame(decoder, h265s, _texture);
return Decoder::handle_vram_video_frame(decoder, h265s, _texture);
}
#[cfg(feature = "hwcodec")]
if let Some(decoder) = &mut self.h265_ram {
return Decoder::handle_hw_video_frame(decoder, h265s, rgb, &mut self.i420);
return Decoder::handle_hwram_video_frame(decoder, h265s, rgb, &mut self.i420);
}
Err(anyhow!("don't support h265!"))
}
@@ -710,8 +711,8 @@ impl Decoder {
// rgb [in/out] fmt and stride must be set in ImageRgb
#[cfg(feature = "hwcodec")]
fn handle_hw_video_frame(
decoder: &mut HwDecoder,
fn handle_hwram_video_frame(
decoder: &mut HwRamDecoder,
frames: &EncodedVideoFrames,
rgb: &mut ImageRgb,
i420: &mut Vec<u8>,
@@ -728,9 +729,9 @@ impl Decoder {
return Ok(ret);
}
#[cfg(feature = "gpucodec")]
fn handle_gpu_video_frame(
decoder: &mut GpuDecoder,
#[cfg(feature = "vram")]
fn handle_vram_video_frame(
decoder: &mut VRamDecoder,
frames: &EncodedVideoFrames,
texture: &mut *mut c_void,
) -> ResultType<bool> {
@@ -791,13 +792,16 @@ impl Decoder {
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
pub fn enable_hwcodec_option() -> bool {
if let Some(v) = Config2::get().options.get("enable-hwcodec") {
return v != "N";
if cfg!(windows) || cfg!(target_os = "linux") || cfg!(feature = "mediacodec") {
if let Some(v) = Config2::get().options.get("enable-hwcodec") {
return v != "N";
}
return true; // default is true
}
return true; // default is true
false
}
#[cfg(feature = "gpucodec")]
pub fn enable_gpucodec_option() -> bool {
#[cfg(feature = "vram")]
pub fn enable_vram_option() -> bool {
if let Some(v) = Config2::get().options.get("enable-hwcodec") {
return v != "N";
}

View File

@@ -18,7 +18,7 @@ pub mod hw {
use super::*;
use crate::ImageFormat;
#[cfg(target_os = "windows")]
use hwcodec::{ffmpeg::ffmpeg_linesize_offset_length, AVPixelFormat};
use hwcodec::{ffmpeg::AVPixelFormat, ffmpeg_ram::ffmpeg_linesize_offset_length};
#[cfg(target_os = "windows")]
pub fn hw_nv12_to(
@@ -222,9 +222,7 @@ pub fn convert_to_yuv(
);
}
}
let align = |x:usize| {
(x + 63) / 64 * 64
};
let align = |x: usize| (x + 63) / 64 * 64;
match (src_pixfmt, dst_fmt.pixfmt) {
(crate::Pixfmt::BGRA, crate::Pixfmt::I420) | (crate::Pixfmt::RGBA, crate::Pixfmt::I420) => {
@@ -282,7 +280,8 @@ pub fn convert_to_yuv(
let dst_stride_u = dst_fmt.stride[1];
let dst_stride_v = dst_fmt.stride[2];
dst.resize(
align(dst_fmt.h) * (align(dst_stride_y) + align(dst_stride_u) + align(dst_stride_v)),
align(dst_fmt.h)
* (align(dst_stride_y) + align(dst_stride_u) + align(dst_stride_v)),
0,
);
let dst_y = dst.as_mut_ptr();

View File

@@ -1,4 +1,4 @@
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
use crate::AdapterDevice;
use crate::{common::TraitCapturer, dxgi, Frame, Pixfmt};
use std::{
@@ -57,12 +57,12 @@ impl TraitCapturer for Capturer {
self.inner.set_gdi()
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn device(&self) -> AdapterDevice {
self.inner.device()
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn set_output_texture(&mut self, texture: bool) {
self.inner.set_output_texture(texture);
}
@@ -197,7 +197,7 @@ impl Display {
self.origin() == (0, 0)
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
pub fn adapter_luid(&self) -> Option<i64> {
self.0.adapter_luid()
}
@@ -247,11 +247,11 @@ impl TraitCapturer for CapturerMag {
false
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn device(&self) -> AdapterDevice {
AdapterDevice::default()
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn set_output_texture(&mut self, _texture: bool) {}
}

View File

@@ -1,28 +1,30 @@
use crate::{
codec::{base_bitrate, codec_thread_num, EncoderApi, EncoderCfg, Quality as Q},
codec::{
base_bitrate, codec_thread_num, enable_hwcodec_option, EncoderApi, EncoderCfg, Quality as Q,
},
hw, CodecFormat, EncodeInput, ImageFormat, ImageRgb, Pixfmt, HW_STRIDE_ALIGN,
};
use hbb_common::{
allow_err,
anyhow::{anyhow, bail, Context},
bytes::Bytes,
config::HwCodecConfig,
log,
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
ResultType,
serde_derive::{Deserialize, Serialize},
serde_json, ResultType,
};
use hwcodec::{
decode::{DecodeContext, DecodeFrame, Decoder},
encode::{EncodeContext, EncodeFrame, Encoder},
ffmpeg::{CodecInfo, CodecInfos, DataFormat},
AVPixelFormat,
Quality::{self, *},
RateControl::{self, *},
common::DataFormat,
ffmpeg::AVPixelFormat,
ffmpeg_ram::{
decode::{DecodeContext, DecodeFrame, Decoder},
encode::{EncodeContext, EncodeFrame, Encoder},
CodecInfo, CodecInfos,
Quality::{self, *},
RateControl::{self, *},
},
};
const CFG_KEY_ENCODER: &str = "bestHwEncoders";
const CFG_KEY_DECODER: &str = "bestHwDecoders";
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_NV12;
pub const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
const DEFAULT_GOP: i32 = i32::MAX;
@@ -30,7 +32,7 @@ const DEFAULT_HW_QUALITY: Quality = Quality_Default;
const DEFAULT_RC: RateControl = RC_DEFAULT;
#[derive(Debug, Clone)]
pub struct HwEncoderConfig {
pub struct HwRamEncoderConfig {
pub name: String,
pub width: usize,
pub height: usize,
@@ -38,7 +40,7 @@ pub struct HwEncoderConfig {
pub keyframe_interval: Option<usize>,
}
pub struct HwEncoder {
pub struct HwRamEncoder {
encoder: Encoder,
name: String,
pub format: DataFormat,
@@ -48,13 +50,13 @@ pub struct HwEncoder {
bitrate: u32, //kbs
}
impl EncoderApi for HwEncoder {
impl EncoderApi for HwRamEncoder {
fn new(cfg: EncoderCfg, _i444: bool) -> ResultType<Self>
where
Self: Sized,
{
match cfg {
EncoderCfg::HW(config) => {
EncoderCfg::HWRAM(config) => {
let b = Self::convert_quality(config.quality);
let base_bitrate = base_bitrate(config.width as _, config.height as _);
let mut bitrate = base_bitrate * b / 100;
@@ -85,7 +87,7 @@ impl EncoderApi for HwEncoder {
}
};
match Encoder::new(ctx.clone()) {
Ok(encoder) => Ok(HwEncoder {
Ok(encoder) => Ok(HwRamEncoder {
encoder,
name: config.name,
format,
@@ -95,7 +97,7 @@ impl EncoderApi for HwEncoder {
bitrate,
}),
Err(_) => {
HwCodecConfig::clear();
HwCodecConfig::clear_ram();
Err(anyhow!(format!("Failed to create encoder")))
}
}
@@ -126,6 +128,7 @@ impl EncoderApi for HwEncoder {
match self.format {
DataFormat::H264 => vf.set_h264s(frames),
DataFormat::H265 => vf.set_h265s(frames),
_ => bail!("unsupported format: {:?}", self.format),
}
Ok(vf)
} else {
@@ -160,7 +163,7 @@ impl EncoderApi for HwEncoder {
}
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn input_texture(&self) -> bool {
false
}
@@ -184,9 +187,9 @@ impl EncoderApi for HwEncoder {
}
}
impl HwEncoder {
impl HwRamEncoder {
pub fn best() -> CodecInfos {
get_config(CFG_KEY_ENCODER).unwrap_or(CodecInfos {
get_config().map(|c| c.e).unwrap_or(CodecInfos {
h264: None,
h265: None,
})
@@ -214,28 +217,30 @@ impl HwEncoder {
}
}
pub struct HwDecoder {
pub struct HwRamDecoder {
decoder: Decoder,
pub info: CodecInfo,
}
#[derive(Default)]
pub struct HwDecoders {
pub h264: Option<HwDecoder>,
pub h265: Option<HwDecoder>,
}
impl HwDecoder {
impl HwRamDecoder {
pub fn best() -> CodecInfos {
get_config(CFG_KEY_DECODER).unwrap_or(CodecInfos {
h264: None,
h265: None,
})
let mut info = CodecInfo::soft();
if enable_hwcodec_option() {
if let Ok(hw) = get_config().map(|c| c.d) {
if let Some(h264) = hw.h264 {
info.h264 = Some(h264);
}
if let Some(h265) = hw.h265 {
info.h265 = Some(h265);
}
}
}
info
}
pub fn new(format: CodecFormat) -> ResultType<Self> {
log::info!("try create {format:?} ram decoder");
let best = HwDecoder::best();
let best = HwRamDecoder::best();
let info = match format {
CodecFormat::H264 => {
if let Some(info) = best.h264 {
@@ -259,26 +264,26 @@ impl HwDecoder {
thread_count: codec_thread_num(16) as _,
};
match Decoder::new(ctx) {
Ok(decoder) => Ok(HwDecoder { decoder, info }),
Ok(decoder) => Ok(HwRamDecoder { decoder, info }),
Err(_) => {
HwCodecConfig::clear();
HwCodecConfig::clear_ram();
Err(anyhow!(format!("Failed to create decoder")))
}
}
}
pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<HwDecoderImage>> {
pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<HwRamDecoderImage>> {
match self.decoder.decode(data) {
Ok(v) => Ok(v.iter().map(|f| HwDecoderImage { frame: f }).collect()),
Ok(v) => Ok(v.iter().map(|f| HwRamDecoderImage { frame: f }).collect()),
Err(e) => Err(anyhow!(e)),
}
}
}
pub struct HwDecoderImage<'a> {
pub struct HwRamDecoderImage<'a> {
frame: &'a DecodeFrame,
}
impl HwDecoderImage<'_> {
impl HwRamDecoderImage<'_> {
// rgb [in/out] fmt and stride must be set in ImageRgb
pub fn to_fmt(&self, rgb: &mut ImageRgb, i420: &mut Vec<u8>) -> ResultType<()> {
let frame = self.frame;
@@ -332,23 +337,24 @@ impl HwDecoderImage<'_> {
}
}
fn get_config(k: &str) -> ResultType<CodecInfos> {
let v = HwCodecConfig::load()
.options
.get(k)
.unwrap_or(&"".to_owned())
.to_owned();
match CodecInfos::deserialize(&v) {
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
struct Available {
e: CodecInfos,
d: CodecInfos,
}
fn get_config() -> ResultType<Available> {
match serde_json::from_str(&HwCodecConfig::load().ram) {
Ok(v) => Ok(v),
Err(_) => Err(anyhow!("Failed to get config:{}", k)),
Err(e) => Err(anyhow!("Failed to get config:{e:?}")),
}
}
pub fn check_available_hwcodec() {
let ctx = EncodeContext {
name: String::from(""),
width: 1920,
height: 1080,
width: 1280,
height: 720,
pixfmt: DEFAULT_PIXFMT,
align: HW_STRIDE_ALIGN as _,
bitrate: 0,
@@ -358,30 +364,27 @@ pub fn check_available_hwcodec() {
rc: DEFAULT_RC,
thread_count: 4,
};
let encoders = CodecInfo::score(Encoder::available_encoders(ctx));
let decoders = CodecInfo::score(Decoder::available_decoders());
if let Ok(old_encoders) = get_config(CFG_KEY_ENCODER) {
if let Ok(old_decoders) = get_config(CFG_KEY_DECODER) {
if encoders == old_encoders && decoders == old_decoders {
return;
}
}
#[cfg(feature = "vram")]
let vram = crate::vram::check_available_vram();
#[cfg(not(feature = "vram"))]
let vram = "".to_owned();
let encoders = CodecInfo::prioritized(Encoder::available_encoders(ctx, Some(vram.clone())));
let decoders = CodecInfo::prioritized(Decoder::available_decoders(Some(vram.clone())));
let ram = Available {
e: encoders,
d: decoders,
};
if let Ok(ram) = serde_json::to_string_pretty(&ram) {
HwCodecConfig { ram, vram }.store();
}
if let Ok(encoders) = encoders.serialize() {
if let Ok(decoders) = decoders.serialize() {
let mut config = HwCodecConfig::load();
config.options.insert(CFG_KEY_ENCODER.to_owned(), encoders);
config.options.insert(CFG_KEY_DECODER.to_owned(), decoders);
config.store();
return;
}
}
log::error!("Failed to serialize codec info");
}
pub fn hwcodec_new_check_process() {
#[cfg(any(target_os = "windows", target_os = "linux"))]
pub fn start_check_process(force: bool) {
if !force && !enable_hwcodec_option() {
return;
}
use hbb_common::allow_err;
use std::sync::Once;
let f = || {
// Clear to avoid checking process errors
@@ -421,7 +424,11 @@ pub fn hwcodec_new_check_process() {
};
};
static ONCE: Once = Once::new();
ONCE.call_once(|| {
if force && ONCE.is_completed() {
std::thread::spawn(f);
});
} else {
ONCE.call_once(|| {
std::thread::spawn(f);
});
}
}

View File

@@ -37,13 +37,13 @@ cfg_if! {
pub mod codec;
pub mod convert;
#[cfg(feature = "gpucodec")]
pub mod gpucodec;
#[cfg(feature = "hwcodec")]
pub mod hwcodec;
#[cfg(feature = "mediacodec")]
pub mod mediacodec;
pub mod vpxcodec;
#[cfg(feature = "vram")]
pub mod vram;
pub use self::convert::*;
pub const STRIDE_ALIGN: usize = 64; // commonly used in libvpx vpx_img_alloc caller
pub const HW_STRIDE_ALIGN: usize = 0; // recommended by av_frame_get_buffer
@@ -111,10 +111,10 @@ pub trait TraitCapturer {
#[cfg(windows)]
fn set_gdi(&mut self) -> bool;
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn device(&self) -> AdapterDevice;
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn set_output_texture(&mut self, texture: bool);
}
@@ -245,10 +245,10 @@ pub enum CodecName {
VP8,
VP9,
AV1,
H264HW(String),
H265HW(String),
H264GPU,
H265GPU,
H264RAM(String),
H265RAM(String),
H264VRAM,
H265VRAM,
}
#[derive(PartialEq, Debug, Clone, Copy)]
@@ -280,8 +280,8 @@ impl From<&CodecName> for CodecFormat {
CodecName::VP8 => Self::VP8,
CodecName::VP9 => Self::VP9,
CodecName::AV1 => Self::AV1,
CodecName::H264HW(_) | CodecName::H264GPU => Self::H264,
CodecName::H265HW(_) | CodecName::H265GPU => Self::H265,
CodecName::H264RAM(_) | CodecName::H264VRAM => Self::H264,
CodecName::H265RAM(_) | CodecName::H265VRAM => Self::H265,
}
}
}

View File

@@ -207,7 +207,7 @@ impl EncoderApi for VpxEncoder {
self.yuvfmt.clone()
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn input_texture(&self) -> bool {
false
}

View File

@@ -5,24 +5,24 @@ use std::{
};
use crate::{
codec::{base_bitrate, enable_gpucodec_option, EncoderApi, EncoderCfg, Quality},
codec::{base_bitrate, enable_vram_option, EncoderApi, EncoderCfg, Quality},
AdapterDevice, CodecFormat, CodecName, EncodeInput, EncodeYuvFormat, Pixfmt,
};
use gpucodec::gpu_common::{
self, Available, DecodeContext, DynamicContext, EncodeContext, FeatureContext, MAX_GOP,
};
use gpucodec::{
decode::{self, DecodeFrame, Decoder},
encode::{self, EncodeFrame, Encoder},
};
use hbb_common::{
allow_err,
anyhow::{anyhow, bail, Context},
bytes::Bytes,
log,
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
ResultType,
};
use hwcodec::{
common::{DataFormat, Driver, MAX_GOP},
native::{
decode::{self, DecodeFrame, Decoder},
encode::{self, EncodeFrame, Encoder},
Available, DecodeContext, DynamicContext, EncodeContext, FeatureContext,
},
};
const OUTPUT_SHARED_HANDLE: bool = false;
@@ -35,31 +35,31 @@ lazy_static::lazy_static! {
}
#[derive(Debug, Clone)]
pub struct GpuEncoderConfig {
pub struct VRamEncoderConfig {
pub device: AdapterDevice,
pub width: usize,
pub height: usize,
pub quality: Quality,
pub feature: gpucodec::gpu_common::FeatureContext,
pub feature: FeatureContext,
pub keyframe_interval: Option<usize>,
}
pub struct GpuEncoder {
pub struct VRamEncoder {
encoder: Encoder,
pub format: gpu_common::DataFormat,
pub format: DataFormat,
ctx: EncodeContext,
bitrate: u32,
last_frame_len: usize,
same_bad_len_counter: usize,
}
impl EncoderApi for GpuEncoder {
impl EncoderApi for VRamEncoder {
fn new(cfg: EncoderCfg, _i444: bool) -> ResultType<Self>
where
Self: Sized,
{
match cfg {
EncoderCfg::GPU(config) => {
EncoderCfg::VRAM(config) => {
let b = Self::convert_quality(config.quality, &config.feature);
let base_bitrate = base_bitrate(config.width as _, config.height as _);
let mut bitrate = base_bitrate * b / 100;
@@ -79,7 +79,7 @@ impl EncoderApi for GpuEncoder {
},
};
match Encoder::new(ctx.clone()) {
Ok(encoder) => Ok(GpuEncoder {
Ok(encoder) => Ok(VRamEncoder {
encoder,
ctx,
format: config.feature.data_format,
@@ -88,7 +88,7 @@ impl EncoderApi for GpuEncoder {
same_bad_len_counter: 0,
}),
Err(_) => {
hbb_common::config::GpucodecConfig::clear();
hbb_common::config::HwCodecConfig::clear_vram();
Err(anyhow!(format!("Failed to create encoder")))
}
}
@@ -138,8 +138,8 @@ impl EncoderApi for GpuEncoder {
..Default::default()
};
match self.format {
gpu_common::DataFormat::H264 => vf.set_h264s(frames),
gpu_common::DataFormat::H265 => vf.set_h265s(frames),
DataFormat::H264 => vf.set_h264s(frames),
DataFormat::H265 => vf.set_h265s(frames),
_ => bail!("{:?} not supported", self.format),
}
Ok(vf)
@@ -160,7 +160,7 @@ impl EncoderApi for GpuEncoder {
}
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn input_texture(&self) -> bool {
true
}
@@ -181,11 +181,11 @@ impl EncoderApi for GpuEncoder {
}
fn support_abr(&self) -> bool {
self.ctx.f.driver != gpu_common::EncodeDriver::VPL
self.ctx.f.driver != Driver::VPL
}
}
impl GpuEncoder {
impl VRamEncoder {
pub fn try_get(device: &AdapterDevice, name: CodecName) -> Option<FeatureContext> {
let v: Vec<_> = Self::available(name)
.drain(..)
@@ -201,12 +201,12 @@ impl GpuEncoder {
pub fn available(name: CodecName) -> Vec<FeatureContext> {
let not_use = ENOCDE_NOT_USE.lock().unwrap().clone();
if not_use.values().any(|not_use| *not_use) {
log::info!("currently not use gpucodec encoders: {not_use:?}");
log::info!("currently not use vram encoders: {not_use:?}");
return vec![];
}
let data_format = match name {
CodecName::H264GPU => gpu_common::DataFormat::H264,
CodecName::H265GPU => gpu_common::DataFormat::H265,
CodecName::H264VRAM => DataFormat::H264,
CodecName::H265VRAM => DataFormat::H265,
_ => return vec![],
};
let Ok(displays) = crate::Display::all() else {
@@ -252,27 +252,21 @@ impl GpuEncoder {
pub fn convert_quality(quality: Quality, f: &FeatureContext) -> u32 {
match quality {
Quality::Best => {
if f.driver == gpu_common::EncodeDriver::VPL
&& f.data_format == gpu_common::DataFormat::H264
{
if f.driver == Driver::VPL && f.data_format == DataFormat::H264 {
200
} else {
150
}
}
Quality::Balanced => {
if f.driver == gpu_common::EncodeDriver::VPL
&& f.data_format == gpu_common::DataFormat::H264
{
if f.driver == Driver::VPL && f.data_format == DataFormat::H264 {
150
} else {
100
}
}
Quality::Low => {
if f.driver == gpu_common::EncodeDriver::VPL
&& f.data_format == gpu_common::DataFormat::H264
{
if f.driver == Driver::VPL && f.data_format == DataFormat::H264 {
75
} else {
50
@@ -283,7 +277,7 @@ impl GpuEncoder {
}
pub fn set_not_use(display: usize, not_use: bool) {
log::info!("set display#{display} not use gpucodec encode to {not_use}");
log::info!("set display#{display} not use vram encode to {not_use}");
ENOCDE_NOT_USE.lock().unwrap().insert(display, not_use);
}
@@ -292,17 +286,11 @@ impl GpuEncoder {
}
}
pub struct GpuDecoder {
pub struct VRamDecoder {
decoder: Decoder,
}
#[derive(Default)]
pub struct GpuDecoders {
pub h264: Option<GpuDecoder>,
pub h265: Option<GpuDecoder>,
}
impl GpuDecoder {
impl VRamDecoder {
pub fn try_get(format: CodecFormat, luid: Option<i64>) -> Option<DecodeContext> {
let v: Vec<_> = Self::available(format, luid);
if v.len() > 0 {
@@ -315,8 +303,8 @@ impl GpuDecoder {
pub fn available(format: CodecFormat, luid: Option<i64>) -> Vec<DecodeContext> {
let luid = luid.unwrap_or_default();
let data_format = match format {
CodecFormat::H264 => gpu_common::DataFormat::H264,
CodecFormat::H265 => gpu_common::DataFormat::H265,
CodecFormat::H264 => DataFormat::H264,
CodecFormat::H265 => DataFormat::H265,
_ => return vec![],
};
get_available_config()
@@ -328,15 +316,13 @@ impl GpuDecoder {
}
pub fn possible_available_without_check() -> (bool, bool) {
if !enable_gpucodec_option() {
if !enable_vram_option() {
return (false, false);
}
let v = get_available_config().map(|c| c.d).unwrap_or_default();
(
v.iter()
.any(|d| d.data_format == gpu_common::DataFormat::H264),
v.iter()
.any(|d| d.data_format == gpu_common::DataFormat::H265),
v.iter().any(|d| d.data_format == DataFormat::H264),
v.iter().any(|d| d.data_format == DataFormat::H265),
)
}
@@ -346,7 +332,7 @@ impl GpuDecoder {
match Decoder::new(ctx) {
Ok(decoder) => Ok(Self { decoder }),
Err(_) => {
hbb_common::config::GpucodecConfig::clear();
hbb_common::config::HwCodecConfig::clear_vram();
Err(anyhow!(format!(
"Failed to create decoder, format: {:?}",
format
@@ -354,33 +340,33 @@ impl GpuDecoder {
}
}
}
pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<GpuDecoderImage>> {
pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<VRamDecoderImage>> {
match self.decoder.decode(data) {
Ok(v) => Ok(v.iter().map(|f| GpuDecoderImage { frame: f }).collect()),
Ok(v) => Ok(v.iter().map(|f| VRamDecoderImage { frame: f }).collect()),
Err(e) => Err(anyhow!(e)),
}
}
}
pub struct GpuDecoderImage<'a> {
pub struct VRamDecoderImage<'a> {
pub frame: &'a DecodeFrame,
}
impl GpuDecoderImage<'_> {}
impl VRamDecoderImage<'_> {}
fn get_available_config() -> ResultType<Available> {
let available = hbb_common::config::GpucodecConfig::load().available;
let available = hbb_common::config::HwCodecConfig::load().vram;
match Available::deserialize(&available) {
Ok(v) => Ok(v),
Err(_) => Err(anyhow!("Failed to deserialize:{}", available)),
}
}
pub fn check_available_gpucodec() {
pub(crate) fn check_available_vram() -> String {
let d = DynamicContext {
device: None,
width: 1920,
height: 1080,
width: 1280,
height: 720,
kbitrate: 5000,
framerate: 60,
gop: MAX_GOP as _,
@@ -391,54 +377,5 @@ pub fn check_available_gpucodec() {
e: encoders,
d: decoders,
};
if let Ok(available) = available.serialize() {
let mut config = hbb_common::config::GpucodecConfig::load();
config.available = available;
config.store();
return;
}
log::error!("Failed to serialize gpucodec");
}
pub fn gpucodec_new_check_process() {
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
std::thread::spawn(move || {
// Remove to avoid checking process errors
// But when the program is just started, the configuration file has not been updated, and the new connection will read an empty configuration
hbb_common::config::GpucodecConfig::clear();
if let Ok(exe) = std::env::current_exe() {
let arg = "--check-gpucodec-config";
if let Ok(mut child) = std::process::Command::new(exe).arg(arg).spawn() {
// wait up to 30 seconds
for _ in 0..30 {
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(Some(_)) = child.try_wait() {
break;
}
}
allow_err!(child.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
match child.try_wait() {
Ok(Some(status)) => {
log::info!("Check gpucodec config, exit with: {status}")
}
Ok(None) => {
log::info!(
"Check gpucodec config, status not ready yet, let's really wait"
);
let res = child.wait();
log::info!("Check gpucodec config, wait result: {res:?}");
}
Err(e) => {
log::error!("Check gpucodec config, error attempting to wait: {e}")
}
}
}
};
});
});
available.serialize().unwrap_or_default()
}

View File

@@ -185,7 +185,7 @@ impl Capturer {
self.gdi_capturer.take();
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
pub fn set_output_texture(&mut self, texture: bool) {
self.output_texture = texture;
}
@@ -620,7 +620,7 @@ impl Display {
)
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
pub fn adapter_luid(&self) -> Option<i64> {
unsafe {
if !self.adapter.is_null() {

143
res/devices.py Executable file
View File

@@ -0,0 +1,143 @@
#!/usr/bin/env python3
import requests
import argparse
from datetime import datetime, timedelta
def view(
url,
token,
id=None,
device_name=None,
user_name=None,
group_name=None,
offline_days=None,
):
headers = {"Authorization": f"Bearer {token}"}
pageSize = 30
params = {
"id": id,
"device_name": device_name,
"user_name": user_name,
"group_name": group_name,
}
params = {
k: "%" + v + "%" if (v != "-" and "%" not in v) else v
for k, v in params.items()
if v is not None
}
params["pageSize"] = pageSize
devices = []
current = 1
while True:
params["current"] = current
response = requests.get(f"{url}/api/devices", headers=headers, params=params)
response_json = response.json()
data = response_json.get("data", [])
for device in data:
if offline_days is None:
devices.append(device)
continue
last_online = datetime.strptime(
device["last_online"], "%Y-%m-%dT%H:%M:%S"
) # assuming date is in this format
if (datetime.utcnow() - last_online).days >= offline_days:
devices.append(device)
total = response_json.get("total", 0)
current += pageSize
if len(data) < pageSize or current > total:
break
return devices
def check(response):
if response.status_code == 200:
try:
response_json = response.json()
return response_json
except ValueError:
return response.text or "Success"
else:
return "Failed", response.status_code, response.text
def disable(url, token, guid, id):
print("Disable", id)
headers = {"Authorization": f"Bearer {token}"}
response = requests.post(f"{url}/api/devices/{guid}/disable", headers=headers)
return check(response)
def enable(url, token, guid, id):
print("Enable", id)
headers = {"Authorization": f"Bearer {token}"}
response = requests.post(f"{url}/api/devices/{guid}/enable", headers=headers)
return check(response)
def delete(url, token, guid, id):
print("Delete", id)
headers = {"Authorization": f"Bearer {token}"}
response = requests.delete(f"{url}/api/devices/{guid}", headers=headers)
return check(response)
def main():
parser = argparse.ArgumentParser(description="Device manager")
parser.add_argument(
"command",
choices=["view", "disable", "enable", "delete"],
help="Command to execute",
)
parser.add_argument("--url", required=True, help="URL of the API")
parser.add_argument(
"--token", required=True, help="Bearer token for authentication"
)
parser.add_argument("--id", help="Device ID")
parser.add_argument("--device_name", help="Device name")
parser.add_argument("--user_name", help="User name")
parser.add_argument("--group_name", help="Group name")
parser.add_argument(
"--offline_days", type=int, help="Offline duration in days, e.g., 7"
)
args = parser.parse_args()
devices = view(
args.url,
args.token,
args.id,
args.device_name,
args.user_name,
args.group_name,
args.offline_days,
)
if args.command == "view":
for device in devices:
print(device)
elif args.command == "disable":
for device in devices:
response = disable(args.url, args.token, device["guid"], device["id"])
print(response)
elif args.command == "enable":
for device in devices:
response = enable(args.url, args.token, device["guid"], device["id"])
print(response)
elif args.command == "delete":
for device in devices:
response = delete(args.url, args.token, device["guid"], device["id"])
print(response)
if __name__ == "__main__":
main()

7
res/msi/.gitignore vendored
View File

@@ -2,3 +2,10 @@
**/bin
**/obj
x64
packages
CustomActions/x64
CustomActions/*.user
CustomActions/*.filters

View File

@@ -0,0 +1,16 @@
#pragma once
#include <Windows.h>
#include <string>
bool AddFirewallRule(bool add, LPWSTR exeName, LPWSTR exeFile);
bool IsServiceRunningW(LPCWSTR serviceName);
bool MyCreateServiceW(LPCWSTR serviceName, LPCWSTR displayName, LPCWSTR binaryPath);
bool MyDeleteServiceW(LPCWSTR serviceName);
bool MyStartServiceW(LPCWSTR serviceName);
bool MyStopServiceW(LPCWSTR serviceName);
std::wstring ReadConfig(const std::wstring& filename, const std::wstring& key);
void UninstallDriver(LPCWSTR hardwareId, BOOL &rebootRequired);

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<!--
Use supportedRuntime tags to explicitly specify the version(s) of the .NET Framework runtime that
the custom action should run on. If no versions are specified, the chosen version of the runtime
will be the "best" match to what WixToolset.Dtf.CustomAction.dll was built against.
WARNING: leaving the version unspecified is dangerous as it introduces a risk of compatibility
problems with future versions of the .NET Framework runtime. It is highly recommended that you specify
only the version(s) of the .NET Framework runtime that you have tested against.
For more information https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/startup/startup-element
-->
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View File

@@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Runtime.InteropServices;
using WixToolset.Dtf.WindowsInstaller;
namespace CustomActions
{
public class CustomActions
{
[CustomAction]
public static ActionResult CustomActionHello(Session session)
{
try
{
session.Log("================= Example CustomAction Hello");
return ActionResult.Success;
}
catch (Exception e)
{
session.Log("An error occurred: " + e.Message);
return ActionResult.Failure;
}
}
[CustomAction]
public static ActionResult RunCommandAsSystem(Session session)
{
try
{
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c " + session["CMD"],
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
Verb = "runas"
};
using (Process process = Process.Start(psi))
{
process.WaitForExit();
}
return ActionResult.Success;
}
catch (Exception e)
{
session.Log("An error occurred: " + e.Message);
return ActionResult.Failure;
}
}
}
}

View File

@@ -0,0 +1,611 @@
// CustomAction.cpp : Defines the entry point for the custom action.
#include "pch.h"
#include <stdlib.h>
#include <strutil.h>
#include <shellapi.h>
#include <tlhelp32.h>
#include <winternl.h>
#include <netfw.h>
#include <shlwapi.h>
#include "./Common.h"
#pragma comment(lib, "Shlwapi.lib")
UINT __stdcall CustomActionHello(
__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CustomActionHello");
ExitOnFailure(hr, "Failed to initialize");
WcaLog(LOGMSG_STANDARD, "Initialized.");
// TODO: Add your custom action code here.
WcaLog(LOGMSG_STANDARD, "================= Example CustomAction Hello");
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall RemoveInstallFolder(
__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
int nResult = 0;
LPWSTR installFolder = NULL;
LPWSTR pwz = NULL;
LPWSTR pwzData = NULL;
hr = WcaInitialize(hInstall, "RemoveInstallFolder");
ExitOnFailure(hr, "Failed to initialize");
hr = WcaGetProperty(L"CustomActionData", &pwzData);
ExitOnFailure(hr, "failed to get CustomActionData");
pwz = pwzData;
hr = WcaReadStringFromCaData(&pwz, &installFolder);
ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
SHFILEOPSTRUCTW fileOp;
ZeroMemory(&fileOp, sizeof(SHFILEOPSTRUCT));
fileOp.wFunc = FO_DELETE;
fileOp.pFrom = installFolder;
fileOp.fFlags = FOF_NOCONFIRMATION | FOF_SILENT;
nResult = SHFileOperationW(&fileOp);
if (nResult == 0)
{
WcaLog(LOGMSG_STANDARD, "The directory \"%ls\" has been deleted.", installFolder);
}
else
{
WcaLog(LOGMSG_STANDARD, "The directory \"%ls\" has not been deleted, error code: 0X%02X. Please refer to https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shfileoperationa for the error codes.", installFolder, nResult);
}
LExit:
ReleaseStr(pwzData);
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess
// **NtQueryInformationProcess** may be altered or unavailable in future versions of Windows.
// Applications should use the alternate functions listed in this topic.
// But I do not find the alternate functions.
// https://github.com/heim-rs/heim/issues/105#issuecomment-683647573
typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
bool TerminateProcessIfNotContainsParam(pfnNtQueryInformationProcess NtQueryInformationProcess, HANDLE process, LPCWSTR excludeParam)
{
bool processClosed = false;
PROCESS_BASIC_INFORMATION processInfo;
NTSTATUS status = NtQueryInformationProcess(process, ProcessBasicInformation, &processInfo, sizeof(processInfo), NULL);
if (status == 0 && processInfo.PebBaseAddress != NULL)
{
PEB peb;
SIZE_T dwBytesRead;
if (ReadProcessMemory(process, processInfo.PebBaseAddress, &peb, sizeof(peb), &dwBytesRead))
{
RTL_USER_PROCESS_PARAMETERS pebUpp;
if (ReadProcessMemory(process,
peb.ProcessParameters,
&pebUpp,
sizeof(RTL_USER_PROCESS_PARAMETERS),
&dwBytesRead))
{
if (pebUpp.CommandLine.Length > 0)
{
WCHAR *commandLine = (WCHAR *)malloc(pebUpp.CommandLine.Length);
if (commandLine != NULL)
{
if (ReadProcessMemory(process, pebUpp.CommandLine.Buffer,
commandLine, pebUpp.CommandLine.Length, &dwBytesRead))
{
if (wcsstr(commandLine, excludeParam) == NULL)
{
WcaLog(LOGMSG_STANDARD, "Terminate process : %ls", commandLine);
TerminateProcess(process, 0);
processClosed = true;
}
}
free(commandLine);
}
}
}
}
}
return processClosed;
}
// Terminate processes that do not have parameter [excludeParam]
// Note. This function relies on "NtQueryInformationProcess",
// which may not be found.
// Then all processes of [processName] will be terminated.
bool TerminateProcessesByNameW(LPCWSTR processName, LPCWSTR excludeParam)
{
HMODULE hntdll = GetModuleHandleW(L"ntdll.dll");
if (hntdll == NULL)
{
WcaLog(LOGMSG_STANDARD, "Failed to load ntdll.");
}
pfnNtQueryInformationProcess NtQueryInformationProcess = NULL;
if (hntdll != NULL)
{
NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(
hntdll, "NtQueryInformationProcess");
}
if (NtQueryInformationProcess == NULL)
{
WcaLog(LOGMSG_STANDARD, "Failed to get address of NtQueryInformationProcess.");
}
bool processClosed = false;
// Create a snapshot of the current system processes
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32W processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32W);
if (Process32FirstW(snapshot, &processEntry))
{
do
{
if (lstrcmpW(processName, processEntry.szExeFile) == 0)
{
HANDLE process = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processEntry.th32ProcessID);
if (process != NULL)
{
if (NtQueryInformationProcess == NULL)
{
WcaLog(LOGMSG_STANDARD, "Terminate process : %ls, while NtQueryInformationProcess is NULL", processName);
TerminateProcess(process, 0);
processClosed = true;
}
else
{
processClosed = TerminateProcessIfNotContainsParam(
NtQueryInformationProcess,
process,
excludeParam);
}
CloseHandle(process);
}
}
} while (Process32NextW(snapshot, &processEntry));
}
CloseHandle(snapshot);
}
if (hntdll != NULL)
{
CloseHandle(hntdll);
}
return processClosed;
}
UINT __stdcall TerminateProcesses(
__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
int nResult = 0;
wchar_t szProcess[256] = {0};
DWORD cchProcess = sizeof(szProcess) / sizeof(szProcess[0]);
hr = WcaInitialize(hInstall, "TerminateProcesses");
ExitOnFailure(hr, "Failed to initialize");
MsiGetPropertyW(hInstall, L"TerminateProcesses", szProcess, &cchProcess);
WcaLog(LOGMSG_STANDARD, "Try terminate processes : %ls", szProcess);
TerminateProcessesByNameW(szProcess, L"--install");
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
// No use for now, it can be refer as an example of ShellExecuteW.
void AddFirewallRuleCmdline(LPWSTR exeName, LPWSTR exeFile, LPCWSTR dir)
{
HRESULT hr = S_OK;
HINSTANCE hi = 0;
WCHAR cmdline[1024] = { 0, };
WCHAR rulename[500] = { 0, };
StringCchPrintfW(rulename, sizeof(rulename) / sizeof(rulename[0]), L"%ls Service", exeName);
if (hr < 0) {
WcaLog(LOGMSG_STANDARD, "Failed to make rulename: %ls", exeName);
return;
}
StringCchPrintfW(cmdline, sizeof(cmdline) / sizeof(cmdline[0]), L"advfirewall firewall add rule name=\"%ls\" dir=%ls action=allow program=\"%ls\" enable=yes", rulename, dir, exeFile);
if (hr < 0) {
WcaLog(LOGMSG_STANDARD, "Failed to make cmdline: %ls", exeName);
return;
}
hi = ShellExecuteW(NULL, L"open", L"netsh", cmdline, NULL, SW_HIDE);
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
if ((int)hi <= 32) {
WcaLog(LOGMSG_STANDARD, "Failed to change firewall rule : %d, last error: %d", (int)hi, GetLastError());
}
else {
WcaLog(LOGMSG_STANDARD, "Firewall rule \"%ls\" (%ls) is added", rulename, dir);
}
}
// No use for now, it can be refer as an example of ShellExecuteW.
void RemoveFirewallRuleCmdline(LPWSTR exeName)
{
HRESULT hr = S_OK;
HINSTANCE hi = 0;
WCHAR cmdline[1024] = { 0, };
WCHAR rulename[500] = { 0, };
StringCchPrintfW(rulename, sizeof(rulename) / sizeof(rulename[0]), L"%ls Service", exeName);
if (hr < 0) {
WcaLog(LOGMSG_STANDARD, "Failed to make rulename: %ls", exeName);
return;
}
StringCchPrintfW(cmdline, sizeof(cmdline) / sizeof(cmdline[0]), L"advfirewall firewall delete rule name=\"%ls\"", rulename);
if (hr < 0) {
WcaLog(LOGMSG_STANDARD, "Failed to make cmdline: %ls", exeName);
return;
}
hi = ShellExecuteW(NULL, L"open", L"netsh", cmdline, NULL, SW_HIDE);
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
if ((int)hi <= 32) {
WcaLog(LOGMSG_STANDARD, "Failed to change firewall rule \"%ls\" : %d, last error: %d", rulename, (int)hi, GetLastError());
}
else {
WcaLog(LOGMSG_STANDARD, "Firewall rule \"%ls\" is removed", rulename);
}
}
UINT __stdcall AddFirewallRules(
__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
int nResult = 0;
LPWSTR exeFile = NULL;
LPWSTR exeName = NULL;
WCHAR exeNameNoExt[500] = { 0, };
LPWSTR pwz = NULL;
LPWSTR pwzData = NULL;
size_t szNameLen = 0;
hr = WcaInitialize(hInstall, "AddFirewallRules");
ExitOnFailure(hr, "Failed to initialize");
hr = WcaGetProperty(L"CustomActionData", &pwzData);
ExitOnFailure(hr, "failed to get CustomActionData");
pwz = pwzData;
hr = WcaReadStringFromCaData(&pwz, &exeFile);
ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
WcaLog(LOGMSG_STANDARD, "Try add firewall exceptions for file : %ls", exeFile);
exeName = PathFindFileNameW(exeFile + 1);
hr = StringCchPrintfW(exeNameNoExt, 500, exeName);
ExitOnFailure(hr, "Failed to copy exe name: %ls", exeName);
szNameLen = wcslen(exeNameNoExt);
if (szNameLen >= 4 && wcscmp(exeNameNoExt + szNameLen - 4, L".exe") == 0) {
exeNameNoExt[szNameLen - 4] = L'\0';
}
//if (exeFile[0] == L'1') {
// AddFirewallRuleCmdline(exeNameNoExt, exeFile, L"in");
// AddFirewallRuleCmdline(exeNameNoExt, exeFile, L"out");
//}
//else {
// RemoveFirewallRuleCmdline(exeNameNoExt);
//}
AddFirewallRule(exeFile[0] == L'1', exeNameNoExt, exeFile + 1);
LExit:
if (pwzData) {
ReleaseStr(pwzData);
}
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall SetPropertyIsServiceRunning(__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
wchar_t szAppName[500] = { 0 };
DWORD cchAppName = sizeof(szAppName) / sizeof(szAppName[0]);
wchar_t szPropertyName[500] = { 0 };
DWORD cchPropertyName = sizeof(szPropertyName) / sizeof(szPropertyName[0]);
bool isRunning = false;
hr = WcaInitialize(hInstall, "SetPropertyIsServiceRunning");
ExitOnFailure(hr, "Failed to initialize");
MsiGetPropertyW(hInstall, L"AppName", szAppName, &cchAppName);
WcaLog(LOGMSG_STANDARD, "Try query service of : \"%ls\"", szAppName);
MsiGetPropertyW(hInstall, L"PropertyName", szPropertyName, &cchPropertyName);
WcaLog(LOGMSG_STANDARD, "Try set is service running, property name : \"%ls\"", szPropertyName);
isRunning = IsServiceRunningW(szAppName);
MsiSetPropertyW(hInstall, szPropertyName, isRunning ? L"'N'" : L"'Y'");
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall CreateStartService(__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
LPWSTR svcParams = NULL;
LPWSTR pwz = NULL;
LPWSTR pwzData = NULL;
LPWSTR svcName = NULL;
LPWSTR svcBinary = NULL;
wchar_t szSvcDisplayName[500] = { 0 };
DWORD cchSvcDisplayName = sizeof(szSvcDisplayName) / sizeof(szSvcDisplayName[0]);
hr = WcaInitialize(hInstall, "CreateStartService");
ExitOnFailure(hr, "Failed to initialize");
hr = WcaGetProperty(L"CustomActionData", &pwzData);
ExitOnFailure(hr, "failed to get CustomActionData");
pwz = pwzData;
hr = WcaReadStringFromCaData(&pwz, &svcParams);
ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
WcaLog(LOGMSG_STANDARD, "Try create start service : %ls", svcParams);
svcName = svcParams;
svcBinary = wcschr(svcParams, L';');
if (svcBinary == NULL) {
WcaLog(LOGMSG_STANDARD, "Failed to find binary : %ls", svcParams);
goto LExit;
}
svcBinary[0] = L'\0';
svcBinary += 1;
hr = StringCchPrintfW(szSvcDisplayName, cchSvcDisplayName, L"%ls Service", svcName);
ExitOnFailure(hr, "Failed to compose a resource identifier string");
if (MyCreateServiceW(svcName, szSvcDisplayName, svcBinary)) {
WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is created.", svcName);
if (MyStartServiceW(svcName)) {
WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is started.", svcName);
}
else {
WcaLog(LOGMSG_STANDARD, "Failed to start service: \"%ls\"", svcName);
}
}
else {
WcaLog(LOGMSG_STANDARD, "Failed to create service: \"%ls\"", svcName);
}
LExit:
if (pwzData) {
ReleaseStr(pwzData);
}
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TryStopDeleteService(__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
int nResult = 0;
LPWSTR svcName = NULL;
LPWSTR pwz = NULL;
LPWSTR pwzData = NULL;
wchar_t szExeFile[500] = { 0 };
DWORD cchExeFile = sizeof(szExeFile) / sizeof(szExeFile[0]);
hr = WcaInitialize(hInstall, "TryStopDeleteService");
ExitOnFailure(hr, "Failed to initialize");
hr = WcaGetProperty(L"CustomActionData", &pwzData);
ExitOnFailure(hr, "failed to get CustomActionData");
pwz = pwzData;
hr = WcaReadStringFromCaData(&pwz, &svcName);
ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz);
WcaLog(LOGMSG_STANDARD, "Try stop and delete service : %ls", svcName);
if (MyStopServiceW(svcName)) {
for (int i = 0; i < 10; i++) {
if (IsServiceRunningW(svcName)) {
Sleep(100);
}
else {
break;
}
}
WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is stopped", svcName);
}
else {
WcaLog(LOGMSG_STANDARD, "Failed to stop service: \"%ls\"", svcName);
}
if (MyDeleteServiceW(svcName)) {
WcaLog(LOGMSG_STANDARD, "Service \"%ls\" is deleted", svcName);
}
else {
WcaLog(LOGMSG_STANDARD, "Failed to delete service: \"%ls\"", svcName);
}
// It's really strange that we need sleep here.
// But the upgrading may be stucked at "copying new files" because the file is in using.
// Steps to reproduce: Install -> stop service in tray --> start service -> upgrade
// Sleep(300);
// Or we can terminate the process
hr = StringCchPrintfW(szExeFile, cchExeFile, L"%ls.exe", svcName);
ExitOnFailure(hr, "Failed to compose a resource identifier string");
TerminateProcessesByNameW(szExeFile, L"--not-in-use");
LExit:
if (pwzData) {
ReleaseStr(pwzData);
}
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall TryDeleteStartupShortcut(__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
wchar_t szShortcut[500] = { 0 };
DWORD cchShortcut = sizeof(szShortcut) / sizeof(szShortcut[0]);
wchar_t szStartupDir[500] = { 0 };
DWORD cchStartupDir = sizeof(szStartupDir) / sizeof(szStartupDir[0]);
WCHAR pwszTemp[1024] = L"";
hr = WcaInitialize(hInstall, "DeleteStartupShortcut");
ExitOnFailure(hr, "Failed to initialize");
MsiGetPropertyW(hInstall, L"StartupFolder", szStartupDir, &cchStartupDir);
MsiGetPropertyW(hInstall, L"ShortcutName", szShortcut, &cchShortcut);
WcaLog(LOGMSG_STANDARD, "Try delete startup shortcut of : \"%ls\"", szShortcut);
hr = StringCchPrintfW(pwszTemp, 1024, L"%ls%ls.lnk", szStartupDir, szShortcut);
ExitOnFailure(hr, "Failed to compose a resource identifier string");
if (DeleteFileW(pwszTemp)) {
WcaLog(LOGMSG_STANDARD, "Failed to delete startup shortcut of : \"%ls\"", pwszTemp);
}
else {
WcaLog(LOGMSG_STANDARD, "Startup shortcut is deleted : \"%ls\"", pwszTemp);
}
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall SetPropertyFromConfig(__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
wchar_t szConfigFile[1024] = { 0 };
DWORD cchConfigFile = sizeof(szConfigFile) / sizeof(szConfigFile[0]);
wchar_t szConfigKey[500] = { 0 };
DWORD cchConfigKey = sizeof(szConfigKey) / sizeof(szConfigKey[0]);
wchar_t szPropertyName[500] = { 0 };
DWORD cchPropertyName = sizeof(szPropertyName) / sizeof(szPropertyName[0]);
std::wstring configValue;
hr = WcaInitialize(hInstall, "SetPropertyFromConfig");
ExitOnFailure(hr, "Failed to initialize");
MsiGetPropertyW(hInstall, L"ConfigFile", szConfigFile, &cchConfigFile);
WcaLog(LOGMSG_STANDARD, "Try read config file of : \"%ls\"", szConfigFile);
MsiGetPropertyW(hInstall, L"ConfigKey", szConfigKey, &cchConfigKey);
WcaLog(LOGMSG_STANDARD, "Try read configuration, config key : \"%ls\"", szConfigKey);
MsiGetPropertyW(hInstall, L"PropertyName", szPropertyName, &cchPropertyName);
WcaLog(LOGMSG_STANDARD, "Try read configuration, property name : \"%ls\"", szPropertyName);
configValue = ReadConfig(szConfigFile, szConfigKey);
MsiSetPropertyW(hInstall, szPropertyName, configValue.c_str());
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall AddRegSoftwareSASGeneration(__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
LSTATUS result = 0;
HKEY hKey;
LPCWSTR subKey = L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
LPCWSTR valueName = L"SoftwareSASGeneration";
DWORD valueType = REG_DWORD;
DWORD valueData = 1;
DWORD valueDataSize = sizeof(DWORD);
HINSTANCE hi = 0;
hr = WcaInitialize(hInstall, "AddRegSoftwareSASGeneration");
ExitOnFailure(hr, "Failed to initialize");
hi = ShellExecuteW(NULL, L"open", L"reg", L" add HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /f /v SoftwareSASGeneration /t REG_DWORD /d 1", NULL, SW_HIDE);
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
if ((int)hi <= 32) {
WcaLog(LOGMSG_STANDARD, "Failed to add registry name \"%ls\", %d, %d", valueName, (int)hi, GetLastError());
}
else {
WcaLog(LOGMSG_STANDARD, "Registry name \"%ls\" is added", valueName);
}
// Why RegSetValueExW always return 998?
//
result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, subKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
if (result != ERROR_SUCCESS) {
WcaLog(LOGMSG_STANDARD, "Failed to create or open registry key: %d", result);
goto LExit;
}
result = RegSetValueExW(hKey, valueName, 0, valueType, reinterpret_cast<const BYTE*>(valueData), valueDataSize);
if (result != ERROR_SUCCESS) {
WcaLog(LOGMSG_STANDARD, "Failed to set registry value: %d", result);
RegCloseKey(hKey);
goto LExit;
}
WcaLog(LOGMSG_STANDARD, "Registry value has been successfully set.");
RegCloseKey(hKey);
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
UINT __stdcall RemoveAmyuniIdd(
__in MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
DWORD er = ERROR_SUCCESS;
BOOL rebootRequired = FALSE;
hr = WcaInitialize(hInstall, "RemoveAmyuniIdd");
ExitOnFailure(hr, "Failed to initialize");
UninstallDriver(L"usbmmidd", rebootRequired);
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}

View File

@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<Configurations>Release</Configurations>
</PropertyGroup>
<ItemGroup>
<Content Include="CustomAction.config" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="WixToolset.Dtf.CustomAction" Version="4.0.5" />
<PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.5" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,14 @@
LIBRARY "CustomActions"
EXPORTS
CustomActionHello
RemoveInstallFolder
TerminateProcesses
AddFirewallRules
SetPropertyIsServiceRunning
TryStopDeleteService
CreateStartService
TryDeleteStartupShortcut
SetPropertyFromConfig
AddRegSoftwareSASGeneration
RemoveAmyuniIdd

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props" Condition="Exists('..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props')" />
<Import Project="..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props" Condition="Exists('..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{6b3647e0-b4a3-46ae-8757-a22ee51c1dac}</ProjectGuid>
<RootNamespace>CustomActions</RootNamespace>
<PlatformToolset>v143</PlatformToolset>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;EXAMPLECADLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalDependencies>msi.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>CustomActions.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Common.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="CustomActions.cpp" />
<ClCompile Include="DeviceUtils.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="FirewallRules.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ReadConfig.cpp" />
<ClCompile Include="ServiceUtils.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="CustomActions.def" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props'))" />
<Error Condition="!Exists('..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props'))" />
</Target>
</Project>

View File

@@ -0,0 +1,84 @@
#include "pch.h"
#include <Windows.h>
#include <setupapi.h>
#include <devguid.h>
#include <cfgmgr32.h>
#pragma comment(lib, "SetupAPI.lib")
void UninstallDriver(LPCWSTR hardwareId, BOOL &rebootRequired)
{
HDEVINFO deviceInfoSet = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, NULL, NULL, DIGCF_PRESENT);
if (deviceInfoSet == INVALID_HANDLE_VALUE)
{
WcaLog(LOGMSG_STANDARD, "Failed to get device information set, last error: %d", GetLastError());
return;
}
SP_DEVINFO_LIST_DETAIL_DATA devInfoListDetail;
devInfoListDetail.cbSize = sizeof(SP_DEVINFO_LIST_DETAIL_DATA);
if (!SetupDiGetDeviceInfoListDetailW(deviceInfoSet, &devInfoListDetail))
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
WcaLog(LOGMSG_STANDARD, "Failed to call SetupDiGetDeviceInfoListDetail, last error: %d", GetLastError());
return;
}
SP_DEVINFO_DATA deviceInfoData;
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
DWORD dataType;
WCHAR deviceId[MAX_DEVICE_ID_LEN] = { 0, };
DWORD deviceIndex = 0;
while (SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex, &deviceInfoData))
{
if (!SetupDiGetDeviceRegistryPropertyW(deviceInfoSet, &deviceInfoData, SPDRP_HARDWAREID, &dataType, (PBYTE)deviceId, MAX_DEVICE_ID_LEN, NULL))
{
WcaLog(LOGMSG_STANDARD, "Failed to get hardware id, last error: %d", GetLastError());
deviceIndex++;
continue;
}
if (wcscmp(deviceId, hardwareId) != 0)
{
deviceIndex++;
continue;
}
SP_REMOVEDEVICE_PARAMS remove_device_params;
remove_device_params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
remove_device_params.ClassInstallHeader.InstallFunction = DIF_REMOVE;
remove_device_params.Scope = DI_REMOVEDEVICE_GLOBAL;
remove_device_params.HwProfile = 0;
if (!SetupDiSetClassInstallParamsW(deviceInfoSet, &deviceInfoData, &remove_device_params.ClassInstallHeader, sizeof(SP_REMOVEDEVICE_PARAMS)))
{
WcaLog(LOGMSG_STANDARD, "Failed to set class install params, last error: %d", GetLastError());
deviceIndex++;
continue;
}
if (!SetupDiCallClassInstaller(DIF_REMOVE, deviceInfoSet, &deviceInfoData))
{
WcaLog(LOGMSG_STANDARD, "ailed to uninstall driver, last error: %d", GetLastError());
deviceIndex++;
continue;
}
SP_DEVINSTALL_PARAMS deviceParams;
if (SetupDiGetDeviceInstallParamsW(deviceInfoSet, &deviceInfoData, &deviceParams))
{
if (deviceParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT))
{
rebootRequired = true;
}
}
WcaLog(LOGMSG_STANDARD, "Driver uninstalled successfully");
deviceIndex++;
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}

View File

@@ -0,0 +1,413 @@
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ics/c-adding-an-application-rule-edge-traversal
/********************************************************************
Copyright (C) Microsoft. All Rights Reserved.
Abstract:
This C++ file includes sample code that adds a firewall rule with
EdgeTraversalOptions (one of the EdgeTraversalOptions values).
********************************************************************/
#include "pch.h"
#include <windows.h>
#include <stdio.h>
#include <netfw.h>
#include <strsafe.h>
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
#define STRING_BUFFER_SIZE 500
// Forward declarations
HRESULT WFCOMInitialize(INetFwPolicy2** ppNetFwPolicy2);
void WFCOMCleanup(INetFwPolicy2* pNetFwPolicy2);
HRESULT RemoveFirewallRule(
__in INetFwPolicy2* pNetFwPolicy2,
__in LPWSTR exeName);
HRESULT AddFirewallRuleWithEdgeTraversal(__in INetFwPolicy2* pNetFwPolicy2,
__in bool in,
__in LPWSTR exeName,
__in LPWSTR exeFile);
bool AddFirewallRule(bool add, LPWSTR exeName, LPWSTR exeFile)
{
bool result = false;
HRESULT hrComInit = S_OK;
HRESULT hr = S_OK;
INetFwPolicy2* pNetFwPolicy2 = NULL;
// Initialize COM.
hrComInit = CoInitializeEx(
0,
COINIT_APARTMENTTHREADED
);
// Ignore RPC_E_CHANGED_MODE; this just means that COM has already been
// initialized with a different mode. Since we don't care what the mode is,
// we'll just use the existing mode.
if (hrComInit != RPC_E_CHANGED_MODE)
{
if (FAILED(hrComInit))
{
WcaLog(LOGMSG_STANDARD, "CoInitializeEx failed: 0x%08lx\n", hrComInit);
goto Cleanup;
}
}
// Retrieve INetFwPolicy2
hr = WFCOMInitialize(&pNetFwPolicy2);
if (FAILED(hr))
{
goto Cleanup;
}
if (add) {
// Add firewall rule with EdgeTraversalOption=DeferApp (Windows7+) if available
// else add with Edge=True (Vista and Server 2008).
hr = AddFirewallRuleWithEdgeTraversal(pNetFwPolicy2, true, exeName, exeFile);
hr = AddFirewallRuleWithEdgeTraversal(pNetFwPolicy2, false, exeName, exeFile);
}
else {
hr = RemoveFirewallRule(pNetFwPolicy2, exeName);
}
result = SUCCEEDED(hr);
Cleanup:
// Release INetFwPolicy2
WFCOMCleanup(pNetFwPolicy2);
// Uninitialize COM.
if (SUCCEEDED(hrComInit))
{
CoUninitialize();
}
return result;
}
BSTR MakeRuleName(__in LPWSTR exeName)
{
WCHAR pwszTemp[STRING_BUFFER_SIZE] = L"";
HRESULT hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"%ls Service", exeName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to compose a resource identifier string: 0x%08lx\n", hr);
return NULL;
}
return SysAllocString(pwszTemp);
}
HRESULT RemoveFirewallRule(
__in INetFwPolicy2* pNetFwPolicy2,
__in LPWSTR exeName)
{
HRESULT hr = S_OK;
INetFwRules* pNetFwRules = NULL;
WCHAR pwszTemp[STRING_BUFFER_SIZE] = L"";
BSTR RuleName = NULL;
RuleName = MakeRuleName(exeName);
if (NULL == RuleName)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
hr = pNetFwPolicy2->get_Rules(&pNetFwRules);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to retrieve firewall rules collection : 0x%08lx\n", hr);
goto Cleanup;
}
// We need to "Remove()" twice, because both "in" and "out" rules are added?
// There's no remarks for this case https://learn.microsoft.com/en-us/windows/win32/api/netfw/nf-netfw-inetfwrules-remove
hr = pNetFwRules->Remove(RuleName);
hr = pNetFwRules->Remove(RuleName);
if (FAILED(hr)) {
WcaLog(LOGMSG_STANDARD, "Failed to remove firewall rule \"%ls\" : 0x%08lx\n", exeName, hr);
}
else {
WcaLog(LOGMSG_STANDARD, "Firewall rule \"%ls\" is removed\n", exeName);
}
Cleanup:
SysFreeString(RuleName);
if (pNetFwRules != NULL)
{
pNetFwRules->Release();
}
return hr;
}
// Add firewall rule with EdgeTraversalOption=DeferApp (Windows7+) if available
// else add with Edge=True (Vista and Server 2008).
HRESULT AddFirewallRuleWithEdgeTraversal(
__in INetFwPolicy2* pNetFwPolicy2,
__in bool in,
__in LPWSTR exeName,
__in LPWSTR exeFile)
{
HRESULT hr = S_OK;
INetFwRules* pNetFwRules = NULL;
INetFwRule* pNetFwRule = NULL;
INetFwRule2* pNetFwRule2 = NULL;
WCHAR pwszTemp[STRING_BUFFER_SIZE] = L"";
BSTR RuleName = NULL;
BSTR RuleGroupName = NULL;
BSTR RuleDescription = NULL;
BSTR RuleAppPath = NULL;
long CurrentProfilesBitMask = 0;
// For localization purposes, the rule name, description, and group can be
// provided as indirect strings. These indirect strings can be defined in an rc file.
// Examples of the indirect string definitions in the rc file -
// 127 "EdgeTraversalOptions Sample Application"
// 128 "Allow inbound TCP traffic to application EdgeTraversalOptions.exe"
// 129 "Allow EdgeTraversalOptions.exe to receive inbound traffic for TCP protocol
// from remote machines located within your network as well as from
// the Internet (i.e from outside of your Edge device like Firewall or NAT"
// Examples of using indirect strings -
// hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"@EdgeTraversalOptions.exe,-128");
RuleName = MakeRuleName(exeName);
if (NULL == RuleName)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
// Examples of using indirect strings -
// hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"@EdgeTraversalOptions.exe,-127");
hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, exeName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to compose a resource identifier string: 0x%08lx\n", hr);
goto Cleanup;
}
RuleGroupName = SysAllocString(pwszTemp); // Used for grouping together multiple rules
if (NULL == RuleGroupName)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
// Examples of using indirect strings -
// hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"@EdgeTraversalOptions.exe,-129");
hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"Allow %ls to receive \
inbound traffic from remote machines located within your network as well as \
from the Internet", exeName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to compose a resource identifier string: 0x%08lx\n", hr);
goto Cleanup;
}
RuleDescription = SysAllocString(pwszTemp);
if (NULL == RuleDescription)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
RuleAppPath = SysAllocString(exeFile);
if (NULL == RuleAppPath)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
hr = pNetFwPolicy2->get_Rules(&pNetFwRules);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to retrieve firewall rules collection : 0x%08lx\n", hr);
goto Cleanup;
}
hr = CoCreateInstance(
__uuidof(NetFwRule), //CLSID of the class whose object is to be created
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(INetFwRule), // Identifier of the Interface used for communicating with the object
(void**)&pNetFwRule);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "CoCreateInstance for INetFwRule failed: 0x%08lx\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Name(RuleName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Name failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Grouping(RuleGroupName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Grouping failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Description(RuleDescription);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Description failed with error: 0x %x.\n", hr);
goto Cleanup;
}
// If you want the rule to avoid public, you can refer to
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ics/c-adding-an-outbound-rule
CurrentProfilesBitMask = NET_FW_PROFILE2_ALL;
hr = pNetFwRule->put_Direction(in ? NET_FW_RULE_DIR_IN : NET_FW_RULE_DIR_OUT);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Direction failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Action(NET_FW_ACTION_ALLOW);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Action failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_ApplicationName(RuleAppPath);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_ApplicationName failed with error: 0x %x.\n", hr);
goto Cleanup;
}
//hr = pNetFwRule->put_Protocol(6); // TCP
//if (FAILED(hr))
//{
// WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Protocol failed with error: 0x %x.\n", hr);
// goto Cleanup;
//}
hr = pNetFwRule->put_Profiles(CurrentProfilesBitMask);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Profiles failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Enabled(VARIANT_TRUE);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Enabled failed with error: 0x %x.\n", hr);
goto Cleanup;
}
if (in) {
// Check if INetFwRule2 interface is available (i.e Windows7+)
// If supported, then use EdgeTraversalOptions
// Else use the EdgeTraversal boolean flag.
if (SUCCEEDED(pNetFwRule->QueryInterface(__uuidof(INetFwRule2), (void**)&pNetFwRule2)))
{
hr = pNetFwRule2->put_EdgeTraversalOptions(NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_APP);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_EdgeTraversalOptions failed with error: 0x %x.\n", hr);
goto Cleanup;
}
}
else
{
hr = pNetFwRule->put_EdgeTraversal(VARIANT_TRUE);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_EdgeTraversal failed with error: 0x %x.\n", hr);
goto Cleanup;
}
}
}
hr = pNetFwRules->Add(pNetFwRule);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to add firewall rule to the firewall rules collection : 0x%08lx\n", hr);
goto Cleanup;
}
WcaLog(LOGMSG_STANDARD, "Successfully added firewall rule !\n");
Cleanup:
SysFreeString(RuleName);
SysFreeString(RuleGroupName);
SysFreeString(RuleDescription);
SysFreeString(RuleAppPath);
if (pNetFwRule2 != NULL)
{
pNetFwRule2->Release();
}
if (pNetFwRule != NULL)
{
pNetFwRule->Release();
}
if (pNetFwRules != NULL)
{
pNetFwRules->Release();
}
return hr;
}
// Instantiate INetFwPolicy2
HRESULT WFCOMInitialize(INetFwPolicy2** ppNetFwPolicy2)
{
HRESULT hr = S_OK;
hr = CoCreateInstance(
__uuidof(NetFwPolicy2),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(INetFwPolicy2),
(void**)ppNetFwPolicy2);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "CoCreateInstance for INetFwPolicy2 failed: 0x%08lx\n", hr);
goto Cleanup;
}
Cleanup:
return hr;
}
// Release INetFwPolicy2
void WFCOMCleanup(INetFwPolicy2* pNetFwPolicy2)
{
// Release the INetFwPolicy2 object (Vista+)
if (pNetFwPolicy2 != NULL)
{
pNetFwPolicy2->Release();
}
}

View File

@@ -0,0 +1,36 @@
#include "pch.h"
#include <iostream>
#include <fstream>
#include <string>
#include <cwctype>
void trim(std::wstring& str) {
str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](wchar_t ch) {
return !std::iswspace(ch);
}));
str.erase(std::find_if(str.rbegin(), str.rend(), [](wchar_t ch) {
return !std::iswspace(ch);
}).base(), str.end());
}
std::wstring ReadConfig(const std::wstring& filename, const std::wstring& key)
{
std::wstring configValue;
std::wstring line;
std::wifstream file(filename);
while (std::getline(file, line)) {
trim(line);
if (line.find(key) == 0) {
std::size_t position = line.find(L"=", key.size());
if (position != std::string::npos) {
configValue = line.substr(position + 1);
trim(configValue);
break;
}
}
}
file.close();
return configValue;
}

View File

@@ -0,0 +1,173 @@
// https://learn.microsoft.com/en-us/windows/win32/services/installing-a-service
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <strsafe.h>
bool MyCreateServiceW(LPCWSTR serviceName, LPCWSTR displayName, LPCWSTR binaryPath)
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
// Get a handle to the SCM database.
schSCManager = OpenSCManagerW(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
WcaLog(LOGMSG_STANDARD, "OpenSCManager failed (%d)\n", GetLastError());
return false;
}
// Create the service
schService = CreateServiceW(
schSCManager, // SCM database
serviceName, // name of service
displayName, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // start type
SERVICE_ERROR_NORMAL, // error control type
binaryPath, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
if (schService == NULL)
{
WcaLog(LOGMSG_STANDARD, "CreateService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return false;
}
else
{
WcaLog(LOGMSG_STANDARD, "Service installed successfully\n");
}
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return true;
}
bool MyDeleteServiceW(LPCWSTR serviceName)
{
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
if (hSCManager == NULL) {
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager");
return false;
}
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_STOP | DELETE);
if (hService == NULL) {
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls", serviceName);
CloseServiceHandle(hSCManager);
return false;
}
SERVICE_STATUS serviceStatus;
if (ControlService(hService, SERVICE_CONTROL_STOP, &serviceStatus)) {
WcaLog(LOGMSG_STANDARD, "Stopping service: %ls", serviceName);
}
bool success = DeleteService(hService);
if (!success) {
WcaLog(LOGMSG_STANDARD, "Failed to delete service: %ls", serviceName);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return success;
}
bool MyStartServiceW(LPCWSTR serviceName)
{
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
if (hSCManager == NULL) {
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager");
return false;
}
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_START);
if (hService == NULL) {
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls", serviceName);
CloseServiceHandle(hSCManager);
return false;
}
bool success = StartServiceW(hService, 0, NULL);
if (!success) {
WcaLog(LOGMSG_STANDARD, "Failed to start service: %ls", serviceName);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return success;
}
bool MyStopServiceW(LPCWSTR serviceName)
{
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
if (hSCManager == NULL) {
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager");
return false;
}
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_STOP);
if (hService == NULL) {
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls", serviceName);
CloseServiceHandle(hSCManager);
return false;
}
SERVICE_STATUS serviceStatus;
if (!ControlService(hService, SERVICE_CONTROL_STOP, &serviceStatus)) {
WcaLog(LOGMSG_STANDARD, "Failed to stop service: %ls", serviceName);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return false;
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return true;
}
bool IsServiceRunningW(LPCWSTR serviceName)
{
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
if (hSCManager == NULL) {
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager");
return false;
}
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_QUERY_STATUS);
if (hService == NULL) {
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls", serviceName);
CloseServiceHandle(hSCManager);
return false;
}
SERVICE_STATUS_PROCESS serviceStatus;
DWORD bytesNeeded;
if (!QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, reinterpret_cast<LPBYTE>(&serviceStatus), sizeof(serviceStatus), &bytesNeeded)) {
WcaLog(LOGMSG_STANDARD, "Failed to query service: %ls", serviceName);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return false;
}
bool isRunning = (serviceStatus.dwCurrentState == SERVICE_RUNNING);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return isRunning;
}

View File

@@ -0,0 +1,26 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
BOOL APIENTRY DllMain(
__in HMODULE hModule,
__in DWORD ulReasonForCall,
__in LPVOID
)
{
switch (ulReasonForCall)
{
case DLL_PROCESS_ATTACH:
WcaGlobalInitialize(hModule);
break;
case DLL_PROCESS_DETACH:
WcaGlobalFinalize();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
#include <strsafe.h>
#include <msiquery.h>
// WiX Header Files:
#include <wcautil.h>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="WixToolset.DUtil" version="4.0.5" targetFramework="native" />
<package id="WixToolset.WcaUtil" version="4.0.5" targetFramework="native" />
</packages>

View File

@@ -0,0 +1,5 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

View File

@@ -0,0 +1,13 @@
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
// add headers that you want to pre-compile here
#include "framework.h"
#endif //PCH_H

View File

@@ -14,7 +14,7 @@
<Component Id="Product.Registry.DefaultIcon" Guid="6DBF2690-0955-4C6A-940F-634DDA503F49">
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\DefaultIcon">
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product)",0' />
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product).exe",0' />
</RegistryKey>
</Component>
@@ -22,7 +22,7 @@
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell" />
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell\open" />
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell\open\command">
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product)" "--play" "%1"' />
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product).exe" --play "%1"' />
</RegistryKey>
</Component>
@@ -36,10 +36,20 @@
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell" />
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell\open" />
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell\open\command">
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product)" "%1"' />
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product).exe" "%1"' />
</RegistryKey>
</Component>
<!--For compatibility with registry values from previous versions-->
<Component Id="Product.Registry.UninstallApp" Guid="FC1A3D2E-5642-FBD8-CFA6-5ECAC6DE69A8">
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\$(var.Product)" >
<RegistryValue Type="string" Name="BuildDate" Value="$(var.BuildDate)" />
<RegistryValue Type="string" Name="share_rdp" Value="" />
<!--$ArpStart$-->
<!--$ArpEnd$-->
</RegistryKey>
</Component>
</DirectoryRef>
</Fragment>

View File

@@ -5,87 +5,125 @@
<?include ../Includes.wxi?>
<DirectoryRef Id="INSTALLFOLDER" FileSource="$(var.BuildDir)">
<Component Id="RustDesk.exe" Guid="620F0F69-4C17-4320-A619-495E329712A4">
<File Id="$(var.Product).exe" Name="$(var.Product).exe" KeyPath="yes" Checksum="yes">
<fire:FirewallException Id="RustDeskExTCPDom" Name="$(var.Product) TCP Domain" Profile="domain" Protocol="tcp" Scope="any" IgnoreFailure="yes" />
<fire:FirewallException Id="RustDeskExTCPPriv" Name="$(var.Product) TCP Private" Profile="private" Protocol="tcp" Scope="any" IgnoreFailure="yes" />
<fire:FirewallException Id="RustDeskExUDPDom" Name="$(var.Product) UDP Domain" Profile="domain" Protocol="udp" Scope="any" IgnoreFailure="yes" />
<fire:FirewallException Id="RustDeskExUDPPriv" Name="$(var.Product) UDP Private" Profile="private" Protocol="udp" Scope="any" IgnoreFailure="yes" />
<Component Id="App.exe" Guid="620F0F69-4C17-4320-A619-495E329712A4">
<File Id="App.exe" Name="$(var.Product).exe" KeyPath="yes" Checksum="yes">
<!--<fire:FirewallException Id="AppEx" Name="$(var.Product) Service" Scope="any" IgnoreFailure="yes" />-->
</File>
<ServiceInstall Id="ServiceInstaller" Type="ownProcess" Vital="yes" Name="$(var.Product)" DisplayName="!(loc.Service_DisplayName)" Description="!(loc.Service_Description)" Start="auto" Account="LocalSystem" ErrorControl="ignore" Interactive="no" Arguments="--service" />
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="$(var.Product)" Wait="yes" />
</Component>
</DirectoryRef>
<SetProperty Id="RestartService" Value="&quot;net&quot; start $(var.Product)" Before="RestartService" Sequence="execute" />
<CustomAction Id="RestartService" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
<CustomAction Id="RemoveInstallFolder.SetParam" Return="check" Property="RemoveInstallFolder" Value="[INSTALLFOLDER]" />
<CustomAction Id="AddFirewallRules.SetParam" Return="check" Property="AddFirewallRules" Value="1[INSTALLFOLDER]$(var.Product).exe" />
<CustomAction Id="RemoveFirewallRules.SetParam" Return="check" Property="RemoveFirewallRules" Value="0[INSTALLFOLDER]$(var.Product).exe" />
<CustomAction Id="CreateStartService.SetParam" Return="check" Property="CreateStartService" Value="$(var.Product);&quot;[INSTALLFOLDER]$(var.Product).exe&quot; --service" />
<CustomAction Id="TryStopDeleteService.SetParam" Return="check" Property="TryStopDeleteService" Value="$(var.Product)" />
<CustomAction Id="LaunchApp" ExeCommand="" Return="asyncNoWait" FileRef="App.exe" />
<CustomAction Id="LaunchAppTray" ExeCommand=" --tray" Return="asyncNoWait" FileRef="App.exe" />
<Property Id="TerminateProcesses" Value="AppTest.exe" />
<CustomAction Id="TerminateProcesses.SetParam" Return="check" Property="TerminateProcesses" Value="$(var.Product).exe" />
<CustomAction Id="TerminateBrokers.SetParam" Return="check" Property="TerminateProcesses" Value="RuntimeBroker_rustdesk.exe" />
<CustomAction Id="SetPropertyIsServiceRunning.SetParam.AppName" Return="check" Property="AppName" Value="$(var.Product)" />
<CustomAction Id="SetPropertyIsServiceRunning.SetParam.PropertyName" Return="check" Property="PropertyName" Value="STOP_SERVICE" />
<CustomAction Id="SetPropertyServiceStop.SetParam.ConfigFile" Return="check" Property="ConfigFile" Value="[AppDataFolder]$(var.Product)\config\$(var.Product)2.toml" />
<CustomAction Id="SetPropertyServiceStop.SetParam.ConfigKey" Return="check" Property="ConfigKey" Value="stop-service" />
<CustomAction Id="SetPropertyServiceStop.SetParam.PropertyName" Return="check" Property="PropertyName" Value="STOP_SERVICE" />
<CustomAction Id="TryDeleteStartupShortcut.SetParam" Return="check" Property="ShortcutName" Value="$(var.Product) Tray" />
<InstallExecuteSequence>
<!--The ServiceControl element above handles starting/stopping the server on install/uninstall,
however it also needs to be restarted after a modify or repair install. This action does
that provided the server feature is already installed and not being uninstalled.-->
<Custom Action="SetPropertyIsServiceRunning" After="InstallInitialize" Condition="Installed" />
<Custom Action="SetPropertyIsServiceRunning.SetParam.AppName" Before="SetPropertyIsServiceRunning" Condition="Installed" />
<Custom Action="SetPropertyIsServiceRunning.SetParam.PropertyName" Before="SetPropertyIsServiceRunning" Condition="Installed" />
<Custom Action="RestartService" Before="InstallFinalize" />
<Custom Action="SetPropertyServiceStop" After="InstallInitialize" Condition="NOT Installed" />
<Custom Action="SetPropertyServiceStop.SetParam.ConfigFile" Before="SetPropertyServiceStop" Condition="NOT Installed" />
<Custom Action="SetPropertyServiceStop.SetParam.ConfigKey" Before="SetPropertyServiceStop" Condition="NOT Installed" />
<Custom Action="SetPropertyServiceStop.SetParam.PropertyName" Before="SetPropertyServiceStop" Condition="NOT Installed" />
<Custom Action="CreateStartService" Before="InstallFinalize" Condition="NOT STOP_SERVICE=&quot;&apos;Y&apos;&quot;" />
<Custom Action="CreateStartService.SetParam" Before="CreateStartService" Condition="NOT STOP_SERVICE=&quot;&apos;Y&apos;&quot;" />
<Custom Action="CustomActionHello" Before="InstallFinalize" />
<Custom Action="SetCmdRemoveDir" After="RemoveFiles"/>
<Custom Action="RunCommandAsSystem" After="SetCmdRemoveDir"/>
<!--Shortcut is in InstallValidate section. So we just let it be created, then try delete if stopping service.-->
<Custom Action="TryDeleteStartupShortcut" After="InstallFinalize" Condition="STOP_SERVICE=&quot;&apos;Y&apos;&quot;" />
<Custom Action="TryDeleteStartupShortcut.SetParam" Before="SetPropertyIsServiceRunning" Condition="STOP_SERVICE=&quot;&apos;Y&apos;&quot;" />
<!-- Launch ClientLauncher if installing or already installed and not uninstalling -->
<Custom Action="LaunchApp" After="InstallFinalize" />
<Custom Action="LaunchAppTray" After="InstallFinalize" Condition="NOT STOP_SERVICE=&quot;&apos;Y&apos;&quot;"/>
<!--Workaround of "fire:FirewallException". If Outbound="Yes" or Outbound="true", the following error occurs.-->
<!--ExecFirewallExceptions: Error 0x80070057: failed to add app to the authorized apps list-->
<Custom Action="AddFirewallRules" Before="InstallFinalize"/>
<Custom Action="AddFirewallRules.SetParam" Before="AddFirewallRules" Condition="NOT Installed"/>
<Custom Action="AddRegSoftwareSASGeneration" Before="InstallFinalize" />
<Custom Action="RemoveInstallFolder" Before="RemoveFiles" Condition="Installed AND NOT UPGRADINGPRODUCTCODE AND REMOVE"/>
<Custom Action="RemoveInstallFolder.SetParam" Before="RemoveInstallFolder" Condition="Installed AND NOT UPGRADINGPRODUCTCODE AND REMOVE"/>
<Custom Action="TryStopDeleteService" Before="RemoveInstallFolder.SetParam" />
<Custom Action="TryStopDeleteService.SetParam" Before="TryStopDeleteService" />
<Custom Action="RemoveFirewallRules" Before="RemoveFiles"/>
<Custom Action="RemoveFirewallRules.SetParam" Before="RemoveFirewallRules"/>
<Custom Action="TerminateProcesses" Before="RemoveInstallFolder"/>
<Custom Action="TerminateProcesses.SetParam" Before="TerminateProcesses"/>
<Custom Action="TerminateBrokers" Before="RemoveInstallFolder"/>
<Custom Action="TerminateBrokers.SetParam" Before="TerminateBrokers"/>
<Custom Action="RemoveAmyuniIdd" Before="RemoveInstallFolder"/>
</InstallExecuteSequence>
<!-- Shortcuts -->
<DirectoryRef Id="App.StartMenu">
<Component Id="App.StartMenu" Guid="30F6D57A-B805-4DA4-A071-05A3B22400CA">
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu" Type="string" Value="1" KeyPath="yes" />
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu" Type="string" Value="1" KeyPath="yes" />
<RemoveFolder Id="Remove.App.StartMenu" On="uninstall" />
</Component>
</DirectoryRef>
<DirectoryRef Id="App.StartMenu">
<Component Id="App.StartMenu.Shortcut" Guid="43ABCAC7-E47D-42D8-A408-25EC70DBB993" Condition="STARTMENUSHORTCUTS = 1">
<Shortcut Id="App.StartMenu.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!RustDesk.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
<Shortcut Id="App.StartMenu.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!App.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
<!--
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
-->
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
https://learn.microsoft.com/en-us/windows/win32/msi/ice38
-->
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
</Component>
<Component Id="App.StartMenu.ShortcutTray" Guid="9362C316-40BB-41C1-859C-08182AA47E8D" Condition="STARTMENUSHORTCUTS = 1">
<Shortcut Id="App.StartMenu.ShortcutTray" Name="!(loc.SC_Client_Tray)" Description="!(loc.SC_Client_Tray_Desc)" Target="[!RustDesk.exe]" Arguments="--tray" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
<!--
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
-->
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
<Component Id="App.StartMenu.ShortcutUninstall" Guid="E100D7F8-D607-4513-28DA-2C95E5EA698E" Condition="STARTMENUSHORTCUTS = 1">
<Shortcut Id="App.StartMenu.ShortcutUninstall" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System6432Folder]msiexec.exe" Arguments="/x [ProductCode]" Icon="AppIcon" />
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.ShortcutUninstall" Type="string" Value="1" KeyPath="yes" />
</Component>
</DirectoryRef>
<StandardDirectory Id="DesktopFolder">
<Component Id="App.Desktop.Shortcut" Guid="CA8FB7AA-17F7-4E36-A58A-5A016A303709" Condition="DESKTOPSHORTCUTS = 1">
<Shortcut Id="App.Desktop.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!RustDesk.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
<!--
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
-->
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.Desktop.Shortcut" Type="string" Value="1" KeyPath="yes" />
<Shortcut Id="App.Desktop.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!App.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.Desktop.Shortcut" Type="string" Value="1" KeyPath="yes" />
</Component>
</StandardDirectory>
<StandardDirectory Id="StartupFolder">
<Component Id="App.StartupFolder.ShortcutTray" Guid="B1D1E2BB-E53E-E159-DB7C-744D5C726A8C" Condition="STARTUPSHORTCUTS = 1">
<Shortcut Id="App.StartupFolder.ShortcutTray" Name="!(loc.SC_Client_Tray)" Description="!(loc.SC_Client_Tray_Desc)" Target="[!App.exe]" Arguments="--tray" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartupFolder.ShortcutTray" Type="string" Value="1" KeyPath="yes" />
</Component>
</StandardDirectory>
<DirectoryRef Id="INSTALLFOLDER">
<!--<DirectoryRef Id="INSTALLFOLDER">
<Component Id="App.UninstallShortcut" Guid="FB0F2AC7-2AE5-4C54-B860-5E472620B6B1">
<Shortcut Id="App.UninstallShortcut" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System64Folder]msiexec.exe" Arguments="/x [ProductCode]" IconIndex="0" />
<Shortcut Id="App.UninstallShortcut" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System6432Folder]msiexec.exe" Arguments="/x [ProductCode]" Icon="AppIcon" />
</Component>
</DirectoryRef>
</DirectoryRef>-->
<ComponentGroup Id="Components" Directory="INSTALLFOLDER">
<ComponentRef Id="RustDesk.exe" />
<ComponentRef Id="App.exe" />
<ComponentRef Id="App.Desktop.Shortcut" />
<ComponentRef Id="App.UninstallShortcut" />
<!--<ComponentRef Id="App.UninstallShortcut" />-->
<ComponentRef Id="App.StartMenu.Shortcut" />
<ComponentRef Id="App.StartMenu.ShortcutTray" />
<ComponentRef Id="App.StartMenu.ShortcutUninstall" />
<ComponentRef Id="App.StartupFolder.ShortcutTray" />
<!--$AutoComonentStart$-->
<!--$AutoComponentEnd$-->

View File

@@ -1,30 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<?include ../Includes.wxi?>
<Fragment>
<Property Id="AddRemovePropertiesFile" Value="1" />
<!--STOP_SERVICE is set to 'Y'. Because the cofig value may be empty or 'Y'-->
<Property Id="STOP_SERVICE" Value="&apos;Y&apos;" />
<!--
Support entries shown when clicking "Click here for support information"
in Control Panel's Add/Remove Programs http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/configuration_properties.asp
-->
<Property Id="ARPCOMMENTS" Value="!(loc.AR_Comment)" />
Support entries shown when clicking "Click here for support information"
in Control Panel's Add/Remove Programs https://learn.microsoft.com/en-us/windows/win32/msi/property-reference
-->
<!--<Property Id="ARPCOMMENTS" Value="!(loc.AR_Comment)" />
<Property Id="ARPCONTACT" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPHELPLINK" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPREADME" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPURLINFOABOUT" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/rustdesk/rustdesk" />-->
<Property Id="ARPPRODUCTICON" Value="AppIcon" />
<Property Id="RUSTDESKNATIVEINSTALL">
<RegistrySearch Id="RustDeskNativeInstallSearch" Root="HKCR" Key="$(var.RegKeyRoot)\DefaultIcon" Type="raw" />
</Property>
<Property Id="RUSTDESKNATIVEINSTALLFOLDER">
<RegistrySearch Id="RustDeskNativeInstallFolderSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="INSTALLFOLDER" Type="raw" />
</Property>
<!--$ArpStart$-->
<!--$ArpEnd$-->
<Property Id="APP_WINDOWS_INSTALLER">
<RegistrySearch Id="AppWindowsInstallerFolderSearch" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\$(var.Product)" Name="WindowsInstaller" Type="raw" />
</Property>
</Fragment>
</Wix>

View File

@@ -1,26 +1,21 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<?include ../Includes.wxi?>
<Binary Id="Custom_Actions_Dll" SourceFile="$(var.CustomActions.TargetDir)$(var.CustomActions.TargetName).CA.dll" />
<Fragment>
<?include ../Includes.wxi?>
<CustomAction Id="CustomActionHello" DllEntry="CustomActionHello" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="RunCommandAsSystem" DllEntry="RunCommandAsSystem" Impersonate="no" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<Binary Id="Custom_Actions_Dll" SourceFile="$(var.CustomActions.TargetDir)$(var.CustomActions.TargetName).dll" />
<Property Id="CMD" Value='echo "Hello"' />
<CustomAction Id="SetCmdRemoveDir" Property="CMD" Value='rmdir /s /q "[INSTALLFOLDER]"' />
<!-- Use WixQuietExec to run the commands to avoid the command window popping up. The command line to run needs to be stored
in a property with the same id as the WixQuietExec custom action and the path to the exe needs to be quoted.
A SetProperty action is used to allow the [SystemFolder] reference to be resolved and needs to be scheduled to run before the action.-->
<SetProperty Id="FirewallPortOutAdd" Value="&quot;[SystemFolder]netsh.exe&quot; advfirewall firewall add rule name=&quot;$(var.Product) Service&quot; dir=out action=allow programe=$(var.ProductLower) enable=yes" Before="FirewallPortOutAdd" Sequence="execute" />
<CustomAction Id="FirewallPortOutAdd" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
<SetProperty Id="FirewallPortInAdd" Value="&quot;[SystemFolder]netsh.exe&quot; advfirewall firewall add rule name=&quot;$(var.Product) Service&quot; dir=in action=allow programe=$(var.ProductLower) enable=yes" Before="FirewallPortInAdd" Sequence="execute" />
<CustomAction Id="FirewallPortInAdd" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
<SetProperty Id="FirewallPortRemove" Value="&quot;[SystemFolder]netsh.exe&quot; advfirewall firewall delete rule name=&quot;$(var.Product) Service&quot;" Before="FirewallPortRemove" Sequence="execute" />
<CustomAction Id="FirewallPortRemove" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
</Fragment>
<CustomAction Id="CustomActionHello" DllEntry="CustomActionHello" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="RemoveInstallFolder" DllEntry="RemoveInstallFolder" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="TerminateProcesses" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="TerminateBrokers" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="AddFirewallRules" DllEntry="AddFirewallRules" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="RemoveFirewallRules" DllEntry="AddFirewallRules" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="SetPropertyIsServiceRunning" DllEntry="SetPropertyIsServiceRunning" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="CreateStartService" DllEntry="CreateStartService" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="TryStopDeleteService" DllEntry="TryStopDeleteService" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="TryDeleteStartupShortcut" DllEntry="TryDeleteStartupShortcut" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="SetPropertyServiceStop" DllEntry="SetPropertyFromConfig" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="AddRegSoftwareSASGeneration" DllEntry="AddRegSoftwareSASGeneration" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="RemoveAmyuniIdd" DllEntry="RemoveAmyuniIdd" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
</Fragment>
</Wix>

View File

@@ -1,51 +1,52 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<Fragment>
<?include ..\Includes.wxi?>
<?include ..\Includes.wxi?>
<!--
Properties and related actions for specifying whether to install start menu/desktop shortcuts.
-->
<!--
Properties and related actions for specifying whether to install start menu/desktop shortcuts.
-->
<!-- These are the actual properties that get used in conditions to determine whether to
install start menu shortcuts, they are initialized with a default value to install shortcuts.
They should not be set directly from the command line or registry, instead the CREATE* properties
below should be set, then they will update these properties with their values only if set. -->
<Property Id="STARTMENUSHORTCUTS" Value="1" Secure="yes"></Property>
<Property Id="DESKTOPSHORTCUTS" Value="1" Secure="yes"></Property>
<!-- These are the actual properties that get used in conditions to determine whether to
install start menu shortcuts, they are initialized with a default value to install shortcuts.
They should not be set directly from the command line or registry, instead the CREATE* properties
below should be set, then they will update these properties with their values only if set. -->
<Property Id="STARTMENUSHORTCUTS" Value="1" Secure="yes"></Property>
<Property Id="DESKTOPSHORTCUTS" Value="1" Secure="yes"></Property>
<Property Id="STARTUPSHORTCUTS" Value="1" Secure="yes"></Property>
<!-- These properties get set from either the command line, bundle or registry value,
if set they update the properties above with their value. -->
<Property Id="CREATESTARTMENUSHORTCUTS" Secure="yes">
<RegistrySearch Id="CreateStartMenuShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="STARTMENUSHORTCUTS" Type="raw" />
</Property>
<Property Id="CREATEDESKTOPSHORTCUTS" Secure="yes">
<RegistrySearch Id="CreateDesktopShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="DESKTOPSHORTCUTS" Type="raw" />
</Property>
<!-- These properties get set from either the command line, bundle or registry value,
if set they update the properties above with their value. -->
<Property Id="CREATESTARTMENUSHORTCUTS" Secure="yes">
<RegistrySearch Id="CreateStartMenuShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="STARTMENUSHORTCUTS" Type="raw" />
</Property>
<Property Id="CREATEDESKTOPSHORTCUTS" Secure="yes">
<RegistrySearch Id="CreateDesktopShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="DESKTOPSHORTCUTS" Type="raw" />
</Property>
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
<DirectoryRef Id="INSTALLFOLDER">
<Component Id="Product.Registry.PersistedShortcutProperties" Guid="1BBAD054-6EC2-4362-BF1B-E8BDE988B597">
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
<RegistryValue Type="string" Name="STARTMENUSHORTCUTS" Value="[STARTMENUSHORTCUTS]" KeyPath="yes" />
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="[DESKTOPSHORTCUTS]" />
</RegistryKey>
</Component>
</DirectoryRef>
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
<DirectoryRef Id="INSTALLFOLDER">
<Component Id="Product.Registry.PersistedShortcutProperties" Guid="1BBAD054-6EC2-4362-BF1B-E8BDE988B597">
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
<RegistryValue Type="string" Name="STARTMENUSHORTCUTS" Value="[STARTMENUSHORTCUTS]" KeyPath="yes" />
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="[DESKTOPSHORTCUTS]" />
</RegistryKey>
</Component>
</DirectoryRef>
<!-- If a property value has been passed via the command line (which includes when set from the bundle), the registry search will
overwrite the command line value, these actions temporarily store the command line value before the registry search
is performed so they can be restored after the registry search is complete -->
<SetProperty Id="SavedStartMenuShortcutsCmdLineValue" Value="[CREATESTARTMENUSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
<SetProperty Id="SavedDesktopShortcutsCmdLineValue" Value="[CREATEDESKTOPSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
<!-- If a property value has been passed via the command line (which includes when set from the bundle), the registry search will
overwrite the command line value, these actions temporarily store the command line value before the registry search
is performed so they can be restored after the registry search is complete -->
<SetProperty Id="SavedStartMenuShortcutsCmdLineValue" Value="[CREATESTARTMENUSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
<SetProperty Id="SavedDesktopShortcutsCmdLineValue" Value="[CREATEDESKTOPSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
<!-- If a command line value was stored, restore it after the registry search has been performed -->
<SetProperty Action="RestoreSavedStartMenuShortcutsValue" Id="CREATESTARTMENUSHORTCUTS" Value="[SavedStartMenuShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedStartMenuShortcutsCmdLineValue" />
<SetProperty Action="RestoreSavedDesktopShortcutsValue" Id="CREATEDESKTOPSHORTCUTS" Value="[SavedDesktopShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedDesktopShortcutsCmdLineValue" />
<!-- If a command line value was stored, restore it after the registry search has been performed -->
<SetProperty Action="RestoreSavedStartMenuShortcutsValue" Id="CREATESTARTMENUSHORTCUTS" Value="[SavedStartMenuShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedStartMenuShortcutsCmdLineValue" />
<SetProperty Action="RestoreSavedDesktopShortcutsValue" Id="CREATEDESKTOPSHORTCUTS" Value="[SavedDesktopShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedDesktopShortcutsCmdLineValue" />
<!-- If a command line value or registry value was set, update the main properties with the value -->
<SetProperty Id="STARTMENUSHORTCUTS" Value="[CREATESTARTMENUSHORTCUTS]" After="RestoreSavedStartMenuShortcutsValue" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
<SetProperty Id="DESKTOPSHORTCUTS" Value="[CREATEDESKTOPSHORTCUTS]" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
<!-- If a command line value or registry value was set, update the main properties with the value -->
<SetProperty Id="STARTMENUSHORTCUTS" Value="[CREATESTARTMENUSHORTCUTS]" After="RestoreSavedStartMenuShortcutsValue" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
<SetProperty Id="DESKTOPSHORTCUTS" Value="[CREATEDESKTOPSHORTCUTS]" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
</Fragment>
</Fragment>
</Wix>

View File

@@ -1,11 +1,10 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<Fragment>
<Property Id="UpgradesFile" Value="1" />
<Property Id="UpgradesFile" Value="1" />
<Upgrade Id="9D8E3E95-42B8-427E-B801-79F3FE7B6DD7">
<UpgradeVersion Property="OLD_VERSION_FOUND" Minimum="2.0.0.0" Maximum="2.99.99" IncludeMinimum="yes" IncludeMaximum="yes" OnlyDetect="no" IgnoreRemoveFailure="yes" MigrateFeatures="yes" />
</Upgrade>
<!--$UpgradeStart$-->
<!--$UpgradeEnd$-->
</Fragment>
</Fragment>
</Wix>

View File

@@ -4,7 +4,4 @@
<!--$PreVarsStart$-->
<!--$PreVarsEnd$-->
<!-- This should NEVER be changed ! -->
<?define UpgradeCode = "9D8E3E95-42B8-427E-B801-79F3FE7B6DD7" ?>
</Include>

View File

@@ -47,6 +47,6 @@ This file contains the declaration of all the localizable strings.
<!-- User Interfaces -->
<String Id="AnotherAppDialogTitle" Value="Cancel installation."/>
<String Id="AnotherAppDialogDescription" Value="The application is installed by another method, please uninstall it first."/>
<String Id="AnotherAppDialogDescription" Value="The application is installed by self-installation method, please uninstall it first."/>
</WixLocalization>

View File

@@ -17,6 +17,6 @@
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CustomActions\CustomActions.csproj" />
<ProjectReference Include="..\CustomActions\CustomActions.vcxproj" />
</ItemGroup>
</Project>

View File

@@ -4,12 +4,12 @@
<?include Includes.wxi?>
<Package Name="$(var.Product)" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" Language="!(loc.ProductLanguage)" Codepage="0" UpgradeCode="$(var.UpgradeCode)" Scope="perMachine">
<Package Name="$(var.Product)" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" Language="!(loc.ProductLanguage)" UpgradeCode="$(var.UpgradeCode)" Scope="perMachine">
<SummaryInformation Keywords="Installer" Description="$(var.Description)" Codepage="!(loc.SummaryCodepage)" />
<!--<PropertyRef Id="UpgradesFile" />-->
<PropertyRef Id="AddRemovePropertiesFile" />
<Media Id="1" Cabinet="cab1.cab" EmbedCab="yes" CompressionLevel="high" />
@@ -22,39 +22,20 @@
<UIRef Id="WixUI_ErrorProgressText" />
<InstallUISequence>
<!--<Custom Action="ReadCustomPathsFromExistingPathsFile" Before="CostFinalize" Condition="NOT Installed" />-->
<Show Dialog="AnotherAppDialog" Before="WelcomeDlg" Condition="Not installed AND RUSTDESKNATIVEINSTALL AND Not RUSTDESKNATIVEINSTALLFOLDER"/>
<Show Dialog="AnotherAppDialog" Before="WelcomeDlg" Condition="Not installed AND APP_WINDOWS_INSTALLER=&quot;#0&quot;"/>
</InstallUISequence>
<InstallExecuteSequence>
<!-- Stop all MP2 processes -->
<!--<Custom Action="StopProcesses" Before="ReadCustomPathsFromExistingPathsFile" />-->
<!-- Reads custom paths which maybe have been changed by the user in a former installation -->
<!--<Custom Action="ReadCustomPathsFromExistingPathsFile" Before="PrepareXmlPathVariables" Condition="(NOT Installed) AND (INSTALLTYPE_CUSTOM = 0)" />-->
<!--<Custom Action="PrepareXmlPathVariables" Before="FileCost" Condition="NOT Installed" />-->
<!--<Custom Action="AttachClientToServer" After="InstallFinalize" Condition="NOT Installed" />-->
<Custom Action="FirewallPortRemove" Before="InstallFinalize" Condition="REMOVE~=&quot;ALL&quot;" />
<Custom Action="FirewallPortOutAdd" Before="InstallFinalize" Condition="NOT Installed" />
<Custom Action="FirewallPortInAdd" Before="InstallFinalize" Condition="NOT Installed" />
<!-- Launch ClientLauncher if installing or already installed and not uninstalling -->
<Custom Action="LaunchApp" After="InstallFinalize" />
<Custom Action="LaunchAppTray" After="InstallFinalize" />
<InstallExecute After="RemoveExistingProducts" />
<Custom Action="CloseProcesses" Before="RemoveFiles" />
<!--Only do InstallValidate if is not Uninstall-->
<!--<InstallValidate Condition="NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE )" />-->
<!--Only do InstallValidate if is Install-->
<InstallValidate Condition="NOT Installed" />
</InstallExecuteSequence>
<CustomAction Id="LaunchApp" ExeCommand="" Return="asyncNoWait" FileRef="RustDesk.exe" />
<CustomAction Id="LaunchAppTray" ExeCommand=" --tray" Return="asyncNoWait" FileRef="RustDesk.exe" />
<CustomAction Id="CloseProcesses" ExeCommand='taskkill /F /IM "$(var.Product).exe"' Return="asyncNoWait" Directory="INSTALLFOLDER" />
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" Schedule="afterInstallInitialize" />
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" Schedule="afterInstallInitialize" AllowSameVersionUpgrades="yes" />
<Feature Id="App" Level="1" AllowAdvertise="no" Display="expand" Title="!(loc.F_App)" Description="!(loc.F_App_Desc)" AllowAbsent="no">
<ComponentGroupRef Id="Components" />
@@ -64,8 +45,13 @@
<ComponentRef Id="Product.Registry.CommandPlay" />
<ComponentRef Id="Product.Registry.URLProtocol" />
<ComponentRef Id="Product.Registry.Command" />
<ComponentRef Id="Product.Registry.UninstallApp" />
<ComponentRef Id="App.StartMenu" />
<ComponentRef Id="Product.Registry.PersistedShortcutProperties" />
</Feature>
<!--https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set-->
<!--$CustomBitmapsStart$-->
<!--$CustomBitmapsEnd$-->
</Package>
</Wix>

View File

@@ -1,15 +1,15 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<UI>
<Dialog Id="AnotherAppDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)">
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="!(loc.WixUICancel)">
<Publish Event="EndDialog" Value="ErrorAbort" />
</Control>
<Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="!(loc.ExitDialogBitmap)" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogDescription)" />
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogTitle)" />
</Dialog>
</UI>
</Fragment>
<Fragment>
<UI>
<Dialog Id="AnotherAppDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)">
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="!(loc.WixUICancel)">
<Publish Event="EndDialog" Value="ErrorAbort" />
</Control>
<Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="!(loc.ExitDialogBitmap)" />
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
<Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogDescription)" />
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogTitle)" />
</Dialog>
</UI>
</Fragment>
</Wix>

View File

@@ -4,14 +4,41 @@ Use Visual Studio 2022 to compile this project.
This project is mainly derived from <https://github.com/MediaPortal/MediaPortal-2.git> .
## Steps
1. `python preprocess.py`, see `python preprocess.py -h` for help.
2. Build the .sln solution.
Run `msiexec /i package.msi /l*v install.log` to record the log.
## Usage
1. Put the custom dialog bitmaps in "Resources" directory. The supported bitmaps are `['WixUIBannerBmp', 'WixUIDialogBmp', 'WixUIExclamationIco', 'WixUIInfoIco', 'WixUINewIco', 'WixUIUpIco']`.
## Knowledge
### properties
[wix-toolset-set-custom-action-run-only-on-uninstall](https://www.advancedinstaller.com/versus/wix-toolset/wix-toolset-set-custom-action-run-only-on-uninstall.html)
| Property Name | Install | Uninstall | Change | Repair | Upgrade |
| ------ | ------ | ------ | ------ | ------ | ------ |
| Installed | False | True | True | True | True |
| REINSTALL | False | False | False | True | False |
| UPGRADINGPRODUCTCODE | False | False | False | False | True |
| REMOVE | False | True | False | False | True |
## TODOs
1. tray, uninstall shortcut
1. launch client after installation
1. github ci
1. options
1. Start menu. Uninstall
1. custom options
1. Custom client.
1. firewall and tcp allow. Outgoing
1. Custom icon. Current `Resources/icon.ico`.
1. Show license ?
1. Do create service. Outgoing.
## Refs
1. [windows-installer-portal](https://learn.microsoft.com/en-us/windows/win32/Msi/windows-installer-portal)
1. [wxs](https://wixtoolset.org/docs/schema/wxs/)
1. [wxs github](https://github.com/wixtoolset/wix)

View File

@@ -4,34 +4,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00
VisualStudioVersion = 17.7.34003.232
MinimumVisualStudioVersion = 10.0.40219.1
Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Package", "Package\Package.wixproj", "{F403A403-CEFF-4399-B51C-CC646C8E98CF}"
ProjectSection(ProjectDependencies) = postProject
{95BE171E-6438-4F45-9876-0B667D9F7830} = {95BE171E-6438-4F45-9876-0B667D9F7830}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomActions", "CustomActions\CustomActions.csproj", "{95BE171E-6438-4F45-9876-0B667D9F7830}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CustomActions", "CustomActions\CustomActions.vcxproj", "{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|Any CPU.ActiveCfg = Release|x64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|Any CPU.Build.0 = Release|x64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|x64.ActiveCfg = Release|x64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|x64.Build.0 = Release|x64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|Any CPU.ActiveCfg = Release|x64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|Any CPU.Build.0 = Release|x64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.ActiveCfg = Release|x64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.Build.0 = Release|x64
{95BE171E-6438-4F45-9876-0B667D9F7830}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{95BE171E-6438-4F45-9876-0B667D9F7830}.Debug|x64.ActiveCfg = Release|Any CPU
{95BE171E-6438-4F45-9876-0B667D9F7830}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95BE171E-6438-4F45-9876-0B667D9F7830}.Release|Any CPU.Build.0 = Release|Any CPU
{95BE171E-6438-4F45-9876-0B667D9F7830}.Release|x64.ActiveCfg = Release|Any CPU
{95BE171E-6438-4F45-9876-0B667D9F7830}.Release|x64.Build.0 = Release|Any CPU
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.ActiveCfg = Release|x64
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,69 +1,111 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import sys
import uuid
import argparse
import datetime
import subprocess
import re
from pathlib import Path
g_indent_unit = "\t"
g_version = ""
g_build_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
# Replace the following links with your own in the custom arp properties.
# https://learn.microsoft.com/en-us/windows/win32/msi/property-reference
g_arpsystemcomponent = {
"Comments": {
"msi": "ARPCOMMENTS",
"t": "string",
"v": "!(loc.AR_Comment)",
},
"Contact": {
"msi": "ARPCONTACT",
"v": "https://github.com/rustdesk/rustdesk",
},
"HelpLink": {
"msi": "ARPHELPLINK",
"v": "https://github.com/rustdesk/rustdesk/issues/",
},
"ReadMe": {
"msi": "ARPREADME",
"v": "https://github.com/fufesou/rustdesk",
},
}
def make_parser():
parser = argparse.ArgumentParser(description="Msi preprocess script.")
parser.add_argument(
"-d", "--debug", action="store_true", help="Is debug", default=False
"-d",
"--dist-dir",
type=str,
default="../../rustdesk",
help="The dist direcotry to install.",
)
parser.add_argument(
"--arp",
action="store_true",
help="Is ARPSYSTEMCOMPONENT",
default=False,
)
parser.add_argument(
"--custom-arp",
type=str,
default="{}",
help='Custom arp properties, e.g. \'["Comments": {"msi": "ARPCOMMENTS", "v": "Remote control application."}]\'',
)
parser.add_argument(
"-c", "--custom", action="store_true", help="Is custom client", default=False
)
parser.add_argument(
"-an", "--app-name", type=str, default="RustDesk", help="The app name."
"--app-name", type=str, default="RustDesk", help="The app name."
)
parser.add_argument(
"-v", "--version", type=str, default="1.2.4", help="The app version."
"-v", "--version", type=str, default="", help="The app version."
)
parser.add_argument(
"-m",
"--manufacturer",
type=str,
default="Purslane Ltd",
default="PURSLANE",
help="The app manufacturer.",
)
return parser
def read_lines_and_start_index(file_path, start_tag, end_tag):
def read_lines_and_start_index(file_path, tag_start, tag_end):
with open(file_path, "r") as f:
lines = f.readlines()
start_index = -1
end_index = -1
index_start = -1
index_end = -1
for i, line in enumerate(lines):
if start_tag in line:
start_index = i
if end_tag in line:
end_index = i
if tag_start in line:
index_start = i
if tag_end in line:
index_end = i
if start_index == -1 or end_index == -1:
print("Error: start or end tag not found")
if index_start == -1:
print(f'Error: start tag "{tag_start}" not found')
return None, None
return lines, start_index
if index_end == -1:
print(f'Error: end tag "{tag_end}" not found')
return None, None
return lines, index_start
def insert_components_between_tags(lines, start_index, app_name, build_dir):
def insert_components_between_tags(lines, index_start, app_name, dist_dir):
indent = g_indent_unit * 3
path = Path(build_dir)
path = Path(dist_dir)
idx = 1
for file_path in path.glob("**/*"):
if file_path.is_file():
if file_path.name.lower() == f"{app_name}.exe".lower():
continue
relative_file_path = file_path.relative_to(path)
guid = uuid.uuid5(
uuid.NAMESPACE_OID, app_name + "/" + str(relative_file_path)
)
subdir = str(file_path.parent.relative_to(path))
dir_attr = ""
if subdir != ".":
@@ -73,67 +115,59 @@ def insert_components_between_tags(lines, start_index, app_name, build_dir):
# because it will cause error
# "Error WIX0130 The primary key 'xxxx' is duplicated in table 'Directory'"
to_insert_lines = f"""
{indent}<Component Guid="{guid}" {dir_attr}>
{indent}<Component Guid="{uuid.uuid4()}" {dir_attr}>
{indent}{g_indent_unit}<File Source="{file_path.as_posix()}" KeyPath="yes" Checksum="yes" />
{indent}</Component>
"""
lines.insert(start_index + 1, to_insert_lines[1:])
start_index += 1
lines.insert(index_start + 1, to_insert_lines[1:])
index_start += 1
idx += 1
return True
def gen_auto_component(app_name, build_dir):
target_file = Path(sys.argv[0]).parent.joinpath("Package/Components/RustDesk.wxs")
start_tag = "<!--$AutoComonentStart$-->"
end_tag = "<!--$AutoComponentEnd$-->"
lines, start_index = read_lines_and_start_index(target_file, start_tag, end_tag)
if lines is None:
return False
if not insert_components_between_tags(lines, start_index, app_name, build_dir):
return False
with open(target_file, "w") as f:
f.writelines(lines)
return True
def gen_auto_component(app_name, dist_dir):
return gen_content_between_tags(
"Package/Components/RustDesk.wxs",
"<!--$AutoComonentStart$-->",
"<!--$AutoComponentEnd$-->",
lambda lines, index_start: insert_components_between_tags(
lines, index_start, app_name, dist_dir
),
)
def gen_pre_vars(args, build_dir):
target_file = Path(sys.argv[0]).parent.joinpath("Package/Includes.wxi")
start_tag = "<!--$PreVarsStart$-->"
end_tag = "<!--$PreVarsEnd$-->"
def gen_pre_vars(args, dist_dir):
def func(lines, index_start):
upgrade_code = uuid.uuid5(uuid.NAMESPACE_OID, app_name + ".exe")
lines, start_index = read_lines_and_start_index(target_file, start_tag, end_tag)
if lines is None:
return False
indent = g_indent_unit * 1
to_insert_lines = [
f'{indent}<?define Version="{g_version}" ?>\n',
f'{indent}<?define Manufacturer="{args.manufacturer}" ?>\n',
f'{indent}<?define Product="{args.app_name}" ?>\n',
f'{indent}<?define Description="{args.app_name} Installer" ?>\n',
f'{indent}<?define ProductLower="{args.app_name.lower()}" ?>\n',
f'{indent}<?define RegKeyRoot=".$(var.ProductLower)" ?>\n',
f'{indent}<?define RegKeyInstall="$(var.RegKeyRoot)\\Install" ?>\n',
f'{indent}<?define BuildDir="{dist_dir}" ?>\n',
f'{indent}<?define BuildDate="{g_build_date}" ?>\n',
"\n",
f"{indent}<!-- The UpgradeCode must be consistent for each product. ! -->\n"
f'{indent}<?define UpgradeCode = "{upgrade_code}" ?>\n',
]
indent = g_indent_unit * 1
to_insert_lines = [
f'{indent}<?define Version="{args.version}" ?>\n',
f'{indent}<?define Manufacturer="{args.manufacturer}" ?>\n',
f'{indent}<?define Product="{args.app_name}" ?>\n',
f'{indent}<?define Description="{args.app_name} Installer" ?>\n',
f'{indent}<?define ProductLower="{args.app_name.lower()}" ?>\n',
f'{indent}<?define RegKeyRoot=".$(var.ProductLower)" ?>\n',
f'{indent}<?define RegKeyInstall="$(var.RegKeyRoot)\Install" ?>\n',
f'{indent}<?define BuildDir="{build_dir}" ?>\n',
]
for i, line in enumerate(to_insert_lines):
lines.insert(index_start + i + 1, line)
return lines
for i, line in enumerate(to_insert_lines):
lines.insert(start_index + i + 1, line)
with open(target_file, "w") as f:
f.writelines(lines)
return True
return gen_content_between_tags(
"Package/Includes.wxi", "<!--$PreVarsStart$-->", "<!--$PreVarsEnd$-->", func
)
def replace_app_name_in_lans(app_name):
def replace_app_name_in_langs(app_name):
langs_dir = Path(sys.argv[0]).parent.joinpath("Package/Language")
for file_path in langs_dir.glob("*.wxs"):
for file_path in langs_dir.glob("*.wxl"):
with open(file_path, "r") as f:
lines = f.readlines()
for i, line in enumerate(lines):
@@ -142,23 +176,295 @@ def replace_app_name_in_lans(app_name):
f.writelines(lines)
def gen_upgrade_info():
def func(lines, index_start):
indent = g_indent_unit * 3
vs = g_version.split(".")
major = vs[0]
upgrade_id = uuid.uuid4()
to_insert_lines = [
f'{indent}<Upgrade Id="{upgrade_id}">\n',
f'{indent}{g_indent_unit}<UpgradeVersion Property="OLD_VERSION_FOUND" Minimum="{major}.0.0" Maximum="{major}.99.99" IncludeMinimum="yes" IncludeMaximum="yes" OnlyDetect="no" IgnoreRemoveFailure="yes" MigrateFeatures="yes" />\n',
f"{indent}</Upgrade>\n",
]
for i, line in enumerate(to_insert_lines):
lines.insert(index_start + i + 1, line)
return lines
return gen_content_between_tags(
"Package/Fragments/Upgrades.wxs",
"<!--$UpgradeStart$-->",
"<!--$UpgradeEnd$-->",
func,
)
def gen_custom_dialog_bitmaps():
def func(lines, index_start):
indent = g_indent_unit * 2
# https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set
vars = [
"WixUIBannerBmp",
"WixUIDialogBmp",
"WixUIExclamationIco",
"WixUIInfoIco",
"WixUINewIco",
"WixUIUpIco",
]
to_insert_lines = []
for var in vars:
if Path(f"Package/Resources/{var}.bmp").exists():
to_insert_lines.append(
f'{indent}<WixVariable Id="{var}" Value="Resources\\{var}.bmp" />\n'
)
for i, line in enumerate(to_insert_lines):
lines.insert(index_start + i + 1, line)
return lines
return gen_content_between_tags(
"Package/Package.wxs",
"<!--$CustomBitmapsStart$-->",
"<!--$CustomBitmapsEnd$-->",
func,
)
def gen_custom_ARPSYSTEMCOMPONENT_False(args):
def func(lines, index_start):
indent = g_indent_unit * 2
lines_new = []
lines_new.append(
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/arpsystemcomponent?redirectedfrom=MSDN-->\n"
)
lines_new.append(
f'{indent}<!--<Property Id="ARPSYSTEMCOMPONENT" Value="1" />-->\n\n'
)
lines_new.append(
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/property-reference-->\n"
)
for _, v in g_arpsystemcomponent.items():
if "msi" in v and "v" in v:
lines_new.append(
f'{indent}<Property Id="{v["msi"]}" Value="{v["v"]}" />\n'
)
for i, line in enumerate(lines_new):
lines.insert(index_start + i + 1, line)
return lines
return gen_content_between_tags(
"Package/Fragments/AddRemoveProperties.wxs",
"<!--$ArpStart$-->",
"<!--$ArpEnd$-->",
func,
)
def get_folder_size(folder_path):
total_size = 0
folder = Path(folder_path)
for file in folder.glob("**/*"):
if file.is_file():
total_size += file.stat().st_size
return total_size
def gen_custom_ARPSYSTEMCOMPONENT_True(args, dist_dir):
def func(lines, index_start):
indent = g_indent_unit * 5
lines_new = []
lines_new.append(
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/property-reference-->\n"
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="DisplayName" Value="{args.app_name}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="DisplayIcon" Value="[INSTALLFOLDER]{args.app_name}.exe" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="DisplayVersion" Value="{g_version}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="Publisher" Value="{args.manufacturer}" />\n'
)
installDate = datetime.datetime.now().strftime("%Y%m%d")
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="InstallDate" Value="{installDate}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="InstallLocation" Value="[INSTALLFOLDER]" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="InstallSource" Value="[InstallSource]" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="Language" Value="[ProductLanguage]" />\n'
)
estimated_size = get_folder_size(dist_dir)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="EstimatedSize" Value="{estimated_size}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="expandable" Name="ModifyPath" Value="MsiExec.exe /X [ProductCode]" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Id="NoModify" Value="1" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="expandable" Name="UninstallString" Value="MsiExec.exe /X [ProductCode]" />\n'
)
vs = g_version.split(".")
major, minor, build = vs[0], vs[1], vs[2]
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="Version" Value="{g_version}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="VersionMajor" Value="{major}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="VersionMinor" Value="{minor}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="VersionBuild" Value="{build}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="WindowsInstaller" Value="1" />\n'
)
for k, v in g_arpsystemcomponent.items():
if "v" in v:
t = v["t"] if "t" in v is None else "string"
lines_new.append(
f'{indent}<RegistryValue Type="{t}" Name="{k}" Value="{v["v"]}" />\n'
)
for i, line in enumerate(lines_new):
lines.insert(index_start + i + 1, line)
return lines
return gen_content_between_tags(
"Package/Components/Regs.wxs",
"<!--$ArpStart$-->",
"<!--$ArpEnd$-->",
func,
)
def gen_custom_ARPSYSTEMCOMPONENT(args, dist_dir):
try:
custom_arp = json.loads(args.custom_arp)
g_arpsystemcomponent.update(custom_arp)
except json.JSONDecodeError as e:
print(f"Failed to decode custom arp: {e}")
return False
if args.arp:
return gen_custom_ARPSYSTEMCOMPONENT_True(args, dist_dir)
else:
return gen_custom_ARPSYSTEMCOMPONENT_False(args)
def gen_content_between_tags(filename, tag_start, tag_end, func):
target_file = Path(sys.argv[0]).parent.joinpath(filename)
lines, index_start = read_lines_and_start_index(target_file, tag_start, tag_end)
if lines is None:
return False
func(lines, index_start)
with open(target_file, "w") as f:
f.writelines(lines)
return True
def init_global_vars(dist_dir, app_name, args):
dist_app = dist_dir.joinpath(app_name + ".exe")
def read_process_output(args):
process = subprocess.Popen(
f"{dist_app} {args}",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True,
)
output, _ = process.communicate()
return output.decode("utf-8").strip()
global g_version
global g_build_date
g_version = args.version.replace("-", ".")
if g_version == "":
g_version = read_process_output("--version")
version_pattern = re.compile(r"\d+\.\d+\.\d+.*")
if not version_pattern.match(g_version):
print(f"Error: version {g_version} not found in {dist_app}")
return False
g_build_date = read_process_output("--build-date")
build_date_pattern = re.compile(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}")
if not build_date_pattern.match(g_build_date):
print(f"Error: build date {g_build_date} not found in {dist_app}")
return False
return True
def replace_component_guids_in_wxs():
langs_dir = Path(sys.argv[0]).parent.joinpath("Package")
for file_path in langs_dir.glob("**/*.wxs"):
with open(file_path, "r") as f:
lines = f.readlines()
# <Component Id="Product.Registry.DefaultIcon" Guid="6DBF2690-0955-4C6A-940F-634DDA503F49">
for i, line in enumerate(lines):
match = re.search(r'Component.+Guid="([^"]+)"', line)
if match:
lines[i] = re.sub(r'Guid="[^"]+"', f'Guid="{uuid.uuid4()}"', line)
with open(file_path, "w") as f:
f.writelines(lines)
if __name__ == "__main__":
parser = make_parser()
args = parser.parse_args()
app_name = args.app_name
build_dir = (
Path(sys.argv[0])
.parent.joinpath(
f'../../flutter/build/windows/x64/runner/{"Debug" if args.debug else "Release"}'
)
.resolve()
)
dist_dir = Path(sys.argv[0]).parent.joinpath(args.dist_dir).resolve()
if not gen_pre_vars(args, build_dir):
if not init_global_vars(dist_dir, app_name, args):
sys.exit(-1)
if not gen_auto_component(app_name, build_dir):
if not gen_pre_vars(args, dist_dir):
sys.exit(-1)
replace_app_name_in_lans(args.app_name)
if app_name != "RustDesk":
replace_component_guids_in_wxs()
if not gen_upgrade_info():
sys.exit(-1)
if not gen_custom_ARPSYSTEMCOMPONENT(args, dist_dir):
sys.exit(-1)
if not gen_auto_component(app_name, dist_dir):
sys.exit(-1)
if not gen_custom_dialog_bitmaps():
sys.exit(-1)
replace_app_name_in_langs(args.app_name)

View File

@@ -1038,9 +1038,9 @@ pub struct VideoHandler {
impl VideoHandler {
/// Create a new video handler.
pub fn new(format: CodecFormat, _display: usize) -> Self {
#[cfg(all(feature = "gpucodec", feature = "flutter"))]
#[cfg(all(feature = "vram", feature = "flutter"))]
let luid = crate::flutter::get_adapter_luid();
#[cfg(not(all(feature = "gpucodec", feature = "flutter")))]
#[cfg(not(all(feature = "vram", feature = "flutter")))]
let luid = Default::default();
log::info!("new video handler for display #{_display}, format: {format:?}, luid: {luid:?}");
VideoHandler {
@@ -1097,9 +1097,9 @@ impl VideoHandler {
/// Reset the decoder, change format if it is Some
pub fn reset(&mut self, format: Option<CodecFormat>) {
#[cfg(all(feature = "flutter", feature = "gpucodec"))]
#[cfg(all(feature = "flutter", feature = "vram"))]
let luid = crate::flutter::get_adapter_luid();
#[cfg(not(all(feature = "flutter", feature = "gpucodec")))]
#[cfg(not(all(feature = "flutter", feature = "vram")))]
let luid = None;
let format = format.unwrap_or(self.decoder.format());
self.decoder = Decoder::new(format, luid);
@@ -1564,22 +1564,13 @@ impl LoginConfigHandler {
///
/// * `ignore_default` - If `true`, ignore the default value of the option.
fn get_option_message(&self, ignore_default: bool) -> Option<OptionMessage> {
if self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP) {
if self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP) || self.conn_type.eq(&ConnType::FILE_TRANSFER) {
return None;
}
let mut n = 0;
let mut msg = OptionMessage::new();
// Version 1.2.5 can remove this, and OptionMessage is not needed for file transfer
msg.support_windows_specific_session = BoolOption::Yes.into();
n += 1;
if self.conn_type.eq(&ConnType::FILE_TRANSFER) {
return Some(msg);
}
let q = self.image_quality.clone();
if let Some(q) = self.get_image_quality_enum(&q, ignore_default) {
msg.image_quality = q.into();
n += 1;
} else if q == "custom" {
let config = self.load_config();
let allow_more = !crate::using_public_server() || self.direct == Some(true);
@@ -1602,32 +1593,25 @@ impl LoginConfigHandler {
msg.custom_fps = custom_fps;
*self.custom_fps.lock().unwrap() = Some(custom_fps as _);
}
n += 1;
}
let view_only = self.get_toggle_option("view-only");
if view_only {
msg.disable_keyboard = BoolOption::Yes.into();
n += 1;
}
if view_only || self.get_toggle_option("show-remote-cursor") {
msg.show_remote_cursor = BoolOption::Yes.into();
n += 1;
}
if !view_only && self.get_toggle_option("lock-after-session-end") {
msg.lock_after_session_end = BoolOption::Yes.into();
n += 1;
}
if self.get_toggle_option("disable-audio") {
msg.disable_audio = BoolOption::Yes.into();
n += 1;
}
if !view_only && self.get_toggle_option("enable-file-transfer") {
msg.enable_file_transfer = BoolOption::Yes.into();
n += 1;
}
if view_only || self.get_toggle_option("disable-clipboard") {
msg.disable_clipboard = BoolOption::Yes.into();
n += 1;
}
msg.supported_decoding =
hbb_common::protobuf::MessageField::some(Decoder::supported_decodings(
@@ -1636,12 +1620,7 @@ impl LoginConfigHandler {
self.adapter_luid,
&self.mark_unsupported,
));
n += 1;
if n > 0 {
Some(msg)
} else {
None
}
Some(msg)
}
pub fn get_option_message_after_login(&self) -> Option<OptionMessage> {

View File

@@ -1304,8 +1304,14 @@ pub async fn get_next_nonkeyexchange_msg(
None
}
#[allow(unused_mut)]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn check_process(arg: &str, same_uid: bool) -> bool {
pub fn check_process(arg: &str, mut same_uid: bool) -> bool {
#[cfg(target_os = "macos")]
if !crate::platform::is_root() && !same_uid {
log::warn!("Can not get other process's command line arguments on macos without root");
same_uid = true;
}
use hbb_common::sysinfo::System;
let mut sys = System::new();
sys.refresh_processes();
@@ -1332,8 +1338,13 @@ pub fn check_process(arg: &str, same_uid: bool) -> bool {
if same_uid && p.user_id() != my_uid {
continue;
}
// on mac, p.cmd() get "/Applications/RustDesk.app/Contents/MacOS/RustDesk", "XPC_SERVICE_NAME=com.carriez.RustDesk_server"
let parg = if p.cmd().len() <= 1 { "" } else { &p.cmd()[1] };
if arg == parg {
if arg.is_empty() {
if !parg.starts_with("--") {
return true;
}
} else if arg == parg {
return true;
}
}

View File

@@ -114,9 +114,14 @@ pub fn core_main() -> Option<Vec<String>> {
if args.contains(&"--noinstall".to_string()) {
args.clear();
}
if args.len() > 0 && args[0] == "--version" {
println!("{}", crate::VERSION);
return None;
if args.len() > 0 {
if args[0] == "--version" {
println!("{}", crate::VERSION);
return None;
} else if args[0] == "--build-date" {
println!("{}", crate::BUILD_DATE);
return None;
}
}
#[cfg(windows)]
{
@@ -208,34 +213,16 @@ pub fn core_main() -> Option<Vec<String>> {
.show()
.ok();
return None;
} else if args[0] == "--install-cert" {
#[cfg(windows)]
hbb_common::allow_err!(crate::platform::windows::install_cert(
crate::platform::windows::DRIVER_CERT_FILE
));
if args.len() > 1 && args[1] == "silent" {
return None;
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
if crate::virtual_display_manager::is_virtual_display_supported() {
hbb_common::allow_err!(crate::virtual_display_manager::install_update_driver());
}
return None;
} else if args[0] == "--uninstall-cert" {
#[cfg(windows)]
hbb_common::allow_err!(crate::platform::windows::uninstall_cert());
return None;
} else if args[0] == "--install-idd" {
#[cfg(windows)]
{
// It's ok to install cert multiple times.
hbb_common::allow_err!(crate::platform::windows::install_cert(
crate::platform::windows::DRIVER_CERT_FILE
));
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
if crate::virtual_display_manager::is_virtual_display_supported() {
hbb_common::allow_err!(crate::virtual_display_manager::install_update_driver());
hbb_common::allow_err!(
crate::virtual_display_manager::rustdesk_idd::install_update_driver()
);
}
return None;
} else if args[0] == "--portable-service" {
@@ -245,6 +232,12 @@ pub fn core_main() -> Option<Vec<String>> {
_is_run_as_system,
);
return None;
} else if args[0] == "--uninstall-amyuni-idd" {
#[cfg(all(windows, feature = "virtual_display_driver"))]
hbb_common::allow_err!(
crate::virtual_display_manager::amyuni_idd::uninstall_driver()
);
return None;
}
}
if args[0] == "--remove" {
@@ -274,7 +267,7 @@ pub fn core_main() -> Option<Vec<String>> {
} else if args[0] == "--server" {
log::info!("start --server with user {}", crate::username());
#[cfg(all(windows, feature = "virtual_display_driver"))]
crate::privacy_mode::restore_reg_connectivity();
crate::privacy_mode::restore_reg_connectivity(true);
#[cfg(any(target_os = "linux", target_os = "windows"))]
{
crate::start_server(true);
@@ -430,10 +423,6 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(feature = "hwcodec")]
scrap::hwcodec::check_available_hwcodec();
return None;
} else if args[0] == "--check-gpucodec-config" {
#[cfg(feature = "gpucodec")]
scrap::gpucodec::check_available_gpucodec();
return None;
} else if args[0] == "--cm" {
// call connection manager to establish connections
// meanwhile, return true to call flutter window to show control panel

View File

@@ -4,7 +4,7 @@ use crate::{
ui_session_interface::{io_loop, InvokeUiSession, Session},
};
use flutter_rust_bridge::StreamSink;
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
use hbb_common::dlopen::{
symbor::{Library, Symbol},
Error as LibError,
@@ -16,7 +16,7 @@ use hbb_common::{
use serde::Serialize;
use serde_json::json;
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
use std::os::raw::c_void;
use std::{
@@ -63,7 +63,7 @@ lazy_static::lazy_static! {
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open_self();
}
#[cfg(all(target_os = "windows", feature = "gpucodec"))]
#[cfg(all(target_os = "windows", feature = "vram"))]
lazy_static::lazy_static! {
pub static ref TEXTURE_GPU_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("flutter_gpu_texture_renderer_plugin.dll");
}
@@ -168,15 +168,15 @@ pub unsafe extern "C" fn get_rustdesk_app_name(buffer: *mut u16, length: i32) ->
#[derive(Default)]
struct SessionHandler {
event_stream: Option<StreamSink<EventToUI>>,
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
renderer: VideoRenderer,
}
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum RenderType {
PixelBuffer,
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
Texture,
}
@@ -214,41 +214,41 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
dst_rgba_stride: c_int,
);
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
pub type FlutterGpuTextureRendererPluginCApiSetTexture =
unsafe extern "C" fn(output: *mut c_void, texture: *mut c_void);
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
pub type FlutterGpuTextureRendererPluginCApiGetAdapterLuid = unsafe extern "C" fn() -> i64;
#[cfg(feature = "flutter_texture_render")]
pub(super) type TextureRgbaPtr = usize;
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
struct DisplaySessionInfo {
// TextureRgba pointer in flutter native.
#[cfg(feature = "flutter_texture_render")]
texture_rgba_ptr: TextureRgbaPtr,
#[cfg(feature = "flutter_texture_render")]
size: (usize, usize),
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
gpu_output_ptr: usize,
notify_render_type: Option<RenderType>,
}
// Video Texture Renderer in Flutter
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
#[derive(Clone)]
struct VideoRenderer {
is_support_multi_ui_session: bool,
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
#[cfg(feature = "flutter_texture_render")]
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
on_texture_func: Option<Symbol<'static, FlutterGpuTextureRendererPluginCApiSetTexture>>,
}
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
impl Default for VideoRenderer {
fn default() -> Self {
#[cfg(feature = "flutter_texture_render")]
@@ -270,7 +270,7 @@ impl Default for VideoRenderer {
None
}
};
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
let on_texture_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
Ok(lib) => {
let find_sym_res = unsafe {
@@ -297,13 +297,13 @@ impl Default for VideoRenderer {
is_support_multi_ui_session: false,
#[cfg(feature = "flutter_texture_render")]
on_rgba_func,
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
on_texture_func,
}
}
}
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
impl VideoRenderer {
#[inline]
#[cfg(feature = "flutter_texture_render")]
@@ -318,7 +318,7 @@ impl VideoRenderer {
DisplaySessionInfo {
texture_rgba_ptr: usize::default(),
size: (width, height),
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
gpu_output_ptr: usize::default(),
notify_render_type: None,
},
@@ -345,7 +345,7 @@ impl VideoRenderer {
DisplaySessionInfo {
texture_rgba_ptr: ptr as _,
size: (0, 0),
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
gpu_output_ptr: usize::default(),
notify_render_type: None,
},
@@ -355,7 +355,7 @@ impl VideoRenderer {
}
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
pub fn register_gpu_output(&self, display: usize, ptr: usize) {
let mut sessions_lock = self.map_display_sessions.write().unwrap();
if ptr == 0 {
@@ -434,7 +434,7 @@ impl VideoRenderer {
}
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
pub fn on_texture(&self, display: usize, texture: *mut c_void) -> bool {
let mut write_lock = self.map_display_sessions.write().unwrap();
let opt_info = if !self.is_support_multi_ui_session {
@@ -793,7 +793,7 @@ impl InvokeUiSession for FlutterHandler {
}
#[inline]
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
fn on_texture(&self, display: usize, texture: *mut c_void) {
for (_, session) in self.session_handlers.read().unwrap().iter() {
if session.renderer.on_texture(display, texture) {
@@ -1073,9 +1073,9 @@ pub fn session_add(
Some(switch_uuid.to_string())
};
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
let adapter_luid = get_adapter_luid();
#[cfg(not(feature = "gpucodec"))]
#[cfg(not(feature = "vram"))]
let adapter_luid = None;
session.lc.write().unwrap().initialize(
@@ -1106,7 +1106,7 @@ pub fn session_start_(
) -> ResultType<()> {
// is_connected is used to indicate whether to start a peer connection. For two cases:
// 1. "Move tab to new window"
// 2. multi ui session within the same peer connnection.
// 2. multi ui session within the same peer connection.
let mut is_connected = false;
let mut is_found = false;
for s in sessions::get_sessions() {
@@ -1453,7 +1453,7 @@ pub fn session_register_pixelbuffer_texture(_session_id: SessionID, _display: us
#[inline]
pub fn session_register_gpu_texture(_session_id: SessionID, _display: usize, _output_ptr: usize) {
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
for s in sessions::get_sessions() {
if let Some(h) = s
.ui_handler
@@ -1468,7 +1468,7 @@ pub fn session_register_gpu_texture(_session_id: SessionID, _display: usize, _ou
}
}
#[cfg(feature = "gpucodec")]
#[cfg(feature = "vram")]
pub fn get_adapter_luid() -> Option<i64> {
let get_adapter_luid_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
Ok(lib) => {

View File

@@ -39,11 +39,15 @@ lazy_static::lazy_static! {
static ref TEXTURE_RENDER_KEY: Arc<AtomicI32> = Arc::new(AtomicI32::new(0));
}
fn initialize(app_dir: &str) {
fn initialize(app_dir: &str, custom_client_config: &str) {
flutter::async_tasks::start_flutter_async_runner();
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
// core_main's load_custom_client does not work for flutter since it is only applied to its load_library in main.c
crate::load_custom_client();
if custom_client_config.is_empty() {
crate::load_custom_client();
} else {
crate::read_custom_client(custom_client_config);
}
#[cfg(target_os = "android")]
{
// flexi_logger can't work when android_logger initialized.
@@ -1282,8 +1286,8 @@ pub fn cm_get_clients_length() -> usize {
crate::ui_cm_interface::get_clients_length()
}
pub fn main_init(app_dir: String) {
initialize(&app_dir);
pub fn main_init(app_dir: String, custom_client_config: String) {
initialize(&app_dir, &custom_client_config);
}
pub fn main_device_id(id: String) {
@@ -1302,8 +1306,8 @@ pub fn main_has_hwcodec() -> SyncReturn<bool> {
SyncReturn(has_hwcodec())
}
pub fn main_has_gpucodec() -> SyncReturn<bool> {
SyncReturn(has_gpucodec())
pub fn main_has_vram() -> SyncReturn<bool> {
SyncReturn(has_vram())
}
pub fn main_supported_hwdecodings() -> SyncReturn<String> {
@@ -1789,7 +1793,7 @@ pub fn main_has_file_clipboard() -> SyncReturn<bool> {
}
pub fn main_has_gpu_texture_render() -> SyncReturn<bool> {
SyncReturn(cfg!(feature = "gpucodec"))
SyncReturn(cfg!(feature = "vram"))
}
pub fn cm_init() {
@@ -2103,6 +2107,16 @@ pub fn main_get_hard_option(key: String) -> SyncReturn<String> {
SyncReturn(get_hard_option(key))
}
pub fn main_check_hwcodec() {
check_hwcodec()
}
pub fn session_request_new_display_init_msgs(session_id: SessionID, display: usize) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.request_init_msgs(display);
}
}
#[cfg(target_os = "android")]
pub mod server_side {
use hbb_common::{config, log};
@@ -2115,31 +2129,35 @@ pub mod server_side {
use crate::start_server;
#[no_mangle]
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer(
pub unsafe extern "system" fn Java_ffi_FFI_startServer(
env: JNIEnv,
_class: JClass,
app_dir: JString,
custom_client_config: JString,
) {
log::debug!("startServer from jvm");
let mut env = env;
if let Ok(app_dir) = env.get_string(&app_dir) {
*config::APP_DIR.write().unwrap() = app_dir.into();
}
if let Ok(custom_client_config) = env.get_string(&custom_client_config) {
if !custom_client_config.is_empty() {
let custom_client_config: String = custom_client_config.into();
crate::read_custom_client(&custom_client_config);
}
}
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,
) {
pub unsafe extern "system" fn Java_ffi_FFI_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]
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
pub unsafe extern "system" fn Java_ffi_FFI_translateLocale(
env: JNIEnv,
_class: JClass,
locale: JString,
@@ -2158,10 +2176,7 @@ pub mod server_side {
}
#[no_mangle]
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_refreshScreen(
_env: JNIEnv,
_class: JClass,
) {
pub unsafe extern "system" fn Java_ffi_FFI_refreshScreen(_env: JNIEnv, _class: JClass) {
crate::server::video_service::refresh()
}
}

View File

@@ -232,6 +232,7 @@ pub enum Data {
#[cfg(windows)]
ControlledSessionCount(usize),
CmErr(String),
CheckHwcodec,
}
#[tokio::main(flavor = "current_thread")]
@@ -502,6 +503,14 @@ async fn handle(data: Data, stream: &mut Connection) {
.await
);
}
Data::CheckHwcodec =>
{
#[cfg(feature = "hwcodec")]
#[cfg(any(target_os = "windows", target_os = "linux"))]
if crate::platform::is_root() {
scrap::hwcodec::start_check_process(true);
}
}
_ => {}
}
}
@@ -929,6 +938,12 @@ pub async fn connect_to_user_session(usid: Option<u32>) -> ResultType<()> {
Ok(())
}
#[tokio::main(flavor = "current_thread")]
pub async fn notify_server_to_check_hwcodec() -> ResultType<()> {
connect(1_000, "").await?.send(&&Data::CheckHwcodec).await?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -16,6 +16,7 @@ mod et;
mod fa;
mod fr;
mod he;
mod hr;
mod hu;
mod id;
mod it;
@@ -81,6 +82,7 @@ pub const LANGS: &[(&str, &str)] = &[
("lv", "Latviešu"),
("ar", "العربية"),
("he", "עברית"),
("hr", "Hrvatski"),
];
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -152,6 +154,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"ar" => ar::T.deref(),
"bg" => bg::T.deref(),
"he" => he::T.deref(),
"hr" => hr::T.deref(),
_ => en::T.deref(),
};
let (name, placeholder_value) = extract_placeholder(&name);

View File

@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("share_warning_tip", ""),
("Everyone", ""),
("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect();
}

View File

@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("share_warning_tip", ""),
("Everyone", ""),
("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect();
}

View File

@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("share_warning_tip", ""),
("Everyone", ""),
("ab_web_console_tip", ""),
("allow-only-conn-window-open-tip", ""),
("no_need_privacy_mode_no_physical_displays_tip", ""),
].iter().cloned().collect();
}

View File

@@ -590,7 +590,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("powered_by_me", "由 RustDesk 提供支持"),
("outgoing_only_desk_tip", "当前版本的软件是定制版本。\n您可以连接至其他设备,但是其他设备无法连接至您的设备。"),
("preset_password_warning", "此定制版本附有预设密码。 任何知晓此密码的人都能完全控制您的设备。如果这不是您所预期的,请立即卸载此软件。"),
("Security Alert", ""),
("Security Alert", "安全警告"),
("My address book", "我的地址簿"),
("Personal", "个人的"),
("Owner", "所有者"),
@@ -602,5 +602,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("share_warning_tip", "上述的字段為共享且对其他人可见。"),
("Everyone", "所有人"),
("ab_web_console_tip", "打开 Web 控制台以执行更多操作"),
("allow-only-conn-window-open-tip", "仅当 RustDesk 窗口打开时允许连接"),
("no_need_privacy_mode_no_physical_displays_tip", "没有物理显示器,没必要使用隐私模式。"),
].iter().cloned().collect();
}

View File

@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("share_warning_tip", "Výše uvedená pole jsou sdílená a viditelná pro ostatní."),
("Everyone", "Každý"),
("ab_web_console_tip", "Více na webové konzoli"),
("allow-only-conn-window-open-tip", "Povolit připojení pouze v případě, že je otevřené okno RustDesk"),
("no_need_privacy_mode_no_physical_displays_tip", "Žádné fyzické displeje, není třeba používat režim soukromí."),
].iter().cloned().collect();
}

Some files were not shown because too many files have changed in this diff Show More