mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-06 14:51:28 +03:00
Merge branch 'master' into master
This commit is contained in:
5
.github/workflows/bridge.yml
vendored
5
.github/workflows/bridge.yml
vendored
@@ -8,6 +8,7 @@ on:
|
|||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: "3.16.9"
|
FLUTTER_VERSION: "3.16.9"
|
||||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
||||||
|
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
generate_bridge:
|
generate_bridge:
|
||||||
@@ -49,9 +50,9 @@ jobs:
|
|||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@v1
|
uses: dtolnay/rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: ${{ env.RUST_VERSION }}
|
||||||
targets: ${{ matrix.job.target }}
|
targets: ${{ matrix.job.target }}
|
||||||
components: ''
|
components: "rustfmt"
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
5
.github/workflows/build-macos-arm64.yml
vendored
5
.github/workflows/build-macos-arm64.yml
vendored
@@ -54,8 +54,3 @@ jobs:
|
|||||||
- name: Run
|
- name: Run
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
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
|
|
||||||
|
|||||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -71,12 +71,12 @@ jobs:
|
|||||||
# - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
# - { 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-gnueabihf , os: ubuntu-20.04, use-cross: true }
|
||||||
# - { target: arm-unknown-linux-musleabihf, 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-gnu , os: ubuntu-20.04, use-cross: true }
|
||||||
# - { target: i686-unknown-linux-musl , 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-apple-darwin , os: macos-10.15 }
|
||||||
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
|
||||||
# - { target: x86_64-pc-windows-msvc , os: windows-2019 }
|
# - { target: x86_64-pc-windows-msvc , os: windows-2022 }
|
||||||
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 }
|
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 }
|
||||||
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
68
.github/workflows/flutter-build.yml
vendored
68
.github/workflows/flutter-build.yml
vendored
@@ -14,7 +14,7 @@ env:
|
|||||||
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
|
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
|
||||||
CARGO_NDK_VERSION: "3.1.2"
|
CARGO_NDK_VERSION: "3.1.2"
|
||||||
LLVM_VERSION: "15.0.6"
|
LLVM_VERSION: "15.0.6"
|
||||||
FLUTTER_VERSION: "3.16.9"
|
FLUTTER_VERSION: "3.19.6"
|
||||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
||||||
# for arm64 linux because official Dart SDK does not work
|
# for arm64 linux because official Dart SDK does not work
|
||||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
|
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
|
||||||
with:
|
with:
|
||||||
upload-artifact: ${{ inputs.upload-artifact }}
|
upload-artifact: ${{ inputs.upload-artifact }}
|
||||||
target: windows-2019
|
target: windows-2022
|
||||||
configuration: Release
|
configuration: Release
|
||||||
platform: x64
|
platform: x64
|
||||||
target_version: Windows10
|
target_version: Windows10
|
||||||
@@ -56,10 +56,10 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
# - { target: i686-pc-windows-msvc , os: windows-2022 }
|
||||||
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
|
||||||
- { target: x86_64-pc-windows-msvc, os: windows-2019, arch: x86_64 }
|
- { target: x86_64-pc-windows-msvc, os: windows-2022, arch: x86_64 }
|
||||||
# - { target: aarch64-pc-windows-msvc, os: windows-2019, arch: aarch64 }
|
# - { target: aarch64-pc-windows-msvc, os: windows-2022, arch: aarch64 }
|
||||||
steps:
|
steps:
|
||||||
- name: Export GitHub Actions cache environment variables
|
- name: Export GitHub Actions cache environment variables
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
@@ -83,14 +83,6 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
cache: true
|
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
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@v1
|
uses: dtolnay/rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
@@ -121,7 +113,13 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build rustdesk
|
- 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
|
- name: find Runner.res
|
||||||
# Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./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
|
mkdir -p ./SignOutput
|
||||||
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.exe
|
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
|
- name: Sign rustdesk self-extracted file
|
||||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != ''
|
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != ''
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -183,6 +194,7 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
tag_name: ${{ env.TAG_NAME }}
|
tag_name: ${{ env.TAG_NAME }}
|
||||||
files: |
|
files: |
|
||||||
|
./SignOutput/rustdesk-*.msi
|
||||||
./SignOutput/rustdesk-*.exe
|
./SignOutput/rustdesk-*.exe
|
||||||
./rustdesk-*.tar.gz
|
./rustdesk-*.tar.gz
|
||||||
|
|
||||||
@@ -196,10 +208,10 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
# - { target: i686-pc-windows-msvc , os: windows-2022 }
|
||||||
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
|
||||||
- { target: i686-pc-windows-msvc, os: windows-2019, arch: x86 }
|
- { target: i686-pc-windows-msvc, os: windows-2022, arch: x86 }
|
||||||
# - { target: aarch64-pc-windows-msvc, os: windows-2019 }
|
# - { target: aarch64-pc-windows-msvc, os: windows-2022 }
|
||||||
steps:
|
steps:
|
||||||
- name: Export GitHub Actions cache environment variables
|
- name: Export GitHub Actions cache environment variables
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
@@ -245,11 +257,15 @@ jobs:
|
|||||||
python3 res/inline-sciter.py
|
python3 res/inline-sciter.py
|
||||||
# Patch sciter x86
|
# Patch sciter x86
|
||||||
sed -i 's/branch = "dyn"/branch = "dyn_x86"/g' ./Cargo.toml
|
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
|
mkdir -p ./Release
|
||||||
mv ./target/release/rustdesk.exe ./Release/rustdesk.exe
|
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
|
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
|
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
|
- name: find Runner.res
|
||||||
# Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./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
|
- name: Build rustdesk
|
||||||
run: |
|
run: |
|
||||||
# --hwcodec not supported on macos yet
|
./build.py --flutter --hwcodec
|
||||||
./build.py --flutter
|
|
||||||
|
|
||||||
- name: create unsigned dmg
|
- name: create unsigned dmg
|
||||||
if: env.UPLOAD_ARTIFACT == 'true'
|
if: env.UPLOAD_ARTIFACT == 'true'
|
||||||
@@ -480,8 +495,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
run: |
|
run: |
|
||||||
# --hwcodec not supported on macos yet
|
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
|
||||||
./build.py --flutter ${{ matrix.job.extra-build-args }}
|
|
||||||
|
|
||||||
- name: create unsigned dmg
|
- name: create unsigned dmg
|
||||||
if: env.UPLOAD_ARTIFACT == 'true'
|
if: env.UPLOAD_ARTIFACT == 'true'
|
||||||
@@ -630,7 +644,7 @@ jobs:
|
|||||||
- name: Build rustdesk lib
|
- name: Build rustdesk lib
|
||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.job.target }}
|
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
|
- name: Build rustdesk
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -1251,7 +1265,7 @@ jobs:
|
|||||||
export DEFAULT_FEAT=linux_headless
|
export DEFAULT_FEAT=linux_headless
|
||||||
fi
|
fi
|
||||||
export CARGO_INCREMENTAL=0
|
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
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@master
|
uses: actions/upload-artifact@master
|
||||||
@@ -1594,7 +1608,7 @@ jobs:
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
export CARGO_INCREMENTAL=0
|
export CARGO_INCREMENTAL=0
|
||||||
python3 ./build.py --flutter --hwcodec --skip-cargo
|
python3 ./build.py --flutter --skip-cargo
|
||||||
# rpm package
|
# rpm package
|
||||||
echo -e "start packaging fedora package"
|
echo -e "start packaging fedora package"
|
||||||
pushd /workspace
|
pushd /workspace
|
||||||
|
|||||||
2
.github/workflows/history.yml
vendored
2
.github/workflows/history.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
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:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ on:
|
|||||||
description: 'Target'
|
description: 'Target'
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
default: 'windows-2019'
|
default: 'windows-2022'
|
||||||
configuration:
|
configuration:
|
||||||
description: 'Configuration'
|
description: 'Configuration'
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
84
Cargo.lock
generated
84
Cargo.lock
generated
@@ -115,17 +115,6 @@ dependencies = [
|
|||||||
"pkg-config",
|
"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]]
|
[[package]]
|
||||||
name = "android-tzdata"
|
name = "android-tzdata"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -2716,36 +2705,6 @@ dependencies = [
|
|||||||
"system-deps 6.1.2",
|
"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]]
|
[[package]]
|
||||||
name = "gstreamer"
|
name = "gstreamer"
|
||||||
version = "0.16.7"
|
version = "0.16.7"
|
||||||
@@ -3140,8 +3099,8 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hwcodec"
|
name = "hwcodec"
|
||||||
version = "0.2.0"
|
version = "0.3.3"
|
||||||
source = "git+https://github.com/21pages/hwcodec?branch=stable#52e1da2aae86acec5f374bc065f5921945b55e7b"
|
source = "git+https://github.com/21pages/hwcodec#eeebf980d4eb41daaf05090b097d5a59d688d3d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen 0.59.2",
|
"bindgen 0.59.2",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -4224,17 +4183,6 @@ dependencies = [
|
|||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "objc"
|
name = "objc"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
@@ -4852,9 +4800,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf"
|
name = "protobuf"
|
||||||
version = "3.3.0"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190"
|
checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -4864,9 +4812,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf-codegen"
|
name = "protobuf-codegen"
|
||||||
version = "3.3.0"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e85514a216b1c73111d9032e26cc7a5ecb1bb3d4d9539e91fb72a4395060f78"
|
checksum = "32777b0b3f6538d9d2e012b3fad85c7e4b9244b5958d04a6415f4333782b7a77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -4879,9 +4827,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf-parse"
|
name = "protobuf-parse"
|
||||||
version = "3.3.0"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77d6fbd6697c9e531873e81cec565a85e226b99a0f10e1acc079be057fe2fcba"
|
checksum = "96cb37955261126624a25b5e6bda40ae34cf3989d52a783087ca6091b29b5642"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
@@ -4895,9 +4843,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "protobuf-support"
|
name = "protobuf-support"
|
||||||
version = "3.3.0"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c"
|
checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
@@ -5856,7 +5804,6 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"dbus",
|
"dbus",
|
||||||
"docopt",
|
"docopt",
|
||||||
"gpucodec",
|
|
||||||
"gstreamer",
|
"gstreamer",
|
||||||
"gstreamer-app",
|
"gstreamer-app",
|
||||||
"gstreamer-video",
|
"gstreamer-video",
|
||||||
@@ -7097,17 +7044,6 @@ dependencies = [
|
|||||||
"lazy_static",
|
"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]]
|
[[package]]
|
||||||
name = "waker-fn"
|
name = "waker-fn"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
|
|||||||
17
Cargo.toml
17
Cargo.toml
@@ -28,7 +28,7 @@ use_dasp = ["dasp"]
|
|||||||
flutter = ["flutter_rust_bridge"]
|
flutter = ["flutter_rust_bridge"]
|
||||||
default = ["use_dasp"]
|
default = ["use_dasp"]
|
||||||
hwcodec = ["scrap/hwcodec"]
|
hwcodec = ["scrap/hwcodec"]
|
||||||
gpucodec = ["scrap/gpucodec"]
|
vram = ["scrap/vram"]
|
||||||
mediacodec = ["scrap/mediacodec"]
|
mediacodec = ["scrap/mediacodec"]
|
||||||
linux_headless = ["pam" ]
|
linux_headless = ["pam" ]
|
||||||
virtual_display_driver = ["virtual_display"]
|
virtual_display_driver = ["virtual_display"]
|
||||||
@@ -99,7 +99,20 @@ system_shutdown = "4.0"
|
|||||||
qrcode-generator = "4.1"
|
qrcode-generator = "4.1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[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"
|
winreg = "0.11"
|
||||||
windows-service = "0.6"
|
windows-service = "0.6"
|
||||||
virtual_display = { path = "libs/virtual_display", optional = true }
|
virtual_display = { path = "libs/virtual_display", optional = true }
|
||||||
|
|||||||
23
build.py
23
build.py
@@ -33,9 +33,9 @@ def get_arch() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def system2(cmd):
|
def system2(cmd):
|
||||||
err = os.system(cmd)
|
exit_code = os.system(cmd)
|
||||||
if err != 0:
|
if exit_code != 0:
|
||||||
print(f"Error occurred when executing: {cmd}. Exiting.")
|
sys.stderr.write(f"Error occurred when executing: `{cmd}`. Exiting.\n")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
@@ -118,9 +118,9 @@ def make_parser():
|
|||||||
'' if windows or osx else ', need libva-dev, libvdpau-dev.')
|
'' if windows or osx else ', need libva-dev, libvdpau-dev.')
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--gpucodec',
|
'--vram',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Enable feature gpucodec, only available on windows now.'
|
help='Enable feature vram, only available on windows now.'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--portable',
|
'--portable',
|
||||||
@@ -153,6 +153,12 @@ def make_parser():
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='Skip packing, only flutter version + Windows supported'
|
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(
|
parser.add_argument(
|
||||||
"--package",
|
"--package",
|
||||||
type=str
|
type=str
|
||||||
@@ -282,8 +288,8 @@ def get_features(args):
|
|||||||
features = ['inline'] if not args.flutter else []
|
features = ['inline'] if not args.flutter else []
|
||||||
if args.hwcodec:
|
if args.hwcodec:
|
||||||
features.append('hwcodec')
|
features.append('hwcodec')
|
||||||
if args.gpucodec:
|
if args.vram:
|
||||||
features.append('gpucodec')
|
features.append('vram')
|
||||||
if args.flutter:
|
if args.flutter:
|
||||||
features.append('flutter')
|
features.append('flutter')
|
||||||
features.append('flutter_texture_render')
|
features.append('flutter_texture_render')
|
||||||
@@ -293,6 +299,9 @@ def get_features(args):
|
|||||||
features.append('appimage')
|
features.append('appimage')
|
||||||
if args.unix_file_copy_paste:
|
if args.unix_file_copy_paste:
|
||||||
features.append('unix-file-copy-paste')
|
features.append('unix-file-copy-paste')
|
||||||
|
if windows:
|
||||||
|
if args.virtual_display:
|
||||||
|
features.append('virtual_display_driver')
|
||||||
print("features:", features)
|
print("features:", features)
|
||||||
return features
|
return features
|
||||||
|
|
||||||
|
|||||||
4
build.rs
4
build.rs
@@ -1,9 +1,11 @@
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn build_windows() {
|
fn build_windows() {
|
||||||
let file = "src/platform/windows.cc";
|
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:rustc-link-lib=WtsApi32");
|
||||||
println!("cargo:rerun-if-changed={}", file);
|
println!("cargo:rerun-if-changed={}", file);
|
||||||
|
println!("cargo:rerun-if-changed={}", file2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<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>
|
<img src="../res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||||
<a href="#free-public-servers">Máy chủ</a> •
|
<a href="#free-public-servers">Server</a> •
|
||||||
<a href="#raw-steps-to-build">Build</a> •
|
<a href="#raw-steps-to-build">Build</a> •
|
||||||
<a href="#how-to-build-with-docker">Docker</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="#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>
|
[<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>
|
</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)
|
||||||
|
|
||||||
[](https://ko-fi.com/I2I04VU09)
|
[](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).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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)
|
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||||
|
[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases)
|
||||||
[**CÁC BẢN PHÂN PHÁT MÃ NHỊ PHÂN**](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"
|
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||||
alt="Get it on F-Droid"
|
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
|
## 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 bạn 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 bản 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) |
|
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.
|
||||||
[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)
|
|
||||||
|
|
||||||
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.
|
- 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`.
|
||||||
|
|
||||||
- Đố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
|
|
||||||
|
|
||||||
|
- 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`
|
- Chạy lệnh `cargo run`
|
||||||
|
|
||||||
## [Build](https://rustdesk.com/docs/en/dev/build/)
|
## [Build](https://rustdesk.com/docs/en/dev/build/)
|
||||||
|
|
||||||
## Cách để build cho Linux
|
## Cách build cho Linux
|
||||||
|
|
||||||
### Ubuntu 18 (Debian 10)
|
### 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
|
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
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
@@ -82,7 +81,7 @@ export VCPKG_ROOT=$HOME/vcpkg
|
|||||||
vcpkg/vcpkg install libvpx libyuv opus aom
|
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
|
```sh
|
||||||
cd vcpkg/buildtrees/libvpx/src
|
cd vcpkg/buildtrees/libvpx/src
|
||||||
@@ -95,7 +94,7 @@ cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
|||||||
cd
|
cd
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cách build
|
### Build
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | 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
|
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
|
```sh
|
||||||
git clone https://github.com/rustdesk/rustdesk
|
git clone https://github.com/rustdesk/rustdesk
|
||||||
@@ -118,37 +117,37 @@ cd rustdesk
|
|||||||
docker build -t "rustdesk-builder" .
|
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
|
```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
|
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, những 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
|
```sh
|
||||||
target/debug/rustdesk
|
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
|
```sh
|
||||||
target/release/rustdesk
|
target/release/rustdesk
|
||||||
```
|
```
|
||||||
|
|
||||||
Hãy đảm bảo là 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
|
## 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/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/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/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/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/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/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/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
|
- **[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
|
- **[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
|
## Snapshot
|
||||||
|
|||||||
@@ -108,4 +108,3 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("$kotlin_version") } }
|
implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("$kotlin_version") } }
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.google.gms.google-services'
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.carriez.flutter_hbb
|
package com.carriez.flutter_hbb
|
||||||
|
|
||||||
|
import ffi.FFI
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Capture screen,get video and audio,send to rust.
|
* Capture screen,get video and audio,send to rust.
|
||||||
* Dispatch notifications
|
* Dispatch notifications
|
||||||
@@ -64,10 +66,6 @@ const val AUDIO_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO
|
|||||||
|
|
||||||
class MainService : Service() {
|
class MainService : Service() {
|
||||||
|
|
||||||
init {
|
|
||||||
System.loadLibrary("rustdesk")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
fun rustPointerInput(kind: String, mask: Int, x: Int, y: Int) {
|
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 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")}
|
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 {
|
private fun translate(input: String): String {
|
||||||
Log.d(logTag, "translate:$LOCAL_NAME")
|
Log.d(logTag, "translate:$LOCAL_NAME")
|
||||||
return translateLocale(LOCAL_NAME, input)
|
return FFI.translateLocale(LOCAL_NAME, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -211,7 +195,7 @@ class MainService : Service() {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Log.d(logTag,"MainService onCreate")
|
Log.d(logTag,"MainService onCreate")
|
||||||
init(this)
|
FFI.init(this)
|
||||||
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
|
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
|
||||||
start()
|
start()
|
||||||
serviceLooper = looper
|
serviceLooper = looper
|
||||||
@@ -223,7 +207,7 @@ class MainService : Service() {
|
|||||||
// keep the config dir same with flutter
|
// keep the config dir same with flutter
|
||||||
val prefs = applicationContext.getSharedPreferences(KEY_SHARED_PREFERENCES, FlutterActivity.MODE_PRIVATE)
|
val prefs = applicationContext.getSharedPreferences(KEY_SHARED_PREFERENCES, FlutterActivity.MODE_PRIVATE)
|
||||||
val configPath = prefs.getString(KEY_APP_DIR_CONFIG_PATH, "") ?: ""
|
val configPath = prefs.getString(KEY_APP_DIR_CONFIG_PATH, "") ?: ""
|
||||||
startServer(configPath)
|
FFI.startServer(configPath, "")
|
||||||
|
|
||||||
createForegroundNotification()
|
createForegroundNotification()
|
||||||
}
|
}
|
||||||
@@ -278,7 +262,7 @@ class MainService : Service() {
|
|||||||
SCREEN_INFO.dpi = dpi
|
SCREEN_INFO.dpi = dpi
|
||||||
if (isStart) {
|
if (isStart) {
|
||||||
stopCapture()
|
stopCapture()
|
||||||
refreshScreen()
|
FFI.refreshScreen()
|
||||||
startCapture()
|
startCapture()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,7 +290,7 @@ class MainService : Service() {
|
|||||||
createForegroundNotification()
|
createForegroundNotification()
|
||||||
|
|
||||||
if (intent.getBooleanExtra(EXT_INIT_FROM_BOOT, false)) {
|
if (intent.getBooleanExtra(EXT_INIT_FROM_BOOT, false)) {
|
||||||
startService()
|
FFI.startService()
|
||||||
}
|
}
|
||||||
Log.d(logTag, "service starting: ${startId}:${Thread.currentThread()}")
|
Log.d(logTag, "service starting: ${startId}:${Thread.currentThread()}")
|
||||||
val mediaProjectionManager =
|
val mediaProjectionManager =
|
||||||
@@ -354,12 +338,15 @@ class MainService : Service() {
|
|||||||
).apply {
|
).apply {
|
||||||
setOnImageAvailableListener({ imageReader: ImageReader ->
|
setOnImageAvailableListener({ imageReader: ImageReader ->
|
||||||
try {
|
try {
|
||||||
|
if (!isStart) {
|
||||||
|
return@setOnImageAvailableListener
|
||||||
|
}
|
||||||
imageReader.acquireLatestImage().use { image ->
|
imageReader.acquireLatestImage().use { image ->
|
||||||
if (image == null) return@setOnImageAvailableListener
|
if (image == null || !isStart) return@setOnImageAvailableListener
|
||||||
val planes = image.planes
|
val planes = image.planes
|
||||||
val buffer = planes[0].buffer
|
val buffer = planes[0].buffer
|
||||||
buffer.rewind()
|
buffer.rewind()
|
||||||
onVideoFrameUpdate(buffer)
|
FFI.onVideoFrameUpdate(buffer)
|
||||||
}
|
}
|
||||||
} catch (ignored: java.lang.Exception) {
|
} catch (ignored: java.lang.Exception) {
|
||||||
}
|
}
|
||||||
@@ -393,21 +380,24 @@ class MainService : Service() {
|
|||||||
}
|
}
|
||||||
checkMediaPermission()
|
checkMediaPermission()
|
||||||
_isStart = true
|
_isStart = true
|
||||||
setFrameRawEnable("video",true)
|
FFI.setFrameRawEnable("video",true)
|
||||||
setFrameRawEnable("audio",true)
|
FFI.setFrameRawEnable("audio",true)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun stopCapture() {
|
fun stopCapture() {
|
||||||
Log.d(logTag, "Stop Capture")
|
Log.d(logTag, "Stop Capture")
|
||||||
setFrameRawEnable("video",false)
|
FFI.setFrameRawEnable("video",false)
|
||||||
setFrameRawEnable("audio",false)
|
FFI.setFrameRawEnable("audio",false)
|
||||||
_isStart = false
|
_isStart = false
|
||||||
// release video
|
// release video
|
||||||
virtualDisplay?.release()
|
virtualDisplay?.release()
|
||||||
surface?.release()
|
|
||||||
imageReader?.close()
|
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 {
|
videoEncoder?.let {
|
||||||
it.signalEndOfInputStream()
|
it.signalEndOfInputStream()
|
||||||
it.stop()
|
it.stop()
|
||||||
@@ -418,9 +408,6 @@ class MainService : Service() {
|
|||||||
|
|
||||||
// release audio
|
// release audio
|
||||||
audioRecordStat = false
|
audioRecordStat = false
|
||||||
audioRecorder?.release()
|
|
||||||
audioRecorder = null
|
|
||||||
minBufferSize = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
@@ -428,8 +415,6 @@ class MainService : Service() {
|
|||||||
_isReady = false
|
_isReady = false
|
||||||
|
|
||||||
stopCapture()
|
stopCapture()
|
||||||
imageReader?.close()
|
|
||||||
imageReader = null
|
|
||||||
|
|
||||||
mediaProjection = null
|
mediaProjection = null
|
||||||
checkMediaPermission()
|
checkMediaPermission()
|
||||||
@@ -537,9 +522,13 @@ class MainService : Service() {
|
|||||||
thread {
|
thread {
|
||||||
while (audioRecordStat) {
|
while (audioRecordStat) {
|
||||||
audioReader!!.readSync(audioRecorder!!)?.let {
|
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")
|
Log.d(logTag, "Exit audio thread")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
21
flutter/android/app/src/main/kotlin/ffi.kt
Normal file
21
flutter/android/app/src/main/kotlin/ffi.kt
Normal 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 |
@@ -3106,6 +3106,27 @@ Color? disabledTextColor(BuildContext context, bool enabled) {
|
|||||||
: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6);
|
: 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
|
// max 300 x 60
|
||||||
Widget loadLogo() {
|
Widget loadLogo() {
|
||||||
return FutureBuilder<ByteData>(
|
return FutureBuilder<ByteData>(
|
||||||
@@ -3155,3 +3176,41 @@ bool isInHomePage() {
|
|||||||
final controller = Get.find<DesktopTabController>();
|
final controller = Get.find<DesktopTabController>();
|
||||||
return controller.state.value.selected == 0;
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -137,11 +137,13 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
|
|
||||||
Widget _createSwitchBar(BuildContext context) {
|
Widget _createSwitchBar(BuildContext context) {
|
||||||
final model = Provider.of<PeerTabModel>(context);
|
final model = Provider.of<PeerTabModel>(context);
|
||||||
|
var counter = -1;
|
||||||
return ListView(
|
return ReorderableListView(
|
||||||
|
buildDefaultDragHandles: false,
|
||||||
|
onReorder: model.reorder,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
physics: NeverScrollableScrollPhysics(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
children: model.visibleIndexs.map((t) {
|
children: model.visibleEnabledOrderedIndexs.map((t) {
|
||||||
final selected = model.currentTab == t;
|
final selected = model.currentTab == t;
|
||||||
final color = selected
|
final color = selected
|
||||||
? MyTheme.tabbar(context).selectedTextColor
|
? MyTheme.tabbar(context).selectedTextColor
|
||||||
@@ -155,43 +157,47 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(width: 2, color: color!),
|
bottom: BorderSide(width: 2, color: color!),
|
||||||
));
|
));
|
||||||
return Obx(() => Tooltip(
|
counter += 1;
|
||||||
preferBelow: false,
|
return ReorderableDragStartListener(
|
||||||
message: model.tabTooltip(t),
|
key: ValueKey(t),
|
||||||
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
index: counter,
|
||||||
child: InkWell(
|
child: Obx(() => Tooltip(
|
||||||
child: Container(
|
preferBelow: false,
|
||||||
decoration: (hover.value
|
message: model.tabTooltip(t),
|
||||||
? (selected ? decoBorder : deco)
|
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
||||||
: (selected ? decoBorder : null)),
|
child: InkWell(
|
||||||
child: Icon(model.tabIcon(t), color: color)
|
child: Container(
|
||||||
.paddingSymmetric(horizontal: 4),
|
decoration: (hover.value
|
||||||
).paddingSymmetric(horizontal: 4),
|
? (selected ? decoBorder : deco)
|
||||||
onTap: () async {
|
: (selected ? decoBorder : null)),
|
||||||
await handleTabSelection(t);
|
child: Icon(model.tabIcon(t), color: color)
|
||||||
await bind.setLocalFlutterOption(
|
.paddingSymmetric(horizontal: 4),
|
||||||
k: 'peer-tab-index', v: t.toString());
|
).paddingSymmetric(horizontal: 4),
|
||||||
},
|
onTap: () async {
|
||||||
onHover: (value) => hover.value = value,
|
await handleTabSelection(t);
|
||||||
),
|
await bind.setLocalFlutterOption(
|
||||||
));
|
k: PeerTabModel.kPeerTabIndex, v: t.toString());
|
||||||
|
},
|
||||||
|
onHover: (value) => hover.value = value,
|
||||||
|
),
|
||||||
|
)));
|
||||||
}).toList());
|
}).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _createPeersView() {
|
Widget _createPeersView() {
|
||||||
final model = Provider.of<PeerTabModel>(context);
|
final model = Provider.of<PeerTabModel>(context);
|
||||||
Widget child;
|
Widget child;
|
||||||
if (model.visibleIndexs.isEmpty) {
|
if (model.visibleEnabledOrderedIndexs.isEmpty) {
|
||||||
child = visibleContextMenuListener(Row(
|
child = visibleContextMenuListener(Row(
|
||||||
children: [Expanded(child: InkWell())],
|
children: [Expanded(child: InkWell())],
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
if (model.visibleIndexs.contains(model.currentTab)) {
|
if (model.visibleEnabledOrderedIndexs.contains(model.currentTab)) {
|
||||||
child = entries[model.currentTab].widget;
|
child = entries[model.currentTab].widget;
|
||||||
} else {
|
} else {
|
||||||
debugPrint("should not happen! currentTab not in visibleIndexs");
|
debugPrint("should not happen! currentTab not in visibleIndexs");
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
model.setCurrentTab(model.indexs[0]);
|
model.setCurrentTab(model.visibleEnabledOrderedIndexs[0]);
|
||||||
});
|
});
|
||||||
child = entries[0].widget;
|
child = entries[0].widget;
|
||||||
}
|
}
|
||||||
@@ -255,16 +261,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
void mobileShowTabVisibilityMenu() {
|
void mobileShowTabVisibilityMenu() {
|
||||||
final model = gFFI.peerTabModel;
|
final model = gFFI.peerTabModel;
|
||||||
final items = List<PopupMenuItem>.empty(growable: true);
|
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(
|
items.add(PopupMenuItem(
|
||||||
height: kMinInteractiveDimension * 0.8,
|
height: kMinInteractiveDimension * 0.8,
|
||||||
onTap: () => model.setTabVisible(i, !model.isVisible[i]),
|
onTap: () => model.setTabVisible(i, !model.isVisibleEnabled[i]),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: model.isVisible[i],
|
value: model.isVisibleEnabled[i],
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
model.setTabVisible(i, !model.isVisible[i]);
|
model.setTabVisible(i, !model.isVisibleEnabled[i]);
|
||||||
if (Navigator.canPop(context)) {
|
if (Navigator.canPop(context)) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
@@ -314,16 +321,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
|
|
||||||
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
||||||
final model = Provider.of<PeerTabModel>(context);
|
final model = Provider.of<PeerTabModel>(context);
|
||||||
final menu = List<MenuEntrySwitch>.empty(growable: true);
|
final menu = List<MenuEntrySwitchSync>.empty(growable: true);
|
||||||
for (int i = 0; i < model.tabNames.length; i++) {
|
for (int i = 0; i < model.orders.length; i++) {
|
||||||
menu.add(MenuEntrySwitch(
|
int tabIndex = model.orders[i];
|
||||||
|
if (tabIndex < 0 || tabIndex >= PeerTabModel.maxTabCount) continue;
|
||||||
|
if (!model.isEnabled[tabIndex]) continue;
|
||||||
|
menu.add(MenuEntrySwitchSync(
|
||||||
switchType: SwitchType.scheckbox,
|
switchType: SwitchType.scheckbox,
|
||||||
text: model.tabTooltip(i),
|
text: model.tabTooltip(tabIndex),
|
||||||
getter: () async {
|
currentValue: model.isVisibleEnabled[tabIndex],
|
||||||
return model.isVisible[i];
|
|
||||||
},
|
|
||||||
setter: (show) async {
|
setter: (show) async {
|
||||||
model.setTabVisible(i, show);
|
model.setTabVisible(tabIndex, show);
|
||||||
cancelFunc();
|
cancelFunc();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -434,7 +442,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
model.setMultiSelectionMode(false);
|
model.setMultiSelectionMode(false);
|
||||||
showToast(translate('Successful'));
|
showToast(translate('Successful'));
|
||||||
},
|
},
|
||||||
child: Icon(model.icons[PeerTabIndex.fav.index]),
|
child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]),
|
||||||
).marginOnly(left: isMobile ? 11 : 6),
|
).marginOnly(left: isMobile ? 11 : 6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -455,7 +463,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
addPeersToAbDialog(peers);
|
addPeersToAbDialog(peers);
|
||||||
model.setMultiSelectionMode(false);
|
model.setMultiSelectionMode(false);
|
||||||
},
|
},
|
||||||
child: Icon(model.icons[PeerTabIndex.ab.index]),
|
child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]),
|
||||||
).marginOnly(left: isMobile ? 11 : 6),
|
).marginOnly(left: isMobile ? 11 : 6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -563,7 +571,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
|
final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
|
||||||
final leftActionsSize =
|
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 availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2;
|
||||||
final searchWidth = 120;
|
final searchWidth = 120;
|
||||||
final otherActionWidth = 18 + 10;
|
final otherActionWidth = 18 + 10;
|
||||||
|
|||||||
@@ -441,10 +441,17 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
child: Text(translate('Mute'))));
|
child: Text(translate('Mute'))));
|
||||||
}
|
}
|
||||||
// file copy and paste
|
// 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 &&
|
if (ffiModel.keyboard &&
|
||||||
perms['file'] != false &&
|
perms['file'] != false &&
|
||||||
bind.mainHasFileClipboard() &&
|
(isSupportIfPeer_1_2_3 || isSupportIfPeer_1_2_4)) {
|
||||||
pi.platformAdditions.containsKey(kPlatformAdditionsHasFileClipboard)) {
|
|
||||||
final enabled = !ffiModel.viewOnly;
|
final enabled = !ffiModel.viewOnly;
|
||||||
final option = 'enable-file-transfer';
|
final option = 'enable-file-transfer';
|
||||||
final value =
|
final value =
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ const kKeyTranslateMode = 'translate';
|
|||||||
const String kPlatformAdditionsIsWayland = "is_wayland";
|
const String kPlatformAdditionsIsWayland = "is_wayland";
|
||||||
const String kPlatformAdditionsHeadless = "headless";
|
const String kPlatformAdditionsHeadless = "headless";
|
||||||
const String kPlatformAdditionsIsInstalled = "is_installed";
|
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 kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
|
||||||
const String kPlatformAdditionsSupportedPrivacyModeImpl =
|
const String kPlatformAdditionsSupportedPrivacyModeImpl =
|
||||||
"supported_privacy_mode_impl";
|
"supported_privacy_mode_impl";
|
||||||
@@ -121,12 +123,11 @@ double kNewWindowOffset = isWindows
|
|||||||
? 30.0
|
? 30.0
|
||||||
: 50.0;
|
: 50.0;
|
||||||
|
|
||||||
EdgeInsets get kDragToResizeAreaPadding =>
|
EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && isLinux
|
||||||
!kUseCompatibleUiMode && isLinux
|
? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value
|
||||||
? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value
|
? EdgeInsets.zero
|
||||||
? EdgeInsets.zero
|
: EdgeInsets.all(5.0)
|
||||||
: EdgeInsets.all(5.0)
|
: EdgeInsets.zero;
|
||||||
: EdgeInsets.zero;
|
|
||||||
// https://en.wikipedia.org/wiki/Non-breaking_space
|
// https://en.wikipedia.org/wiki/Non-breaking_space
|
||||||
const int $nbsp = 0x00A0;
|
const int $nbsp = 0x00A0;
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
Widget buildLeftPane(BuildContext context) {
|
||||||
final isIncomingOnly = bind.isIncomingOnly();
|
final isIncomingOnly = bind.isIncomingOnly();
|
||||||
final isOutgoingOnly = bind.isOutgoingOnly();
|
final isOutgoingOnly = bind.isOutgoingOnly();
|
||||||
@@ -115,22 +77,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
if (bind.isCustomClient())
|
if (bind.isCustomClient())
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: MouseRegion(
|
child: loadPowered(context),
|
||||||
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),
|
|
||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@@ -298,19 +245,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
RxBool hover = false.obs;
|
RxBool hover = false.obs;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: DesktopTabPage.onAddSetting,
|
onTap: DesktopTabPage.onAddSetting,
|
||||||
child: Obx(
|
child: Tooltip(
|
||||||
() => CircleAvatar(
|
message: translate('Settings'),
|
||||||
radius: 15,
|
child: Obx(
|
||||||
backgroundColor: hover.value
|
() => CircleAvatar(
|
||||||
? Theme.of(context).scaffoldBackgroundColor
|
radius: 15,
|
||||||
: Theme.of(context).colorScheme.background,
|
backgroundColor: hover.value
|
||||||
child: Tooltip(
|
? Theme.of(context).scaffoldBackgroundColor
|
||||||
message: translate('Settings'),
|
: Theme.of(context).colorScheme.background,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.more_vert_outlined,
|
Icons.more_vert_outlined,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onHover: (value) => hover.value = value,
|
onHover: (value) => hover.value = value,
|
||||||
@@ -371,31 +319,33 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
),
|
),
|
||||||
AnimatedRotationWidget(
|
AnimatedRotationWidget(
|
||||||
onPressed: () => bind.mainUpdateTemporaryPassword(),
|
onPressed: () => bind.mainUpdateTemporaryPassword(),
|
||||||
child: Obx(() => RotatedBox(
|
child: Tooltip(
|
||||||
quarterTurns: 2,
|
message: translate('Refresh Password'),
|
||||||
child: Tooltip(
|
child: Obx(() => RotatedBox(
|
||||||
message: translate('Refresh Password'),
|
quarterTurns: 2,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.refresh,
|
Icons.refresh,
|
||||||
color: refreshHover.value
|
color: refreshHover.value
|
||||||
? textColor
|
? textColor
|
||||||
: Color(0xFFDDDDDD),
|
: Color(0xFFDDDDDD),
|
||||||
size: 22,
|
size: 22,
|
||||||
)))),
|
))),
|
||||||
|
),
|
||||||
onHover: (value) => refreshHover.value = value,
|
onHover: (value) => refreshHover.value = value,
|
||||||
).marginOnly(right: 8, top: 4),
|
).marginOnly(right: 8, top: 4),
|
||||||
if (!bind.isDisableSettings())
|
if (!bind.isDisableSettings())
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Obx(
|
child: Tooltip(
|
||||||
() => Tooltip(
|
message: translate('Change Password'),
|
||||||
message: translate('Change Password'),
|
child: Obx(
|
||||||
child: Icon(
|
() => Icon(
|
||||||
Icons.edit,
|
Icons.edit,
|
||||||
color: editHover.value
|
color: editHover.value
|
||||||
? textColor
|
? textColor
|
||||||
: Color(0xFFDDDDDD),
|
: Color(0xFFDDDDDD),
|
||||||
size: 22,
|
size: 22,
|
||||||
)).marginOnly(right: 8, top: 4),
|
).marginOnly(right: 8, top: 4),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTap: () => DesktopSettingPage.switch2page(0),
|
onTap: () => DesktopSettingPage.switch2page(0),
|
||||||
onHover: (value) => editHover.value = value,
|
onHover: (value) => editHover.value = value,
|
||||||
|
|||||||
@@ -400,11 +400,20 @@ class _GeneralState extends State<_General> {
|
|||||||
|
|
||||||
Widget hwcodec() {
|
Widget hwcodec() {
|
||||||
final hwcodec = bind.mainHasHwcodec();
|
final hwcodec = bind.mainHasHwcodec();
|
||||||
final gpucodec = bind.mainHasGpucodec();
|
final vram = bind.mainHasVram();
|
||||||
return Offstage(
|
return Offstage(
|
||||||
offstage: !(hwcodec || gpucodec),
|
offstage: !(hwcodec || vram),
|
||||||
child: _Card(title: 'Hardware Codec', children: [
|
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),
|
...directIp(context),
|
||||||
whitelist(),
|
whitelist(),
|
||||||
...autoDisconnect(context),
|
...autoDisconnect(context),
|
||||||
|
if (bind.mainIsInstalled())
|
||||||
|
_OptionCheckBox(context, 'allow-only-conn-window-open-tip',
|
||||||
|
'allow-only-conn-window-open',
|
||||||
|
reverse: false, enabled: enabled),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 Switch2Getter = RxBool Function();
|
||||||
typedef Switch2Setter = Future<void> Function(bool);
|
typedef Switch2Setter = Future<void> Function(bool);
|
||||||
|
|
||||||
|
|||||||
@@ -636,7 +636,7 @@ class _MonitorMenu extends StatelessWidget {
|
|||||||
menuStyle: MenuStyle(
|
menuStyle: MenuStyle(
|
||||||
padding:
|
padding:
|
||||||
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
|
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
|
||||||
menuChildren: [buildMonitorSubmenuWidget()]);
|
menuChildrenGetter: () => [buildMonitorSubmenuWidget()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildMultiMonitorMenu() {
|
Widget buildMultiMonitorMenu() {
|
||||||
@@ -843,17 +843,17 @@ class _ControlMenu extends StatelessWidget {
|
|||||||
color: _ToolbarTheme.blueColor,
|
color: _ToolbarTheme.blueColor,
|
||||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||||
ffi: ffi,
|
ffi: ffi,
|
||||||
menuChildren: toolbarControls(context, id, ffi).map((e) {
|
menuChildrenGetter: () => toolbarControls(context, id, ffi).map((e) {
|
||||||
if (e.divider) {
|
if (e.divider) {
|
||||||
return Divider();
|
return Divider();
|
||||||
} else {
|
} else {
|
||||||
return MenuButton(
|
return MenuButton(
|
||||||
child: e.child,
|
child: e.child,
|
||||||
onPressed: e.onPressed,
|
onPressed: e.onPressed,
|
||||||
ffi: ffi,
|
ffi: ffi,
|
||||||
trailingIcon: e.trailingIcon);
|
trailingIcon: e.trailingIcon);
|
||||||
}
|
}
|
||||||
}).toList());
|
}).toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1046,53 +1046,61 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_screenAdjustor.updateScreen();
|
_screenAdjustor.updateScreen();
|
||||||
|
|
||||||
final menuChildren = <Widget>[
|
menuChildrenGetter() {
|
||||||
_screenAdjustor.adjustWindow(context),
|
final menuChildren = <Widget>[
|
||||||
viewStyle(),
|
_screenAdjustor.adjustWindow(context),
|
||||||
scrollStyle(),
|
viewStyle(),
|
||||||
imageQuality(),
|
scrollStyle(),
|
||||||
codec(),
|
imageQuality(),
|
||||||
_ResolutionsMenu(
|
codec(),
|
||||||
id: widget.id,
|
_ResolutionsMenu(
|
||||||
ffi: widget.ffi,
|
id: widget.id,
|
||||||
screenAdjustor: _screenAdjustor,
|
ffi: widget.ffi,
|
||||||
),
|
screenAdjustor: _screenAdjustor,
|
||||||
// We may add this feature if it is needed and we have an EV certificate.
|
),
|
||||||
// _VirtualDisplayMenu(
|
if (pi.isRustDeskIdd)
|
||||||
// id: widget.id,
|
_RustDeskVirtualDisplayMenu(
|
||||||
// ffi: widget.ffi,
|
id: widget.id,
|
||||||
// ),
|
ffi: widget.ffi,
|
||||||
Divider(),
|
),
|
||||||
toggles(),
|
if (pi.isAmyuniIdd)
|
||||||
];
|
_AmyuniVirtualDisplayMenu(
|
||||||
// privacy mode
|
id: widget.id,
|
||||||
if (ffiModel.keyboard && pi.features.privacyMode) {
|
ffi: widget.ffi,
|
||||||
final privacyModeState = PrivacyModeState.find(id);
|
),
|
||||||
final privacyModeList =
|
Divider(),
|
||||||
toolbarPrivacyMode(privacyModeState, context, id, ffi);
|
toggles(),
|
||||||
if (privacyModeList.length == 1) {
|
];
|
||||||
menuChildren.add(CkbMenuButton(
|
// privacy mode
|
||||||
value: privacyModeList[0].value,
|
if (ffiModel.keyboard && pi.features.privacyMode) {
|
||||||
onChanged: privacyModeList[0].onChanged,
|
final privacyModeState = PrivacyModeState.find(id);
|
||||||
child: privacyModeList[0].child,
|
final privacyModeList =
|
||||||
ffi: ffi));
|
toolbarPrivacyMode(privacyModeState, context, id, ffi);
|
||||||
} else if (privacyModeList.length > 1) {
|
if (privacyModeList.length == 1) {
|
||||||
menuChildren.addAll([
|
menuChildren.add(CkbMenuButton(
|
||||||
Divider(),
|
value: privacyModeList[0].value,
|
||||||
_SubmenuButton(
|
onChanged: privacyModeList[0].onChanged,
|
||||||
ffi: widget.ffi,
|
child: privacyModeList[0].child,
|
||||||
child: Text(translate('Privacy mode')),
|
ffi: ffi));
|
||||||
menuChildren: privacyModeList
|
} else if (privacyModeList.length > 1) {
|
||||||
.map((e) => CkbMenuButton(
|
menuChildren.addAll([
|
||||||
value: e.value,
|
Divider(),
|
||||||
onChanged: e.onChanged,
|
_SubmenuButton(
|
||||||
child: e.child,
|
ffi: widget.ffi,
|
||||||
ffi: ffi))
|
child: Text(translate('Privacy mode')),
|
||||||
.toList()),
|
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(
|
return _IconSubmenuButton(
|
||||||
tooltip: 'Display Settings',
|
tooltip: 'Display Settings',
|
||||||
@@ -1100,7 +1108,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
color: _ToolbarTheme.blueColor,
|
color: _ToolbarTheme.blueColor,
|
||||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
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 String id;
|
||||||
final FFI ffi;
|
final FFI ffi;
|
||||||
|
|
||||||
_VirtualDisplayMenu({
|
_RustDeskVirtualDisplayMenu({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.ffi,
|
required this.ffi,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_VirtualDisplayMenu> createState() => _VirtualDisplayMenuState();
|
State<_RustDeskVirtualDisplayMenu> createState() =>
|
||||||
|
_RustDeskVirtualDisplayMenuState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
|
class _RustDeskVirtualDisplayMenuState
|
||||||
|
extends State<_RustDeskVirtualDisplayMenu> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -1566,7 +1576,7 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
|
|||||||
return Offstage();
|
return Offstage();
|
||||||
}
|
}
|
||||||
|
|
||||||
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
|
final virtualDisplays = widget.ffi.ffiModel.pi.RustDeskVirtualDisplays;
|
||||||
final privacyModeState = PrivacyModeState.find(widget.id);
|
final privacyModeState = PrivacyModeState.find(widget.id);
|
||||||
|
|
||||||
final children = <Widget>[];
|
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 {
|
class _KeyboardMenu extends StatelessWidget {
|
||||||
final String id;
|
final String id;
|
||||||
final FFI ffi;
|
final FFI ffi;
|
||||||
@@ -1623,19 +1709,7 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var ffiModel = Provider.of<FfiModel>(context);
|
var ffiModel = Provider.of<FfiModel>(context);
|
||||||
if (!ffiModel.keyboard) return Offstage();
|
if (!ffiModel.keyboard) return Offstage();
|
||||||
// If use flutter to grab keys, we can only use one mode.
|
toolbarToggles() => toolbarKeyboardToggles(ffi)
|
||||||
// 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)
|
|
||||||
.map((e) => CkbMenuButton(
|
.map((e) => CkbMenuButton(
|
||||||
value: e.value, onChanged: e.onChanged, child: e.child, ffi: ffi))
|
value: e.value, onChanged: e.onChanged, child: e.child, ffi: ffi))
|
||||||
.toList();
|
.toList();
|
||||||
@@ -1645,18 +1719,18 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
ffi: ffi,
|
ffi: ffi,
|
||||||
color: _ToolbarTheme.blueColor,
|
color: _ToolbarTheme.blueColor,
|
||||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||||
menuChildren: [
|
menuChildrenGetter: () => [
|
||||||
keyboardMode(modeOnly),
|
keyboardMode(),
|
||||||
localKeyboardType(),
|
localKeyboardType(),
|
||||||
inputSource(),
|
inputSource(),
|
||||||
Divider(),
|
Divider(),
|
||||||
viewMode(),
|
viewMode(),
|
||||||
Divider(),
|
Divider(),
|
||||||
...toolbarToggles,
|
...toolbarToggles(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboardMode(String? modeOnly) {
|
keyboardMode() {
|
||||||
return futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ??
|
return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ??
|
||||||
kKeyLegacyMode;
|
kKeyLegacyMode;
|
||||||
@@ -1676,6 +1750,19 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
await ffi.inputModel.updateKeyboardMode();
|
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) {
|
for (InputModeMenu mode in modes) {
|
||||||
if (modeOnly != null && mode.key != modeOnly) {
|
if (modeOnly != null && mode.key != modeOnly) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1804,7 +1891,7 @@ class _ChatMenuState extends State<_ChatMenu> {
|
|||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
color: _ToolbarTheme.blueColor,
|
color: _ToolbarTheme.blueColor,
|
||||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||||
menuChildren: [textChat(), voiceCall()]);
|
menuChildrenGetter: () => [textChat(), voiceCall()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
textChat() {
|
textChat() {
|
||||||
@@ -2008,7 +2095,7 @@ class _IconSubmenuButton extends StatefulWidget {
|
|||||||
final Widget? icon;
|
final Widget? icon;
|
||||||
final Color color;
|
final Color color;
|
||||||
final Color hoverColor;
|
final Color hoverColor;
|
||||||
final List<Widget> menuChildren;
|
final List<Widget> Function() menuChildrenGetter;
|
||||||
final MenuStyle? menuStyle;
|
final MenuStyle? menuStyle;
|
||||||
final FFI ffi;
|
final FFI ffi;
|
||||||
final double? width;
|
final double? width;
|
||||||
@@ -2020,7 +2107,7 @@ class _IconSubmenuButton extends StatefulWidget {
|
|||||||
required this.tooltip,
|
required this.tooltip,
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.hoverColor,
|
required this.hoverColor,
|
||||||
required this.menuChildren,
|
required this.menuChildrenGetter,
|
||||||
required this.ffi,
|
required this.ffi,
|
||||||
this.menuStyle,
|
this.menuStyle,
|
||||||
this.width,
|
this.width,
|
||||||
@@ -2064,7 +2151,8 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
|
|||||||
color: hover ? widget.hoverColor : widget.color,
|
color: hover ? widget.hoverColor : widget.color,
|
||||||
),
|
),
|
||||||
child: icon))),
|
child: icon))),
|
||||||
menuChildren: widget.menuChildren
|
menuChildren: widget
|
||||||
|
.menuChildrenGetter()
|
||||||
.map((e) => _buildPointerTrackWidget(e, widget.ffi))
|
.map((e) => _buildPointerTrackWidget(e, widget.ffi))
|
||||||
.toList()));
|
.toList()));
|
||||||
return MenuBar(children: [
|
return MenuBar(children: [
|
||||||
|
|||||||
@@ -865,6 +865,7 @@ class _ListView extends StatelessWidget {
|
|||||||
label: labelGetter == null
|
label: labelGetter == null
|
||||||
? Rx<String>(tab.label)
|
? Rx<String>(tab.label)
|
||||||
: labelGetter!(tab.label),
|
: labelGetter!(tab.label),
|
||||||
|
tabType: controller.tabType,
|
||||||
selectedIcon: tab.selectedIcon,
|
selectedIcon: tab.selectedIcon,
|
||||||
unselectedIcon: tab.unselectedIcon,
|
unselectedIcon: tab.unselectedIcon,
|
||||||
closable: tab.closable,
|
closable: tab.closable,
|
||||||
@@ -896,6 +897,7 @@ class _Tab extends StatefulWidget {
|
|||||||
final int index;
|
final int index;
|
||||||
final String tabInfoKey;
|
final String tabInfoKey;
|
||||||
final Rx<String> label;
|
final Rx<String> label;
|
||||||
|
final DesktopTabType tabType;
|
||||||
final IconData? selectedIcon;
|
final IconData? selectedIcon;
|
||||||
final IconData? unselectedIcon;
|
final IconData? unselectedIcon;
|
||||||
final bool closable;
|
final bool closable;
|
||||||
@@ -914,6 +916,7 @@ class _Tab extends StatefulWidget {
|
|||||||
required this.index,
|
required this.index,
|
||||||
required this.tabInfoKey,
|
required this.tabInfoKey,
|
||||||
required this.label,
|
required this.label,
|
||||||
|
required this.tabType,
|
||||||
this.selectedIcon,
|
this.selectedIcon,
|
||||||
this.unselectedIcon,
|
this.unselectedIcon,
|
||||||
this.tabBuilder,
|
this.tabBuilder,
|
||||||
@@ -953,7 +956,9 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
|||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
|
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: translate(widget.label.value),
|
message: widget.tabType == DesktopTabType.main
|
||||||
|
? ''
|
||||||
|
: translate(widget.label.value),
|
||||||
child: Text(
|
child: Text(
|
||||||
translate(widget.label.value),
|
translate(widget.label.value),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
slivers: [
|
slivers: [
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate([
|
delegate: SliverChildListDelegate([
|
||||||
_buildUpdateUI(),
|
if (!bind.isCustomClient()) _buildUpdateUI(),
|
||||||
_buildRemoteIDTextField(),
|
_buildRemoteIDTextField(),
|
||||||
])),
|
])),
|
||||||
SliverFillRemaining(
|
SliverFillRemaining(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter_hbb/mobile/pages/settings_page.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/chat_page.dart';
|
import '../../common/widgets/chat_page.dart';
|
||||||
|
import '../../models/platform_model.dart';
|
||||||
import 'connection_page.dart';
|
import 'connection_page.dart';
|
||||||
|
|
||||||
abstract class PageShape extends Widget {
|
abstract class PageShape extends Widget {
|
||||||
@@ -25,8 +26,9 @@ class HomePageState extends State<HomePage> {
|
|||||||
var _selectedIndex = 0;
|
var _selectedIndex = 0;
|
||||||
int get selectedIndex => _selectedIndex;
|
int get selectedIndex => _selectedIndex;
|
||||||
final List<PageShape> _pages = [];
|
final List<PageShape> _pages = [];
|
||||||
|
int _chatPageTabIndex = -1;
|
||||||
bool get isChatPageCurrentTab => isAndroid
|
bool get isChatPageCurrentTab => isAndroid
|
||||||
? _selectedIndex == 1
|
? _selectedIndex == _chatPageTabIndex
|
||||||
: false; // change this when ios have chat page
|
: false; // change this when ios have chat page
|
||||||
|
|
||||||
void refreshPages() {
|
void refreshPages() {
|
||||||
@@ -43,8 +45,9 @@ class HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
void initPages() {
|
void initPages() {
|
||||||
_pages.clear();
|
_pages.clear();
|
||||||
_pages.add(ConnectionPage());
|
if (!bind.isIncomingOnly()) _pages.add(ConnectionPage());
|
||||||
if (isAndroid) {
|
if (isAndroid && !bind.isOutgoingOnly()) {
|
||||||
|
_chatPageTabIndex = _pages.length;
|
||||||
_pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]);
|
_pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]);
|
||||||
}
|
}
|
||||||
_pages.add(SettingsPage());
|
_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,
|
// backgroundColor: MyTheme.grayBg,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: Text("RustDesk${isWeb ? " (Beta) " : ""}"),
|
title: Text(bind.mainGetAppNameSync()),
|
||||||
actions: connectionPage.appBarActions,
|
actions: connectionPage.appBarActions,
|
||||||
),
|
),
|
||||||
body: connectionPage,
|
body: connectionPage,
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
gFFI.dialogManager
|
gFFI.dialogManager
|
||||||
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
||||||
});
|
});
|
||||||
WakelockPlus.enable();
|
if (!isWeb) {
|
||||||
|
WakelockPlus.enable();
|
||||||
|
}
|
||||||
_physicalFocusNode.requestFocus();
|
_physicalFocusNode.requestFocus();
|
||||||
gFFI.inputModel.listenToMouse(true);
|
gFFI.inputModel.listenToMouse(true);
|
||||||
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
||||||
@@ -95,7 +97,9 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
gFFI.dialogManager.dismissAll();
|
gFFI.dialogManager.dismissAll();
|
||||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||||
overlays: SystemUiOverlay.values);
|
overlays: SystemUiOverlay.values);
|
||||||
await WakelockPlus.disable();
|
if (!isWeb) {
|
||||||
|
await WakelockPlus.disable();
|
||||||
|
}
|
||||||
await keyboardSubscription.cancel();
|
await keyboardSubscription.cancel();
|
||||||
removeSharedStates(widget.id);
|
removeSharedStates(widget.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ class _ServerPageState extends State<ServerPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
buildPresetPasswordWarning(),
|
||||||
gFFI.serverModel.isStart
|
gFFI.serverModel.isStart
|
||||||
? ServerInfo()
|
? ServerInfo()
|
||||||
: ServiceNotRunningNotification(),
|
: ServiceNotRunningNotification(),
|
||||||
@@ -226,7 +227,7 @@ class ScamWarningDialog extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ScamWarningDialogState extends State<ScamWarningDialog> {
|
class ScamWarningDialogState extends State<ScamWarningDialog> {
|
||||||
int _countdown = 12;
|
int _countdown = bind.isCustomClient() ? 0 : 12;
|
||||||
bool show_warning = false;
|
bool show_warning = false;
|
||||||
late Timer _timer;
|
late Timer _timer;
|
||||||
late ServerModel _serverModel;
|
late ServerModel _serverModel;
|
||||||
@@ -383,7 +384,7 @@ class ScamWarningDialogState extends State<ScamWarningDialog> {
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
primary: Colors.blueAccent,
|
backgroundColor: Colors.blueAccent,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
translate("Decline"),
|
translate("Decline"),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class SettingsPage extends StatefulWidget implements PageShape {
|
|||||||
final icon = Icon(Icons.settings);
|
final icon = Icon(Icons.settings);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final appBarActions = [ScanButton()];
|
final appBarActions = bind.isDisableSettings() ? [] : [ScanButton()];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SettingsPage> createState() => _SettingsState();
|
State<SettingsPage> createState() => _SettingsState();
|
||||||
@@ -218,6 +218,21 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Provider.of<FfiModel>(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> enhancementsTiles = [];
|
||||||
final List<AbstractSettingsTile> shareScreenTiles = [
|
final List<AbstractSettingsTile> shareScreenTiles = [
|
||||||
SettingsTile.switchTile(
|
SettingsTile.switchTile(
|
||||||
@@ -448,33 +463,37 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
|
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return SettingsList(
|
final disabledSettings = bind.isDisableSettings();
|
||||||
|
final settings = SettingsList(
|
||||||
sections: [
|
sections: [
|
||||||
SettingsSection(
|
customClientSection,
|
||||||
title: Text(translate('Account')),
|
if (!bind.isDisableAccount())
|
||||||
tiles: [
|
SettingsSection(
|
||||||
SettingsTile(
|
title: Text(translate('Account')),
|
||||||
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
|
tiles: [
|
||||||
? translate('Login')
|
SettingsTile(
|
||||||
: '${translate('Logout')} (${gFFI.userModel.userName.value})')),
|
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
|
||||||
leading: Icon(Icons.person),
|
? translate('Login')
|
||||||
onPressed: (context) {
|
: '${translate('Logout')} (${gFFI.userModel.userName.value})')),
|
||||||
if (gFFI.userModel.userName.value.isEmpty) {
|
leading: Icon(Icons.person),
|
||||||
loginDialog();
|
onPressed: (context) {
|
||||||
} else {
|
if (gFFI.userModel.userName.value.isEmpty) {
|
||||||
logOutConfirmDialog();
|
loginDialog();
|
||||||
}
|
} else {
|
||||||
},
|
logOutConfirmDialog();
|
||||||
),
|
}
|
||||||
],
|
},
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
SettingsSection(title: Text(translate("Settings")), tiles: [
|
SettingsSection(title: Text(translate("Settings")), tiles: [
|
||||||
SettingsTile(
|
if (!disabledSettings)
|
||||||
title: Text(translate('ID/Relay Server')),
|
SettingsTile(
|
||||||
leading: Icon(Icons.cloud),
|
title: Text(translate('ID/Relay Server')),
|
||||||
onPressed: (context) {
|
leading: Icon(Icons.cloud),
|
||||||
showServerSettings(gFFI.dialogManager);
|
onPressed: (context) {
|
||||||
}),
|
showServerSettings(gFFI.dialogManager);
|
||||||
|
}),
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
title: Text(translate('Language')),
|
title: Text(translate('Language')),
|
||||||
leading: Icon(Icons.translate),
|
leading: Icon(Icons.translate),
|
||||||
@@ -494,7 +513,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
if (isAndroid)
|
if (isAndroid && !outgoingOnly)
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
title: Text(translate("Recording")),
|
title: Text(translate("Recording")),
|
||||||
tiles: [
|
tiles: [
|
||||||
@@ -523,13 +542,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isAndroid)
|
if (isAndroid && !disabledSettings && !outgoingOnly)
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
title: Text(translate("Share Screen")),
|
title: Text(translate("Share Screen")),
|
||||||
tiles: shareScreenTiles,
|
tiles: shareScreenTiles,
|
||||||
),
|
),
|
||||||
defaultDisplaySection(),
|
if (!bind.isIncomingOnly()) defaultDisplaySection(),
|
||||||
if (isAndroid)
|
if (isAndroid && !disabledSettings && !outgoingOnly)
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
title: Text(translate("Enhancements")),
|
title: Text(translate("Enhancements")),
|
||||||
tiles: enhancementsTiles,
|
tiles: enhancementsTiles,
|
||||||
@@ -578,6 +597,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> canStartOnBoot() async {
|
Future<bool> canStartOnBoot() async {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import './platform_model.dart';
|
|||||||
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart'
|
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart'
|
||||||
if (dart.library.html) 'package:flutter_hbb/web/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 &&
|
final useTextureRender = !isWeb &&
|
||||||
(bind.mainHasPixelbufferTextureRender() || bind.mainHasGpuTextureRender());
|
(bind.mainHasPixelbufferTextureRender() || bind.mainHasGpuTextureRender());
|
||||||
|
|
||||||
|
|||||||
@@ -561,8 +561,12 @@ class FfiModel with ChangeNotifier {
|
|||||||
showRelayHintDialog(sessionId, type, title, text, dialogManager, peerId);
|
showRelayHintDialog(sessionId, type, title, text, dialogManager, peerId);
|
||||||
} else if (text == 'Connected, waiting for image...') {
|
} else if (text == 'Connected, waiting for image...') {
|
||||||
showConnectedWaitingForImage(dialogManager, sessionId, type, title, text);
|
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 {
|
} else {
|
||||||
var hasRetry = evt['hasRetry'] == 'true';
|
final hasRetry = evt['hasRetry'] == 'true';
|
||||||
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
|
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -657,6 +661,27 @@ class FfiModel with ChangeNotifier {
|
|||||||
bind.sessionOnWaitingForImageDialogShow(sessionId: sessionId);
|
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) {
|
_updateSessionWidthHeight(SessionID sessionId) {
|
||||||
if (_rect == null) return;
|
if (_rect == null) return;
|
||||||
if (_rect!.width <= 0 || _rect!.height <= 0) {
|
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.
|
// 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.
|
// Because this function is asynchronous, there's an "await" in this function.
|
||||||
cachedPeerData.peerInfo = {...evt};
|
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)
|
// Recent peer is updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
|
||||||
bind.mainLoadRecentPeers();
|
bind.mainLoadRecentPeers();
|
||||||
@@ -745,7 +772,9 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
Map<String, dynamic> features = json.decode(evt['features']);
|
Map<String, dynamic> features = json.decode(evt['features']);
|
||||||
_pi.features.privacyMode = features['privacy_mode'] == 1;
|
_pi.features.privacyMode = features['privacy_mode'] == 1;
|
||||||
handleResolutions(peerId, evt["resolutions"]);
|
if (!isCache) {
|
||||||
|
handleResolutions(peerId, evt["resolutions"]);
|
||||||
|
}
|
||||||
parent.target?.elevationModel.onPeerInfo(_pi);
|
parent.target?.elevationModel.onPeerInfo(_pi);
|
||||||
}
|
}
|
||||||
if (connType == ConnType.defaultConn) {
|
if (connType == ConnType.defaultConn) {
|
||||||
@@ -986,15 +1015,21 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (updateData.isEmpty) {
|
if (updateData.isEmpty) {
|
||||||
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
|
_pi.platformAdditions.remove(kPlatformAdditionsRustDeskVirtualDisplays);
|
||||||
|
_pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
final updateJson = json.decode(updateData) as Map<String, dynamic>;
|
final updateJson = json.decode(updateData) as Map<String, dynamic>;
|
||||||
for (final key in updateJson.keys) {
|
for (final key in updateJson.keys) {
|
||||||
_pi.platformAdditions[key] = updateJson[key];
|
_pi.platformAdditions[key] = updateJson[key];
|
||||||
}
|
}
|
||||||
if (!updateJson.containsKey(kPlatformAdditionsVirtualDisplays)) {
|
if (!updateJson
|
||||||
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
|
.containsKey(kPlatformAdditionsRustDeskVirtualDisplays)) {
|
||||||
|
_pi.platformAdditions
|
||||||
|
.remove(kPlatformAdditionsRustDeskVirtualDisplays);
|
||||||
|
}
|
||||||
|
if (!updateJson.containsKey(kPlatformAdditionsAmyuniVirtualDisplays)) {
|
||||||
|
_pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Failed to decode platformAdditions $e');
|
debugPrint('Failed to decode platformAdditions $e');
|
||||||
@@ -2286,6 +2321,8 @@ class FFI {
|
|||||||
}
|
}
|
||||||
await ffiModel.handleCachedPeerData(data, id);
|
await ffiModel.handleCachedPeerData(data, id);
|
||||||
await sessionRefreshVideo(sessionId, ffiModel.pi);
|
await sessionRefreshVideo(sessionId, ffiModel.pi);
|
||||||
|
await bind.sessionRequestNewDisplayInitMsgs(
|
||||||
|
sessionId: sessionId, display: ffiModel.pi.currentDisplay);
|
||||||
});
|
});
|
||||||
isToNewWindowNotified.value = true;
|
isToNewWindowNotified.value = true;
|
||||||
}
|
}
|
||||||
@@ -2490,14 +2527,21 @@ class PeerInfo with ChangeNotifier {
|
|||||||
bool get isInstalled =>
|
bool get isInstalled =>
|
||||||
platform != kPeerPlatformWindows ||
|
platform != kPeerPlatformWindows ||
|
||||||
platformAdditions[kPlatformAdditionsIsInstalled] == true;
|
platformAdditions[kPlatformAdditionsIsInstalled] == true;
|
||||||
List<int> get virtualDisplays => List<int>.from(
|
List<int> get RustDeskVirtualDisplays => List<int>.from(
|
||||||
platformAdditions[kPlatformAdditionsVirtualDisplays] ?? []);
|
platformAdditions[kPlatformAdditionsRustDeskVirtualDisplays] ?? []);
|
||||||
|
int get amyuniVirtualDisplayCount =>
|
||||||
|
platformAdditions[kPlatformAdditionsAmyuniVirtualDisplays] ?? 0;
|
||||||
|
|
||||||
bool get isSupportMultiDisplay =>
|
bool get isSupportMultiDisplay =>
|
||||||
(isDesktop || isWebDesktop) && isSupportMultiUiSession;
|
(isDesktop || isWebDesktop) && isSupportMultiUiSession;
|
||||||
|
|
||||||
bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false;
|
bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false;
|
||||||
|
|
||||||
|
bool get isRustDeskIdd =>
|
||||||
|
platformAdditions[kPlatformAdditionsIddImpl] == 'rustdesk_idd';
|
||||||
|
bool get isAmyuniIdd =>
|
||||||
|
platformAdditions[kPlatformAdditionsIddImpl] == 'amyuni_idd';
|
||||||
|
|
||||||
Display? tryGetDisplay() {
|
Display? tryGetDisplay() {
|
||||||
if (displays.isEmpty) {
|
if (displays.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -198,7 +198,10 @@ class PlatformFFI {
|
|||||||
await _ffiBind.mainDeviceId(id: id);
|
await _ffiBind.mainDeviceId(id: id);
|
||||||
await _ffiBind.mainDeviceName(name: name);
|
await _ffiBind.mainDeviceName(name: name);
|
||||||
await _ffiBind.mainSetHomeDir(home: _homeDir);
|
await _ffiBind.mainSetHomeDir(home: _homeDir);
|
||||||
await _ffiBind.mainInit(appDir: _dir);
|
await _ffiBind.mainInit(
|
||||||
|
appDir: _dir,
|
||||||
|
customClientConfig: '',
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrintStack(label: 'initialize failed: $e');
|
debugPrintStack(label: 'initialize failed: $e');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,24 +21,43 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
WeakReference<FFI> parent;
|
WeakReference<FFI> parent;
|
||||||
int get currentTab => _currentTab;
|
int get currentTab => _currentTab;
|
||||||
int _currentTab = 0; // index in tabNames
|
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',
|
'Recent sessions',
|
||||||
'Favorites',
|
'Favorites',
|
||||||
if (!isWeb) 'Discovered',
|
'Discovered',
|
||||||
if (!(bind.isDisableAb() || bind.isDisableAccount())) 'Address book',
|
'Address book',
|
||||||
if (!bind.isDisableAccount()) 'Group',
|
'Group',
|
||||||
];
|
];
|
||||||
final List<IconData> icons = [
|
static const List<IconData> icons = [
|
||||||
Icons.access_time_filled,
|
Icons.access_time_filled,
|
||||||
Icons.star,
|
Icons.star,
|
||||||
if (!isWeb) Icons.explore,
|
Icons.explore,
|
||||||
if (!(bind.isDisableAb() || bind.isDisableAccount())) IconFont.addressBook,
|
IconFont.addressBook,
|
||||||
if (!bind.isDisableAccount()) Icons.group,
|
Icons.group,
|
||||||
];
|
];
|
||||||
final List<bool> _isVisible = List.filled(5, true, growable: false);
|
List<bool> isEnabled = List.from([
|
||||||
List<bool> get isVisible => _isVisible;
|
true,
|
||||||
List<int> get indexs => List.generate(tabNames.length, (index) => index);
|
true,
|
||||||
List<int> get visibleIndexs => indexs.where((e) => _isVisible[e]).toList();
|
!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> _selectedPeers = List.empty(growable: true);
|
||||||
List<Peer> get selectedPeers => _selectedPeers;
|
List<Peer> get selectedPeers => _selectedPeers;
|
||||||
bool _multiSelectionMode = false;
|
bool _multiSelectionMode = false;
|
||||||
@@ -53,7 +72,7 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
PeerTabModel(this.parent) {
|
PeerTabModel(this.parent) {
|
||||||
// visible
|
// visible
|
||||||
try {
|
try {
|
||||||
final option = bind.getLocalFlutterOption(k: 'peer-tab-visible');
|
final option = bind.getLocalFlutterOption(k: kPeerTabVisible);
|
||||||
if (option.isNotEmpty) {
|
if (option.isNotEmpty) {
|
||||||
List<dynamic> decodeList = jsonDecode(option);
|
List<dynamic> decodeList = jsonDecode(option);
|
||||||
if (decodeList.length == _isVisible.length) {
|
if (decodeList.length == _isVisible.length) {
|
||||||
@@ -67,13 +86,37 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("failed to get peer tab visible list:$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
|
// init currentTab
|
||||||
_currentTab =
|
_currentTab =
|
||||||
int.tryParse(bind.getLocalFlutterOption(k: 'peer-tab-index')) ?? 0;
|
int.tryParse(bind.getLocalFlutterOption(k: kPeerTabIndex)) ?? 0;
|
||||||
if (_currentTab < 0 || _currentTab >= tabNames.length) {
|
if (_currentTab < 0 || _currentTab >= maxTabCount) {
|
||||||
_currentTab = 0;
|
_currentTab = 0;
|
||||||
}
|
}
|
||||||
_trySetCurrentTabToFirstVisible();
|
_trySetCurrentTabToFirstVisibleEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentTab(int index) {
|
setCurrentTab(int index) {
|
||||||
@@ -87,15 +130,13 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
if (index >= 0 && index < tabNames.length) {
|
if (index >= 0 && index < tabNames.length) {
|
||||||
return translate(tabNames[index]);
|
return translate(tabNames[index]);
|
||||||
}
|
}
|
||||||
assert(false);
|
|
||||||
return index.toString();
|
return index.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
IconData tabIcon(int index) {
|
IconData tabIcon(int index) {
|
||||||
if (index >= 0 && index < tabNames.length) {
|
if (index >= 0 && index < icons.length) {
|
||||||
return icons[index];
|
return icons[index];
|
||||||
}
|
}
|
||||||
assert(false);
|
|
||||||
return Icons.help;
|
return Icons.help;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,29 +212,54 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTabVisible(int index, bool visible) {
|
setTabVisible(int index, bool visible) {
|
||||||
if (index >= 0 && index < _isVisible.length) {
|
if (index >= 0 && index < maxTabCount) {
|
||||||
if (_isVisible[index] != visible) {
|
if (_isVisible[index] != visible) {
|
||||||
_isVisible[index] = visible;
|
_isVisible[index] = visible;
|
||||||
if (index == _currentTab && !visible) {
|
if (index == _currentTab && !visible) {
|
||||||
_trySetCurrentTabToFirstVisible();
|
_trySetCurrentTabToFirstVisibleEnabled();
|
||||||
} else if (visible && visibleIndexs.length == 1) {
|
} else if (visible && visibleEnabledOrderedIndexs.length == 1) {
|
||||||
_currentTab = index;
|
_currentTab = index;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
bind.setLocalFlutterOption(
|
bind.setLocalFlutterOption(
|
||||||
k: 'peer-tab-visible', v: jsonEncode(_isVisible));
|
k: kPeerTabVisible, v: jsonEncode(_isVisible));
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_trySetCurrentTabToFirstVisible() {
|
_trySetCurrentTabToFirstVisibleEnabled() {
|
||||||
if (!_isVisible[_currentTab]) {
|
if (!visibleEnabledOrderedIndexs.contains(_currentTab)) {
|
||||||
int firstVisible = _isVisible.indexWhere((e) => e);
|
if (visibleEnabledOrderedIndexs.isNotEmpty) {
|
||||||
if (firstVisible >= 0) {
|
_currentTab = visibleEnabledOrderedIndexs.first;
|
||||||
_currentTab = firstVisible;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1052,7 +1052,7 @@ class RustdeskImpl {
|
|||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mainHasGpucodec({dynamic hint}) {
|
bool mainHasVram({dynamic hint}) {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1573,5 +1573,14 @@ class RustdeskImpl {
|
|||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> mainCheckHwcodec({dynamic hint}) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sessionRequestNewDisplayInitMsgs(
|
||||||
|
{required UuidValue sessionId, required int display, dynamic hint}) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
void dispose() {}
|
void dispose() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,7 +210,7 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 0920;
|
LastSwiftUpdateCheck = 0920;
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1510;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
33CC10EC2044A3C60003C045 = {
|
33CC10EC2044A3C60003C045 = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1510"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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
|
||||||
|
|||||||
@@ -849,18 +849,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.8.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.11.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -921,10 +921,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.3"
|
version: "1.9.0"
|
||||||
path_parsing:
|
path_parsing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1526,10 +1526,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.4.2"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
flexi_logger = { version = "0.27", features = ["async"] }
|
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 = { version = "1.36", features = ["full"] }
|
||||||
tokio-util = { version = "0.7", features = ["full"] }
|
tokio-util = { version = "0.7", features = ["full"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
@@ -44,6 +44,7 @@ thiserror = "1.0"
|
|||||||
httparse = "1.5"
|
httparse = "1.5"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
mac_address = "1.1"
|
mac_address = "1.1"
|
||||||
machine-uid = { git = "https://github.com/21pages/machine-uid" }
|
machine-uid = { git = "https://github.com/21pages/machine-uid" }
|
||||||
@@ -59,7 +60,7 @@ quic = []
|
|||||||
flatpak = []
|
flatpak = []
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
protobuf-codegen = { version = "3.3" }
|
protobuf-codegen = { version = "3.4" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3", features = ["winuser", "synchapi", "pdh", "memoryapi"] }
|
winapi = { version = "0.3", features = ["winuser", "synchapi", "pdh", "memoryapi"] }
|
||||||
|
|||||||
@@ -504,6 +504,11 @@ message Resolution {
|
|||||||
int32 height = 2;
|
int32 height = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message DisplayResolution {
|
||||||
|
int32 display = 1;
|
||||||
|
Resolution resolution = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message SupportedResolutions { repeated Resolution resolutions = 1; }
|
message SupportedResolutions { repeated Resolution resolutions = 1; }
|
||||||
|
|
||||||
message SwitchDisplay {
|
message SwitchDisplay {
|
||||||
@@ -596,7 +601,8 @@ message OptionMessage {
|
|||||||
BoolOption disable_keyboard = 12;
|
BoolOption disable_keyboard = 12;
|
||||||
// Position 13 is used for Resolution. Remove later.
|
// Position 13 is used for Resolution. Remove later.
|
||||||
// Resolution custom_resolution = 13;
|
// 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 {
|
message TestDelay {
|
||||||
@@ -716,6 +722,13 @@ message WindowsSessions {
|
|||||||
uint32 current_sid = 2;
|
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 {
|
message Misc {
|
||||||
oneof union {
|
oneof union {
|
||||||
ChatMessage chat_message = 4;
|
ChatMessage chat_message = 4;
|
||||||
@@ -736,6 +749,8 @@ message Misc {
|
|||||||
bool portable_service_running = 20;
|
bool portable_service_running = 20;
|
||||||
SwitchSidesRequest switch_sides_request = 21;
|
SwitchSidesRequest switch_sides_request = 21;
|
||||||
SwitchBack switch_back = 22;
|
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;
|
Resolution change_resolution = 24;
|
||||||
PluginRequest plugin_request = 25;
|
PluginRequest plugin_request = 25;
|
||||||
PluginFailure plugin_failure = 26;
|
PluginFailure plugin_failure = 26;
|
||||||
@@ -748,6 +763,8 @@ message Misc {
|
|||||||
TogglePrivacyMode toggle_privacy_mode = 33;
|
TogglePrivacyMode toggle_privacy_mode = 33;
|
||||||
SupportedEncoding supported_encoding = 34;
|
SupportedEncoding supported_encoding = 34;
|
||||||
uint32 selected_sid = 35;
|
uint32 selected_sid = 35;
|
||||||
|
DisplayResolution change_display_resolution = 36;
|
||||||
|
MessageQuery message_query = 37;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -409,9 +409,7 @@ fn patch(path: PathBuf) -> PathBuf {
|
|||||||
if let Ok(user) = crate::platform::linux::run_cmds_trim_newline("whoami") {
|
if let Ok(user) = crate::platform::linux::run_cmds_trim_newline("whoami") {
|
||||||
if user != "root" {
|
if user != "root" {
|
||||||
let cmd = format!("getent passwd '{}' | awk -F':' '{{print $6}}'", user);
|
let cmd = format!("getent passwd '{}' | awk -F':' '{{print $6}}'", user);
|
||||||
if let Ok(output) =
|
if let Ok(output) = crate::platform::linux::run_cmds_trim_newline(&cmd) {
|
||||||
crate::platform::linux::run_cmds_trim_newline(&cmd)
|
|
||||||
{
|
|
||||||
return output.into();
|
return output.into();
|
||||||
}
|
}
|
||||||
return format!("/home/{user}").into();
|
return format!("/home/{user}").into();
|
||||||
@@ -505,7 +503,7 @@ impl Config {
|
|||||||
fn store_<T: serde::Serialize>(config: &T, suffix: &str) {
|
fn store_<T: serde::Serialize>(config: &T, suffix: &str) {
|
||||||
let file = Self::file_(suffix);
|
let file = Self::file_(suffix);
|
||||||
if let Err(err) = store_path(file, config) {
|
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)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct HwCodecConfig {
|
pub struct HwCodecConfig {
|
||||||
#[serde(default, deserialize_with = "deserialize_hashmap_string_string")]
|
#[serde(default, deserialize_with = "deserialize_string")]
|
||||||
pub options: HashMap<String, String>,
|
pub ram: String,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_string")]
|
||||||
|
pub vram: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HwCodecConfig {
|
impl HwCodecConfig {
|
||||||
@@ -1511,25 +1511,17 @@ impl HwCodecConfig {
|
|||||||
pub fn clear() {
|
pub fn clear() {
|
||||||
HwCodecConfig::default().store();
|
HwCodecConfig::default().store();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
pub fn clear_ram() {
|
||||||
pub struct GpucodecConfig {
|
let mut c = Self::load();
|
||||||
#[serde(default, deserialize_with = "deserialize_string")]
|
c.ram = Default::default();
|
||||||
pub available: String,
|
c.store();
|
||||||
}
|
|
||||||
|
|
||||||
impl GpucodecConfig {
|
|
||||||
pub fn load() -> GpucodecConfig {
|
|
||||||
Config::load_::<GpucodecConfig>("_gpucodec")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store(&self) {
|
pub fn clear_vram() {
|
||||||
Config::store_(self, "_gpucodec");
|
let mut c = Self::load();
|
||||||
}
|
c.vram = Default::default();
|
||||||
|
c.store();
|
||||||
pub fn clear() {
|
|
||||||
GpucodecConfig::default().store();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1569,6 +1561,7 @@ impl UserDefaultConfig {
|
|||||||
}
|
}
|
||||||
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 0xFFF as f64),
|
"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),
|
"custom-fps" => self.get_double_string(key, 30.0, 5.0, 120.0),
|
||||||
|
"enable_file_transfer" => self.get_string(key, "Y", vec![""]),
|
||||||
_ => self
|
_ => self
|
||||||
.get_after(key)
|
.get_after(key)
|
||||||
.map(|v| v.to_string())
|
.map(|v| v.to_string())
|
||||||
|
|||||||
@@ -47,10 +47,13 @@ pub mod keyboard;
|
|||||||
pub use dlopen;
|
pub use dlopen;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub use machine_uid;
|
pub use machine_uid;
|
||||||
|
pub use serde_derive;
|
||||||
|
pub use serde_json;
|
||||||
pub use sysinfo;
|
pub use sysinfo;
|
||||||
pub use toml;
|
pub use toml;
|
||||||
pub use uuid;
|
pub use uuid;
|
||||||
pub use base64;
|
pub use base64;
|
||||||
|
pub use thiserror;
|
||||||
|
|
||||||
#[cfg(feature = "quic")]
|
#[cfg(feature = "quic")]
|
||||||
pub type Stream = quic::Connection;
|
pub type Stream = quic::Connection;
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ edition = "2018"
|
|||||||
wayland = ["gstreamer", "gstreamer-app", "gstreamer-video", "dbus", "tracing"]
|
wayland = ["gstreamer", "gstreamer-app", "gstreamer-video", "dbus", "tracing"]
|
||||||
mediacodec = ["ndk"]
|
mediacodec = ["ndk"]
|
||||||
linux-pkg-config = ["dep:pkg-config"]
|
linux-pkg-config = ["dep:pkg-config"]
|
||||||
|
hwcodec = ["dep:hwcodec"]
|
||||||
|
vram = ["hwcodec/vram"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
@@ -20,6 +22,7 @@ num_cpus = "1.15"
|
|||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
hbb_common = { path = "../hbb_common" }
|
hbb_common = { path = "../hbb_common" }
|
||||||
webm = { git = "https://github.com/21pages/rust-webm" }
|
webm = { git = "https://github.com/21pages/rust-webm" }
|
||||||
|
serde = {version="1.0", features=["derive"]}
|
||||||
|
|
||||||
[dependencies.winapi]
|
[dependencies.winapi]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
@@ -41,7 +44,6 @@ ndk-context = "0.1"
|
|||||||
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
|
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
|
||||||
repng = "0.2"
|
repng = "0.2"
|
||||||
docopt = "1.1"
|
docopt = "1.1"
|
||||||
serde = {version="1.0", features=["derive"]}
|
|
||||||
quest = "0.3"
|
quest = "0.3"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
@@ -56,8 +58,8 @@ gstreamer = { version = "0.16", optional = true }
|
|||||||
gstreamer-app = { version = "0.16", features = ["v1_10"], optional = true }
|
gstreamer-app = { version = "0.16", features = ["v1_10"], optional = true }
|
||||||
gstreamer-video = { version = "0.16", optional = true }
|
gstreamer-video = { version = "0.16", optional = true }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
|
[dependencies.hwcodec]
|
||||||
hwcodec = { git = "https://github.com/21pages/hwcodec", branch = "stable", optional = true }
|
git = "https://github.com/21pages/hwcodec"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
|
||||||
gpucodec = { git = "https://github.com/21pages/gpucodec", optional = true }
|
|
||||||
|
|||||||
@@ -239,16 +239,16 @@ fn test_av1(
|
|||||||
|
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
mod hw {
|
mod hw {
|
||||||
use hwcodec::ffmpeg::CodecInfo;
|
use hwcodec::ffmpeg_ram::CodecInfo;
|
||||||
use scrap::{
|
use scrap::{
|
||||||
hwcodec::{HwDecoder, HwEncoder, HwEncoderConfig},
|
hwcodec::{HwRamDecoder, HwRamEncoder, HwRamEncoderConfig},
|
||||||
CodecFormat,
|
CodecFormat,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn test(c: &mut Capturer, width: usize, height: usize, quality: Q, yuv_count: usize) {
|
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 h264s = Vec::new();
|
||||||
let mut h265s = Vec::new();
|
let mut h265s = Vec::new();
|
||||||
if let Some(info) = best.h264 {
|
if let Some(info) = best.h264 {
|
||||||
@@ -270,8 +270,8 @@ mod hw {
|
|||||||
yuv_count: usize,
|
yuv_count: usize,
|
||||||
h26xs: &mut Vec<Vec<u8>>,
|
h26xs: &mut Vec<Vec<u8>>,
|
||||||
) {
|
) {
|
||||||
let mut encoder = HwEncoder::new(
|
let mut encoder = HwRamEncoder::new(
|
||||||
EncoderCfg::HW(HwEncoderConfig {
|
EncoderCfg::HWRAM(HwRamEncoderConfig {
|
||||||
name: info.name.clone(),
|
name: info.name.clone(),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@@ -321,7 +321,7 @@ mod hw {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_decoder(format: CodecFormat, h26xs: &Vec<Vec<u8>>) {
|
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 start = Instant::now();
|
||||||
let mut cnt = 0;
|
let mut cnt = 0;
|
||||||
for h26x in h26xs {
|
for h26x in h26xs {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ pub fn get_audio_raw<'a>() -> Option<&'a [u8]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onVideoFrameUpdate(
|
pub extern "system" fn Java_ffi_FFI_onVideoFrameUpdate(
|
||||||
env: JNIEnv,
|
env: JNIEnv,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
buffer: JObject,
|
buffer: JObject,
|
||||||
@@ -108,7 +108,7 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onVideoFrameUpd
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onAudioFrameUpdate(
|
pub extern "system" fn Java_ffi_FFI_onAudioFrameUpdate(
|
||||||
env: JNIEnv,
|
env: JNIEnv,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
buffer: JObject,
|
buffer: JObject,
|
||||||
@@ -122,7 +122,7 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onAudioFrameUpd
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_setFrameRawEnable(
|
pub extern "system" fn Java_ffi_FFI_setFrameRawEnable(
|
||||||
env: JNIEnv,
|
env: JNIEnv,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
name: JString,
|
name: JString,
|
||||||
@@ -141,7 +141,7 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_setFrameRawEnab
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_init(
|
pub extern "system" fn Java_ffi_FFI_init(
|
||||||
env: JNIEnv,
|
env: JNIEnv,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
ctx: JObject,
|
ctx: JObject,
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ impl EncoderApi for AomEncoder {
|
|||||||
self.yuvfmt.clone()
|
self.yuvfmt.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn input_texture(&self) -> bool {
|
fn input_texture(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
|
||||||
use crate::gpucodec::*;
|
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
use crate::hwcodec::*;
|
use crate::hwcodec::*;
|
||||||
#[cfg(feature = "mediacodec")]
|
#[cfg(feature = "mediacodec")]
|
||||||
use crate::mediacodec::{MediaCodecDecoder, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT};
|
use crate::mediacodec::{MediaCodecDecoder, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT};
|
||||||
|
#[cfg(feature = "vram")]
|
||||||
|
use crate::vram::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
aom::{self, AomDecoder, AomEncoder, AomEncoderConfig},
|
aom::{self, AomDecoder, AomEncoder, AomEncoderConfig},
|
||||||
common::GoogleImage,
|
common::GoogleImage,
|
||||||
@@ -31,7 +31,7 @@ use hbb_common::{
|
|||||||
tokio::time::Instant,
|
tokio::time::Instant,
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec", feature = "gpucodec"))]
|
#[cfg(any(feature = "hwcodec", feature = "mediacodec", feature = "vram"))]
|
||||||
use hbb_common::{config::Config2, lazy_static};
|
use hbb_common::{config::Config2, lazy_static};
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
@@ -47,9 +47,9 @@ pub enum EncoderCfg {
|
|||||||
VPX(VpxEncoderConfig),
|
VPX(VpxEncoderConfig),
|
||||||
AOM(AomEncoderConfig),
|
AOM(AomEncoderConfig),
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
HW(HwEncoderConfig),
|
HWRAM(HwRamEncoderConfig),
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
GPU(GpuEncoderConfig),
|
VRAM(VRamEncoderConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait EncoderApi {
|
pub trait EncoderApi {
|
||||||
@@ -61,7 +61,7 @@ pub trait EncoderApi {
|
|||||||
|
|
||||||
fn yuvfmt(&self) -> EncodeYuvFormat;
|
fn yuvfmt(&self) -> EncodeYuvFormat;
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn input_texture(&self) -> bool;
|
fn input_texture(&self) -> bool;
|
||||||
|
|
||||||
fn set_quality(&mut self, quality: Quality) -> ResultType<()>;
|
fn set_quality(&mut self, quality: Quality) -> ResultType<()>;
|
||||||
@@ -94,13 +94,13 @@ pub struct Decoder {
|
|||||||
vp9: Option<VpxDecoder>,
|
vp9: Option<VpxDecoder>,
|
||||||
av1: Option<AomDecoder>,
|
av1: Option<AomDecoder>,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
h264_ram: Option<HwDecoder>,
|
h264_ram: Option<HwRamDecoder>,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
h265_ram: Option<HwDecoder>,
|
h265_ram: Option<HwRamDecoder>,
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
h264_vram: Option<GpuDecoder>,
|
h264_vram: Option<VRamDecoder>,
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
h265_vram: Option<GpuDecoder>,
|
h265_vram: Option<VRamDecoder>,
|
||||||
#[cfg(feature = "mediacodec")]
|
#[cfg(feature = "mediacodec")]
|
||||||
h264_media_codec: MediaCodecDecoder,
|
h264_media_codec: MediaCodecDecoder,
|
||||||
#[cfg(feature = "mediacodec")]
|
#[cfg(feature = "mediacodec")]
|
||||||
@@ -131,25 +131,25 @@ impl Encoder {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
EncoderCfg::HW(_) => match HwEncoder::new(config, i444) {
|
EncoderCfg::HWRAM(_) => match HwRamEncoder::new(config, i444) {
|
||||||
Ok(hw) => Ok(Encoder {
|
Ok(hw) => Ok(Encoder {
|
||||||
codec: Box::new(hw),
|
codec: Box::new(hw),
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("new hw encoder failed: {e:?}, clear config");
|
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;
|
*ENCODE_CODEC_NAME.lock().unwrap() = CodecName::VP9;
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
EncoderCfg::GPU(_) => match GpuEncoder::new(config, i444) {
|
EncoderCfg::VRAM(_) => match VRamEncoder::new(config, i444) {
|
||||||
Ok(tex) => Ok(Encoder {
|
Ok(tex) => Ok(Encoder {
|
||||||
codec: Box::new(tex),
|
codec: Box::new(tex),
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("new gpu encoder failed: {e:?}, clear config");
|
log::error!("new vram encoder failed: {e:?}, clear config");
|
||||||
hbb_common::config::GpucodecConfig::clear();
|
hbb_common::config::HwCodecConfig::clear_vram();
|
||||||
*ENCODE_CODEC_NAME.lock().unwrap() = CodecName::VP9;
|
*ENCODE_CODEC_NAME.lock().unwrap() = CodecName::VP9;
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
@@ -186,19 +186,19 @@ impl Encoder {
|
|||||||
let _all_support_h265_decoding =
|
let _all_support_h265_decoding =
|
||||||
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h265 > 0);
|
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h265 > 0);
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut h264gpu_encoding = false;
|
let mut h264vram_encoding = false;
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut h265gpu_encoding = false;
|
let mut h265vram_encoding = false;
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
if enable_gpucodec_option() {
|
if enable_vram_option() {
|
||||||
if _all_support_h264_decoding {
|
if _all_support_h264_decoding {
|
||||||
if GpuEncoder::available(CodecName::H264GPU).len() > 0 {
|
if VRamEncoder::available(CodecName::H264VRAM).len() > 0 {
|
||||||
h264gpu_encoding = true;
|
h264vram_encoding = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _all_support_h265_decoding {
|
if _all_support_h265_decoding {
|
||||||
if GpuEncoder::available(CodecName::H265GPU).len() > 0 {
|
if VRamEncoder::available(CodecName::H265VRAM).len() > 0 {
|
||||||
h265gpu_encoding = true;
|
h265vram_encoding = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ impl Encoder {
|
|||||||
let mut h265hw_encoding = None;
|
let mut h265hw_encoding = None;
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if enable_hwcodec_option() {
|
if enable_hwcodec_option() {
|
||||||
let best = HwEncoder::best();
|
let best = HwRamEncoder::best();
|
||||||
if _all_support_h264_decoding {
|
if _all_support_h264_decoding {
|
||||||
h264hw_encoding = best.h264.map_or(None, |c| Some(c.name));
|
h264hw_encoding = best.h264.map_or(None, |c| Some(c.name));
|
||||||
}
|
}
|
||||||
@@ -217,9 +217,9 @@ impl Encoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let h264_useable =
|
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 =
|
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 name = ENCODE_CODEC_NAME.lock().unwrap();
|
||||||
let mut preference = PreferCodec::Auto;
|
let mut preference = PreferCodec::Auto;
|
||||||
let preferences: Vec<_> = decodings
|
let preferences: Vec<_> = decodings
|
||||||
@@ -239,7 +239,8 @@ impl Encoder {
|
|||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut auto_codec = CodecName::VP9;
|
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;
|
auto_codec = CodecName::AV1;
|
||||||
}
|
}
|
||||||
let mut system = System::new();
|
let mut system = System::new();
|
||||||
@@ -254,19 +255,19 @@ impl Encoder {
|
|||||||
PreferCodec::VP9 => CodecName::VP9,
|
PreferCodec::VP9 => CodecName::VP9,
|
||||||
PreferCodec::AV1 => CodecName::AV1,
|
PreferCodec::AV1 => CodecName::AV1,
|
||||||
PreferCodec::H264 => {
|
PreferCodec::H264 => {
|
||||||
if h264gpu_encoding {
|
if h264vram_encoding {
|
||||||
CodecName::H264GPU
|
CodecName::H264VRAM
|
||||||
} else if let Some(v) = h264hw_encoding {
|
} else if let Some(v) = h264hw_encoding {
|
||||||
CodecName::H264HW(v)
|
CodecName::H264RAM(v)
|
||||||
} else {
|
} else {
|
||||||
auto_codec
|
auto_codec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PreferCodec::H265 => {
|
PreferCodec::H265 => {
|
||||||
if h265gpu_encoding {
|
if h265vram_encoding {
|
||||||
CodecName::H265GPU
|
CodecName::H265VRAM
|
||||||
} else if let Some(v) = h265hw_encoding {
|
} else if let Some(v) = h265hw_encoding {
|
||||||
CodecName::H265HW(v)
|
CodecName::H265RAM(v)
|
||||||
} else {
|
} else {
|
||||||
auto_codec
|
auto_codec
|
||||||
}
|
}
|
||||||
@@ -306,14 +307,14 @@ impl Encoder {
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if enable_hwcodec_option() {
|
if enable_hwcodec_option() {
|
||||||
let best = HwEncoder::best();
|
let best = HwRamEncoder::best();
|
||||||
encoding.h264 |= best.h264.is_some();
|
encoding.h264 |= best.h264.is_some();
|
||||||
encoding.h265 |= best.h265.is_some();
|
encoding.h265 |= best.h265.is_some();
|
||||||
}
|
}
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
if enable_gpucodec_option() {
|
if enable_vram_option() {
|
||||||
encoding.h264 |= GpuEncoder::available(CodecName::H264GPU).len() > 0;
|
encoding.h264 |= VRamEncoder::available(CodecName::H264VRAM).len() > 0;
|
||||||
encoding.h265 |= GpuEncoder::available(CodecName::H265GPU).len() > 0;
|
encoding.h265 |= VRamEncoder::available(CodecName::H265VRAM).len() > 0;
|
||||||
}
|
}
|
||||||
encoding
|
encoding
|
||||||
}
|
}
|
||||||
@@ -326,21 +327,21 @@ impl Encoder {
|
|||||||
},
|
},
|
||||||
EncoderCfg::AOM(_) => CodecName::AV1,
|
EncoderCfg::AOM(_) => CodecName::AV1,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
EncoderCfg::HW(hw) => {
|
EncoderCfg::HWRAM(hw) => {
|
||||||
if hw.name.to_lowercase().contains("h264") {
|
if hw.name.to_lowercase().contains("h264") {
|
||||||
CodecName::H264HW(hw.name.clone())
|
CodecName::H264RAM(hw.name.clone())
|
||||||
} else {
|
} else {
|
||||||
CodecName::H265HW(hw.name.clone())
|
CodecName::H265RAM(hw.name.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
EncoderCfg::GPU(gpu) => match gpu.feature.data_format {
|
EncoderCfg::VRAM(vram) => match vram.feature.data_format {
|
||||||
gpucodec::gpu_common::DataFormat::H264 => CodecName::H264GPU,
|
hwcodec::common::DataFormat::H264 => CodecName::H264VRAM,
|
||||||
gpucodec::gpu_common::DataFormat::H265 => CodecName::H265GPU,
|
hwcodec::common::DataFormat::H265 => CodecName::H265VRAM,
|
||||||
_ => {
|
_ => {
|
||||||
log::error!(
|
log::error!(
|
||||||
"should not reach here, gpucodec not support {:?}",
|
"should not reach here, vram not support {:?}",
|
||||||
gpu.feature.data_format
|
vram.feature.data_format
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -365,9 +366,9 @@ impl Encoder {
|
|||||||
},
|
},
|
||||||
EncoderCfg::AOM(_) => decodings.iter().all(|d| d.1.i444.av1),
|
EncoderCfg::AOM(_) => decodings.iter().all(|d| d.1.i444.av1),
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
EncoderCfg::HW(_) => false,
|
EncoderCfg::HWRAM(_) => false,
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
EncoderCfg::GPU(_) => false,
|
EncoderCfg::VRAM(_) => false,
|
||||||
};
|
};
|
||||||
prefer_i444 && i444_useable && !decodings.is_empty()
|
prefer_i444 && i444_useable && !decodings.is_empty()
|
||||||
}
|
}
|
||||||
@@ -398,19 +399,19 @@ impl Decoder {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
#[cfg(feature = "hwcodec")]
|
#[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_h264 |= if best.h264.is_some() { 1 } else { 0 };
|
||||||
decoding.ability_h265 |= if best.h265.is_some() { 1 } else { 0 };
|
decoding.ability_h265 |= if best.h265.is_some() { 1 } else { 0 };
|
||||||
}
|
}
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
if enable_gpucodec_option() && _flutter {
|
if enable_vram_option() && _flutter {
|
||||||
decoding.ability_h264 |= if GpuDecoder::available(CodecFormat::H264, _luid).len() > 0 {
|
decoding.ability_h264 |= if VRamDecoder::available(CodecFormat::H264, _luid).len() > 0 {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
decoding.ability_h265 |= if GpuDecoder::available(CodecFormat::H265, _luid).len() > 0 {
|
decoding.ability_h265 |= if VRamDecoder::available(CodecFormat::H265, _luid).len() > 0 {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
@@ -449,7 +450,7 @@ impl Decoder {
|
|||||||
let (mut vp8, mut vp9, mut av1) = (None, None, None);
|
let (mut vp8, mut vp9, mut av1) = (None, None, None);
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
let (mut h264_ram, mut h265_ram) = (None, None);
|
let (mut h264_ram, mut h265_ram) = (None, None);
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
let (mut h264_vram, mut h265_vram) = (None, None);
|
let (mut h264_vram, mut h265_vram) = (None, None);
|
||||||
#[cfg(feature = "mediacodec")]
|
#[cfg(feature = "mediacodec")]
|
||||||
let (mut h264_media_codec, mut h265_media_codec) = (None, None);
|
let (mut h264_media_codec, mut h265_media_codec) = (None, None);
|
||||||
@@ -482,17 +483,17 @@ impl Decoder {
|
|||||||
valid = av1.is_some();
|
valid = av1.is_some();
|
||||||
}
|
}
|
||||||
CodecFormat::H264 => {
|
CodecFormat::H264 => {
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
if !valid && enable_gpucodec_option() && _luid.clone().unwrap_or_default() != 0 {
|
if !valid && enable_vram_option() && _luid.clone().unwrap_or_default() != 0 {
|
||||||
match GpuDecoder::new(format, _luid) {
|
match VRamDecoder::new(format, _luid) {
|
||||||
Ok(v) => h264_vram = Some(v),
|
Ok(v) => h264_vram = Some(v),
|
||||||
Err(e) => log::error!("create H264 vram decoder failed: {}", e),
|
Err(e) => log::error!("create H264 vram decoder failed: {}", e),
|
||||||
}
|
}
|
||||||
valid = h264_vram.is_some();
|
valid = h264_vram.is_some();
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if !valid && enable_hwcodec_option() {
|
if !valid {
|
||||||
match HwDecoder::new(format) {
|
match HwRamDecoder::new(format) {
|
||||||
Ok(v) => h264_ram = Some(v),
|
Ok(v) => h264_ram = Some(v),
|
||||||
Err(e) => log::error!("create H264 ram decoder failed: {}", e),
|
Err(e) => log::error!("create H264 ram decoder failed: {}", e),
|
||||||
}
|
}
|
||||||
@@ -508,17 +509,17 @@ impl Decoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CodecFormat::H265 => {
|
CodecFormat::H265 => {
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
if !valid && enable_gpucodec_option() && _luid.clone().unwrap_or_default() != 0 {
|
if !valid && enable_vram_option() && _luid.clone().unwrap_or_default() != 0 {
|
||||||
match GpuDecoder::new(format, _luid) {
|
match VRamDecoder::new(format, _luid) {
|
||||||
Ok(v) => h265_vram = Some(v),
|
Ok(v) => h265_vram = Some(v),
|
||||||
Err(e) => log::error!("create H265 vram decoder failed: {}", e),
|
Err(e) => log::error!("create H265 vram decoder failed: {}", e),
|
||||||
}
|
}
|
||||||
valid = h265_vram.is_some();
|
valid = h265_vram.is_some();
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if !valid && enable_hwcodec_option() {
|
if !valid {
|
||||||
match HwDecoder::new(format) {
|
match HwRamDecoder::new(format) {
|
||||||
Ok(v) => h265_ram = Some(v),
|
Ok(v) => h265_ram = Some(v),
|
||||||
Err(e) => log::error!("create H265 ram decoder failed: {}", e),
|
Err(e) => log::error!("create H265 ram decoder failed: {}", e),
|
||||||
}
|
}
|
||||||
@@ -550,9 +551,9 @@ impl Decoder {
|
|||||||
h264_ram,
|
h264_ram,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
h265_ram,
|
h265_ram,
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
h264_vram,
|
h264_vram,
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
h265_vram,
|
h265_vram,
|
||||||
#[cfg(feature = "mediacodec")]
|
#[cfg(feature = "mediacodec")]
|
||||||
h264_media_codec,
|
h264_media_codec,
|
||||||
@@ -604,31 +605,31 @@ impl Decoder {
|
|||||||
bail!("av1 decoder not available");
|
bail!("av1 decoder not available");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(any(feature = "hwcodec", feature = "gpucodec"))]
|
#[cfg(any(feature = "hwcodec", feature = "vram"))]
|
||||||
video_frame::Union::H264s(h264s) => {
|
video_frame::Union::H264s(h264s) => {
|
||||||
*chroma = Some(Chroma::I420);
|
*chroma = Some(Chroma::I420);
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
if let Some(decoder) = &mut self.h264_vram {
|
if let Some(decoder) = &mut self.h264_vram {
|
||||||
*_pixelbuffer = false;
|
*_pixelbuffer = false;
|
||||||
return Decoder::handle_gpu_video_frame(decoder, h264s, _texture);
|
return Decoder::handle_vram_video_frame(decoder, h264s, _texture);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if let Some(decoder) = &mut self.h264_ram {
|
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!"))
|
Err(anyhow!("don't support h264!"))
|
||||||
}
|
}
|
||||||
#[cfg(any(feature = "hwcodec", feature = "gpucodec"))]
|
#[cfg(any(feature = "hwcodec", feature = "vram"))]
|
||||||
video_frame::Union::H265s(h265s) => {
|
video_frame::Union::H265s(h265s) => {
|
||||||
*chroma = Some(Chroma::I420);
|
*chroma = Some(Chroma::I420);
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
if let Some(decoder) = &mut self.h265_vram {
|
if let Some(decoder) = &mut self.h265_vram {
|
||||||
*_pixelbuffer = false;
|
*_pixelbuffer = false;
|
||||||
return Decoder::handle_gpu_video_frame(decoder, h265s, _texture);
|
return Decoder::handle_vram_video_frame(decoder, h265s, _texture);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if let Some(decoder) = &mut self.h265_ram {
|
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!"))
|
Err(anyhow!("don't support h265!"))
|
||||||
}
|
}
|
||||||
@@ -710,8 +711,8 @@ impl Decoder {
|
|||||||
|
|
||||||
// rgb [in/out] fmt and stride must be set in ImageRgb
|
// rgb [in/out] fmt and stride must be set in ImageRgb
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
fn handle_hw_video_frame(
|
fn handle_hwram_video_frame(
|
||||||
decoder: &mut HwDecoder,
|
decoder: &mut HwRamDecoder,
|
||||||
frames: &EncodedVideoFrames,
|
frames: &EncodedVideoFrames,
|
||||||
rgb: &mut ImageRgb,
|
rgb: &mut ImageRgb,
|
||||||
i420: &mut Vec<u8>,
|
i420: &mut Vec<u8>,
|
||||||
@@ -728,9 +729,9 @@ impl Decoder {
|
|||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn handle_gpu_video_frame(
|
fn handle_vram_video_frame(
|
||||||
decoder: &mut GpuDecoder,
|
decoder: &mut VRamDecoder,
|
||||||
frames: &EncodedVideoFrames,
|
frames: &EncodedVideoFrames,
|
||||||
texture: &mut *mut c_void,
|
texture: &mut *mut c_void,
|
||||||
) -> ResultType<bool> {
|
) -> ResultType<bool> {
|
||||||
@@ -791,13 +792,16 @@ impl Decoder {
|
|||||||
|
|
||||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
||||||
pub fn enable_hwcodec_option() -> bool {
|
pub fn enable_hwcodec_option() -> bool {
|
||||||
if let Some(v) = Config2::get().options.get("enable-hwcodec") {
|
if cfg!(windows) || cfg!(target_os = "linux") || cfg!(feature = "mediacodec") {
|
||||||
return v != "N";
|
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")]
|
#[cfg(feature = "vram")]
|
||||||
pub fn enable_gpucodec_option() -> bool {
|
pub fn enable_vram_option() -> bool {
|
||||||
if let Some(v) = Config2::get().options.get("enable-hwcodec") {
|
if let Some(v) = Config2::get().options.get("enable-hwcodec") {
|
||||||
return v != "N";
|
return v != "N";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub mod hw {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::ImageFormat;
|
use crate::ImageFormat;
|
||||||
#[cfg(target_os = "windows")]
|
#[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")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn hw_nv12_to(
|
pub fn hw_nv12_to(
|
||||||
@@ -222,9 +222,7 @@ pub fn convert_to_yuv(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let align = |x:usize| {
|
let align = |x: usize| (x + 63) / 64 * 64;
|
||||||
(x + 63) / 64 * 64
|
|
||||||
};
|
|
||||||
|
|
||||||
match (src_pixfmt, dst_fmt.pixfmt) {
|
match (src_pixfmt, dst_fmt.pixfmt) {
|
||||||
(crate::Pixfmt::BGRA, crate::Pixfmt::I420) | (crate::Pixfmt::RGBA, crate::Pixfmt::I420) => {
|
(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_u = dst_fmt.stride[1];
|
||||||
let dst_stride_v = dst_fmt.stride[2];
|
let dst_stride_v = dst_fmt.stride[2];
|
||||||
dst.resize(
|
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,
|
0,
|
||||||
);
|
);
|
||||||
let dst_y = dst.as_mut_ptr();
|
let dst_y = dst.as_mut_ptr();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
use crate::AdapterDevice;
|
use crate::AdapterDevice;
|
||||||
use crate::{common::TraitCapturer, dxgi, Frame, Pixfmt};
|
use crate::{common::TraitCapturer, dxgi, Frame, Pixfmt};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -57,12 +57,12 @@ impl TraitCapturer for Capturer {
|
|||||||
self.inner.set_gdi()
|
self.inner.set_gdi()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn device(&self) -> AdapterDevice {
|
fn device(&self) -> AdapterDevice {
|
||||||
self.inner.device()
|
self.inner.device()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn set_output_texture(&mut self, texture: bool) {
|
fn set_output_texture(&mut self, texture: bool) {
|
||||||
self.inner.set_output_texture(texture);
|
self.inner.set_output_texture(texture);
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ impl Display {
|
|||||||
self.origin() == (0, 0)
|
self.origin() == (0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
pub fn adapter_luid(&self) -> Option<i64> {
|
pub fn adapter_luid(&self) -> Option<i64> {
|
||||||
self.0.adapter_luid()
|
self.0.adapter_luid()
|
||||||
}
|
}
|
||||||
@@ -247,11 +247,11 @@ impl TraitCapturer for CapturerMag {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn device(&self) -> AdapterDevice {
|
fn device(&self) -> AdapterDevice {
|
||||||
AdapterDevice::default()
|
AdapterDevice::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn set_output_texture(&mut self, _texture: bool) {}
|
fn set_output_texture(&mut self, _texture: bool) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,30 @@
|
|||||||
use crate::{
|
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,
|
hw, CodecFormat, EncodeInput, ImageFormat, ImageRgb, Pixfmt, HW_STRIDE_ALIGN,
|
||||||
};
|
};
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
|
||||||
anyhow::{anyhow, bail, Context},
|
anyhow::{anyhow, bail, Context},
|
||||||
bytes::Bytes,
|
bytes::Bytes,
|
||||||
config::HwCodecConfig,
|
config::HwCodecConfig,
|
||||||
log,
|
log,
|
||||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
||||||
ResultType,
|
serde_derive::{Deserialize, Serialize},
|
||||||
|
serde_json, ResultType,
|
||||||
};
|
};
|
||||||
use hwcodec::{
|
use hwcodec::{
|
||||||
decode::{DecodeContext, DecodeFrame, Decoder},
|
common::DataFormat,
|
||||||
encode::{EncodeContext, EncodeFrame, Encoder},
|
ffmpeg::AVPixelFormat,
|
||||||
ffmpeg::{CodecInfo, CodecInfos, DataFormat},
|
ffmpeg_ram::{
|
||||||
AVPixelFormat,
|
decode::{DecodeContext, DecodeFrame, Decoder},
|
||||||
Quality::{self, *},
|
encode::{EncodeContext, EncodeFrame, Encoder},
|
||||||
RateControl::{self, *},
|
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;
|
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_NV12;
|
||||||
pub const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
|
pub const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
|
||||||
const DEFAULT_GOP: i32 = i32::MAX;
|
const DEFAULT_GOP: i32 = i32::MAX;
|
||||||
@@ -30,7 +32,7 @@ const DEFAULT_HW_QUALITY: Quality = Quality_Default;
|
|||||||
const DEFAULT_RC: RateControl = RC_DEFAULT;
|
const DEFAULT_RC: RateControl = RC_DEFAULT;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HwEncoderConfig {
|
pub struct HwRamEncoderConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
@@ -38,7 +40,7 @@ pub struct HwEncoderConfig {
|
|||||||
pub keyframe_interval: Option<usize>,
|
pub keyframe_interval: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HwEncoder {
|
pub struct HwRamEncoder {
|
||||||
encoder: Encoder,
|
encoder: Encoder,
|
||||||
name: String,
|
name: String,
|
||||||
pub format: DataFormat,
|
pub format: DataFormat,
|
||||||
@@ -48,13 +50,13 @@ pub struct HwEncoder {
|
|||||||
bitrate: u32, //kbs
|
bitrate: u32, //kbs
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EncoderApi for HwEncoder {
|
impl EncoderApi for HwRamEncoder {
|
||||||
fn new(cfg: EncoderCfg, _i444: bool) -> ResultType<Self>
|
fn new(cfg: EncoderCfg, _i444: bool) -> ResultType<Self>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
match cfg {
|
match cfg {
|
||||||
EncoderCfg::HW(config) => {
|
EncoderCfg::HWRAM(config) => {
|
||||||
let b = Self::convert_quality(config.quality);
|
let b = Self::convert_quality(config.quality);
|
||||||
let base_bitrate = base_bitrate(config.width as _, config.height as _);
|
let base_bitrate = base_bitrate(config.width as _, config.height as _);
|
||||||
let mut bitrate = base_bitrate * b / 100;
|
let mut bitrate = base_bitrate * b / 100;
|
||||||
@@ -85,7 +87,7 @@ impl EncoderApi for HwEncoder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
match Encoder::new(ctx.clone()) {
|
match Encoder::new(ctx.clone()) {
|
||||||
Ok(encoder) => Ok(HwEncoder {
|
Ok(encoder) => Ok(HwRamEncoder {
|
||||||
encoder,
|
encoder,
|
||||||
name: config.name,
|
name: config.name,
|
||||||
format,
|
format,
|
||||||
@@ -95,7 +97,7 @@ impl EncoderApi for HwEncoder {
|
|||||||
bitrate,
|
bitrate,
|
||||||
}),
|
}),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
HwCodecConfig::clear();
|
HwCodecConfig::clear_ram();
|
||||||
Err(anyhow!(format!("Failed to create encoder")))
|
Err(anyhow!(format!("Failed to create encoder")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,6 +128,7 @@ impl EncoderApi for HwEncoder {
|
|||||||
match self.format {
|
match self.format {
|
||||||
DataFormat::H264 => vf.set_h264s(frames),
|
DataFormat::H264 => vf.set_h264s(frames),
|
||||||
DataFormat::H265 => vf.set_h265s(frames),
|
DataFormat::H265 => vf.set_h265s(frames),
|
||||||
|
_ => bail!("unsupported format: {:?}", self.format),
|
||||||
}
|
}
|
||||||
Ok(vf)
|
Ok(vf)
|
||||||
} else {
|
} else {
|
||||||
@@ -160,7 +163,7 @@ impl EncoderApi for HwEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn input_texture(&self) -> bool {
|
fn input_texture(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -184,9 +187,9 @@ impl EncoderApi for HwEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HwEncoder {
|
impl HwRamEncoder {
|
||||||
pub fn best() -> CodecInfos {
|
pub fn best() -> CodecInfos {
|
||||||
get_config(CFG_KEY_ENCODER).unwrap_or(CodecInfos {
|
get_config().map(|c| c.e).unwrap_or(CodecInfos {
|
||||||
h264: None,
|
h264: None,
|
||||||
h265: None,
|
h265: None,
|
||||||
})
|
})
|
||||||
@@ -214,28 +217,30 @@ impl HwEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HwDecoder {
|
pub struct HwRamDecoder {
|
||||||
decoder: Decoder,
|
decoder: Decoder,
|
||||||
pub info: CodecInfo,
|
pub info: CodecInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
impl HwRamDecoder {
|
||||||
pub struct HwDecoders {
|
|
||||||
pub h264: Option<HwDecoder>,
|
|
||||||
pub h265: Option<HwDecoder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HwDecoder {
|
|
||||||
pub fn best() -> CodecInfos {
|
pub fn best() -> CodecInfos {
|
||||||
get_config(CFG_KEY_DECODER).unwrap_or(CodecInfos {
|
let mut info = CodecInfo::soft();
|
||||||
h264: None,
|
if enable_hwcodec_option() {
|
||||||
h265: None,
|
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> {
|
pub fn new(format: CodecFormat) -> ResultType<Self> {
|
||||||
log::info!("try create {format:?} ram decoder");
|
log::info!("try create {format:?} ram decoder");
|
||||||
let best = HwDecoder::best();
|
let best = HwRamDecoder::best();
|
||||||
let info = match format {
|
let info = match format {
|
||||||
CodecFormat::H264 => {
|
CodecFormat::H264 => {
|
||||||
if let Some(info) = best.h264 {
|
if let Some(info) = best.h264 {
|
||||||
@@ -259,26 +264,26 @@ impl HwDecoder {
|
|||||||
thread_count: codec_thread_num(16) as _,
|
thread_count: codec_thread_num(16) as _,
|
||||||
};
|
};
|
||||||
match Decoder::new(ctx) {
|
match Decoder::new(ctx) {
|
||||||
Ok(decoder) => Ok(HwDecoder { decoder, info }),
|
Ok(decoder) => Ok(HwRamDecoder { decoder, info }),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
HwCodecConfig::clear();
|
HwCodecConfig::clear_ram();
|
||||||
Err(anyhow!(format!("Failed to create decoder")))
|
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) {
|
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)),
|
Err(e) => Err(anyhow!(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HwDecoderImage<'a> {
|
pub struct HwRamDecoderImage<'a> {
|
||||||
frame: &'a DecodeFrame,
|
frame: &'a DecodeFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HwDecoderImage<'_> {
|
impl HwRamDecoderImage<'_> {
|
||||||
// rgb [in/out] fmt and stride must be set in ImageRgb
|
// rgb [in/out] fmt and stride must be set in ImageRgb
|
||||||
pub fn to_fmt(&self, rgb: &mut ImageRgb, i420: &mut Vec<u8>) -> ResultType<()> {
|
pub fn to_fmt(&self, rgb: &mut ImageRgb, i420: &mut Vec<u8>) -> ResultType<()> {
|
||||||
let frame = self.frame;
|
let frame = self.frame;
|
||||||
@@ -332,23 +337,24 @@ impl HwDecoderImage<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config(k: &str) -> ResultType<CodecInfos> {
|
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
let v = HwCodecConfig::load()
|
struct Available {
|
||||||
.options
|
e: CodecInfos,
|
||||||
.get(k)
|
d: CodecInfos,
|
||||||
.unwrap_or(&"".to_owned())
|
}
|
||||||
.to_owned();
|
|
||||||
match CodecInfos::deserialize(&v) {
|
fn get_config() -> ResultType<Available> {
|
||||||
|
match serde_json::from_str(&HwCodecConfig::load().ram) {
|
||||||
Ok(v) => Ok(v),
|
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() {
|
pub fn check_available_hwcodec() {
|
||||||
let ctx = EncodeContext {
|
let ctx = EncodeContext {
|
||||||
name: String::from(""),
|
name: String::from(""),
|
||||||
width: 1920,
|
width: 1280,
|
||||||
height: 1080,
|
height: 720,
|
||||||
pixfmt: DEFAULT_PIXFMT,
|
pixfmt: DEFAULT_PIXFMT,
|
||||||
align: HW_STRIDE_ALIGN as _,
|
align: HW_STRIDE_ALIGN as _,
|
||||||
bitrate: 0,
|
bitrate: 0,
|
||||||
@@ -358,30 +364,27 @@ pub fn check_available_hwcodec() {
|
|||||||
rc: DEFAULT_RC,
|
rc: DEFAULT_RC,
|
||||||
thread_count: 4,
|
thread_count: 4,
|
||||||
};
|
};
|
||||||
let encoders = CodecInfo::score(Encoder::available_encoders(ctx));
|
#[cfg(feature = "vram")]
|
||||||
let decoders = CodecInfo::score(Decoder::available_decoders());
|
let vram = crate::vram::check_available_vram();
|
||||||
|
#[cfg(not(feature = "vram"))]
|
||||||
if let Ok(old_encoders) = get_config(CFG_KEY_ENCODER) {
|
let vram = "".to_owned();
|
||||||
if let Ok(old_decoders) = get_config(CFG_KEY_DECODER) {
|
let encoders = CodecInfo::prioritized(Encoder::available_encoders(ctx, Some(vram.clone())));
|
||||||
if encoders == old_encoders && decoders == old_decoders {
|
let decoders = CodecInfo::prioritized(Decoder::available_decoders(Some(vram.clone())));
|
||||||
return;
|
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;
|
use std::sync::Once;
|
||||||
let f = || {
|
let f = || {
|
||||||
// Clear to avoid checking process errors
|
// Clear to avoid checking process errors
|
||||||
@@ -421,7 +424,11 @@ pub fn hwcodec_new_check_process() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
static ONCE: Once = Once::new();
|
static ONCE: Once = Once::new();
|
||||||
ONCE.call_once(|| {
|
if force && ONCE.is_completed() {
|
||||||
std::thread::spawn(f);
|
std::thread::spawn(f);
|
||||||
});
|
} else {
|
||||||
|
ONCE.call_once(|| {
|
||||||
|
std::thread::spawn(f);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,13 +37,13 @@ cfg_if! {
|
|||||||
|
|
||||||
pub mod codec;
|
pub mod codec;
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
#[cfg(feature = "gpucodec")]
|
|
||||||
pub mod gpucodec;
|
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
pub mod hwcodec;
|
pub mod hwcodec;
|
||||||
#[cfg(feature = "mediacodec")]
|
#[cfg(feature = "mediacodec")]
|
||||||
pub mod mediacodec;
|
pub mod mediacodec;
|
||||||
pub mod vpxcodec;
|
pub mod vpxcodec;
|
||||||
|
#[cfg(feature = "vram")]
|
||||||
|
pub mod vram;
|
||||||
pub use self::convert::*;
|
pub use self::convert::*;
|
||||||
pub const STRIDE_ALIGN: usize = 64; // commonly used in libvpx vpx_img_alloc caller
|
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
|
pub const HW_STRIDE_ALIGN: usize = 0; // recommended by av_frame_get_buffer
|
||||||
@@ -111,10 +111,10 @@ pub trait TraitCapturer {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn set_gdi(&mut self) -> bool;
|
fn set_gdi(&mut self) -> bool;
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn device(&self) -> AdapterDevice;
|
fn device(&self) -> AdapterDevice;
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn set_output_texture(&mut self, texture: bool);
|
fn set_output_texture(&mut self, texture: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,10 +245,10 @@ pub enum CodecName {
|
|||||||
VP8,
|
VP8,
|
||||||
VP9,
|
VP9,
|
||||||
AV1,
|
AV1,
|
||||||
H264HW(String),
|
H264RAM(String),
|
||||||
H265HW(String),
|
H265RAM(String),
|
||||||
H264GPU,
|
H264VRAM,
|
||||||
H265GPU,
|
H265VRAM,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||||
@@ -280,8 +280,8 @@ impl From<&CodecName> for CodecFormat {
|
|||||||
CodecName::VP8 => Self::VP8,
|
CodecName::VP8 => Self::VP8,
|
||||||
CodecName::VP9 => Self::VP9,
|
CodecName::VP9 => Self::VP9,
|
||||||
CodecName::AV1 => Self::AV1,
|
CodecName::AV1 => Self::AV1,
|
||||||
CodecName::H264HW(_) | CodecName::H264GPU => Self::H264,
|
CodecName::H264RAM(_) | CodecName::H264VRAM => Self::H264,
|
||||||
CodecName::H265HW(_) | CodecName::H265GPU => Self::H265,
|
CodecName::H265RAM(_) | CodecName::H265VRAM => Self::H265,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ impl EncoderApi for VpxEncoder {
|
|||||||
self.yuvfmt.clone()
|
self.yuvfmt.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn input_texture(&self) -> bool {
|
fn input_texture(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,24 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
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,
|
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::{
|
use hbb_common::{
|
||||||
allow_err,
|
|
||||||
anyhow::{anyhow, bail, Context},
|
anyhow::{anyhow, bail, Context},
|
||||||
bytes::Bytes,
|
bytes::Bytes,
|
||||||
log,
|
log,
|
||||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
||||||
ResultType,
|
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;
|
const OUTPUT_SHARED_HANDLE: bool = false;
|
||||||
|
|
||||||
@@ -35,31 +35,31 @@ lazy_static::lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GpuEncoderConfig {
|
pub struct VRamEncoderConfig {
|
||||||
pub device: AdapterDevice,
|
pub device: AdapterDevice,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub quality: Quality,
|
pub quality: Quality,
|
||||||
pub feature: gpucodec::gpu_common::FeatureContext,
|
pub feature: FeatureContext,
|
||||||
pub keyframe_interval: Option<usize>,
|
pub keyframe_interval: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GpuEncoder {
|
pub struct VRamEncoder {
|
||||||
encoder: Encoder,
|
encoder: Encoder,
|
||||||
pub format: gpu_common::DataFormat,
|
pub format: DataFormat,
|
||||||
ctx: EncodeContext,
|
ctx: EncodeContext,
|
||||||
bitrate: u32,
|
bitrate: u32,
|
||||||
last_frame_len: usize,
|
last_frame_len: usize,
|
||||||
same_bad_len_counter: usize,
|
same_bad_len_counter: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EncoderApi for GpuEncoder {
|
impl EncoderApi for VRamEncoder {
|
||||||
fn new(cfg: EncoderCfg, _i444: bool) -> ResultType<Self>
|
fn new(cfg: EncoderCfg, _i444: bool) -> ResultType<Self>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
match cfg {
|
match cfg {
|
||||||
EncoderCfg::GPU(config) => {
|
EncoderCfg::VRAM(config) => {
|
||||||
let b = Self::convert_quality(config.quality, &config.feature);
|
let b = Self::convert_quality(config.quality, &config.feature);
|
||||||
let base_bitrate = base_bitrate(config.width as _, config.height as _);
|
let base_bitrate = base_bitrate(config.width as _, config.height as _);
|
||||||
let mut bitrate = base_bitrate * b / 100;
|
let mut bitrate = base_bitrate * b / 100;
|
||||||
@@ -79,7 +79,7 @@ impl EncoderApi for GpuEncoder {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
match Encoder::new(ctx.clone()) {
|
match Encoder::new(ctx.clone()) {
|
||||||
Ok(encoder) => Ok(GpuEncoder {
|
Ok(encoder) => Ok(VRamEncoder {
|
||||||
encoder,
|
encoder,
|
||||||
ctx,
|
ctx,
|
||||||
format: config.feature.data_format,
|
format: config.feature.data_format,
|
||||||
@@ -88,7 +88,7 @@ impl EncoderApi for GpuEncoder {
|
|||||||
same_bad_len_counter: 0,
|
same_bad_len_counter: 0,
|
||||||
}),
|
}),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
hbb_common::config::GpucodecConfig::clear();
|
hbb_common::config::HwCodecConfig::clear_vram();
|
||||||
Err(anyhow!(format!("Failed to create encoder")))
|
Err(anyhow!(format!("Failed to create encoder")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,8 +138,8 @@ impl EncoderApi for GpuEncoder {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
match self.format {
|
match self.format {
|
||||||
gpu_common::DataFormat::H264 => vf.set_h264s(frames),
|
DataFormat::H264 => vf.set_h264s(frames),
|
||||||
gpu_common::DataFormat::H265 => vf.set_h265s(frames),
|
DataFormat::H265 => vf.set_h265s(frames),
|
||||||
_ => bail!("{:?} not supported", self.format),
|
_ => bail!("{:?} not supported", self.format),
|
||||||
}
|
}
|
||||||
Ok(vf)
|
Ok(vf)
|
||||||
@@ -160,7 +160,7 @@ impl EncoderApi for GpuEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn input_texture(&self) -> bool {
|
fn input_texture(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -181,11 +181,11 @@ impl EncoderApi for GpuEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn support_abr(&self) -> bool {
|
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> {
|
pub fn try_get(device: &AdapterDevice, name: CodecName) -> Option<FeatureContext> {
|
||||||
let v: Vec<_> = Self::available(name)
|
let v: Vec<_> = Self::available(name)
|
||||||
.drain(..)
|
.drain(..)
|
||||||
@@ -201,12 +201,12 @@ impl GpuEncoder {
|
|||||||
pub fn available(name: CodecName) -> Vec<FeatureContext> {
|
pub fn available(name: CodecName) -> Vec<FeatureContext> {
|
||||||
let not_use = ENOCDE_NOT_USE.lock().unwrap().clone();
|
let not_use = ENOCDE_NOT_USE.lock().unwrap().clone();
|
||||||
if not_use.values().any(|not_use| *not_use) {
|
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![];
|
return vec![];
|
||||||
}
|
}
|
||||||
let data_format = match name {
|
let data_format = match name {
|
||||||
CodecName::H264GPU => gpu_common::DataFormat::H264,
|
CodecName::H264VRAM => DataFormat::H264,
|
||||||
CodecName::H265GPU => gpu_common::DataFormat::H265,
|
CodecName::H265VRAM => DataFormat::H265,
|
||||||
_ => return vec![],
|
_ => return vec![],
|
||||||
};
|
};
|
||||||
let Ok(displays) = crate::Display::all() else {
|
let Ok(displays) = crate::Display::all() else {
|
||||||
@@ -252,27 +252,21 @@ impl GpuEncoder {
|
|||||||
pub fn convert_quality(quality: Quality, f: &FeatureContext) -> u32 {
|
pub fn convert_quality(quality: Quality, f: &FeatureContext) -> u32 {
|
||||||
match quality {
|
match quality {
|
||||||
Quality::Best => {
|
Quality::Best => {
|
||||||
if f.driver == gpu_common::EncodeDriver::VPL
|
if f.driver == Driver::VPL && f.data_format == DataFormat::H264 {
|
||||||
&& f.data_format == gpu_common::DataFormat::H264
|
|
||||||
{
|
|
||||||
200
|
200
|
||||||
} else {
|
} else {
|
||||||
150
|
150
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Quality::Balanced => {
|
Quality::Balanced => {
|
||||||
if f.driver == gpu_common::EncodeDriver::VPL
|
if f.driver == Driver::VPL && f.data_format == DataFormat::H264 {
|
||||||
&& f.data_format == gpu_common::DataFormat::H264
|
|
||||||
{
|
|
||||||
150
|
150
|
||||||
} else {
|
} else {
|
||||||
100
|
100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Quality::Low => {
|
Quality::Low => {
|
||||||
if f.driver == gpu_common::EncodeDriver::VPL
|
if f.driver == Driver::VPL && f.data_format == DataFormat::H264 {
|
||||||
&& f.data_format == gpu_common::DataFormat::H264
|
|
||||||
{
|
|
||||||
75
|
75
|
||||||
} else {
|
} else {
|
||||||
50
|
50
|
||||||
@@ -283,7 +277,7 @@ impl GpuEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_not_use(display: usize, not_use: bool) {
|
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);
|
ENOCDE_NOT_USE.lock().unwrap().insert(display, not_use);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,17 +286,11 @@ impl GpuEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GpuDecoder {
|
pub struct VRamDecoder {
|
||||||
decoder: Decoder,
|
decoder: Decoder,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
impl VRamDecoder {
|
||||||
pub struct GpuDecoders {
|
|
||||||
pub h264: Option<GpuDecoder>,
|
|
||||||
pub h265: Option<GpuDecoder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GpuDecoder {
|
|
||||||
pub fn try_get(format: CodecFormat, luid: Option<i64>) -> Option<DecodeContext> {
|
pub fn try_get(format: CodecFormat, luid: Option<i64>) -> Option<DecodeContext> {
|
||||||
let v: Vec<_> = Self::available(format, luid);
|
let v: Vec<_> = Self::available(format, luid);
|
||||||
if v.len() > 0 {
|
if v.len() > 0 {
|
||||||
@@ -315,8 +303,8 @@ impl GpuDecoder {
|
|||||||
pub fn available(format: CodecFormat, luid: Option<i64>) -> Vec<DecodeContext> {
|
pub fn available(format: CodecFormat, luid: Option<i64>) -> Vec<DecodeContext> {
|
||||||
let luid = luid.unwrap_or_default();
|
let luid = luid.unwrap_or_default();
|
||||||
let data_format = match format {
|
let data_format = match format {
|
||||||
CodecFormat::H264 => gpu_common::DataFormat::H264,
|
CodecFormat::H264 => DataFormat::H264,
|
||||||
CodecFormat::H265 => gpu_common::DataFormat::H265,
|
CodecFormat::H265 => DataFormat::H265,
|
||||||
_ => return vec![],
|
_ => return vec![],
|
||||||
};
|
};
|
||||||
get_available_config()
|
get_available_config()
|
||||||
@@ -328,15 +316,13 @@ impl GpuDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn possible_available_without_check() -> (bool, bool) {
|
pub fn possible_available_without_check() -> (bool, bool) {
|
||||||
if !enable_gpucodec_option() {
|
if !enable_vram_option() {
|
||||||
return (false, false);
|
return (false, false);
|
||||||
}
|
}
|
||||||
let v = get_available_config().map(|c| c.d).unwrap_or_default();
|
let v = get_available_config().map(|c| c.d).unwrap_or_default();
|
||||||
(
|
(
|
||||||
v.iter()
|
v.iter().any(|d| d.data_format == DataFormat::H264),
|
||||||
.any(|d| d.data_format == gpu_common::DataFormat::H264),
|
v.iter().any(|d| d.data_format == DataFormat::H265),
|
||||||
v.iter()
|
|
||||||
.any(|d| d.data_format == gpu_common::DataFormat::H265),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +332,7 @@ impl GpuDecoder {
|
|||||||
match Decoder::new(ctx) {
|
match Decoder::new(ctx) {
|
||||||
Ok(decoder) => Ok(Self { decoder }),
|
Ok(decoder) => Ok(Self { decoder }),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
hbb_common::config::GpucodecConfig::clear();
|
hbb_common::config::HwCodecConfig::clear_vram();
|
||||||
Err(anyhow!(format!(
|
Err(anyhow!(format!(
|
||||||
"Failed to create decoder, format: {:?}",
|
"Failed to create decoder, format: {:?}",
|
||||||
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) {
|
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)),
|
Err(e) => Err(anyhow!(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GpuDecoderImage<'a> {
|
pub struct VRamDecoderImage<'a> {
|
||||||
pub frame: &'a DecodeFrame,
|
pub frame: &'a DecodeFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuDecoderImage<'_> {}
|
impl VRamDecoderImage<'_> {}
|
||||||
|
|
||||||
fn get_available_config() -> ResultType<Available> {
|
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) {
|
match Available::deserialize(&available) {
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
Err(_) => Err(anyhow!("Failed to deserialize:{}", available)),
|
Err(_) => Err(anyhow!("Failed to deserialize:{}", available)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_available_gpucodec() {
|
pub(crate) fn check_available_vram() -> String {
|
||||||
let d = DynamicContext {
|
let d = DynamicContext {
|
||||||
device: None,
|
device: None,
|
||||||
width: 1920,
|
width: 1280,
|
||||||
height: 1080,
|
height: 720,
|
||||||
kbitrate: 5000,
|
kbitrate: 5000,
|
||||||
framerate: 60,
|
framerate: 60,
|
||||||
gop: MAX_GOP as _,
|
gop: MAX_GOP as _,
|
||||||
@@ -391,54 +377,5 @@ pub fn check_available_gpucodec() {
|
|||||||
e: encoders,
|
e: encoders,
|
||||||
d: decoders,
|
d: decoders,
|
||||||
};
|
};
|
||||||
|
available.serialize().unwrap_or_default()
|
||||||
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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ impl Capturer {
|
|||||||
self.gdi_capturer.take();
|
self.gdi_capturer.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
pub fn set_output_texture(&mut self, texture: bool) {
|
pub fn set_output_texture(&mut self, texture: bool) {
|
||||||
self.output_texture = texture;
|
self.output_texture = texture;
|
||||||
}
|
}
|
||||||
@@ -620,7 +620,7 @@ impl Display {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
pub fn adapter_luid(&self) -> Option<i64> {
|
pub fn adapter_luid(&self) -> Option<i64> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if !self.adapter.is_null() {
|
if !self.adapter.is_null() {
|
||||||
|
|||||||
143
res/devices.py
Executable file
143
res/devices.py
Executable 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
7
res/msi/.gitignore
vendored
@@ -2,3 +2,10 @@
|
|||||||
|
|
||||||
**/bin
|
**/bin
|
||||||
**/obj
|
**/obj
|
||||||
|
|
||||||
|
x64
|
||||||
|
packages
|
||||||
|
|
||||||
|
CustomActions/x64
|
||||||
|
CustomActions/*.user
|
||||||
|
CustomActions/*.filters
|
||||||
|
|||||||
16
res/msi/CustomActions/Common.h
Normal file
16
res/msi/CustomActions/Common.h
Normal 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);
|
||||||
@@ -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>
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
611
res/msi/CustomActions/CustomActions.cpp
Normal file
611
res/msi/CustomActions/CustomActions.cpp
Normal 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);
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
14
res/msi/CustomActions/CustomActions.def
Normal file
14
res/msi/CustomActions/CustomActions.def
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
LIBRARY "CustomActions"
|
||||||
|
|
||||||
|
EXPORTS
|
||||||
|
CustomActionHello
|
||||||
|
RemoveInstallFolder
|
||||||
|
TerminateProcesses
|
||||||
|
AddFirewallRules
|
||||||
|
SetPropertyIsServiceRunning
|
||||||
|
TryStopDeleteService
|
||||||
|
CreateStartService
|
||||||
|
TryDeleteStartupShortcut
|
||||||
|
SetPropertyFromConfig
|
||||||
|
AddRegSoftwareSASGeneration
|
||||||
|
RemoveAmyuniIdd
|
||||||
85
res/msi/CustomActions/CustomActions.vcxproj
Normal file
85
res/msi/CustomActions/CustomActions.vcxproj
Normal 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>
|
||||||
84
res/msi/CustomActions/DeviceUtils.cpp
Normal file
84
res/msi/CustomActions/DeviceUtils.cpp
Normal 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);
|
||||||
|
}
|
||||||
413
res/msi/CustomActions/FirewallRules.cpp
Normal file
413
res/msi/CustomActions/FirewallRules.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
36
res/msi/CustomActions/ReadConfig.cpp
Normal file
36
res/msi/CustomActions/ReadConfig.cpp
Normal 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;
|
||||||
|
}
|
||||||
173
res/msi/CustomActions/ServiceUtils.cpp
Normal file
173
res/msi/CustomActions/ServiceUtils.cpp
Normal 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;
|
||||||
|
}
|
||||||
26
res/msi/CustomActions/dllmain.cpp
Normal file
26
res/msi/CustomActions/dllmain.cpp
Normal 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;
|
||||||
|
}
|
||||||
10
res/msi/CustomActions/framework.h
Normal file
10
res/msi/CustomActions/framework.h
Normal 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>
|
||||||
5
res/msi/CustomActions/packages.config
Normal file
5
res/msi/CustomActions/packages.config
Normal 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>
|
||||||
5
res/msi/CustomActions/pch.cpp
Normal file
5
res/msi/CustomActions/pch.cpp
Normal 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.
|
||||||
13
res/msi/CustomActions/pch.h
Normal file
13
res/msi/CustomActions/pch.h
Normal 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
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<Component Id="Product.Registry.DefaultIcon" Guid="6DBF2690-0955-4C6A-940F-634DDA503F49">
|
<Component Id="Product.Registry.DefaultIcon" Guid="6DBF2690-0955-4C6A-940F-634DDA503F49">
|
||||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\DefaultIcon">
|
<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>
|
</RegistryKey>
|
||||||
</Component>
|
</Component>
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell" />
|
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell" />
|
||||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell\open" />
|
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell\open" />
|
||||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell\open\command">
|
<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>
|
</RegistryKey>
|
||||||
</Component>
|
</Component>
|
||||||
|
|
||||||
@@ -36,10 +36,20 @@
|
|||||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell" />
|
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell" />
|
||||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell\open" />
|
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell\open" />
|
||||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell\open\command">
|
<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>
|
</RegistryKey>
|
||||||
</Component>
|
</Component>
|
||||||
|
|
||||||
</DirectoryRef>
|
</DirectoryRef>
|
||||||
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -5,87 +5,125 @@
|
|||||||
<?include ../Includes.wxi?>
|
<?include ../Includes.wxi?>
|
||||||
|
|
||||||
<DirectoryRef Id="INSTALLFOLDER" FileSource="$(var.BuildDir)">
|
<DirectoryRef Id="INSTALLFOLDER" FileSource="$(var.BuildDir)">
|
||||||
<Component Id="RustDesk.exe" Guid="620F0F69-4C17-4320-A619-495E329712A4">
|
<Component Id="App.exe" Guid="620F0F69-4C17-4320-A619-495E329712A4">
|
||||||
<File Id="$(var.Product).exe" Name="$(var.Product).exe" KeyPath="yes" Checksum="yes">
|
<File Id="App.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="AppEx" Name="$(var.Product) Service" 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" />
|
|
||||||
</File>
|
</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>
|
</Component>
|
||||||
</DirectoryRef>
|
</DirectoryRef>
|
||||||
|
|
||||||
<SetProperty Id="RestartService" Value=""net" start $(var.Product)" Before="RestartService" Sequence="execute" />
|
<CustomAction Id="RemoveInstallFolder.SetParam" Return="check" Property="RemoveInstallFolder" Value="[INSTALLFOLDER]" />
|
||||||
<CustomAction Id="RestartService" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
|
<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);"[INSTALLFOLDER]$(var.Product).exe" --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>
|
<InstallExecuteSequence>
|
||||||
|
|
||||||
<!--The ServiceControl element above handles starting/stopping the server on install/uninstall,
|
<Custom Action="SetPropertyIsServiceRunning" After="InstallInitialize" Condition="Installed" />
|
||||||
however it also needs to be restarted after a modify or repair install. This action does
|
<Custom Action="SetPropertyIsServiceRunning.SetParam.AppName" Before="SetPropertyIsServiceRunning" Condition="Installed" />
|
||||||
that provided the server feature is already installed and not being uninstalled.-->
|
<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="'Y'"" />
|
||||||
|
<Custom Action="CreateStartService.SetParam" Before="CreateStartService" Condition="NOT STOP_SERVICE="'Y'"" />
|
||||||
|
|
||||||
<Custom Action="CustomActionHello" Before="InstallFinalize" />
|
<Custom Action="CustomActionHello" Before="InstallFinalize" />
|
||||||
|
|
||||||
<Custom Action="SetCmdRemoveDir" After="RemoveFiles"/>
|
<!--Shortcut is in InstallValidate section. So we just let it be created, then try delete if stopping service.-->
|
||||||
<Custom Action="RunCommandAsSystem" After="SetCmdRemoveDir"/>
|
<Custom Action="TryDeleteStartupShortcut" After="InstallFinalize" Condition="STOP_SERVICE="'Y'"" />
|
||||||
|
<Custom Action="TryDeleteStartupShortcut.SetParam" Before="SetPropertyIsServiceRunning" Condition="STOP_SERVICE="'Y'"" />
|
||||||
|
|
||||||
|
<!-- Launch ClientLauncher if installing or already installed and not uninstalling -->
|
||||||
|
<Custom Action="LaunchApp" After="InstallFinalize" />
|
||||||
|
<Custom Action="LaunchAppTray" After="InstallFinalize" Condition="NOT STOP_SERVICE="'Y'""/>
|
||||||
|
|
||||||
|
<!--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>
|
</InstallExecuteSequence>
|
||||||
|
|
||||||
<!-- Shortcuts -->
|
<!-- Shortcuts -->
|
||||||
<DirectoryRef Id="App.StartMenu">
|
<DirectoryRef Id="App.StartMenu">
|
||||||
<Component Id="App.StartMenu" Guid="30F6D57A-B805-4DA4-A071-05A3B22400CA">
|
<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" />
|
<RemoveFolder Id="Remove.App.StartMenu" On="uninstall" />
|
||||||
</Component>
|
</Component>
|
||||||
</DirectoryRef>
|
</DirectoryRef>
|
||||||
|
|
||||||
<DirectoryRef Id="App.StartMenu">
|
<DirectoryRef Id="App.StartMenu">
|
||||||
<Component Id="App.StartMenu.Shortcut" Guid="43ABCAC7-E47D-42D8-A408-25EC70DBB993" Condition="STARTMENUSHORTCUTS = 1">
|
<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="[!App.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
|
||||||
<Shortcut Id="App.StartMenu.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.
|
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
|
https://learn.microsoft.com/en-us/windows/win32/msi/ice38
|
||||||
-->
|
-->
|
||||||
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
||||||
</Component>
|
</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" />
|
<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" />
|
||||||
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
|
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.ShortcutUninstall" Type="string" Value="1" KeyPath="yes" />
|
||||||
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>
|
</Component>
|
||||||
</DirectoryRef>
|
</DirectoryRef>
|
||||||
<StandardDirectory Id="DesktopFolder">
|
<StandardDirectory Id="DesktopFolder">
|
||||||
<Component Id="App.Desktop.Shortcut" Guid="CA8FB7AA-17F7-4E36-A58A-5A016A303709" Condition="DESKTOPSHORTCUTS = 1">
|
<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="[!App.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
|
||||||
<Shortcut Id="App.Desktop.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!RustDesk.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
|
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.Desktop.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
||||||
<!--
|
</Component>
|
||||||
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
|
</StandardDirectory>
|
||||||
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
|
<StandardDirectory Id="StartupFolder">
|
||||||
-->
|
<Component Id="App.StartupFolder.ShortcutTray" Guid="B1D1E2BB-E53E-E159-DB7C-744D5C726A8C" Condition="STARTUPSHORTCUTS = 1">
|
||||||
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.Desktop.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
<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>
|
</Component>
|
||||||
</StandardDirectory>
|
</StandardDirectory>
|
||||||
|
|
||||||
<DirectoryRef Id="INSTALLFOLDER">
|
<!--<DirectoryRef Id="INSTALLFOLDER">
|
||||||
<Component Id="App.UninstallShortcut" Guid="FB0F2AC7-2AE5-4C54-B860-5E472620B6B1">
|
<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>
|
</Component>
|
||||||
</DirectoryRef>
|
</DirectoryRef>-->
|
||||||
|
|
||||||
<ComponentGroup Id="Components" Directory="INSTALLFOLDER">
|
<ComponentGroup Id="Components" Directory="INSTALLFOLDER">
|
||||||
<ComponentRef Id="RustDesk.exe" />
|
<ComponentRef Id="App.exe" />
|
||||||
<ComponentRef Id="App.Desktop.Shortcut" />
|
<ComponentRef Id="App.Desktop.Shortcut" />
|
||||||
<ComponentRef Id="App.UninstallShortcut" />
|
<!--<ComponentRef Id="App.UninstallShortcut" />-->
|
||||||
<ComponentRef Id="App.StartMenu.Shortcut" />
|
<ComponentRef Id="App.StartMenu.Shortcut" />
|
||||||
<ComponentRef Id="App.StartMenu.ShortcutTray" />
|
<ComponentRef Id="App.StartMenu.ShortcutUninstall" />
|
||||||
|
<ComponentRef Id="App.StartupFolder.ShortcutTray" />
|
||||||
|
|
||||||
<!--$AutoComonentStart$-->
|
<!--$AutoComonentStart$-->
|
||||||
<!--$AutoComponentEnd$-->
|
<!--$AutoComponentEnd$-->
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||||
<?include ../Includes.wxi?>
|
<?include ../Includes.wxi?>
|
||||||
|
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
||||||
<Property Id="AddRemovePropertiesFile" Value="1" />
|
<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="'Y'" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Support entries shown when clicking "Click here for support information"
|
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
|
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="ARPCOMMENTS" Value="!(loc.AR_Comment)" />
|
||||||
<Property Id="ARPCONTACT" Value="https://github.com/rustdesk/rustdesk" />
|
<Property Id="ARPCONTACT" Value="https://github.com/rustdesk/rustdesk" />
|
||||||
<Property Id="ARPHELPLINK" 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="ARPREADME" Value="https://github.com/rustdesk/rustdesk" />
|
||||||
<Property Id="ARPURLINFOABOUT" 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="ARPPRODUCTICON" Value="AppIcon" />
|
||||||
|
|
||||||
<Property Id="RUSTDESKNATIVEINSTALL">
|
<!--$ArpStart$-->
|
||||||
<RegistrySearch Id="RustDeskNativeInstallSearch" Root="HKCR" Key="$(var.RegKeyRoot)\DefaultIcon" Type="raw" />
|
<!--$ArpEnd$-->
|
||||||
</Property>
|
|
||||||
<Property Id="RUSTDESKNATIVEINSTALLFOLDER">
|
|
||||||
<RegistrySearch Id="RustDeskNativeInstallFolderSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="INSTALLFOLDER" Type="raw" />
|
|
||||||
</Property>
|
|
||||||
|
|
||||||
|
<Property Id="APP_WINDOWS_INSTALLER">
|
||||||
|
<RegistrySearch Id="AppWindowsInstallerFolderSearch" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\$(var.Product)" Name="WindowsInstaller" Type="raw" />
|
||||||
|
</Property>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</Wix>
|
</Wix>
|
||||||
|
|||||||
@@ -1,26 +1,21 @@
|
|||||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<?include ../Includes.wxi?>
|
<?include ../Includes.wxi?>
|
||||||
|
|
||||||
<Binary Id="Custom_Actions_Dll" SourceFile="$(var.CustomActions.TargetDir)$(var.CustomActions.TargetName).CA.dll" />
|
|
||||||
|
|
||||||
<CustomAction Id="CustomActionHello" DllEntry="CustomActionHello" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
<Binary Id="Custom_Actions_Dll" SourceFile="$(var.CustomActions.TargetDir)$(var.CustomActions.TargetName).dll" />
|
||||||
<CustomAction Id="RunCommandAsSystem" DllEntry="RunCommandAsSystem" Impersonate="no" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
|
||||||
|
|
||||||
<Property Id="CMD" Value='echo "Hello"' />
|
<CustomAction Id="CustomActionHello" DllEntry="CustomActionHello" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||||
<CustomAction Id="SetCmdRemoveDir" Property="CMD" Value='rmdir /s /q "[INSTALLFOLDER]"' />
|
<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"/>
|
||||||
<!-- Use WixQuietExec to run the commands to avoid the command window popping up. The command line to run needs to be stored
|
<CustomAction Id="TerminateBrokers" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||||
in a property with the same id as the WixQuietExec custom action and the path to the exe needs to be quoted.
|
<CustomAction Id="AddFirewallRules" DllEntry="AddFirewallRules" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||||
A SetProperty action is used to allow the [SystemFolder] reference to be resolved and needs to be scheduled to run before the action.-->
|
<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"/>
|
||||||
<SetProperty Id="FirewallPortOutAdd" Value=""[SystemFolder]netsh.exe" advfirewall firewall add rule name="$(var.Product) Service" dir=out action=allow programe=$(var.ProductLower) enable=yes" Before="FirewallPortOutAdd" Sequence="execute" />
|
<CustomAction Id="CreateStartService" DllEntry="CreateStartService" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||||
<CustomAction Id="FirewallPortOutAdd" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
|
<CustomAction Id="TryStopDeleteService" DllEntry="TryStopDeleteService" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||||
<SetProperty Id="FirewallPortInAdd" Value=""[SystemFolder]netsh.exe" advfirewall firewall add rule name="$(var.Product) Service" dir=in action=allow programe=$(var.ProductLower) enable=yes" Before="FirewallPortInAdd" Sequence="execute" />
|
<CustomAction Id="TryDeleteStartupShortcut" DllEntry="TryDeleteStartupShortcut" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||||
<CustomAction Id="FirewallPortInAdd" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
|
<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"/>
|
||||||
<SetProperty Id="FirewallPortRemove" Value=""[SystemFolder]netsh.exe" advfirewall firewall delete rule name="$(var.Product) Service"" Before="FirewallPortRemove" Sequence="execute" />
|
<CustomAction Id="RemoveAmyuniIdd" DllEntry="RemoveAmyuniIdd" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||||
<CustomAction Id="FirewallPortRemove" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
|
</Fragment>
|
||||||
|
|
||||||
</Fragment>
|
|
||||||
</Wix>
|
</Wix>
|
||||||
|
|||||||
@@ -1,51 +1,52 @@
|
|||||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
<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
|
<!-- 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.
|
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
|
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. -->
|
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="STARTMENUSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||||
<Property Id="DESKTOPSHORTCUTS" 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,
|
<!-- These properties get set from either the command line, bundle or registry value,
|
||||||
if set they update the properties above with their value. -->
|
if set they update the properties above with their value. -->
|
||||||
<Property Id="CREATESTARTMENUSHORTCUTS" Secure="yes">
|
<Property Id="CREATESTARTMENUSHORTCUTS" Secure="yes">
|
||||||
<RegistrySearch Id="CreateStartMenuShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="STARTMENUSHORTCUTS" Type="raw" />
|
<RegistrySearch Id="CreateStartMenuShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="STARTMENUSHORTCUTS" Type="raw" />
|
||||||
</Property>
|
</Property>
|
||||||
<Property Id="CREATEDESKTOPSHORTCUTS" Secure="yes">
|
<Property Id="CREATEDESKTOPSHORTCUTS" Secure="yes">
|
||||||
<RegistrySearch Id="CreateDesktopShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="DESKTOPSHORTCUTS" Type="raw" />
|
<RegistrySearch Id="CreateDesktopShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="DESKTOPSHORTCUTS" Type="raw" />
|
||||||
</Property>
|
</Property>
|
||||||
|
|
||||||
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
|
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
|
||||||
<DirectoryRef Id="INSTALLFOLDER">
|
<DirectoryRef Id="INSTALLFOLDER">
|
||||||
<Component Id="Product.Registry.PersistedShortcutProperties" Guid="1BBAD054-6EC2-4362-BF1B-E8BDE988B597">
|
<Component Id="Product.Registry.PersistedShortcutProperties" Guid="1BBAD054-6EC2-4362-BF1B-E8BDE988B597">
|
||||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||||
<RegistryValue Type="string" Name="STARTMENUSHORTCUTS" Value="[STARTMENUSHORTCUTS]" KeyPath="yes" />
|
<RegistryValue Type="string" Name="STARTMENUSHORTCUTS" Value="[STARTMENUSHORTCUTS]" KeyPath="yes" />
|
||||||
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="[DESKTOPSHORTCUTS]" />
|
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="[DESKTOPSHORTCUTS]" />
|
||||||
</RegistryKey>
|
</RegistryKey>
|
||||||
</Component>
|
</Component>
|
||||||
</DirectoryRef>
|
</DirectoryRef>
|
||||||
|
|
||||||
<!-- If a property value has been passed via the command line (which includes when set from the bundle), the registry search will
|
<!-- 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
|
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 -->
|
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="SavedStartMenuShortcutsCmdLineValue" Value="[CREATESTARTMENUSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
|
||||||
<SetProperty Id="SavedDesktopShortcutsCmdLineValue" Value="[CREATEDESKTOPSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
|
<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 -->
|
<!-- 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="RestoreSavedStartMenuShortcutsValue" Id="CREATESTARTMENUSHORTCUTS" Value="[SavedStartMenuShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedStartMenuShortcutsCmdLineValue" />
|
||||||
<SetProperty Action="RestoreSavedDesktopShortcutsValue" Id="CREATEDESKTOPSHORTCUTS" Value="[SavedDesktopShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedDesktopShortcutsCmdLineValue" />
|
<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 -->
|
<!-- 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="STARTMENUSHORTCUTS" Value="[CREATESTARTMENUSHORTCUTS]" After="RestoreSavedStartMenuShortcutsValue" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
|
||||||
<SetProperty Id="DESKTOPSHORTCUTS" Value="[CREATEDESKTOPSHORTCUTS]" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
|
<SetProperty Id="DESKTOPSHORTCUTS" Value="[CREATEDESKTOPSHORTCUTS]" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
|
||||||
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</Wix>
|
</Wix>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
<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">
|
<!--$UpgradeStart$-->
|
||||||
<UpgradeVersion Property="OLD_VERSION_FOUND" Minimum="2.0.0.0" Maximum="2.99.99" IncludeMinimum="yes" IncludeMaximum="yes" OnlyDetect="no" IgnoreRemoveFailure="yes" MigrateFeatures="yes" />
|
<!--$UpgradeEnd$-->
|
||||||
</Upgrade>
|
|
||||||
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</Wix>
|
</Wix>
|
||||||
|
|||||||
@@ -4,7 +4,4 @@
|
|||||||
<!--$PreVarsStart$-->
|
<!--$PreVarsStart$-->
|
||||||
<!--$PreVarsEnd$-->
|
<!--$PreVarsEnd$-->
|
||||||
|
|
||||||
<!-- This should NEVER be changed ! -->
|
|
||||||
<?define UpgradeCode = "9D8E3E95-42B8-427E-B801-79F3FE7B6DD7" ?>
|
|
||||||
|
|
||||||
</Include>
|
</Include>
|
||||||
|
|||||||
@@ -47,6 +47,6 @@ This file contains the declaration of all the localizable strings.
|
|||||||
|
|
||||||
<!-- User Interfaces -->
|
<!-- User Interfaces -->
|
||||||
<String Id="AnotherAppDialogTitle" Value="Cancel installation."/>
|
<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>
|
</WixLocalization>
|
||||||
|
|||||||
@@ -17,6 +17,6 @@
|
|||||||
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.5" />
|
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\CustomActions\CustomActions.csproj" />
|
<ProjectReference Include="..\CustomActions\CustomActions.vcxproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
<?include Includes.wxi?>
|
<?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)" />
|
<SummaryInformation Keywords="Installer" Description="$(var.Description)" Codepage="!(loc.SummaryCodepage)" />
|
||||||
|
|
||||||
<!--<PropertyRef Id="UpgradesFile" />-->
|
<!--<PropertyRef Id="UpgradesFile" />-->
|
||||||
|
|
||||||
<PropertyRef Id="AddRemovePropertiesFile" />
|
<PropertyRef Id="AddRemovePropertiesFile" />
|
||||||
|
|
||||||
<Media Id="1" Cabinet="cab1.cab" EmbedCab="yes" CompressionLevel="high" />
|
<Media Id="1" Cabinet="cab1.cab" EmbedCab="yes" CompressionLevel="high" />
|
||||||
@@ -22,39 +22,20 @@
|
|||||||
<UIRef Id="WixUI_ErrorProgressText" />
|
<UIRef Id="WixUI_ErrorProgressText" />
|
||||||
|
|
||||||
<InstallUISequence>
|
<InstallUISequence>
|
||||||
<!--<Custom Action="ReadCustomPathsFromExistingPathsFile" Before="CostFinalize" Condition="NOT Installed" />-->
|
<Show Dialog="AnotherAppDialog" Before="WelcomeDlg" Condition="Not installed AND APP_WINDOWS_INSTALLER="#0""/>
|
||||||
<Show Dialog="AnotherAppDialog" Before="WelcomeDlg" Condition="Not installed AND RUSTDESKNATIVEINSTALL AND Not RUSTDESKNATIVEINSTALLFOLDER"/>
|
|
||||||
|
|
||||||
</InstallUISequence>
|
</InstallUISequence>
|
||||||
|
|
||||||
<InstallExecuteSequence>
|
<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~="ALL"" />
|
|
||||||
<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" />
|
<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>
|
</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">
|
<Feature Id="App" Level="1" AllowAdvertise="no" Display="expand" Title="!(loc.F_App)" Description="!(loc.F_App_Desc)" AllowAbsent="no">
|
||||||
<ComponentGroupRef Id="Components" />
|
<ComponentGroupRef Id="Components" />
|
||||||
@@ -64,8 +45,13 @@
|
|||||||
<ComponentRef Id="Product.Registry.CommandPlay" />
|
<ComponentRef Id="Product.Registry.CommandPlay" />
|
||||||
<ComponentRef Id="Product.Registry.URLProtocol" />
|
<ComponentRef Id="Product.Registry.URLProtocol" />
|
||||||
<ComponentRef Id="Product.Registry.Command" />
|
<ComponentRef Id="Product.Registry.Command" />
|
||||||
|
<ComponentRef Id="Product.Registry.UninstallApp" />
|
||||||
<ComponentRef Id="App.StartMenu" />
|
<ComponentRef Id="App.StartMenu" />
|
||||||
<ComponentRef Id="Product.Registry.PersistedShortcutProperties" />
|
<ComponentRef Id="Product.Registry.PersistedShortcutProperties" />
|
||||||
</Feature>
|
</Feature>
|
||||||
|
|
||||||
|
<!--https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set-->
|
||||||
|
<!--$CustomBitmapsStart$-->
|
||||||
|
<!--$CustomBitmapsEnd$-->
|
||||||
</Package>
|
</Package>
|
||||||
</Wix>
|
</Wix>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<UI>
|
<UI>
|
||||||
<Dialog Id="AnotherAppDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)">
|
<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)">
|
<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" />
|
<Publish Event="EndDialog" Value="ErrorAbort" />
|
||||||
</Control>
|
</Control>
|
||||||
<Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="!(loc.ExitDialogBitmap)" />
|
<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="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="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)" />
|
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogTitle)" />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</UI>
|
</UI>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
</Wix>
|
</Wix>
|
||||||
|
|||||||
@@ -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> .
|
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
|
## TODOs
|
||||||
|
|
||||||
1. tray, uninstall shortcut
|
1. Start menu. Uninstall
|
||||||
1. launch client after installation
|
1. custom options
|
||||||
1. github ci
|
|
||||||
1. options
|
|
||||||
1. Custom client.
|
1. Custom client.
|
||||||
1. firewall and tcp allow. Outgoing
|
1. firewall and tcp allow. Outgoing
|
||||||
1. Custom icon. Current `Resources/icon.ico`.
|
|
||||||
1. Show license ?
|
1. Show license ?
|
||||||
1. Do create service. Outgoing.
|
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)
|
||||||
|
|||||||
@@ -4,34 +4,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
VisualStudioVersion = 17.7.34003.232
|
VisualStudioVersion = 17.7.34003.232
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Package", "Package\Package.wixproj", "{F403A403-CEFF-4399-B51C-CC646C8E98CF}"
|
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
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Debug|x64 = Debug|x64
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
Release|x64 = Release|x64
|
Release|x64 = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
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.ActiveCfg = Release|x64
|
||||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.Build.0 = 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
|
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.ActiveCfg = Release|x64
|
||||||
{95BE171E-6438-4F45-9876-0B667D9F7830}.Debug|x64.ActiveCfg = Release|Any CPU
|
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.Build.0 = Release|x64
|
||||||
{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
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,69 +1,111 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
import argparse
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
g_indent_unit = "\t"
|
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():
|
def make_parser():
|
||||||
parser = argparse.ArgumentParser(description="Msi preprocess script.")
|
parser = argparse.ArgumentParser(description="Msi preprocess script.")
|
||||||
parser.add_argument(
|
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(
|
parser.add_argument(
|
||||||
"-c", "--custom", action="store_true", help="Is custom client", default=False
|
"-c", "--custom", action="store_true", help="Is custom client", default=False
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
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(
|
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(
|
parser.add_argument(
|
||||||
"-m",
|
"-m",
|
||||||
"--manufacturer",
|
"--manufacturer",
|
||||||
type=str,
|
type=str,
|
||||||
default="Purslane Ltd",
|
default="PURSLANE",
|
||||||
help="The app manufacturer.",
|
help="The app manufacturer.",
|
||||||
)
|
)
|
||||||
return parser
|
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:
|
with open(file_path, "r") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
start_index = -1
|
index_start = -1
|
||||||
end_index = -1
|
index_end = -1
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
if start_tag in line:
|
if tag_start in line:
|
||||||
start_index = i
|
index_start = i
|
||||||
if end_tag in line:
|
if tag_end in line:
|
||||||
end_index = i
|
index_end = i
|
||||||
|
|
||||||
if start_index == -1 or end_index == -1:
|
if index_start == -1:
|
||||||
print("Error: start or end tag not found")
|
print(f'Error: start tag "{tag_start}" not found')
|
||||||
return None, None
|
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
|
indent = g_indent_unit * 3
|
||||||
path = Path(build_dir)
|
path = Path(dist_dir)
|
||||||
idx = 1
|
idx = 1
|
||||||
for file_path in path.glob("**/*"):
|
for file_path in path.glob("**/*"):
|
||||||
if file_path.is_file():
|
if file_path.is_file():
|
||||||
if file_path.name.lower() == f"{app_name}.exe".lower():
|
if file_path.name.lower() == f"{app_name}.exe".lower():
|
||||||
continue
|
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))
|
subdir = str(file_path.parent.relative_to(path))
|
||||||
dir_attr = ""
|
dir_attr = ""
|
||||||
if subdir != ".":
|
if subdir != ".":
|
||||||
@@ -73,67 +115,59 @@ def insert_components_between_tags(lines, start_index, app_name, build_dir):
|
|||||||
# because it will cause error
|
# because it will cause error
|
||||||
# "Error WIX0130 The primary key 'xxxx' is duplicated in table 'Directory'"
|
# "Error WIX0130 The primary key 'xxxx' is duplicated in table 'Directory'"
|
||||||
to_insert_lines = f"""
|
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}{g_indent_unit}<File Source="{file_path.as_posix()}" KeyPath="yes" Checksum="yes" />
|
||||||
{indent}</Component>
|
{indent}</Component>
|
||||||
"""
|
"""
|
||||||
lines.insert(start_index + 1, to_insert_lines[1:])
|
lines.insert(index_start + 1, to_insert_lines[1:])
|
||||||
start_index += 1
|
index_start += 1
|
||||||
idx += 1
|
idx += 1
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def gen_auto_component(app_name, build_dir):
|
def gen_auto_component(app_name, dist_dir):
|
||||||
target_file = Path(sys.argv[0]).parent.joinpath("Package/Components/RustDesk.wxs")
|
return gen_content_between_tags(
|
||||||
start_tag = "<!--$AutoComonentStart$-->"
|
"Package/Components/RustDesk.wxs",
|
||||||
end_tag = "<!--$AutoComponentEnd$-->"
|
"<!--$AutoComonentStart$-->",
|
||||||
|
"<!--$AutoComponentEnd$-->",
|
||||||
lines, start_index = read_lines_and_start_index(target_file, start_tag, end_tag)
|
lambda lines, index_start: insert_components_between_tags(
|
||||||
if lines is None:
|
lines, index_start, app_name, dist_dir
|
||||||
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_pre_vars(args, build_dir):
|
def gen_pre_vars(args, dist_dir):
|
||||||
target_file = Path(sys.argv[0]).parent.joinpath("Package/Includes.wxi")
|
def func(lines, index_start):
|
||||||
start_tag = "<!--$PreVarsStart$-->"
|
upgrade_code = uuid.uuid5(uuid.NAMESPACE_OID, app_name + ".exe")
|
||||||
end_tag = "<!--$PreVarsEnd$-->"
|
|
||||||
|
|
||||||
lines, start_index = read_lines_and_start_index(target_file, start_tag, end_tag)
|
indent = g_indent_unit * 1
|
||||||
if lines is None:
|
to_insert_lines = [
|
||||||
return False
|
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
|
for i, line in enumerate(to_insert_lines):
|
||||||
to_insert_lines = [
|
lines.insert(index_start + i + 1, line)
|
||||||
f'{indent}<?define Version="{args.version}" ?>\n',
|
return lines
|
||||||
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):
|
return gen_content_between_tags(
|
||||||
lines.insert(start_index + i + 1, line)
|
"Package/Includes.wxi", "<!--$PreVarsStart$-->", "<!--$PreVarsEnd$-->", func
|
||||||
|
)
|
||||||
with open(target_file, "w") as f:
|
|
||||||
f.writelines(lines)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
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")
|
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:
|
with open(file_path, "r") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
@@ -142,23 +176,295 @@ def replace_app_name_in_lans(app_name):
|
|||||||
f.writelines(lines)
|
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__":
|
if __name__ == "__main__":
|
||||||
parser = make_parser()
|
parser = make_parser()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
app_name = args.app_name
|
app_name = args.app_name
|
||||||
build_dir = (
|
dist_dir = Path(sys.argv[0]).parent.joinpath(args.dist_dir).resolve()
|
||||||
Path(sys.argv[0])
|
|
||||||
.parent.joinpath(
|
|
||||||
f'../../flutter/build/windows/x64/runner/{"Debug" if args.debug else "Release"}'
|
|
||||||
)
|
|
||||||
.resolve()
|
|
||||||
)
|
|
||||||
|
|
||||||
if not gen_pre_vars(args, build_dir):
|
if not init_global_vars(dist_dir, app_name, args):
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
if not gen_auto_component(app_name, build_dir):
|
if not gen_pre_vars(args, dist_dir):
|
||||||
sys.exit(-1)
|
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)
|
||||||
|
|||||||
@@ -1038,9 +1038,9 @@ pub struct VideoHandler {
|
|||||||
impl VideoHandler {
|
impl VideoHandler {
|
||||||
/// Create a new video handler.
|
/// Create a new video handler.
|
||||||
pub fn new(format: CodecFormat, _display: usize) -> Self {
|
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();
|
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();
|
let luid = Default::default();
|
||||||
log::info!("new video handler for display #{_display}, format: {format:?}, luid: {luid:?}");
|
log::info!("new video handler for display #{_display}, format: {format:?}, luid: {luid:?}");
|
||||||
VideoHandler {
|
VideoHandler {
|
||||||
@@ -1097,9 +1097,9 @@ impl VideoHandler {
|
|||||||
|
|
||||||
/// Reset the decoder, change format if it is Some
|
/// Reset the decoder, change format if it is Some
|
||||||
pub fn reset(&mut self, format: Option<CodecFormat>) {
|
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();
|
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 luid = None;
|
||||||
let format = format.unwrap_or(self.decoder.format());
|
let format = format.unwrap_or(self.decoder.format());
|
||||||
self.decoder = Decoder::new(format, luid);
|
self.decoder = Decoder::new(format, luid);
|
||||||
@@ -1564,22 +1564,13 @@ impl LoginConfigHandler {
|
|||||||
///
|
///
|
||||||
/// * `ignore_default` - If `true`, ignore the default value of the option.
|
/// * `ignore_default` - If `true`, ignore the default value of the option.
|
||||||
fn get_option_message(&self, ignore_default: bool) -> Option<OptionMessage> {
|
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;
|
return None;
|
||||||
}
|
}
|
||||||
let mut n = 0;
|
|
||||||
let mut msg = OptionMessage::new();
|
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();
|
let q = self.image_quality.clone();
|
||||||
if let Some(q) = self.get_image_quality_enum(&q, ignore_default) {
|
if let Some(q) = self.get_image_quality_enum(&q, ignore_default) {
|
||||||
msg.image_quality = q.into();
|
msg.image_quality = q.into();
|
||||||
n += 1;
|
|
||||||
} else if q == "custom" {
|
} else if q == "custom" {
|
||||||
let config = self.load_config();
|
let config = self.load_config();
|
||||||
let allow_more = !crate::using_public_server() || self.direct == Some(true);
|
let allow_more = !crate::using_public_server() || self.direct == Some(true);
|
||||||
@@ -1602,32 +1593,25 @@ impl LoginConfigHandler {
|
|||||||
msg.custom_fps = custom_fps;
|
msg.custom_fps = custom_fps;
|
||||||
*self.custom_fps.lock().unwrap() = Some(custom_fps as _);
|
*self.custom_fps.lock().unwrap() = Some(custom_fps as _);
|
||||||
}
|
}
|
||||||
n += 1;
|
|
||||||
}
|
}
|
||||||
let view_only = self.get_toggle_option("view-only");
|
let view_only = self.get_toggle_option("view-only");
|
||||||
if view_only {
|
if view_only {
|
||||||
msg.disable_keyboard = BoolOption::Yes.into();
|
msg.disable_keyboard = BoolOption::Yes.into();
|
||||||
n += 1;
|
|
||||||
}
|
}
|
||||||
if view_only || self.get_toggle_option("show-remote-cursor") {
|
if view_only || self.get_toggle_option("show-remote-cursor") {
|
||||||
msg.show_remote_cursor = BoolOption::Yes.into();
|
msg.show_remote_cursor = BoolOption::Yes.into();
|
||||||
n += 1;
|
|
||||||
}
|
}
|
||||||
if !view_only && self.get_toggle_option("lock-after-session-end") {
|
if !view_only && self.get_toggle_option("lock-after-session-end") {
|
||||||
msg.lock_after_session_end = BoolOption::Yes.into();
|
msg.lock_after_session_end = BoolOption::Yes.into();
|
||||||
n += 1;
|
|
||||||
}
|
}
|
||||||
if self.get_toggle_option("disable-audio") {
|
if self.get_toggle_option("disable-audio") {
|
||||||
msg.disable_audio = BoolOption::Yes.into();
|
msg.disable_audio = BoolOption::Yes.into();
|
||||||
n += 1;
|
|
||||||
}
|
}
|
||||||
if !view_only && self.get_toggle_option("enable-file-transfer") {
|
if !view_only && self.get_toggle_option("enable-file-transfer") {
|
||||||
msg.enable_file_transfer = BoolOption::Yes.into();
|
msg.enable_file_transfer = BoolOption::Yes.into();
|
||||||
n += 1;
|
|
||||||
}
|
}
|
||||||
if view_only || self.get_toggle_option("disable-clipboard") {
|
if view_only || self.get_toggle_option("disable-clipboard") {
|
||||||
msg.disable_clipboard = BoolOption::Yes.into();
|
msg.disable_clipboard = BoolOption::Yes.into();
|
||||||
n += 1;
|
|
||||||
}
|
}
|
||||||
msg.supported_decoding =
|
msg.supported_decoding =
|
||||||
hbb_common::protobuf::MessageField::some(Decoder::supported_decodings(
|
hbb_common::protobuf::MessageField::some(Decoder::supported_decodings(
|
||||||
@@ -1636,12 +1620,7 @@ impl LoginConfigHandler {
|
|||||||
self.adapter_luid,
|
self.adapter_luid,
|
||||||
&self.mark_unsupported,
|
&self.mark_unsupported,
|
||||||
));
|
));
|
||||||
n += 1;
|
Some(msg)
|
||||||
if n > 0 {
|
|
||||||
Some(msg)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_option_message_after_login(&self) -> Option<OptionMessage> {
|
pub fn get_option_message_after_login(&self) -> Option<OptionMessage> {
|
||||||
|
|||||||
@@ -1304,8 +1304,14 @@ pub async fn get_next_nonkeyexchange_msg(
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[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;
|
use hbb_common::sysinfo::System;
|
||||||
let mut sys = System::new();
|
let mut sys = System::new();
|
||||||
sys.refresh_processes();
|
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 {
|
if same_uid && p.user_id() != my_uid {
|
||||||
continue;
|
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] };
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,9 +114,14 @@ pub fn core_main() -> Option<Vec<String>> {
|
|||||||
if args.contains(&"--noinstall".to_string()) {
|
if args.contains(&"--noinstall".to_string()) {
|
||||||
args.clear();
|
args.clear();
|
||||||
}
|
}
|
||||||
if args.len() > 0 && args[0] == "--version" {
|
if args.len() > 0 {
|
||||||
println!("{}", crate::VERSION);
|
if args[0] == "--version" {
|
||||||
return None;
|
println!("{}", crate::VERSION);
|
||||||
|
return None;
|
||||||
|
} else if args[0] == "--build-date" {
|
||||||
|
println!("{}", crate::BUILD_DATE);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
@@ -208,34 +213,16 @@ pub fn core_main() -> Option<Vec<String>> {
|
|||||||
.show()
|
.show()
|
||||||
.ok();
|
.ok();
|
||||||
return None;
|
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" {
|
} else if args[0] == "--uninstall-cert" {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
hbb_common::allow_err!(crate::platform::windows::uninstall_cert());
|
hbb_common::allow_err!(crate::platform::windows::uninstall_cert());
|
||||||
return None;
|
return None;
|
||||||
} else if args[0] == "--install-idd" {
|
} 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"))]
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
if crate::virtual_display_manager::is_virtual_display_supported() {
|
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;
|
return None;
|
||||||
} else if args[0] == "--portable-service" {
|
} else if args[0] == "--portable-service" {
|
||||||
@@ -245,6 +232,12 @@ pub fn core_main() -> Option<Vec<String>> {
|
|||||||
_is_run_as_system,
|
_is_run_as_system,
|
||||||
);
|
);
|
||||||
return None;
|
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" {
|
if args[0] == "--remove" {
|
||||||
@@ -274,7 +267,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
|||||||
} else if args[0] == "--server" {
|
} else if args[0] == "--server" {
|
||||||
log::info!("start --server with user {}", crate::username());
|
log::info!("start --server with user {}", crate::username());
|
||||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
#[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"))]
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||||
{
|
{
|
||||||
crate::start_server(true);
|
crate::start_server(true);
|
||||||
@@ -430,10 +423,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
|||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
scrap::hwcodec::check_available_hwcodec();
|
scrap::hwcodec::check_available_hwcodec();
|
||||||
return None;
|
return None;
|
||||||
} else if args[0] == "--check-gpucodec-config" {
|
|
||||||
#[cfg(feature = "gpucodec")]
|
|
||||||
scrap::gpucodec::check_available_gpucodec();
|
|
||||||
return None;
|
|
||||||
} else if args[0] == "--cm" {
|
} else if args[0] == "--cm" {
|
||||||
// call connection manager to establish connections
|
// call connection manager to establish connections
|
||||||
// meanwhile, return true to call flutter window to show control panel
|
// meanwhile, return true to call flutter window to show control panel
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
ui_session_interface::{io_loop, InvokeUiSession, Session},
|
ui_session_interface::{io_loop, InvokeUiSession, Session},
|
||||||
};
|
};
|
||||||
use flutter_rust_bridge::StreamSink;
|
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::{
|
use hbb_common::dlopen::{
|
||||||
symbor::{Library, Symbol},
|
symbor::{Library, Symbol},
|
||||||
Error as LibError,
|
Error as LibError,
|
||||||
@@ -16,7 +16,7 @@ use hbb_common::{
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
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::os::raw::c_void;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
@@ -63,7 +63,7 @@ lazy_static::lazy_static! {
|
|||||||
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open_self();
|
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! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref TEXTURE_GPU_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("flutter_gpu_texture_renderer_plugin.dll");
|
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)]
|
#[derive(Default)]
|
||||||
struct SessionHandler {
|
struct SessionHandler {
|
||||||
event_stream: Option<StreamSink<EventToUI>>,
|
event_stream: Option<StreamSink<EventToUI>>,
|
||||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||||
renderer: VideoRenderer,
|
renderer: VideoRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
enum RenderType {
|
enum RenderType {
|
||||||
PixelBuffer,
|
PixelBuffer,
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
Texture,
|
Texture,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,41 +214,41 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
|
|||||||
dst_rgba_stride: c_int,
|
dst_rgba_stride: c_int,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
pub type FlutterGpuTextureRendererPluginCApiSetTexture =
|
pub type FlutterGpuTextureRendererPluginCApiSetTexture =
|
||||||
unsafe extern "C" fn(output: *mut c_void, texture: *mut c_void);
|
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;
|
pub type FlutterGpuTextureRendererPluginCApiGetAdapterLuid = unsafe extern "C" fn() -> i64;
|
||||||
|
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
pub(super) type TextureRgbaPtr = usize;
|
pub(super) type TextureRgbaPtr = usize;
|
||||||
|
|
||||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||||
struct DisplaySessionInfo {
|
struct DisplaySessionInfo {
|
||||||
// TextureRgba pointer in flutter native.
|
// TextureRgba pointer in flutter native.
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
texture_rgba_ptr: TextureRgbaPtr,
|
texture_rgba_ptr: TextureRgbaPtr,
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
size: (usize, usize),
|
size: (usize, usize),
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
gpu_output_ptr: usize,
|
gpu_output_ptr: usize,
|
||||||
notify_render_type: Option<RenderType>,
|
notify_render_type: Option<RenderType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Video Texture Renderer in Flutter
|
// Video Texture Renderer in Flutter
|
||||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct VideoRenderer {
|
struct VideoRenderer {
|
||||||
is_support_multi_ui_session: bool,
|
is_support_multi_ui_session: bool,
|
||||||
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
|
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
on_texture_func: Option<Symbol<'static, FlutterGpuTextureRendererPluginCApiSetTexture>>,
|
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 {
|
impl Default for VideoRenderer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
@@ -270,7 +270,7 @@ impl Default for VideoRenderer {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
let on_texture_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
let on_texture_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
||||||
Ok(lib) => {
|
Ok(lib) => {
|
||||||
let find_sym_res = unsafe {
|
let find_sym_res = unsafe {
|
||||||
@@ -297,13 +297,13 @@ impl Default for VideoRenderer {
|
|||||||
is_support_multi_ui_session: false,
|
is_support_multi_ui_session: false,
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
on_rgba_func,
|
on_rgba_func,
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
on_texture_func,
|
on_texture_func,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||||
impl VideoRenderer {
|
impl VideoRenderer {
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
@@ -318,7 +318,7 @@ impl VideoRenderer {
|
|||||||
DisplaySessionInfo {
|
DisplaySessionInfo {
|
||||||
texture_rgba_ptr: usize::default(),
|
texture_rgba_ptr: usize::default(),
|
||||||
size: (width, height),
|
size: (width, height),
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
gpu_output_ptr: usize::default(),
|
gpu_output_ptr: usize::default(),
|
||||||
notify_render_type: None,
|
notify_render_type: None,
|
||||||
},
|
},
|
||||||
@@ -345,7 +345,7 @@ impl VideoRenderer {
|
|||||||
DisplaySessionInfo {
|
DisplaySessionInfo {
|
||||||
texture_rgba_ptr: ptr as _,
|
texture_rgba_ptr: ptr as _,
|
||||||
size: (0, 0),
|
size: (0, 0),
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
gpu_output_ptr: usize::default(),
|
gpu_output_ptr: usize::default(),
|
||||||
notify_render_type: None,
|
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) {
|
pub fn register_gpu_output(&self, display: usize, ptr: usize) {
|
||||||
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||||
if ptr == 0 {
|
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 {
|
pub fn on_texture(&self, display: usize, texture: *mut c_void) -> bool {
|
||||||
let mut write_lock = self.map_display_sessions.write().unwrap();
|
let mut write_lock = self.map_display_sessions.write().unwrap();
|
||||||
let opt_info = if !self.is_support_multi_ui_session {
|
let opt_info = if !self.is_support_multi_ui_session {
|
||||||
@@ -793,7 +793,7 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
fn on_texture(&self, display: usize, texture: *mut c_void) {
|
fn on_texture(&self, display: usize, texture: *mut c_void) {
|
||||||
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
||||||
if session.renderer.on_texture(display, texture) {
|
if session.renderer.on_texture(display, texture) {
|
||||||
@@ -1073,9 +1073,9 @@ pub fn session_add(
|
|||||||
Some(switch_uuid.to_string())
|
Some(switch_uuid.to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "gpucodec")]
|
#[cfg(feature = "vram")]
|
||||||
let adapter_luid = get_adapter_luid();
|
let adapter_luid = get_adapter_luid();
|
||||||
#[cfg(not(feature = "gpucodec"))]
|
#[cfg(not(feature = "vram"))]
|
||||||
let adapter_luid = None;
|
let adapter_luid = None;
|
||||||
|
|
||||||
session.lc.write().unwrap().initialize(
|
session.lc.write().unwrap().initialize(
|
||||||
@@ -1106,7 +1106,7 @@ pub fn session_start_(
|
|||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
// is_connected is used to indicate whether to start a peer connection. For two cases:
|
// is_connected is used to indicate whether to start a peer connection. For two cases:
|
||||||
// 1. "Move tab to new window"
|
// 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_connected = false;
|
||||||
let mut is_found = false;
|
let mut is_found = false;
|
||||||
for s in sessions::get_sessions() {
|
for s in sessions::get_sessions() {
|
||||||
@@ -1453,7 +1453,7 @@ pub fn session_register_pixelbuffer_texture(_session_id: SessionID, _display: us
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn session_register_gpu_texture(_session_id: SessionID, _display: usize, _output_ptr: usize) {
|
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() {
|
for s in sessions::get_sessions() {
|
||||||
if let Some(h) = s
|
if let Some(h) = s
|
||||||
.ui_handler
|
.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> {
|
pub fn get_adapter_luid() -> Option<i64> {
|
||||||
let get_adapter_luid_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
let get_adapter_luid_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
||||||
Ok(lib) => {
|
Ok(lib) => {
|
||||||
|
|||||||
@@ -39,11 +39,15 @@ lazy_static::lazy_static! {
|
|||||||
static ref TEXTURE_RENDER_KEY: Arc<AtomicI32> = Arc::new(AtomicI32::new(0));
|
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();
|
flutter::async_tasks::start_flutter_async_runner();
|
||||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
*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
|
// 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")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
// flexi_logger can't work when android_logger initialized.
|
// 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()
|
crate::ui_cm_interface::get_clients_length()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_init(app_dir: String) {
|
pub fn main_init(app_dir: String, custom_client_config: String) {
|
||||||
initialize(&app_dir);
|
initialize(&app_dir, &custom_client_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_device_id(id: String) {
|
pub fn main_device_id(id: String) {
|
||||||
@@ -1302,8 +1306,8 @@ pub fn main_has_hwcodec() -> SyncReturn<bool> {
|
|||||||
SyncReturn(has_hwcodec())
|
SyncReturn(has_hwcodec())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_has_gpucodec() -> SyncReturn<bool> {
|
pub fn main_has_vram() -> SyncReturn<bool> {
|
||||||
SyncReturn(has_gpucodec())
|
SyncReturn(has_vram())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_supported_hwdecodings() -> SyncReturn<String> {
|
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> {
|
pub fn main_has_gpu_texture_render() -> SyncReturn<bool> {
|
||||||
SyncReturn(cfg!(feature = "gpucodec"))
|
SyncReturn(cfg!(feature = "vram"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cm_init() {
|
pub fn cm_init() {
|
||||||
@@ -2103,6 +2107,16 @@ pub fn main_get_hard_option(key: String) -> SyncReturn<String> {
|
|||||||
SyncReturn(get_hard_option(key))
|
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")]
|
#[cfg(target_os = "android")]
|
||||||
pub mod server_side {
|
pub mod server_side {
|
||||||
use hbb_common::{config, log};
|
use hbb_common::{config, log};
|
||||||
@@ -2115,31 +2129,35 @@ pub mod server_side {
|
|||||||
use crate::start_server;
|
use crate::start_server;
|
||||||
|
|
||||||
#[no_mangle]
|
#[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,
|
env: JNIEnv,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
app_dir: JString,
|
app_dir: JString,
|
||||||
|
custom_client_config: JString,
|
||||||
) {
|
) {
|
||||||
log::debug!("startServer from jvm");
|
log::debug!("startServer from jvm");
|
||||||
let mut env = env;
|
let mut env = env;
|
||||||
if let Ok(app_dir) = env.get_string(&app_dir) {
|
if let Ok(app_dir) = env.get_string(&app_dir) {
|
||||||
*config::APP_DIR.write().unwrap() = app_dir.into();
|
*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));
|
std::thread::spawn(move || start_server(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startService(
|
pub unsafe extern "system" fn Java_ffi_FFI_startService(_env: JNIEnv, _class: JClass) {
|
||||||
_env: JNIEnv,
|
|
||||||
_class: JClass,
|
|
||||||
) {
|
|
||||||
log::debug!("startService from jvm");
|
log::debug!("startService from jvm");
|
||||||
config::Config::set_option("stop-service".into(), "".into());
|
config::Config::set_option("stop-service".into(), "".into());
|
||||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
|
pub unsafe extern "system" fn Java_ffi_FFI_translateLocale(
|
||||||
env: JNIEnv,
|
env: JNIEnv,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
locale: JString,
|
locale: JString,
|
||||||
@@ -2158,10 +2176,7 @@ pub mod server_side {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_refreshScreen(
|
pub unsafe extern "system" fn Java_ffi_FFI_refreshScreen(_env: JNIEnv, _class: JClass) {
|
||||||
_env: JNIEnv,
|
|
||||||
_class: JClass,
|
|
||||||
) {
|
|
||||||
crate::server::video_service::refresh()
|
crate::server::video_service::refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/ipc.rs
15
src/ipc.rs
@@ -232,6 +232,7 @@ pub enum Data {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
ControlledSessionCount(usize),
|
ControlledSessionCount(usize),
|
||||||
CmErr(String),
|
CmErr(String),
|
||||||
|
CheckHwcodec,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
@@ -502,6 +503,14 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
.await
|
.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(())
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ mod et;
|
|||||||
mod fa;
|
mod fa;
|
||||||
mod fr;
|
mod fr;
|
||||||
mod he;
|
mod he;
|
||||||
|
mod hr;
|
||||||
mod hu;
|
mod hu;
|
||||||
mod id;
|
mod id;
|
||||||
mod it;
|
mod it;
|
||||||
@@ -81,6 +82,7 @@ pub const LANGS: &[(&str, &str)] = &[
|
|||||||
("lv", "Latviešu"),
|
("lv", "Latviešu"),
|
||||||
("ar", "العربية"),
|
("ar", "العربية"),
|
||||||
("he", "עברית"),
|
("he", "עברית"),
|
||||||
|
("hr", "Hrvatski"),
|
||||||
];
|
];
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[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(),
|
"ar" => ar::T.deref(),
|
||||||
"bg" => bg::T.deref(),
|
"bg" => bg::T.deref(),
|
||||||
"he" => he::T.deref(),
|
"he" => he::T.deref(),
|
||||||
|
"hr" => hr::T.deref(),
|
||||||
_ => en::T.deref(),
|
_ => en::T.deref(),
|
||||||
};
|
};
|
||||||
let (name, placeholder_value) = extract_placeholder(&name);
|
let (name, placeholder_value) = extract_placeholder(&name);
|
||||||
|
|||||||
@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("share_warning_tip", ""),
|
("share_warning_tip", ""),
|
||||||
("Everyone", ""),
|
("Everyone", ""),
|
||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
|
("allow-only-conn-window-open-tip", ""),
|
||||||
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("share_warning_tip", ""),
|
("share_warning_tip", ""),
|
||||||
("Everyone", ""),
|
("Everyone", ""),
|
||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
|
("allow-only-conn-window-open-tip", ""),
|
||||||
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("share_warning_tip", ""),
|
("share_warning_tip", ""),
|
||||||
("Everyone", ""),
|
("Everyone", ""),
|
||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
|
("allow-only-conn-window-open-tip", ""),
|
||||||
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -590,7 +590,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("powered_by_me", "由 RustDesk 提供支持"),
|
("powered_by_me", "由 RustDesk 提供支持"),
|
||||||
("outgoing_only_desk_tip", "当前版本的软件是定制版本。\n您可以连接至其他设备,但是其他设备无法连接至您的设备。"),
|
("outgoing_only_desk_tip", "当前版本的软件是定制版本。\n您可以连接至其他设备,但是其他设备无法连接至您的设备。"),
|
||||||
("preset_password_warning", "此定制版本附有预设密码。 任何知晓此密码的人都能完全控制您的设备。如果这不是您所预期的,请立即卸载此软件。"),
|
("preset_password_warning", "此定制版本附有预设密码。 任何知晓此密码的人都能完全控制您的设备。如果这不是您所预期的,请立即卸载此软件。"),
|
||||||
("Security Alert", ""),
|
("Security Alert", "安全警告"),
|
||||||
("My address book", "我的地址簿"),
|
("My address book", "我的地址簿"),
|
||||||
("Personal", "个人的"),
|
("Personal", "个人的"),
|
||||||
("Owner", "所有者"),
|
("Owner", "所有者"),
|
||||||
@@ -602,5 +602,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("share_warning_tip", "上述的字段為共享且对其他人可见。"),
|
("share_warning_tip", "上述的字段為共享且对其他人可见。"),
|
||||||
("Everyone", "所有人"),
|
("Everyone", "所有人"),
|
||||||
("ab_web_console_tip", "打开 Web 控制台以执行更多操作"),
|
("ab_web_console_tip", "打开 Web 控制台以执行更多操作"),
|
||||||
|
("allow-only-conn-window-open-tip", "仅当 RustDesk 窗口打开时允许连接"),
|
||||||
|
("no_need_privacy_mode_no_physical_displays_tip", "没有物理显示器,没必要使用隐私模式。"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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í."),
|
("share_warning_tip", "Výše uvedená pole jsou sdílená a viditelná pro ostatní."),
|
||||||
("Everyone", "Každý"),
|
("Everyone", "Každý"),
|
||||||
("ab_web_console_tip", "Více na webové konzoli"),
|
("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();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user