mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-10 06:41:10 +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:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
||||
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
|
||||
|
||||
jobs:
|
||||
generate_bridge:
|
||||
@@ -49,9 +50,9 @@ jobs:
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
targets: ${{ matrix.job.target }}
|
||||
components: ''
|
||||
components: "rustfmt"
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
|
||||
5
.github/workflows/build-macos-arm64.yml
vendored
5
.github/workflows/build-macos-arm64.yml
vendored
@@ -54,8 +54,3 @@ jobs:
|
||||
- name: Run
|
||||
shell: bash
|
||||
run: |
|
||||
cd /opt/build
|
||||
#./update_mac_template.sh
|
||||
#security default-keychain -s rustdesk.keychain
|
||||
#security unlock-keychain -p ${{ secrets.MACOS_P12_PASSWORD }} rustdesk.keychain
|
||||
./agent.sh
|
||||
|
||||
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: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true }
|
||||
# - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
|
||||
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
||||
# - { target: i686-pc-windows-msvc , os: windows-2022 }
|
||||
# - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
||||
# - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
||||
# - { target: x86_64-apple-darwin , os: macos-10.15 }
|
||||
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
||||
# - { target: x86_64-pc-windows-msvc , os: windows-2019 }
|
||||
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
|
||||
# - { target: x86_64-pc-windows-msvc , os: windows-2022 }
|
||||
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 }
|
||||
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
||||
steps:
|
||||
|
||||
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
|
||||
CARGO_NDK_VERSION: "3.1.2"
|
||||
LLVM_VERSION: "15.0.6"
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
FLUTTER_VERSION: "3.19.6"
|
||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
||||
# for arm64 linux because official Dart SDK does not work
|
||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
|
||||
with:
|
||||
upload-artifact: ${{ inputs.upload-artifact }}
|
||||
target: windows-2019
|
||||
target: windows-2022
|
||||
configuration: Release
|
||||
platform: x64
|
||||
target_version: Windows10
|
||||
@@ -56,10 +56,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
||||
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-2019, arch: x86_64 }
|
||||
# - { target: aarch64-pc-windows-msvc, os: windows-2019, arch: aarch64 }
|
||||
# - { target: i686-pc-windows-msvc , os: windows-2022 }
|
||||
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-2022, arch: x86_64 }
|
||||
# - { target: aarch64-pc-windows-msvc, os: windows-2022, arch: aarch64 }
|
||||
steps:
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v6
|
||||
@@ -83,14 +83,6 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Replace engine with rustdesk custom flutter engine
|
||||
run: |
|
||||
flutter doctor -v
|
||||
flutter precache --windows
|
||||
Invoke-WebRequest -Uri https://github.com/fufesou/flutter-engine/releases/download/bugfix-subwindow-crash-3.16.9-apply-pull-47787/windows-x64-release.zip -OutFile windows-x64-flutter-release.zip
|
||||
Expand-Archive windows-x64-flutter-release.zip -DestinationPath .
|
||||
mv -Force windows-x64-release/* C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@v1
|
||||
with:
|
||||
@@ -121,7 +113,13 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Build rustdesk
|
||||
run: python3 .\build.py --portable --hwcodec --flutter --gpucodec --skip-portable-pack
|
||||
run: |
|
||||
Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip
|
||||
Expand-Archive usbmmidd_v2.zip -DestinationPath .
|
||||
python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack --virtual-display
|
||||
Remove-Item -Path usbmmidd_v2\Win32 -Recurse
|
||||
Remove-Item -Path "usbmmidd_v2\deviceinstaller64.exe", "usbmmidd_v2\deviceinstaller.exe", "usbmmidd_v2\usbmmidd.bat"
|
||||
mv -Force .\usbmmidd_v2 ./flutter/build/windows/x64/runner/Release/
|
||||
|
||||
- name: find Runner.res
|
||||
# Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res
|
||||
@@ -170,6 +168,19 @@ jobs:
|
||||
mkdir -p ./SignOutput
|
||||
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.exe
|
||||
|
||||
- name: Add MSBuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Build msi
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
pushd ./res/msi
|
||||
python preprocess.py --arp -d ../../rustdesk
|
||||
nuget restore msi.sln
|
||||
msbuild msi.sln -p:Configuration=Release -p:Platform=x64 /p:TargetVersion=Windows10
|
||||
mv ./Package/bin/x64/Release/en-us/Package.msi ../../SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-beta.msi
|
||||
sha256sum ../../SignOutput/rustdesk-*.msi
|
||||
|
||||
- name: Sign rustdesk self-extracted file
|
||||
if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != ''
|
||||
shell: bash
|
||||
@@ -183,6 +194,7 @@ jobs:
|
||||
prerelease: true
|
||||
tag_name: ${{ env.TAG_NAME }}
|
||||
files: |
|
||||
./SignOutput/rustdesk-*.msi
|
||||
./SignOutput/rustdesk-*.exe
|
||||
./rustdesk-*.tar.gz
|
||||
|
||||
@@ -196,10 +208,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
||||
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
||||
- { target: i686-pc-windows-msvc, os: windows-2019, arch: x86 }
|
||||
# - { target: aarch64-pc-windows-msvc, os: windows-2019 }
|
||||
# - { target: i686-pc-windows-msvc , os: windows-2022 }
|
||||
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
|
||||
- { target: i686-pc-windows-msvc, os: windows-2022, arch: x86 }
|
||||
# - { target: aarch64-pc-windows-msvc, os: windows-2022 }
|
||||
steps:
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v6
|
||||
@@ -245,11 +257,15 @@ jobs:
|
||||
python3 res/inline-sciter.py
|
||||
# Patch sciter x86
|
||||
sed -i 's/branch = "dyn"/branch = "dyn_x86"/g' ./Cargo.toml
|
||||
cargo build --features inline,gpucodec --release --bins
|
||||
cargo build --features inline,vram,hwcodec,virtual_display_driver --release --bins
|
||||
mkdir -p ./Release
|
||||
mv ./target/release/rustdesk.exe ./Release/rustdesk.exe
|
||||
curl -LJ -o ./Release/sciter.dll https://github.com/c-smile/sciter-sdk/raw/master/bin.win/x32/sciter.dll
|
||||
echo "output_folder=./Release" >> $GITHUB_OUTPUT
|
||||
curl -LJ -o ./usbmmidd_v2.zip https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip
|
||||
unzip usbmmidd_v2.zip
|
||||
rm -rf ./usbmmidd_v2/x64 ./usbmmidd_v2/deviceinstaller.exe ./usbmmidd_v2/deviceinstaller64.exe ./usbmmidd_v2/usbmmidd.bat
|
||||
mv ./usbmmidd_v2 ./Release || true
|
||||
|
||||
- name: find Runner.res
|
||||
# Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res
|
||||
@@ -325,8 +341,7 @@ jobs:
|
||||
|
||||
- name: Build rustdesk
|
||||
run: |
|
||||
# --hwcodec not supported on macos yet
|
||||
./build.py --flutter
|
||||
./build.py --flutter --hwcodec
|
||||
|
||||
- name: create unsigned dmg
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
@@ -480,8 +495,7 @@ jobs:
|
||||
|
||||
- name: Build rustdesk
|
||||
run: |
|
||||
# --hwcodec not supported on macos yet
|
||||
./build.py --flutter ${{ matrix.job.extra-build-args }}
|
||||
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
|
||||
|
||||
- name: create unsigned dmg
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
@@ -630,7 +644,7 @@ jobs:
|
||||
- name: Build rustdesk lib
|
||||
run: |
|
||||
rustup target add ${{ matrix.job.target }}
|
||||
cargo build --features flutter --release --target aarch64-apple-ios --lib
|
||||
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
||||
|
||||
- name: Build rustdesk
|
||||
shell: bash
|
||||
@@ -1251,7 +1265,7 @@ jobs:
|
||||
export DEFAULT_FEAT=linux_headless
|
||||
fi
|
||||
export CARGO_INCREMENTAL=0
|
||||
cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }},$DEFAULT_FEAT --release
|
||||
cargo build --lib --features flutter,flutter_texture_render,hwcodec,${{ matrix.job.extra-build-features }},$DEFAULT_FEAT --release
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@master
|
||||
@@ -1594,7 +1608,7 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
export CARGO_INCREMENTAL=0
|
||||
python3 ./build.py --flutter --hwcodec --skip-cargo
|
||||
python3 ./build.py --flutter --skip-cargo
|
||||
# rpm package
|
||||
echo -e "start packaging fedora package"
|
||||
pushd /workspace
|
||||
|
||||
2
.github/workflows/history.yml
vendored
2
.github/workflows/history.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-2019, arch: x86_64, date: 2023-08-04, ref: 72c198a1e94cc1e0242fce88f92b3f3caedcd0c3 }
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-2022, arch: x86_64, date: 2023-08-04, ref: 72c198a1e94cc1e0242fce88f92b3f3caedcd0c3 }
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
description: 'Target'
|
||||
required: true
|
||||
type: string
|
||||
default: 'windows-2019'
|
||||
default: 'windows-2022'
|
||||
configuration:
|
||||
description: 'Configuration'
|
||||
required: true
|
||||
|
||||
84
Cargo.lock
generated
84
Cargo.lock
generated
@@ -115,17 +115,6 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "amf"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"cc",
|
||||
"gpu_common",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -2716,36 +2705,6 @@ dependencies = [
|
||||
"system-deps 6.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpu_common"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"cc",
|
||||
"log",
|
||||
"serde 1.0.190",
|
||||
"serde_derive",
|
||||
"serde_json 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpucodec"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
|
||||
dependencies = [
|
||||
"amf",
|
||||
"bindgen 0.59.2",
|
||||
"cc",
|
||||
"gpu_common",
|
||||
"log",
|
||||
"nv",
|
||||
"serde 1.0.190",
|
||||
"serde_derive",
|
||||
"serde_json 1.0.107",
|
||||
"vpl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gstreamer"
|
||||
version = "0.16.7"
|
||||
@@ -3140,8 +3099,8 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hwcodec"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/21pages/hwcodec?branch=stable#52e1da2aae86acec5f374bc065f5921945b55e7b"
|
||||
version = "0.3.3"
|
||||
source = "git+https://github.com/21pages/hwcodec#eeebf980d4eb41daaf05090b097d5a59d688d3d8"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"cc",
|
||||
@@ -4224,17 +4183,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nv"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"cc",
|
||||
"gpu_common",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
@@ -4852,9 +4800,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190"
|
||||
checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"once_cell",
|
||||
@@ -4864,9 +4812,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "protobuf-codegen"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e85514a216b1c73111d9032e26cc7a5ecb1bb3d4d9539e91fb72a4395060f78"
|
||||
checksum = "32777b0b3f6538d9d2e012b3fad85c7e4b9244b5958d04a6415f4333782b7a77"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"once_cell",
|
||||
@@ -4879,9 +4827,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "protobuf-parse"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77d6fbd6697c9e531873e81cec565a85e226b99a0f10e1acc079be057fe2fcba"
|
||||
checksum = "96cb37955261126624a25b5e6bda40ae34cf3989d52a783087ca6091b29b5642"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap 1.9.3",
|
||||
@@ -4895,9 +4843,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "protobuf-support"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c"
|
||||
checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
@@ -5856,7 +5804,6 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"dbus",
|
||||
"docopt",
|
||||
"gpucodec",
|
||||
"gstreamer",
|
||||
"gstreamer-app",
|
||||
"gstreamer-video",
|
||||
@@ -7097,17 +7044,6 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vpl"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/21pages/gpucodec#546f7f644ce15a35b833c1531a4fead4b34a1b3b"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"cc",
|
||||
"gpu_common",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.1.1"
|
||||
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -28,7 +28,7 @@ use_dasp = ["dasp"]
|
||||
flutter = ["flutter_rust_bridge"]
|
||||
default = ["use_dasp"]
|
||||
hwcodec = ["scrap/hwcodec"]
|
||||
gpucodec = ["scrap/gpucodec"]
|
||||
vram = ["scrap/vram"]
|
||||
mediacodec = ["scrap/mediacodec"]
|
||||
linux_headless = ["pam" ]
|
||||
virtual_display_driver = ["virtual_display"]
|
||||
@@ -99,7 +99,20 @@ system_shutdown = "4.0"
|
||||
qrcode-generator = "4.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser", "wincrypt", "shellscalingapi", "pdh", "synchapi", "memoryapi", "shellapi"] }
|
||||
winapi = { version = "0.3", features = [
|
||||
"winuser",
|
||||
"wincrypt",
|
||||
"shellscalingapi",
|
||||
"pdh",
|
||||
"synchapi",
|
||||
"memoryapi",
|
||||
"shellapi",
|
||||
"devguid",
|
||||
"setupapi",
|
||||
"cguid",
|
||||
"cfgmgr32",
|
||||
"ioapiset",
|
||||
] }
|
||||
winreg = "0.11"
|
||||
windows-service = "0.6"
|
||||
virtual_display = { path = "libs/virtual_display", optional = true }
|
||||
|
||||
23
build.py
23
build.py
@@ -33,9 +33,9 @@ def get_arch() -> str:
|
||||
|
||||
|
||||
def system2(cmd):
|
||||
err = os.system(cmd)
|
||||
if err != 0:
|
||||
print(f"Error occurred when executing: {cmd}. Exiting.")
|
||||
exit_code = os.system(cmd)
|
||||
if exit_code != 0:
|
||||
sys.stderr.write(f"Error occurred when executing: `{cmd}`. Exiting.\n")
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
@@ -118,9 +118,9 @@ def make_parser():
|
||||
'' if windows or osx else ', need libva-dev, libvdpau-dev.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--gpucodec',
|
||||
'--vram',
|
||||
action='store_true',
|
||||
help='Enable feature gpucodec, only available on windows now.'
|
||||
help='Enable feature vram, only available on windows now.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--portable',
|
||||
@@ -153,6 +153,12 @@ def make_parser():
|
||||
action='store_true',
|
||||
help='Skip packing, only flutter version + Windows supported'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--virtual-display',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Build rustdesk libs with the virtual display feature enabled'
|
||||
)
|
||||
parser.add_argument(
|
||||
"--package",
|
||||
type=str
|
||||
@@ -282,8 +288,8 @@ def get_features(args):
|
||||
features = ['inline'] if not args.flutter else []
|
||||
if args.hwcodec:
|
||||
features.append('hwcodec')
|
||||
if args.gpucodec:
|
||||
features.append('gpucodec')
|
||||
if args.vram:
|
||||
features.append('vram')
|
||||
if args.flutter:
|
||||
features.append('flutter')
|
||||
features.append('flutter_texture_render')
|
||||
@@ -293,6 +299,9 @@ def get_features(args):
|
||||
features.append('appimage')
|
||||
if args.unix_file_copy_paste:
|
||||
features.append('unix-file-copy-paste')
|
||||
if windows:
|
||||
if args.virtual_display:
|
||||
features.append('virtual_display_driver')
|
||||
print("features:", features)
|
||||
return features
|
||||
|
||||
|
||||
4
build.rs
4
build.rs
@@ -1,9 +1,11 @@
|
||||
#[cfg(windows)]
|
||||
fn build_windows() {
|
||||
let file = "src/platform/windows.cc";
|
||||
cc::Build::new().file(file).compile("windows");
|
||||
let file2 = "src/platform/windows_delete_test_cert.cc";
|
||||
cc::Build::new().file(file).file(file2).compile("windows");
|
||||
println!("cargo:rustc-link-lib=WtsApi32");
|
||||
println!("cargo:rerun-if-changed={}", file);
|
||||
println!("cargo:rerun-if-changed={}", file2);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - Phần mềm điểu khiển máy tính từ xa dành cho bạn"><br>
|
||||
<a href="#free-public-servers">Máy chủ</a> •
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#free-public-servers">Server</a> •
|
||||
<a href="#raw-steps-to-build">Build</a> •
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Cấu trúc tệp tin</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
|
||||
<b>Chúng tôi cần sự gíup đỡ của bạn để dịch trang README này, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> và <a href="https://github.com/rustdesk/doc.rustdesk.com">tài liệu</a> sang ngôn ngữ bản địa của bạn</b>
|
||||
<b>Chúng tôi rất hoan nghênh sự hỗ trợ của bạn trong việc dịch trang README, trang giao diện người dùng của RustDesk - <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> và trang tài liệu của RustDesk - <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> sang Tiếng Việt</b>
|
||||
</p>
|
||||
|
||||
Chat với chúng tôi qua: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
Hãy trao đổi với chúng tôi qua: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](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)
|
||||
|
||||
[**CÁC BẢN PHÂN PHÁT MÃ NHỊ PHÂN**](https://github.com/rustdesk/rustdesk/releases)
|
||||
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||
[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases)
|
||||
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/FAQreleases/tag/nightly)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
@@ -29,28 +31,25 @@ Mọi người đều đuợc chào đón để đóng góp vào RustDesk. Để
|
||||
|
||||
## Dependencies
|
||||
|
||||
Phiên bản cho máy tính sử dụng [sciter](https://sciter.com/) cho giao diện của phần mềm, vậy nên 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) |
|
||||
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
|
||||
[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
|
||||
Vui lòng tự tải thư viện `Sciter` về máy theo hướng dẫn cho từng hệ điều hành.
|
||||
|
||||
Phiên bản cho điện thoại sử dụng Flutter. Chúng tôi sẽ chuyển sang sử dụng Flutter thay cho Sciter cho phiên bản máy tính.
|
||||
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) | [Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) | [MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
|
||||
|
||||
## Cách để build
|
||||
## Các bước build cơ bản
|
||||
|
||||
- Chuẩn bị môi trường phát triển Rust và môi trường build C++
|
||||
- Chuẩn bị môi trường phát triển Rust và môi trường biên dịch C++
|
||||
|
||||
- Tải và cài [vcpkg](https://github.com/microsoft/vcpkg), và đặt biến môi trường `VCPKG_ROOT` sao cho đúng.
|
||||
|
||||
- Đối với Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||
- Đối với Linux/MacOS: vcpkg install libvpx libyuv opus aom
|
||||
- Tải và cài đặt [`vcpkg`](https://github.com/microsoft/vcpkg), và thiết lập biến môi trường `VCPKG_ROOT`.
|
||||
|
||||
- Windows: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static`
|
||||
- Linux/MacOS: `vcpkg install libvpx libyuv opus aom`
|
||||
- Chạy lệnh `cargo run`
|
||||
|
||||
## [Build](https://rustdesk.com/docs/en/dev/build/)
|
||||
|
||||
## Cách để build cho Linux
|
||||
## Cách build cho Linux
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
@@ -70,7 +69,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
|
||||
```
|
||||
|
||||
### Cách cài vcpkg
|
||||
### Cách cài đặt `vcpkg`
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
@@ -82,7 +81,7 @@ export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus aom
|
||||
```
|
||||
|
||||
### Cách sửa lỗi libvpx (Dành cho hệ điều hành Fedora)
|
||||
### Cách sửa lỗi `libvpx` (Dành cho hệ điều hành Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
@@ -95,7 +94,7 @@ cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Cách build
|
||||
### Build
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
@@ -108,9 +107,9 @@ mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
## Cách để build sử dụng Docker
|
||||
## Cách build bằng Docker
|
||||
|
||||
Bắt đầu bằng cách sao chép repo này về máy tính và build cái Docker cointainer:
|
||||
Bắt đầu bằng cách sao chép repo này về máy tính của bạn và tạo Docker container:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
@@ -118,37 +117,37 @@ cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Rồi mỗi khi bạn chạy ứng dụng, thì hãy chạy lệnh này:
|
||||
Sau đó, mỗi khi bạn chạy ứng dụng, thì hãy chạy dòng lệnh sau:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||
```
|
||||
|
||||
Chú ý: Lần build đầu tiên có thể sẽ mất lâu hơn truớc khi các dependecies đuợc lưu lại, 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
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Nếu bạn đang chạy bản build đuợc tối ưu hóa, thì bạn có thể chạy với lệnh:
|
||||
Nếu bạn đang chạy bản build được tối ưu hóa, thì bạn có thể chạy với lệnh:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Hãy đảm bảo 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
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, cấu hình, tcp/udp wrapper, protobuf, fs functions để truyền file, và một số hàm tiện ích khác
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: để ghi lại màn hình
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: để điều khiển máy tính/con chuột trên những nền tảng khác nhau
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: ghi lại màn hình
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: điều khiển máy tính/chuột trên các nền tảng khác nhau
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: giao diện người dùng
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: các dịch vụ âm thanh, clipboard, đầu vào, video và các kết nối mạng
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: để bắt đầu kết nối với một peer
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Để liên lạc với [rustdesk-server](https://github.com/rustdesk/rustdesk-server), đợi cho kết nối trực tiếp (TCP hole punching) hoặc kết nối được relayed.
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: bắt đầu kết nối với một peer
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: giao tiếp với [rustdesk-server](https://github.com/rustdesk/rustdesk-server), đợi kết nối trực tiếp (TCP hole punching) hoặc kết nối được chuyển tiếp.
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: mã nguồn riêng cho mỗi nền tảng
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Mã Flutter dành cho điện thoại
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Mã Flutter dành máy tính và điện thoại
|
||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Mã JavaScript dành cho giao diện trên web bằng Flutter
|
||||
|
||||
## Snapshot
|
||||
|
||||
@@ -108,4 +108,3 @@ dependencies {
|
||||
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
|
||||
|
||||
import ffi.FFI
|
||||
|
||||
/**
|
||||
* Capture screen,get video and audio,send to rust.
|
||||
* Dispatch notifications
|
||||
@@ -64,10 +66,6 @@ const val AUDIO_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO
|
||||
|
||||
class MainService : Service() {
|
||||
|
||||
init {
|
||||
System.loadLibrary("rustdesk")
|
||||
}
|
||||
|
||||
@Keep
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun rustPointerInput(kind: String, mask: Int, x: Int, y: Int) {
|
||||
@@ -156,23 +154,9 @@ class MainService : Service() {
|
||||
private val powerManager: PowerManager by lazy { applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager }
|
||||
private val wakeLock: PowerManager.WakeLock by lazy { powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "rustdesk:wakelock")}
|
||||
|
||||
// jvm call rust
|
||||
private external fun init(ctx: Context)
|
||||
|
||||
/// When app start on boot, app_dir will not be passed from flutter
|
||||
/// so pass a app_dir here to rust server
|
||||
private external fun startServer(app_dir: String)
|
||||
private external fun startService()
|
||||
private external fun onVideoFrameUpdate(buf: ByteBuffer)
|
||||
private external fun onAudioFrameUpdate(buf: ByteBuffer)
|
||||
private external fun translateLocale(localeName: String, input: String): String
|
||||
private external fun refreshScreen()
|
||||
private external fun setFrameRawEnable(name: String, value: Boolean)
|
||||
// private external fun sendVp9(data: ByteArray)
|
||||
|
||||
private fun translate(input: String): String {
|
||||
Log.d(logTag, "translate:$LOCAL_NAME")
|
||||
return translateLocale(LOCAL_NAME, input)
|
||||
return FFI.translateLocale(LOCAL_NAME, input)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -211,7 +195,7 @@ class MainService : Service() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(logTag,"MainService onCreate")
|
||||
init(this)
|
||||
FFI.init(this)
|
||||
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
|
||||
start()
|
||||
serviceLooper = looper
|
||||
@@ -223,7 +207,7 @@ class MainService : Service() {
|
||||
// keep the config dir same with flutter
|
||||
val prefs = applicationContext.getSharedPreferences(KEY_SHARED_PREFERENCES, FlutterActivity.MODE_PRIVATE)
|
||||
val configPath = prefs.getString(KEY_APP_DIR_CONFIG_PATH, "") ?: ""
|
||||
startServer(configPath)
|
||||
FFI.startServer(configPath, "")
|
||||
|
||||
createForegroundNotification()
|
||||
}
|
||||
@@ -278,7 +262,7 @@ class MainService : Service() {
|
||||
SCREEN_INFO.dpi = dpi
|
||||
if (isStart) {
|
||||
stopCapture()
|
||||
refreshScreen()
|
||||
FFI.refreshScreen()
|
||||
startCapture()
|
||||
}
|
||||
}
|
||||
@@ -306,7 +290,7 @@ class MainService : Service() {
|
||||
createForegroundNotification()
|
||||
|
||||
if (intent.getBooleanExtra(EXT_INIT_FROM_BOOT, false)) {
|
||||
startService()
|
||||
FFI.startService()
|
||||
}
|
||||
Log.d(logTag, "service starting: ${startId}:${Thread.currentThread()}")
|
||||
val mediaProjectionManager =
|
||||
@@ -354,12 +338,15 @@ class MainService : Service() {
|
||||
).apply {
|
||||
setOnImageAvailableListener({ imageReader: ImageReader ->
|
||||
try {
|
||||
if (!isStart) {
|
||||
return@setOnImageAvailableListener
|
||||
}
|
||||
imageReader.acquireLatestImage().use { image ->
|
||||
if (image == null) return@setOnImageAvailableListener
|
||||
if (image == null || !isStart) return@setOnImageAvailableListener
|
||||
val planes = image.planes
|
||||
val buffer = planes[0].buffer
|
||||
buffer.rewind()
|
||||
onVideoFrameUpdate(buffer)
|
||||
FFI.onVideoFrameUpdate(buffer)
|
||||
}
|
||||
} catch (ignored: java.lang.Exception) {
|
||||
}
|
||||
@@ -393,21 +380,24 @@ class MainService : Service() {
|
||||
}
|
||||
checkMediaPermission()
|
||||
_isStart = true
|
||||
setFrameRawEnable("video",true)
|
||||
setFrameRawEnable("audio",true)
|
||||
FFI.setFrameRawEnable("video",true)
|
||||
FFI.setFrameRawEnable("audio",true)
|
||||
return true
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun stopCapture() {
|
||||
Log.d(logTag, "Stop Capture")
|
||||
setFrameRawEnable("video",false)
|
||||
setFrameRawEnable("audio",false)
|
||||
FFI.setFrameRawEnable("video",false)
|
||||
FFI.setFrameRawEnable("audio",false)
|
||||
_isStart = false
|
||||
// release video
|
||||
virtualDisplay?.release()
|
||||
surface?.release()
|
||||
imageReader?.close()
|
||||
imageReader = null
|
||||
// suface needs to be release after imageReader.close to imageReader access released surface
|
||||
// https://github.com/rustdesk/rustdesk/issues/4118#issuecomment-1515666629
|
||||
surface?.release()
|
||||
videoEncoder?.let {
|
||||
it.signalEndOfInputStream()
|
||||
it.stop()
|
||||
@@ -418,9 +408,6 @@ class MainService : Service() {
|
||||
|
||||
// release audio
|
||||
audioRecordStat = false
|
||||
audioRecorder?.release()
|
||||
audioRecorder = null
|
||||
minBufferSize = 0
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
@@ -428,8 +415,6 @@ class MainService : Service() {
|
||||
_isReady = false
|
||||
|
||||
stopCapture()
|
||||
imageReader?.close()
|
||||
imageReader = null
|
||||
|
||||
mediaProjection = null
|
||||
checkMediaPermission()
|
||||
@@ -537,9 +522,13 @@ class MainService : Service() {
|
||||
thread {
|
||||
while (audioRecordStat) {
|
||||
audioReader!!.readSync(audioRecorder!!)?.let {
|
||||
onAudioFrameUpdate(it)
|
||||
FFI.onAudioFrameUpdate(it)
|
||||
}
|
||||
}
|
||||
// let's release here rather than onDestroy to avoid threading issue
|
||||
audioRecorder?.release()
|
||||
audioRecorder = null
|
||||
minBufferSize = 0
|
||||
Log.d(logTag, "Exit audio thread")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Widget loadPowered(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse('https://rustdesk.com'));
|
||||
},
|
||||
child: Opacity(
|
||||
opacity: 0.5,
|
||||
child: Text(
|
||||
translate("powered_by_me"),
|
||||
overflow: TextOverflow.clip,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(fontSize: 9, decoration: TextDecoration.underline),
|
||||
)),
|
||||
),
|
||||
).marginOnly(top: 6);
|
||||
}
|
||||
|
||||
// max 300 x 60
|
||||
Widget loadLogo() {
|
||||
return FutureBuilder<ByteData>(
|
||||
@@ -3155,3 +3176,41 @@ bool isInHomePage() {
|
||||
final controller = Get.find<DesktopTabController>();
|
||||
return controller.state.value.selected == 0;
|
||||
}
|
||||
|
||||
Widget buildPresetPasswordWarning() {
|
||||
return FutureBuilder<bool>(
|
||||
future: bind.isPresetPassword(),
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return CircularProgressIndicator(); // Show a loading spinner while waiting for the Future to complete
|
||||
} else if (snapshot.hasError) {
|
||||
return Text(
|
||||
'Error: ${snapshot.error}'); // Show an error message if the Future completed with an error
|
||||
} else if (snapshot.hasData && snapshot.data == true) {
|
||||
return Container(
|
||||
color: Colors.yellow,
|
||||
child: Column(
|
||||
children: [
|
||||
Align(
|
||||
child: Text(
|
||||
translate("Security Alert"),
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)).paddingOnly(bottom: 8),
|
||||
Text(
|
||||
translate("preset_password_warning"),
|
||||
style: TextStyle(color: Colors.red),
|
||||
)
|
||||
],
|
||||
).paddingAll(8),
|
||||
); // Show a warning message if the Future completed with true
|
||||
} else {
|
||||
return SizedBox
|
||||
.shrink(); // Show nothing if the Future completed with false or null
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,11 +137,13 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
Widget _createSwitchBar(BuildContext context) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
|
||||
return ListView(
|
||||
var counter = -1;
|
||||
return ReorderableListView(
|
||||
buildDefaultDragHandles: false,
|
||||
onReorder: model.reorder,
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: model.visibleIndexs.map((t) {
|
||||
children: model.visibleEnabledOrderedIndexs.map((t) {
|
||||
final selected = model.currentTab == t;
|
||||
final color = selected
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
@@ -155,43 +157,47 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
border: Border(
|
||||
bottom: BorderSide(width: 2, color: color!),
|
||||
));
|
||||
return Obx(() => Tooltip(
|
||||
preferBelow: false,
|
||||
message: model.tabTooltip(t),
|
||||
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
decoration: (hover.value
|
||||
? (selected ? decoBorder : deco)
|
||||
: (selected ? decoBorder : null)),
|
||||
child: Icon(model.tabIcon(t), color: color)
|
||||
.paddingSymmetric(horizontal: 4),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
onTap: () async {
|
||||
await handleTabSelection(t);
|
||||
await bind.setLocalFlutterOption(
|
||||
k: 'peer-tab-index', v: t.toString());
|
||||
},
|
||||
onHover: (value) => hover.value = value,
|
||||
),
|
||||
));
|
||||
counter += 1;
|
||||
return ReorderableDragStartListener(
|
||||
key: ValueKey(t),
|
||||
index: counter,
|
||||
child: Obx(() => Tooltip(
|
||||
preferBelow: false,
|
||||
message: model.tabTooltip(t),
|
||||
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
decoration: (hover.value
|
||||
? (selected ? decoBorder : deco)
|
||||
: (selected ? decoBorder : null)),
|
||||
child: Icon(model.tabIcon(t), color: color)
|
||||
.paddingSymmetric(horizontal: 4),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
onTap: () async {
|
||||
await handleTabSelection(t);
|
||||
await bind.setLocalFlutterOption(
|
||||
k: PeerTabModel.kPeerTabIndex, v: t.toString());
|
||||
},
|
||||
onHover: (value) => hover.value = value,
|
||||
),
|
||||
)));
|
||||
}).toList());
|
||||
}
|
||||
|
||||
Widget _createPeersView() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
Widget child;
|
||||
if (model.visibleIndexs.isEmpty) {
|
||||
if (model.visibleEnabledOrderedIndexs.isEmpty) {
|
||||
child = visibleContextMenuListener(Row(
|
||||
children: [Expanded(child: InkWell())],
|
||||
));
|
||||
} else {
|
||||
if (model.visibleIndexs.contains(model.currentTab)) {
|
||||
if (model.visibleEnabledOrderedIndexs.contains(model.currentTab)) {
|
||||
child = entries[model.currentTab].widget;
|
||||
} else {
|
||||
debugPrint("should not happen! currentTab not in visibleIndexs");
|
||||
Future.delayed(Duration.zero, () {
|
||||
model.setCurrentTab(model.indexs[0]);
|
||||
model.setCurrentTab(model.visibleEnabledOrderedIndexs[0]);
|
||||
});
|
||||
child = entries[0].widget;
|
||||
}
|
||||
@@ -255,16 +261,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
void mobileShowTabVisibilityMenu() {
|
||||
final model = gFFI.peerTabModel;
|
||||
final items = List<PopupMenuItem>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
for (int i = 0; i < PeerTabModel.maxTabCount; i++) {
|
||||
if (!model.isEnabled[i]) continue;
|
||||
items.add(PopupMenuItem(
|
||||
height: kMinInteractiveDimension * 0.8,
|
||||
onTap: () => model.setTabVisible(i, !model.isVisible[i]),
|
||||
onTap: () => model.setTabVisible(i, !model.isVisibleEnabled[i]),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: model.isVisible[i],
|
||||
value: model.isVisibleEnabled[i],
|
||||
onChanged: (_) {
|
||||
model.setTabVisible(i, !model.isVisible[i]);
|
||||
model.setTabVisible(i, !model.isVisibleEnabled[i]);
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
@@ -314,16 +321,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final menu = List<MenuEntrySwitch>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
menu.add(MenuEntrySwitch(
|
||||
final menu = List<MenuEntrySwitchSync>.empty(growable: true);
|
||||
for (int i = 0; i < model.orders.length; i++) {
|
||||
int tabIndex = model.orders[i];
|
||||
if (tabIndex < 0 || tabIndex >= PeerTabModel.maxTabCount) continue;
|
||||
if (!model.isEnabled[tabIndex]) continue;
|
||||
menu.add(MenuEntrySwitchSync(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: model.tabTooltip(i),
|
||||
getter: () async {
|
||||
return model.isVisible[i];
|
||||
},
|
||||
text: model.tabTooltip(tabIndex),
|
||||
currentValue: model.isVisibleEnabled[tabIndex],
|
||||
setter: (show) async {
|
||||
model.setTabVisible(i, show);
|
||||
model.setTabVisible(tabIndex, show);
|
||||
cancelFunc();
|
||||
}));
|
||||
}
|
||||
@@ -434,7 +442,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
model.setMultiSelectionMode(false);
|
||||
showToast(translate('Successful'));
|
||||
},
|
||||
child: Icon(model.icons[PeerTabIndex.fav.index]),
|
||||
child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]),
|
||||
).marginOnly(left: isMobile ? 11 : 6),
|
||||
);
|
||||
}
|
||||
@@ -455,7 +463,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
addPeersToAbDialog(peers);
|
||||
model.setMultiSelectionMode(false);
|
||||
},
|
||||
child: Icon(model.icons[PeerTabIndex.ab.index]),
|
||||
child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]),
|
||||
).marginOnly(left: isMobile ? 11 : 6),
|
||||
);
|
||||
}
|
||||
@@ -563,7 +571,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
|
||||
final leftActionsSize =
|
||||
(leftIconSize + (4 + 4) * 2) * model.visibleIndexs.length;
|
||||
(leftIconSize + (4 + 4) * 2) * model.visibleEnabledOrderedIndexs.length;
|
||||
final availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2;
|
||||
final searchWidth = 120;
|
||||
final otherActionWidth = 18 + 10;
|
||||
|
||||
@@ -441,10 +441,17 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
child: Text(translate('Mute'))));
|
||||
}
|
||||
// file copy and paste
|
||||
// If the version is less than 1.2.4, file copy and paste is supported on Windows only.
|
||||
final isSupportIfPeer_1_2_3 = versionCmp(pi.version, '1.2.4') < 0 &&
|
||||
isWindows &&
|
||||
pi.platform == kPeerPlatformWindows;
|
||||
// If the version is 1.2.4 or later, file copy and paste is supported when kPlatformAdditionsHasFileClipboard is set.
|
||||
final isSupportIfPeer_1_2_4 = versionCmp(pi.version, '1.2.4') >= 0 &&
|
||||
bind.mainHasFileClipboard() &&
|
||||
pi.platformAdditions.containsKey(kPlatformAdditionsHasFileClipboard);
|
||||
if (ffiModel.keyboard &&
|
||||
perms['file'] != false &&
|
||||
bind.mainHasFileClipboard() &&
|
||||
pi.platformAdditions.containsKey(kPlatformAdditionsHasFileClipboard)) {
|
||||
(isSupportIfPeer_1_2_3 || isSupportIfPeer_1_2_4)) {
|
||||
final enabled = !ffiModel.viewOnly;
|
||||
final option = 'enable-file-transfer';
|
||||
final value =
|
||||
|
||||
@@ -19,7 +19,9 @@ const kKeyTranslateMode = 'translate';
|
||||
const String kPlatformAdditionsIsWayland = "is_wayland";
|
||||
const String kPlatformAdditionsHeadless = "headless";
|
||||
const String kPlatformAdditionsIsInstalled = "is_installed";
|
||||
const String kPlatformAdditionsVirtualDisplays = "virtual_displays";
|
||||
const String kPlatformAdditionsIddImpl = "idd_impl";
|
||||
const String kPlatformAdditionsRustDeskVirtualDisplays = "rustdesk_virtual_displays";
|
||||
const String kPlatformAdditionsAmyuniVirtualDisplays = "amyuni_virtual_displays";
|
||||
const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
|
||||
const String kPlatformAdditionsSupportedPrivacyModeImpl =
|
||||
"supported_privacy_mode_impl";
|
||||
@@ -121,12 +123,11 @@ double kNewWindowOffset = isWindows
|
||||
? 30.0
|
||||
: 50.0;
|
||||
|
||||
EdgeInsets get kDragToResizeAreaPadding =>
|
||||
!kUseCompatibleUiMode && isLinux
|
||||
? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.all(5.0)
|
||||
: EdgeInsets.zero;
|
||||
EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && isLinux
|
||||
? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.all(5.0)
|
||||
: EdgeInsets.zero;
|
||||
// https://en.wikipedia.org/wiki/Non-breaking_space
|
||||
const int $nbsp = 0x00A0;
|
||||
|
||||
|
||||
@@ -69,44 +69,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildPresetPasswordWarning() {
|
||||
return FutureBuilder<bool>(
|
||||
future: bind.isPresetPassword(),
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return CircularProgressIndicator(); // Show a loading spinner while waiting for the Future to complete
|
||||
} else if (snapshot.hasError) {
|
||||
return Text(
|
||||
'Error: ${snapshot.error}'); // Show an error message if the Future completed with an error
|
||||
} else if (snapshot.hasData && snapshot.data == true) {
|
||||
return Container(
|
||||
color: Colors.yellow,
|
||||
child: Column(
|
||||
children: [
|
||||
Align(
|
||||
child: Text(
|
||||
translate("Security Alert"),
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)).paddingOnly(bottom: 8),
|
||||
Text(
|
||||
translate("preset_password_warning"),
|
||||
style: TextStyle(color: Colors.red),
|
||||
)
|
||||
],
|
||||
).paddingAll(8),
|
||||
); // Show a warning message if the Future completed with true
|
||||
} else {
|
||||
return SizedBox
|
||||
.shrink(); // Show nothing if the Future completed with false or null
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLeftPane(BuildContext context) {
|
||||
final isIncomingOnly = bind.isIncomingOnly();
|
||||
final isOutgoingOnly = bind.isOutgoingOnly();
|
||||
@@ -115,22 +77,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
if (bind.isCustomClient())
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse('https://rustdesk.com'));
|
||||
},
|
||||
child: Opacity(
|
||||
opacity: 0.5,
|
||||
child: Text(
|
||||
translate("powered_by_me"),
|
||||
overflow: TextOverflow.clip,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontSize: 9, decoration: TextDecoration.underline),
|
||||
)),
|
||||
),
|
||||
).marginOnly(top: 6),
|
||||
child: loadPowered(context),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
@@ -298,19 +245,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
RxBool hover = false.obs;
|
||||
return InkWell(
|
||||
onTap: DesktopTabPage.onAddSetting,
|
||||
child: Obx(
|
||||
() => CircleAvatar(
|
||||
radius: 15,
|
||||
backgroundColor: hover.value
|
||||
? Theme.of(context).scaffoldBackgroundColor
|
||||
: Theme.of(context).colorScheme.background,
|
||||
child: Tooltip(
|
||||
message: translate('Settings'),
|
||||
child: Icon(
|
||||
Icons.more_vert_outlined,
|
||||
size: 20,
|
||||
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
||||
)),
|
||||
child: Tooltip(
|
||||
message: translate('Settings'),
|
||||
child: Obx(
|
||||
() => CircleAvatar(
|
||||
radius: 15,
|
||||
backgroundColor: hover.value
|
||||
? Theme.of(context).scaffoldBackgroundColor
|
||||
: Theme.of(context).colorScheme.background,
|
||||
child: Icon(
|
||||
Icons.more_vert_outlined,
|
||||
size: 20,
|
||||
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
onHover: (value) => hover.value = value,
|
||||
@@ -371,31 +319,33 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
),
|
||||
AnimatedRotationWidget(
|
||||
onPressed: () => bind.mainUpdateTemporaryPassword(),
|
||||
child: Obx(() => RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: Tooltip(
|
||||
message: translate('Refresh Password'),
|
||||
child: Icon(
|
||||
Icons.refresh,
|
||||
color: refreshHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
)))),
|
||||
child: Tooltip(
|
||||
message: translate('Refresh Password'),
|
||||
child: Obx(() => RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: Icon(
|
||||
Icons.refresh,
|
||||
color: refreshHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
))),
|
||||
),
|
||||
onHover: (value) => refreshHover.value = value,
|
||||
).marginOnly(right: 8, top: 4),
|
||||
if (!bind.isDisableSettings())
|
||||
InkWell(
|
||||
child: Obx(
|
||||
() => Tooltip(
|
||||
message: translate('Change Password'),
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
color: editHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
)).marginOnly(right: 8, top: 4),
|
||||
child: Tooltip(
|
||||
message: translate('Change Password'),
|
||||
child: Obx(
|
||||
() => Icon(
|
||||
Icons.edit,
|
||||
color: editHover.value
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD),
|
||||
size: 22,
|
||||
).marginOnly(right: 8, top: 4),
|
||||
),
|
||||
),
|
||||
onTap: () => DesktopSettingPage.switch2page(0),
|
||||
onHover: (value) => editHover.value = value,
|
||||
|
||||
@@ -400,11 +400,20 @@ class _GeneralState extends State<_General> {
|
||||
|
||||
Widget hwcodec() {
|
||||
final hwcodec = bind.mainHasHwcodec();
|
||||
final gpucodec = bind.mainHasGpucodec();
|
||||
final vram = bind.mainHasVram();
|
||||
return Offstage(
|
||||
offstage: !(hwcodec || gpucodec),
|
||||
offstage: !(hwcodec || vram),
|
||||
child: _Card(title: 'Hardware Codec', children: [
|
||||
_OptionCheckBox(context, 'Enable hardware codec', 'enable-hwcodec')
|
||||
_OptionCheckBox(
|
||||
context,
|
||||
'Enable hardware codec',
|
||||
'enable-hwcodec',
|
||||
update: () {
|
||||
if (mainGetBoolOptionSync('enable-hwcodec')) {
|
||||
bind.mainCheckHwcodec();
|
||||
}
|
||||
},
|
||||
)
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -835,6 +844,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
...directIp(context),
|
||||
whitelist(),
|
||||
...autoDisconnect(context),
|
||||
if (bind.mainIsInstalled())
|
||||
_OptionCheckBox(context, 'allow-only-conn-window-open-tip',
|
||||
'allow-only-conn-window-open',
|
||||
reverse: false, enabled: enabled),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -568,6 +568,47 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Compatible with MenuEntrySwitch, it uses value instead of getter
|
||||
class MenuEntrySwitchSync<T> extends MenuEntrySwitchBase<T> {
|
||||
final SwitchSetter setter;
|
||||
final RxBool _curOption = false.obs;
|
||||
|
||||
MenuEntrySwitchSync({
|
||||
required SwitchType switchType,
|
||||
required String text,
|
||||
required bool currentValue,
|
||||
required this.setter,
|
||||
Rx<TextStyle>? textStyle,
|
||||
EdgeInsets? padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
textStyle: textStyle,
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
) {
|
||||
_curOption.value = currentValue;
|
||||
}
|
||||
|
||||
@override
|
||||
RxBool get curOption => _curOption;
|
||||
@override
|
||||
setOption(bool? option) async {
|
||||
if (option != null) {
|
||||
await setter(option);
|
||||
// Notice: no ensure with getter, best used on menus that are destroyed on click
|
||||
if (_curOption.value != option) {
|
||||
_curOption.value = option;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef Switch2Getter = RxBool Function();
|
||||
typedef Switch2Setter = Future<void> Function(bool);
|
||||
|
||||
|
||||
@@ -636,7 +636,7 @@ class _MonitorMenu extends StatelessWidget {
|
||||
menuStyle: MenuStyle(
|
||||
padding:
|
||||
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
|
||||
menuChildren: [buildMonitorSubmenuWidget()]);
|
||||
menuChildrenGetter: () => [buildMonitorSubmenuWidget()]);
|
||||
}
|
||||
|
||||
Widget buildMultiMonitorMenu() {
|
||||
@@ -843,17 +843,17 @@ class _ControlMenu extends StatelessWidget {
|
||||
color: _ToolbarTheme.blueColor,
|
||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||
ffi: ffi,
|
||||
menuChildren: toolbarControls(context, id, ffi).map((e) {
|
||||
if (e.divider) {
|
||||
return Divider();
|
||||
} else {
|
||||
return MenuButton(
|
||||
child: e.child,
|
||||
onPressed: e.onPressed,
|
||||
ffi: ffi,
|
||||
trailingIcon: e.trailingIcon);
|
||||
}
|
||||
}).toList());
|
||||
menuChildrenGetter: () => toolbarControls(context, id, ffi).map((e) {
|
||||
if (e.divider) {
|
||||
return Divider();
|
||||
} else {
|
||||
return MenuButton(
|
||||
child: e.child,
|
||||
onPressed: e.onPressed,
|
||||
ffi: ffi,
|
||||
trailingIcon: e.trailingIcon);
|
||||
}
|
||||
}).toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1046,53 +1046,61 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
Widget build(BuildContext context) {
|
||||
_screenAdjustor.updateScreen();
|
||||
|
||||
final menuChildren = <Widget>[
|
||||
_screenAdjustor.adjustWindow(context),
|
||||
viewStyle(),
|
||||
scrollStyle(),
|
||||
imageQuality(),
|
||||
codec(),
|
||||
_ResolutionsMenu(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
screenAdjustor: _screenAdjustor,
|
||||
),
|
||||
// We may add this feature if it is needed and we have an EV certificate.
|
||||
// _VirtualDisplayMenu(
|
||||
// id: widget.id,
|
||||
// ffi: widget.ffi,
|
||||
// ),
|
||||
Divider(),
|
||||
toggles(),
|
||||
];
|
||||
// privacy mode
|
||||
if (ffiModel.keyboard && pi.features.privacyMode) {
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
final privacyModeList =
|
||||
toolbarPrivacyMode(privacyModeState, context, id, ffi);
|
||||
if (privacyModeList.length == 1) {
|
||||
menuChildren.add(CkbMenuButton(
|
||||
value: privacyModeList[0].value,
|
||||
onChanged: privacyModeList[0].onChanged,
|
||||
child: privacyModeList[0].child,
|
||||
ffi: ffi));
|
||||
} else if (privacyModeList.length > 1) {
|
||||
menuChildren.addAll([
|
||||
Divider(),
|
||||
_SubmenuButton(
|
||||
ffi: widget.ffi,
|
||||
child: Text(translate('Privacy mode')),
|
||||
menuChildren: privacyModeList
|
||||
.map((e) => CkbMenuButton(
|
||||
value: e.value,
|
||||
onChanged: e.onChanged,
|
||||
child: e.child,
|
||||
ffi: ffi))
|
||||
.toList()),
|
||||
]);
|
||||
menuChildrenGetter() {
|
||||
final menuChildren = <Widget>[
|
||||
_screenAdjustor.adjustWindow(context),
|
||||
viewStyle(),
|
||||
scrollStyle(),
|
||||
imageQuality(),
|
||||
codec(),
|
||||
_ResolutionsMenu(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
screenAdjustor: _screenAdjustor,
|
||||
),
|
||||
if (pi.isRustDeskIdd)
|
||||
_RustDeskVirtualDisplayMenu(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
if (pi.isAmyuniIdd)
|
||||
_AmyuniVirtualDisplayMenu(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
Divider(),
|
||||
toggles(),
|
||||
];
|
||||
// privacy mode
|
||||
if (ffiModel.keyboard && pi.features.privacyMode) {
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
final privacyModeList =
|
||||
toolbarPrivacyMode(privacyModeState, context, id, ffi);
|
||||
if (privacyModeList.length == 1) {
|
||||
menuChildren.add(CkbMenuButton(
|
||||
value: privacyModeList[0].value,
|
||||
onChanged: privacyModeList[0].onChanged,
|
||||
child: privacyModeList[0].child,
|
||||
ffi: ffi));
|
||||
} else if (privacyModeList.length > 1) {
|
||||
menuChildren.addAll([
|
||||
Divider(),
|
||||
_SubmenuButton(
|
||||
ffi: widget.ffi,
|
||||
child: Text(translate('Privacy mode')),
|
||||
menuChildren: privacyModeList
|
||||
.map((e) => CkbMenuButton(
|
||||
value: e.value,
|
||||
onChanged: e.onChanged,
|
||||
child: e.child,
|
||||
ffi: ffi))
|
||||
.toList()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
menuChildren.add(widget.pluginItem);
|
||||
return menuChildren;
|
||||
}
|
||||
menuChildren.add(widget.pluginItem);
|
||||
|
||||
return _IconSubmenuButton(
|
||||
tooltip: 'Display Settings',
|
||||
@@ -1100,7 +1108,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
ffi: widget.ffi,
|
||||
color: _ToolbarTheme.blueColor,
|
||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||
menuChildren: menuChildren,
|
||||
menuChildrenGetter: menuChildrenGetter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1537,21 +1545,23 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
||||
}
|
||||
}
|
||||
|
||||
class _VirtualDisplayMenu extends StatefulWidget {
|
||||
class _RustDeskVirtualDisplayMenu extends StatefulWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
|
||||
_VirtualDisplayMenu({
|
||||
_RustDeskVirtualDisplayMenu({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_VirtualDisplayMenu> createState() => _VirtualDisplayMenuState();
|
||||
State<_RustDeskVirtualDisplayMenu> createState() =>
|
||||
_RustDeskVirtualDisplayMenuState();
|
||||
}
|
||||
|
||||
class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
|
||||
class _RustDeskVirtualDisplayMenuState
|
||||
extends State<_RustDeskVirtualDisplayMenu> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -1566,7 +1576,7 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
|
||||
return Offstage();
|
||||
}
|
||||
|
||||
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
|
||||
final virtualDisplays = widget.ffi.ffiModel.pi.RustDeskVirtualDisplays;
|
||||
final privacyModeState = PrivacyModeState.find(widget.id);
|
||||
|
||||
final children = <Widget>[];
|
||||
@@ -1608,6 +1618,82 @@ class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
|
||||
}
|
||||
}
|
||||
|
||||
class _AmyuniVirtualDisplayMenu extends StatefulWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
|
||||
_AmyuniVirtualDisplayMenu({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_AmyuniVirtualDisplayMenu> createState() =>
|
||||
_AmiyuniVirtualDisplayMenuState();
|
||||
}
|
||||
|
||||
class _AmiyuniVirtualDisplayMenuState extends State<_AmyuniVirtualDisplayMenu> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) {
|
||||
return Offstage();
|
||||
}
|
||||
if (!widget.ffi.ffiModel.pi.isInstalled) {
|
||||
return Offstage();
|
||||
}
|
||||
|
||||
final count = widget.ffi.ffiModel.pi.amyuniVirtualDisplayCount;
|
||||
final privacyModeState = PrivacyModeState.find(widget.id);
|
||||
|
||||
final children = <Widget>[
|
||||
Obx(() => Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: privacyModeState.isNotEmpty || count == 0
|
||||
? null
|
||||
: () => bind.sessionToggleVirtualDisplay(
|
||||
sessionId: widget.ffi.sessionId, index: 0, on: false),
|
||||
child: Icon(Icons.remove),
|
||||
),
|
||||
Text(count.toString()),
|
||||
TextButton(
|
||||
onPressed: privacyModeState.isNotEmpty || count == 4
|
||||
? null
|
||||
: () => bind.sessionToggleVirtualDisplay(
|
||||
sessionId: widget.ffi.sessionId, index: 0, on: true),
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
],
|
||||
)),
|
||||
Divider(),
|
||||
Obx(() => MenuButton(
|
||||
onPressed: privacyModeState.isNotEmpty || count == 0
|
||||
? null
|
||||
: () {
|
||||
bind.sessionToggleVirtualDisplay(
|
||||
sessionId: widget.ffi.sessionId,
|
||||
index: kAllVirtualDisplay,
|
||||
on: false);
|
||||
},
|
||||
ffi: widget.ffi,
|
||||
child: Text(translate('Plug out all')),
|
||||
)),
|
||||
];
|
||||
|
||||
return _SubmenuButton(
|
||||
ffi: widget.ffi,
|
||||
menuChildren: children,
|
||||
child: Text(translate("Virtual display")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _KeyboardMenu extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
@@ -1623,19 +1709,7 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
var ffiModel = Provider.of<FfiModel>(context);
|
||||
if (!ffiModel.keyboard) return Offstage();
|
||||
// If use flutter to grab keys, we can only use one mode.
|
||||
// Map mode and Legacy mode, at least one of them is supported.
|
||||
String? modeOnly;
|
||||
if (isInputSourceFlutter) {
|
||||
if (bind.sessionIsKeyboardModeSupported(
|
||||
sessionId: ffi.sessionId, mode: kKeyMapMode)) {
|
||||
modeOnly = kKeyMapMode;
|
||||
} else if (bind.sessionIsKeyboardModeSupported(
|
||||
sessionId: ffi.sessionId, mode: kKeyLegacyMode)) {
|
||||
modeOnly = kKeyLegacyMode;
|
||||
}
|
||||
}
|
||||
final toolbarToggles = toolbarKeyboardToggles(ffi)
|
||||
toolbarToggles() => toolbarKeyboardToggles(ffi)
|
||||
.map((e) => CkbMenuButton(
|
||||
value: e.value, onChanged: e.onChanged, child: e.child, ffi: ffi))
|
||||
.toList();
|
||||
@@ -1645,18 +1719,18 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
ffi: ffi,
|
||||
color: _ToolbarTheme.blueColor,
|
||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||
menuChildren: [
|
||||
keyboardMode(modeOnly),
|
||||
localKeyboardType(),
|
||||
inputSource(),
|
||||
Divider(),
|
||||
viewMode(),
|
||||
Divider(),
|
||||
...toolbarToggles,
|
||||
]);
|
||||
menuChildrenGetter: () => [
|
||||
keyboardMode(),
|
||||
localKeyboardType(),
|
||||
inputSource(),
|
||||
Divider(),
|
||||
viewMode(),
|
||||
Divider(),
|
||||
...toolbarToggles(),
|
||||
]);
|
||||
}
|
||||
|
||||
keyboardMode(String? modeOnly) {
|
||||
keyboardMode() {
|
||||
return futureBuilder(future: () async {
|
||||
return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ??
|
||||
kKeyLegacyMode;
|
||||
@@ -1676,6 +1750,19 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
await ffi.inputModel.updateKeyboardMode();
|
||||
}
|
||||
|
||||
// If use flutter to grab keys, we can only use one mode.
|
||||
// Map mode and Legacy mode, at least one of them is supported.
|
||||
String? modeOnly;
|
||||
if (isInputSourceFlutter) {
|
||||
if (bind.sessionIsKeyboardModeSupported(
|
||||
sessionId: ffi.sessionId, mode: kKeyMapMode)) {
|
||||
modeOnly = kKeyMapMode;
|
||||
} else if (bind.sessionIsKeyboardModeSupported(
|
||||
sessionId: ffi.sessionId, mode: kKeyLegacyMode)) {
|
||||
modeOnly = kKeyLegacyMode;
|
||||
}
|
||||
}
|
||||
|
||||
for (InputModeMenu mode in modes) {
|
||||
if (modeOnly != null && mode.key != modeOnly) {
|
||||
continue;
|
||||
@@ -1804,7 +1891,7 @@ class _ChatMenuState extends State<_ChatMenu> {
|
||||
ffi: widget.ffi,
|
||||
color: _ToolbarTheme.blueColor,
|
||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||
menuChildren: [textChat(), voiceCall()]);
|
||||
menuChildrenGetter: () => [textChat(), voiceCall()]);
|
||||
}
|
||||
|
||||
textChat() {
|
||||
@@ -2008,7 +2095,7 @@ class _IconSubmenuButton extends StatefulWidget {
|
||||
final Widget? icon;
|
||||
final Color color;
|
||||
final Color hoverColor;
|
||||
final List<Widget> menuChildren;
|
||||
final List<Widget> Function() menuChildrenGetter;
|
||||
final MenuStyle? menuStyle;
|
||||
final FFI ffi;
|
||||
final double? width;
|
||||
@@ -2020,7 +2107,7 @@ class _IconSubmenuButton extends StatefulWidget {
|
||||
required this.tooltip,
|
||||
required this.color,
|
||||
required this.hoverColor,
|
||||
required this.menuChildren,
|
||||
required this.menuChildrenGetter,
|
||||
required this.ffi,
|
||||
this.menuStyle,
|
||||
this.width,
|
||||
@@ -2064,7 +2151,8 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
|
||||
color: hover ? widget.hoverColor : widget.color,
|
||||
),
|
||||
child: icon))),
|
||||
menuChildren: widget.menuChildren
|
||||
menuChildren: widget
|
||||
.menuChildrenGetter()
|
||||
.map((e) => _buildPointerTrackWidget(e, widget.ffi))
|
||||
.toList()));
|
||||
return MenuBar(children: [
|
||||
|
||||
@@ -865,6 +865,7 @@ class _ListView extends StatelessWidget {
|
||||
label: labelGetter == null
|
||||
? Rx<String>(tab.label)
|
||||
: labelGetter!(tab.label),
|
||||
tabType: controller.tabType,
|
||||
selectedIcon: tab.selectedIcon,
|
||||
unselectedIcon: tab.unselectedIcon,
|
||||
closable: tab.closable,
|
||||
@@ -896,6 +897,7 @@ class _Tab extends StatefulWidget {
|
||||
final int index;
|
||||
final String tabInfoKey;
|
||||
final Rx<String> label;
|
||||
final DesktopTabType tabType;
|
||||
final IconData? selectedIcon;
|
||||
final IconData? unselectedIcon;
|
||||
final bool closable;
|
||||
@@ -914,6 +916,7 @@ class _Tab extends StatefulWidget {
|
||||
required this.index,
|
||||
required this.tabInfoKey,
|
||||
required this.label,
|
||||
required this.tabType,
|
||||
this.selectedIcon,
|
||||
this.unselectedIcon,
|
||||
this.tabBuilder,
|
||||
@@ -953,7 +956,9 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
|
||||
child: Tooltip(
|
||||
message: translate(widget.label.value),
|
||||
message: widget.tabType == DesktopTabType.main
|
||||
? ''
|
||||
: translate(widget.label.value),
|
||||
child: Text(
|
||||
translate(widget.label.value),
|
||||
textAlign: TextAlign.center,
|
||||
|
||||
@@ -84,7 +84,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
_buildUpdateUI(),
|
||||
if (!bind.isCustomClient()) _buildUpdateUI(),
|
||||
_buildRemoteIDTextField(),
|
||||
])),
|
||||
SliverFillRemaining(
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_hbb/mobile/pages/settings_page.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../common.dart';
|
||||
import '../../common/widgets/chat_page.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import 'connection_page.dart';
|
||||
|
||||
abstract class PageShape extends Widget {
|
||||
@@ -25,8 +26,9 @@ class HomePageState extends State<HomePage> {
|
||||
var _selectedIndex = 0;
|
||||
int get selectedIndex => _selectedIndex;
|
||||
final List<PageShape> _pages = [];
|
||||
int _chatPageTabIndex = -1;
|
||||
bool get isChatPageCurrentTab => isAndroid
|
||||
? _selectedIndex == 1
|
||||
? _selectedIndex == _chatPageTabIndex
|
||||
: false; // change this when ios have chat page
|
||||
|
||||
void refreshPages() {
|
||||
@@ -43,8 +45,9 @@ class HomePageState extends State<HomePage> {
|
||||
|
||||
void initPages() {
|
||||
_pages.clear();
|
||||
_pages.add(ConnectionPage());
|
||||
if (isAndroid) {
|
||||
if (!bind.isIncomingOnly()) _pages.add(ConnectionPage());
|
||||
if (isAndroid && !bind.isOutgoingOnly()) {
|
||||
_chatPageTabIndex = _pages.length;
|
||||
_pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]);
|
||||
}
|
||||
_pages.add(SettingsPage());
|
||||
@@ -141,7 +144,7 @@ class HomePageState extends State<HomePage> {
|
||||
],
|
||||
);
|
||||
}
|
||||
return Text("RustDesk");
|
||||
return Text(bind.mainGetAppNameSync());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +157,7 @@ class WebHomePage extends StatelessWidget {
|
||||
// backgroundColor: MyTheme.grayBg,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text("RustDesk${isWeb ? " (Beta) " : ""}"),
|
||||
title: Text(bind.mainGetAppNameSync()),
|
||||
actions: connectionPage.appBarActions,
|
||||
),
|
||||
body: connectionPage,
|
||||
|
||||
@@ -68,7 +68,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
gFFI.dialogManager
|
||||
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
||||
});
|
||||
WakelockPlus.enable();
|
||||
if (!isWeb) {
|
||||
WakelockPlus.enable();
|
||||
}
|
||||
_physicalFocusNode.requestFocus();
|
||||
gFFI.inputModel.listenToMouse(true);
|
||||
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
||||
@@ -95,7 +97,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
gFFI.dialogManager.dismissAll();
|
||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values);
|
||||
await WakelockPlus.disable();
|
||||
if (!isWeb) {
|
||||
await WakelockPlus.disable();
|
||||
}
|
||||
await keyboardSubscription.cancel();
|
||||
removeSharedStates(widget.id);
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ class _ServerPageState extends State<ServerPage> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
buildPresetPasswordWarning(),
|
||||
gFFI.serverModel.isStart
|
||||
? ServerInfo()
|
||||
: ServiceNotRunningNotification(),
|
||||
@@ -226,7 +227,7 @@ class ScamWarningDialog extends StatefulWidget {
|
||||
}
|
||||
|
||||
class ScamWarningDialogState extends State<ScamWarningDialog> {
|
||||
int _countdown = 12;
|
||||
int _countdown = bind.isCustomClient() ? 0 : 12;
|
||||
bool show_warning = false;
|
||||
late Timer _timer;
|
||||
late ServerModel _serverModel;
|
||||
@@ -383,7 +384,7 @@ class ScamWarningDialogState extends State<ScamWarningDialog> {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.blueAccent,
|
||||
backgroundColor: Colors.blueAccent,
|
||||
),
|
||||
child: Text(
|
||||
translate("Decline"),
|
||||
|
||||
@@ -27,7 +27,7 @@ class SettingsPage extends StatefulWidget implements PageShape {
|
||||
final icon = Icon(Icons.settings);
|
||||
|
||||
@override
|
||||
final appBarActions = [ScanButton()];
|
||||
final appBarActions = bind.isDisableSettings() ? [] : [ScanButton()];
|
||||
|
||||
@override
|
||||
State<SettingsPage> createState() => _SettingsState();
|
||||
@@ -218,6 +218,21 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Provider.of<FfiModel>(context);
|
||||
final outgoingOnly = bind.isOutgoingOnly();
|
||||
final customClientSection = CustomSettingsSection(
|
||||
child: Column(
|
||||
children: [
|
||||
if (bind.isCustomClient())
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: loadPowered(context),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: loadLogo(),
|
||||
)
|
||||
],
|
||||
));
|
||||
final List<AbstractSettingsTile> enhancementsTiles = [];
|
||||
final List<AbstractSettingsTile> shareScreenTiles = [
|
||||
SettingsTile.switchTile(
|
||||
@@ -448,33 +463,37 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
|
||||
}));
|
||||
|
||||
return SettingsList(
|
||||
final disabledSettings = bind.isDisableSettings();
|
||||
final settings = SettingsList(
|
||||
sections: [
|
||||
SettingsSection(
|
||||
title: Text(translate('Account')),
|
||||
tiles: [
|
||||
SettingsTile(
|
||||
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
|
||||
? translate('Login')
|
||||
: '${translate('Logout')} (${gFFI.userModel.userName.value})')),
|
||||
leading: Icon(Icons.person),
|
||||
onPressed: (context) {
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
loginDialog();
|
||||
} else {
|
||||
logOutConfirmDialog();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
customClientSection,
|
||||
if (!bind.isDisableAccount())
|
||||
SettingsSection(
|
||||
title: Text(translate('Account')),
|
||||
tiles: [
|
||||
SettingsTile(
|
||||
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
|
||||
? translate('Login')
|
||||
: '${translate('Logout')} (${gFFI.userModel.userName.value})')),
|
||||
leading: Icon(Icons.person),
|
||||
onPressed: (context) {
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
loginDialog();
|
||||
} else {
|
||||
logOutConfirmDialog();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SettingsSection(title: Text(translate("Settings")), tiles: [
|
||||
SettingsTile(
|
||||
title: Text(translate('ID/Relay Server')),
|
||||
leading: Icon(Icons.cloud),
|
||||
onPressed: (context) {
|
||||
showServerSettings(gFFI.dialogManager);
|
||||
}),
|
||||
if (!disabledSettings)
|
||||
SettingsTile(
|
||||
title: Text(translate('ID/Relay Server')),
|
||||
leading: Icon(Icons.cloud),
|
||||
onPressed: (context) {
|
||||
showServerSettings(gFFI.dialogManager);
|
||||
}),
|
||||
SettingsTile(
|
||||
title: Text(translate('Language')),
|
||||
leading: Icon(Icons.translate),
|
||||
@@ -494,7 +513,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
},
|
||||
)
|
||||
]),
|
||||
if (isAndroid)
|
||||
if (isAndroid && !outgoingOnly)
|
||||
SettingsSection(
|
||||
title: Text(translate("Recording")),
|
||||
tiles: [
|
||||
@@ -523,13 +542,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isAndroid)
|
||||
if (isAndroid && !disabledSettings && !outgoingOnly)
|
||||
SettingsSection(
|
||||
title: Text(translate("Share Screen")),
|
||||
tiles: shareScreenTiles,
|
||||
),
|
||||
defaultDisplaySection(),
|
||||
if (isAndroid)
|
||||
if (!bind.isIncomingOnly()) defaultDisplaySection(),
|
||||
if (isAndroid && !disabledSettings && !outgoingOnly)
|
||||
SettingsSection(
|
||||
title: Text(translate("Enhancements")),
|
||||
tiles: enhancementsTiles,
|
||||
@@ -578,6 +597,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
),
|
||||
],
|
||||
);
|
||||
return settings;
|
||||
}
|
||||
|
||||
Future<bool> canStartOnBoot() async {
|
||||
|
||||
@@ -10,7 +10,7 @@ import './platform_model.dart';
|
||||
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart'
|
||||
if (dart.library.html) 'package:flutter_hbb/web/texture_rgba_renderer.dart';
|
||||
|
||||
// Feature flutter_texture_render need to be enabled if feature gpucodec is enabled.
|
||||
// Feature flutter_texture_render need to be enabled if feature vram is enabled.
|
||||
final useTextureRender = !isWeb &&
|
||||
(bind.mainHasPixelbufferTextureRender() || bind.mainHasGpuTextureRender());
|
||||
|
||||
|
||||
@@ -561,8 +561,12 @@ class FfiModel with ChangeNotifier {
|
||||
showRelayHintDialog(sessionId, type, title, text, dialogManager, peerId);
|
||||
} else if (text == 'Connected, waiting for image...') {
|
||||
showConnectedWaitingForImage(dialogManager, sessionId, type, title, text);
|
||||
} else if (title == 'Privacy mode') {
|
||||
final hasRetry = evt['hasRetry'] == 'true';
|
||||
showPrivacyFailedDialog(
|
||||
sessionId, type, title, text, link, hasRetry, dialogManager);
|
||||
} else {
|
||||
var hasRetry = evt['hasRetry'] == 'true';
|
||||
final hasRetry = evt['hasRetry'] == 'true';
|
||||
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
|
||||
}
|
||||
}
|
||||
@@ -657,6 +661,27 @@ class FfiModel with ChangeNotifier {
|
||||
bind.sessionOnWaitingForImageDialogShow(sessionId: sessionId);
|
||||
}
|
||||
|
||||
void showPrivacyFailedDialog(
|
||||
SessionID sessionId,
|
||||
String type,
|
||||
String title,
|
||||
String text,
|
||||
String link,
|
||||
bool hasRetry,
|
||||
OverlayDialogManager dialogManager) {
|
||||
if (text == 'no_need_privacy_mode_no_physical_displays_tip' ||
|
||||
text == 'Enter privacy mode') {
|
||||
// There are display changes on the remote side,
|
||||
// which will cause some messages to refresh the canvas and dismiss dialogs.
|
||||
// So we add a delay here to ensure the dialog is displayed.
|
||||
Future.delayed(Duration(milliseconds: 3000), () {
|
||||
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
|
||||
});
|
||||
} else {
|
||||
showMsgBox(sessionId, type, title, text, link, hasRetry, dialogManager);
|
||||
}
|
||||
}
|
||||
|
||||
_updateSessionWidthHeight(SessionID sessionId) {
|
||||
if (_rect == null) return;
|
||||
if (_rect!.width <= 0 || _rect!.height <= 0) {
|
||||
@@ -690,6 +715,8 @@ class FfiModel with ChangeNotifier {
|
||||
// Map clone is required here, otherwise "evt" may be changed by other threads through the reference.
|
||||
// Because this function is asynchronous, there's an "await" in this function.
|
||||
cachedPeerData.peerInfo = {...evt};
|
||||
// Do not cache resolutions, because a new display connection have different resolutions.
|
||||
cachedPeerData.peerInfo.remove('resolutions');
|
||||
|
||||
// Recent peer is updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
|
||||
bind.mainLoadRecentPeers();
|
||||
@@ -745,7 +772,9 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
Map<String, dynamic> features = json.decode(evt['features']);
|
||||
_pi.features.privacyMode = features['privacy_mode'] == 1;
|
||||
handleResolutions(peerId, evt["resolutions"]);
|
||||
if (!isCache) {
|
||||
handleResolutions(peerId, evt["resolutions"]);
|
||||
}
|
||||
parent.target?.elevationModel.onPeerInfo(_pi);
|
||||
}
|
||||
if (connType == ConnType.defaultConn) {
|
||||
@@ -986,15 +1015,21 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
if (updateData.isEmpty) {
|
||||
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
|
||||
_pi.platformAdditions.remove(kPlatformAdditionsRustDeskVirtualDisplays);
|
||||
_pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays);
|
||||
} else {
|
||||
try {
|
||||
final updateJson = json.decode(updateData) as Map<String, dynamic>;
|
||||
for (final key in updateJson.keys) {
|
||||
_pi.platformAdditions[key] = updateJson[key];
|
||||
}
|
||||
if (!updateJson.containsKey(kPlatformAdditionsVirtualDisplays)) {
|
||||
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
|
||||
if (!updateJson
|
||||
.containsKey(kPlatformAdditionsRustDeskVirtualDisplays)) {
|
||||
_pi.platformAdditions
|
||||
.remove(kPlatformAdditionsRustDeskVirtualDisplays);
|
||||
}
|
||||
if (!updateJson.containsKey(kPlatformAdditionsAmyuniVirtualDisplays)) {
|
||||
_pi.platformAdditions.remove(kPlatformAdditionsAmyuniVirtualDisplays);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Failed to decode platformAdditions $e');
|
||||
@@ -2286,6 +2321,8 @@ class FFI {
|
||||
}
|
||||
await ffiModel.handleCachedPeerData(data, id);
|
||||
await sessionRefreshVideo(sessionId, ffiModel.pi);
|
||||
await bind.sessionRequestNewDisplayInitMsgs(
|
||||
sessionId: sessionId, display: ffiModel.pi.currentDisplay);
|
||||
});
|
||||
isToNewWindowNotified.value = true;
|
||||
}
|
||||
@@ -2490,14 +2527,21 @@ class PeerInfo with ChangeNotifier {
|
||||
bool get isInstalled =>
|
||||
platform != kPeerPlatformWindows ||
|
||||
platformAdditions[kPlatformAdditionsIsInstalled] == true;
|
||||
List<int> get virtualDisplays => List<int>.from(
|
||||
platformAdditions[kPlatformAdditionsVirtualDisplays] ?? []);
|
||||
List<int> get RustDeskVirtualDisplays => List<int>.from(
|
||||
platformAdditions[kPlatformAdditionsRustDeskVirtualDisplays] ?? []);
|
||||
int get amyuniVirtualDisplayCount =>
|
||||
platformAdditions[kPlatformAdditionsAmyuniVirtualDisplays] ?? 0;
|
||||
|
||||
bool get isSupportMultiDisplay =>
|
||||
(isDesktop || isWebDesktop) && isSupportMultiUiSession;
|
||||
|
||||
bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false;
|
||||
|
||||
bool get isRustDeskIdd =>
|
||||
platformAdditions[kPlatformAdditionsIddImpl] == 'rustdesk_idd';
|
||||
bool get isAmyuniIdd =>
|
||||
platformAdditions[kPlatformAdditionsIddImpl] == 'amyuni_idd';
|
||||
|
||||
Display? tryGetDisplay() {
|
||||
if (displays.isEmpty) {
|
||||
return null;
|
||||
|
||||
@@ -198,7 +198,10 @@ class PlatformFFI {
|
||||
await _ffiBind.mainDeviceId(id: id);
|
||||
await _ffiBind.mainDeviceName(name: name);
|
||||
await _ffiBind.mainSetHomeDir(home: _homeDir);
|
||||
await _ffiBind.mainInit(appDir: _dir);
|
||||
await _ffiBind.mainInit(
|
||||
appDir: _dir,
|
||||
customClientConfig: '',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrintStack(label: 'initialize failed: $e');
|
||||
}
|
||||
|
||||
@@ -21,24 +21,43 @@ class PeerTabModel with ChangeNotifier {
|
||||
WeakReference<FFI> parent;
|
||||
int get currentTab => _currentTab;
|
||||
int _currentTab = 0; // index in tabNames
|
||||
List<String> tabNames = [
|
||||
static const int maxTabCount = 5;
|
||||
static const String kPeerTabIndex = 'peer-tab-index';
|
||||
static const String kPeerTabOrder = 'peer-tab-order';
|
||||
static const String kPeerTabVisible = 'peer-tab-visible';
|
||||
static const List<String> tabNames = [
|
||||
'Recent sessions',
|
||||
'Favorites',
|
||||
if (!isWeb) 'Discovered',
|
||||
if (!(bind.isDisableAb() || bind.isDisableAccount())) 'Address book',
|
||||
if (!bind.isDisableAccount()) 'Group',
|
||||
'Discovered',
|
||||
'Address book',
|
||||
'Group',
|
||||
];
|
||||
final List<IconData> icons = [
|
||||
static const List<IconData> icons = [
|
||||
Icons.access_time_filled,
|
||||
Icons.star,
|
||||
if (!isWeb) Icons.explore,
|
||||
if (!(bind.isDisableAb() || bind.isDisableAccount())) IconFont.addressBook,
|
||||
if (!bind.isDisableAccount()) Icons.group,
|
||||
Icons.explore,
|
||||
IconFont.addressBook,
|
||||
Icons.group,
|
||||
];
|
||||
final List<bool> _isVisible = List.filled(5, true, growable: false);
|
||||
List<bool> get isVisible => _isVisible;
|
||||
List<int> get indexs => List.generate(tabNames.length, (index) => index);
|
||||
List<int> get visibleIndexs => indexs.where((e) => _isVisible[e]).toList();
|
||||
List<bool> isEnabled = List.from([
|
||||
true,
|
||||
true,
|
||||
!isWeb,
|
||||
!(bind.isDisableAb() || bind.isDisableAccount()),
|
||||
!bind.isDisableAccount(),
|
||||
]);
|
||||
final List<bool> _isVisible = List.filled(maxTabCount, true, growable: false);
|
||||
List<bool> get isVisibleEnabled => () {
|
||||
final list = _isVisible.toList();
|
||||
for (int i = 0; i < maxTabCount; i++) {
|
||||
list[i] = list[i] && isEnabled[i];
|
||||
}
|
||||
return list;
|
||||
}();
|
||||
final List<int> orders =
|
||||
List.generate(maxTabCount, (index) => index, growable: false);
|
||||
List<int> get visibleEnabledOrderedIndexs =>
|
||||
orders.where((e) => isVisibleEnabled[e]).toList();
|
||||
List<Peer> _selectedPeers = List.empty(growable: true);
|
||||
List<Peer> get selectedPeers => _selectedPeers;
|
||||
bool _multiSelectionMode = false;
|
||||
@@ -53,7 +72,7 @@ class PeerTabModel with ChangeNotifier {
|
||||
PeerTabModel(this.parent) {
|
||||
// visible
|
||||
try {
|
||||
final option = bind.getLocalFlutterOption(k: 'peer-tab-visible');
|
||||
final option = bind.getLocalFlutterOption(k: kPeerTabVisible);
|
||||
if (option.isNotEmpty) {
|
||||
List<dynamic> decodeList = jsonDecode(option);
|
||||
if (decodeList.length == _isVisible.length) {
|
||||
@@ -67,13 +86,37 @@ class PeerTabModel with ChangeNotifier {
|
||||
} catch (e) {
|
||||
debugPrint("failed to get peer tab visible list:$e");
|
||||
}
|
||||
// order
|
||||
try {
|
||||
final option = bind.getLocalFlutterOption(k: kPeerTabOrder);
|
||||
if (option.isNotEmpty) {
|
||||
List<dynamic> decodeList = jsonDecode(option);
|
||||
if (decodeList.length == maxTabCount) {
|
||||
var sortedList = decodeList.toList();
|
||||
sortedList.sort();
|
||||
bool valid = true;
|
||||
for (int i = 0; i < maxTabCount; i++) {
|
||||
if (sortedList[i] is! int || sortedList[i] != i) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
for (int i = 0; i < orders.length; i++) {
|
||||
orders[i] = decodeList[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("failed to get peer tab order list: $e");
|
||||
}
|
||||
// init currentTab
|
||||
_currentTab =
|
||||
int.tryParse(bind.getLocalFlutterOption(k: 'peer-tab-index')) ?? 0;
|
||||
if (_currentTab < 0 || _currentTab >= tabNames.length) {
|
||||
int.tryParse(bind.getLocalFlutterOption(k: kPeerTabIndex)) ?? 0;
|
||||
if (_currentTab < 0 || _currentTab >= maxTabCount) {
|
||||
_currentTab = 0;
|
||||
}
|
||||
_trySetCurrentTabToFirstVisible();
|
||||
_trySetCurrentTabToFirstVisibleEnabled();
|
||||
}
|
||||
|
||||
setCurrentTab(int index) {
|
||||
@@ -87,15 +130,13 @@ class PeerTabModel with ChangeNotifier {
|
||||
if (index >= 0 && index < tabNames.length) {
|
||||
return translate(tabNames[index]);
|
||||
}
|
||||
assert(false);
|
||||
return index.toString();
|
||||
}
|
||||
|
||||
IconData tabIcon(int index) {
|
||||
if (index >= 0 && index < tabNames.length) {
|
||||
if (index >= 0 && index < icons.length) {
|
||||
return icons[index];
|
||||
}
|
||||
assert(false);
|
||||
return Icons.help;
|
||||
}
|
||||
|
||||
@@ -171,29 +212,54 @@ class PeerTabModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
setTabVisible(int index, bool visible) {
|
||||
if (index >= 0 && index < _isVisible.length) {
|
||||
if (index >= 0 && index < maxTabCount) {
|
||||
if (_isVisible[index] != visible) {
|
||||
_isVisible[index] = visible;
|
||||
if (index == _currentTab && !visible) {
|
||||
_trySetCurrentTabToFirstVisible();
|
||||
} else if (visible && visibleIndexs.length == 1) {
|
||||
_trySetCurrentTabToFirstVisibleEnabled();
|
||||
} else if (visible && visibleEnabledOrderedIndexs.length == 1) {
|
||||
_currentTab = index;
|
||||
}
|
||||
try {
|
||||
bind.setLocalFlutterOption(
|
||||
k: 'peer-tab-visible', v: jsonEncode(_isVisible));
|
||||
k: kPeerTabVisible, v: jsonEncode(_isVisible));
|
||||
} catch (_) {}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_trySetCurrentTabToFirstVisible() {
|
||||
if (!_isVisible[_currentTab]) {
|
||||
int firstVisible = _isVisible.indexWhere((e) => e);
|
||||
if (firstVisible >= 0) {
|
||||
_currentTab = firstVisible;
|
||||
_trySetCurrentTabToFirstVisibleEnabled() {
|
||||
if (!visibleEnabledOrderedIndexs.contains(_currentTab)) {
|
||||
if (visibleEnabledOrderedIndexs.isNotEmpty) {
|
||||
_currentTab = visibleEnabledOrderedIndexs.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reorder(int oldIndex, int newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
if (oldIndex < 0 || oldIndex >= visibleEnabledOrderedIndexs.length) {
|
||||
return;
|
||||
}
|
||||
if (newIndex < 0 || newIndex >= visibleEnabledOrderedIndexs.length) {
|
||||
return;
|
||||
}
|
||||
final oldTabValue = visibleEnabledOrderedIndexs[oldIndex];
|
||||
final newTabValue = visibleEnabledOrderedIndexs[newIndex];
|
||||
int oldValueIndex = orders.indexOf(oldTabValue);
|
||||
int newValueIndex = orders.indexOf(newTabValue);
|
||||
final list = orders.toList();
|
||||
if (oldIndex != -1 && newIndex != -1) {
|
||||
list.removeAt(oldValueIndex);
|
||||
list.insert(newValueIndex, oldTabValue);
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
orders[i] = list[i];
|
||||
}
|
||||
bind.setLocalFlutterOption(k: kPeerTabOrder, v: jsonEncode(orders));
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1052,7 +1052,7 @@ class RustdeskImpl {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
bool mainHasGpucodec({dynamic hint}) {
|
||||
bool mainHasVram({dynamic hint}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@@ -1573,5 +1573,14 @@ class RustdeskImpl {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future<void> mainCheckHwcodec({dynamic hint}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future<void> sessionRequestNewDisplayInitMsgs(
|
||||
{required UuidValue sessionId, required int display, dynamic hint}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
cargo ndk --platform 21 --target armv7-linux-androideabi build --release --features flutter
|
||||
cargo ndk --platform 21 --target armv7-linux-androideabi build --release --features flutter,hwcodec
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
cargo ndk --platform 21 --target aarch64-linux-android build --release --features flutter
|
||||
cargo ndk --platform 21 --target aarch64-linux-android build --release --features flutter,hwcodec
|
||||
|
||||
@@ -849,18 +849,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "0.8.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
version: "1.11.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -921,10 +921,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
version: "1.9.0"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1526,10 +1526,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
version: "0.4.2"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -8,7 +8,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
flexi_logger = { version = "0.27", features = ["async"] }
|
||||
protobuf = { version = "3.3", features = ["with-bytes"] }
|
||||
protobuf = { version = "3.4", features = ["with-bytes"] }
|
||||
tokio = { version = "1.36", features = ["full"] }
|
||||
tokio-util = { version = "0.7", features = ["full"] }
|
||||
futures = "0.3"
|
||||
@@ -44,6 +44,7 @@ thiserror = "1.0"
|
||||
httparse = "1.5"
|
||||
base64 = "0.22"
|
||||
url = "2.2"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
mac_address = "1.1"
|
||||
machine-uid = { git = "https://github.com/21pages/machine-uid" }
|
||||
@@ -59,7 +60,7 @@ quic = []
|
||||
flatpak = []
|
||||
|
||||
[build-dependencies]
|
||||
protobuf-codegen = { version = "3.3" }
|
||||
protobuf-codegen = { version = "3.4" }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser", "synchapi", "pdh", "memoryapi"] }
|
||||
|
||||
@@ -504,6 +504,11 @@ message Resolution {
|
||||
int32 height = 2;
|
||||
}
|
||||
|
||||
message DisplayResolution {
|
||||
int32 display = 1;
|
||||
Resolution resolution = 2;
|
||||
}
|
||||
|
||||
message SupportedResolutions { repeated Resolution resolutions = 1; }
|
||||
|
||||
message SwitchDisplay {
|
||||
@@ -596,7 +601,8 @@ message OptionMessage {
|
||||
BoolOption disable_keyboard = 12;
|
||||
// Position 13 is used for Resolution. Remove later.
|
||||
// Resolution custom_resolution = 13;
|
||||
BoolOption support_windows_specific_session = 14;
|
||||
// BoolOption support_windows_specific_session = 14;
|
||||
// starting from 15 please, do not use removed fields
|
||||
}
|
||||
|
||||
message TestDelay {
|
||||
@@ -716,6 +722,13 @@ message WindowsSessions {
|
||||
uint32 current_sid = 2;
|
||||
}
|
||||
|
||||
// Query messages from peer.
|
||||
message MessageQuery {
|
||||
// The SwitchDisplay message of the target display.
|
||||
// If the target display is not found, the message will be ignored.
|
||||
int32 switch_display = 1;
|
||||
}
|
||||
|
||||
message Misc {
|
||||
oneof union {
|
||||
ChatMessage chat_message = 4;
|
||||
@@ -736,6 +749,8 @@ message Misc {
|
||||
bool portable_service_running = 20;
|
||||
SwitchSidesRequest switch_sides_request = 21;
|
||||
SwitchBack switch_back = 22;
|
||||
// Deprecated since 1.2.4, use `change_display_resolution` (36) instead.
|
||||
// But we must keep it for compatibility when peer version < 1.2.4.
|
||||
Resolution change_resolution = 24;
|
||||
PluginRequest plugin_request = 25;
|
||||
PluginFailure plugin_failure = 26;
|
||||
@@ -748,6 +763,8 @@ message Misc {
|
||||
TogglePrivacyMode toggle_privacy_mode = 33;
|
||||
SupportedEncoding supported_encoding = 34;
|
||||
uint32 selected_sid = 35;
|
||||
DisplayResolution change_display_resolution = 36;
|
||||
MessageQuery message_query = 37;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -409,9 +409,7 @@ fn patch(path: PathBuf) -> PathBuf {
|
||||
if let Ok(user) = crate::platform::linux::run_cmds_trim_newline("whoami") {
|
||||
if user != "root" {
|
||||
let cmd = format!("getent passwd '{}' | awk -F':' '{{print $6}}'", user);
|
||||
if let Ok(output) =
|
||||
crate::platform::linux::run_cmds_trim_newline(&cmd)
|
||||
{
|
||||
if let Ok(output) = crate::platform::linux::run_cmds_trim_newline(&cmd) {
|
||||
return output.into();
|
||||
}
|
||||
return format!("/home/{user}").into();
|
||||
@@ -505,7 +503,7 @@ impl Config {
|
||||
fn store_<T: serde::Serialize>(config: &T, suffix: &str) {
|
||||
let file = Self::file_(suffix);
|
||||
if let Err(err) = store_path(file, config) {
|
||||
log::error!("Failed to store config: {}", err);
|
||||
log::error!("Failed to store {suffix} config: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1495,8 +1493,10 @@ impl LanPeers {
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct HwCodecConfig {
|
||||
#[serde(default, deserialize_with = "deserialize_hashmap_string_string")]
|
||||
pub options: HashMap<String, String>,
|
||||
#[serde(default, deserialize_with = "deserialize_string")]
|
||||
pub ram: String,
|
||||
#[serde(default, deserialize_with = "deserialize_string")]
|
||||
pub vram: String,
|
||||
}
|
||||
|
||||
impl HwCodecConfig {
|
||||
@@ -1511,25 +1511,17 @@ impl HwCodecConfig {
|
||||
pub fn clear() {
|
||||
HwCodecConfig::default().store();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct GpucodecConfig {
|
||||
#[serde(default, deserialize_with = "deserialize_string")]
|
||||
pub available: String,
|
||||
}
|
||||
|
||||
impl GpucodecConfig {
|
||||
pub fn load() -> GpucodecConfig {
|
||||
Config::load_::<GpucodecConfig>("_gpucodec")
|
||||
pub fn clear_ram() {
|
||||
let mut c = Self::load();
|
||||
c.ram = Default::default();
|
||||
c.store();
|
||||
}
|
||||
|
||||
pub fn store(&self) {
|
||||
Config::store_(self, "_gpucodec");
|
||||
}
|
||||
|
||||
pub fn clear() {
|
||||
GpucodecConfig::default().store();
|
||||
pub fn clear_vram() {
|
||||
let mut c = Self::load();
|
||||
c.vram = Default::default();
|
||||
c.store();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1569,6 +1561,7 @@ impl UserDefaultConfig {
|
||||
}
|
||||
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 0xFFF as f64),
|
||||
"custom-fps" => self.get_double_string(key, 30.0, 5.0, 120.0),
|
||||
"enable_file_transfer" => self.get_string(key, "Y", vec![""]),
|
||||
_ => self
|
||||
.get_after(key)
|
||||
.map(|v| v.to_string())
|
||||
|
||||
@@ -47,10 +47,13 @@ pub mod keyboard;
|
||||
pub use dlopen;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use machine_uid;
|
||||
pub use serde_derive;
|
||||
pub use serde_json;
|
||||
pub use sysinfo;
|
||||
pub use toml;
|
||||
pub use uuid;
|
||||
pub use base64;
|
||||
pub use thiserror;
|
||||
|
||||
#[cfg(feature = "quic")]
|
||||
pub type Stream = quic::Connection;
|
||||
|
||||
@@ -13,6 +13,8 @@ edition = "2018"
|
||||
wayland = ["gstreamer", "gstreamer-app", "gstreamer-video", "dbus", "tracing"]
|
||||
mediacodec = ["ndk"]
|
||||
linux-pkg-config = ["dep:pkg-config"]
|
||||
hwcodec = ["dep:hwcodec"]
|
||||
vram = ["hwcodec/vram"]
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0"
|
||||
@@ -20,6 +22,7 @@ num_cpus = "1.15"
|
||||
lazy_static = "1.4"
|
||||
hbb_common = { path = "../hbb_common" }
|
||||
webm = { git = "https://github.com/21pages/rust-webm" }
|
||||
serde = {version="1.0", features=["derive"]}
|
||||
|
||||
[dependencies.winapi]
|
||||
version = "0.3"
|
||||
@@ -41,7 +44,6 @@ ndk-context = "0.1"
|
||||
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
|
||||
repng = "0.2"
|
||||
docopt = "1.1"
|
||||
serde = {version="1.0", features=["derive"]}
|
||||
quest = "0.3"
|
||||
|
||||
[build-dependencies]
|
||||
@@ -56,8 +58,8 @@ gstreamer = { version = "0.16", optional = true }
|
||||
gstreamer-app = { version = "0.16", features = ["v1_10"], optional = true }
|
||||
gstreamer-video = { version = "0.16", optional = true }
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
|
||||
hwcodec = { git = "https://github.com/21pages/hwcodec", branch = "stable", optional = true }
|
||||
[dependencies.hwcodec]
|
||||
git = "https://github.com/21pages/hwcodec"
|
||||
optional = true
|
||||
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
gpucodec = { git = "https://github.com/21pages/gpucodec", optional = true }
|
||||
|
||||
@@ -239,16 +239,16 @@ fn test_av1(
|
||||
|
||||
#[cfg(feature = "hwcodec")]
|
||||
mod hw {
|
||||
use hwcodec::ffmpeg::CodecInfo;
|
||||
use hwcodec::ffmpeg_ram::CodecInfo;
|
||||
use scrap::{
|
||||
hwcodec::{HwDecoder, HwEncoder, HwEncoderConfig},
|
||||
hwcodec::{HwRamDecoder, HwRamEncoder, HwRamEncoderConfig},
|
||||
CodecFormat,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn test(c: &mut Capturer, width: usize, height: usize, quality: Q, yuv_count: usize) {
|
||||
let best = HwEncoder::best();
|
||||
let best = HwRamEncoder::best();
|
||||
let mut h264s = Vec::new();
|
||||
let mut h265s = Vec::new();
|
||||
if let Some(info) = best.h264 {
|
||||
@@ -270,8 +270,8 @@ mod hw {
|
||||
yuv_count: usize,
|
||||
h26xs: &mut Vec<Vec<u8>>,
|
||||
) {
|
||||
let mut encoder = HwEncoder::new(
|
||||
EncoderCfg::HW(HwEncoderConfig {
|
||||
let mut encoder = HwRamEncoder::new(
|
||||
EncoderCfg::HWRAM(HwRamEncoderConfig {
|
||||
name: info.name.clone(),
|
||||
width,
|
||||
height,
|
||||
@@ -321,7 +321,7 @@ mod hw {
|
||||
}
|
||||
|
||||
fn test_decoder(format: CodecFormat, h26xs: &Vec<Vec<u8>>) {
|
||||
let mut decoder = HwDecoder::new(format).unwrap();
|
||||
let mut decoder = HwRamDecoder::new(format).unwrap();
|
||||
let start = Instant::now();
|
||||
let mut cnt = 0;
|
||||
for h26x in h26xs {
|
||||
|
||||
@@ -94,7 +94,7 @@ pub fn get_audio_raw<'a>() -> Option<&'a [u8]> {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onVideoFrameUpdate(
|
||||
pub extern "system" fn Java_ffi_FFI_onVideoFrameUpdate(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
buffer: JObject,
|
||||
@@ -108,7 +108,7 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onVideoFrameUpd
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onAudioFrameUpdate(
|
||||
pub extern "system" fn Java_ffi_FFI_onAudioFrameUpdate(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
buffer: JObject,
|
||||
@@ -122,7 +122,7 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onAudioFrameUpd
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_setFrameRawEnable(
|
||||
pub extern "system" fn Java_ffi_FFI_setFrameRawEnable(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
name: JString,
|
||||
@@ -141,7 +141,7 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_setFrameRawEnab
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_init(
|
||||
pub extern "system" fn Java_ffi_FFI_init(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
ctx: JObject,
|
||||
|
||||
@@ -268,7 +268,7 @@ impl EncoderApi for AomEncoder {
|
||||
self.yuvfmt.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn input_texture(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
use crate::gpucodec::*;
|
||||
#[cfg(feature = "hwcodec")]
|
||||
use crate::hwcodec::*;
|
||||
#[cfg(feature = "mediacodec")]
|
||||
use crate::mediacodec::{MediaCodecDecoder, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT};
|
||||
#[cfg(feature = "vram")]
|
||||
use crate::vram::*;
|
||||
use crate::{
|
||||
aom::{self, AomDecoder, AomEncoder, AomEncoderConfig},
|
||||
common::GoogleImage,
|
||||
@@ -31,7 +31,7 @@ use hbb_common::{
|
||||
tokio::time::Instant,
|
||||
ResultType,
|
||||
};
|
||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec", feature = "vram"))]
|
||||
use hbb_common::{config::Config2, lazy_static};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -47,9 +47,9 @@ pub enum EncoderCfg {
|
||||
VPX(VpxEncoderConfig),
|
||||
AOM(AomEncoderConfig),
|
||||
#[cfg(feature = "hwcodec")]
|
||||
HW(HwEncoderConfig),
|
||||
#[cfg(feature = "gpucodec")]
|
||||
GPU(GpuEncoderConfig),
|
||||
HWRAM(HwRamEncoderConfig),
|
||||
#[cfg(feature = "vram")]
|
||||
VRAM(VRamEncoderConfig),
|
||||
}
|
||||
|
||||
pub trait EncoderApi {
|
||||
@@ -61,7 +61,7 @@ pub trait EncoderApi {
|
||||
|
||||
fn yuvfmt(&self) -> EncodeYuvFormat;
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn input_texture(&self) -> bool;
|
||||
|
||||
fn set_quality(&mut self, quality: Quality) -> ResultType<()>;
|
||||
@@ -94,13 +94,13 @@ pub struct Decoder {
|
||||
vp9: Option<VpxDecoder>,
|
||||
av1: Option<AomDecoder>,
|
||||
#[cfg(feature = "hwcodec")]
|
||||
h264_ram: Option<HwDecoder>,
|
||||
h264_ram: Option<HwRamDecoder>,
|
||||
#[cfg(feature = "hwcodec")]
|
||||
h265_ram: Option<HwDecoder>,
|
||||
#[cfg(feature = "gpucodec")]
|
||||
h264_vram: Option<GpuDecoder>,
|
||||
#[cfg(feature = "gpucodec")]
|
||||
h265_vram: Option<GpuDecoder>,
|
||||
h265_ram: Option<HwRamDecoder>,
|
||||
#[cfg(feature = "vram")]
|
||||
h264_vram: Option<VRamDecoder>,
|
||||
#[cfg(feature = "vram")]
|
||||
h265_vram: Option<VRamDecoder>,
|
||||
#[cfg(feature = "mediacodec")]
|
||||
h264_media_codec: MediaCodecDecoder,
|
||||
#[cfg(feature = "mediacodec")]
|
||||
@@ -131,25 +131,25 @@ impl Encoder {
|
||||
}),
|
||||
|
||||
#[cfg(feature = "hwcodec")]
|
||||
EncoderCfg::HW(_) => match HwEncoder::new(config, i444) {
|
||||
EncoderCfg::HWRAM(_) => match HwRamEncoder::new(config, i444) {
|
||||
Ok(hw) => Ok(Encoder {
|
||||
codec: Box::new(hw),
|
||||
}),
|
||||
Err(e) => {
|
||||
log::error!("new hw encoder failed: {e:?}, clear config");
|
||||
hbb_common::config::HwCodecConfig::clear();
|
||||
hbb_common::config::HwCodecConfig::clear_ram();
|
||||
*ENCODE_CODEC_NAME.lock().unwrap() = CodecName::VP9;
|
||||
Err(e)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "gpucodec")]
|
||||
EncoderCfg::GPU(_) => match GpuEncoder::new(config, i444) {
|
||||
#[cfg(feature = "vram")]
|
||||
EncoderCfg::VRAM(_) => match VRamEncoder::new(config, i444) {
|
||||
Ok(tex) => Ok(Encoder {
|
||||
codec: Box::new(tex),
|
||||
}),
|
||||
Err(e) => {
|
||||
log::error!("new gpu encoder failed: {e:?}, clear config");
|
||||
hbb_common::config::GpucodecConfig::clear();
|
||||
log::error!("new vram encoder failed: {e:?}, clear config");
|
||||
hbb_common::config::HwCodecConfig::clear_vram();
|
||||
*ENCODE_CODEC_NAME.lock().unwrap() = CodecName::VP9;
|
||||
Err(e)
|
||||
}
|
||||
@@ -186,19 +186,19 @@ impl Encoder {
|
||||
let _all_support_h265_decoding =
|
||||
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h265 > 0);
|
||||
#[allow(unused_mut)]
|
||||
let mut h264gpu_encoding = false;
|
||||
let mut h264vram_encoding = false;
|
||||
#[allow(unused_mut)]
|
||||
let mut h265gpu_encoding = false;
|
||||
#[cfg(feature = "gpucodec")]
|
||||
if enable_gpucodec_option() {
|
||||
let mut h265vram_encoding = false;
|
||||
#[cfg(feature = "vram")]
|
||||
if enable_vram_option() {
|
||||
if _all_support_h264_decoding {
|
||||
if GpuEncoder::available(CodecName::H264GPU).len() > 0 {
|
||||
h264gpu_encoding = true;
|
||||
if VRamEncoder::available(CodecName::H264VRAM).len() > 0 {
|
||||
h264vram_encoding = true;
|
||||
}
|
||||
}
|
||||
if _all_support_h265_decoding {
|
||||
if GpuEncoder::available(CodecName::H265GPU).len() > 0 {
|
||||
h265gpu_encoding = true;
|
||||
if VRamEncoder::available(CodecName::H265VRAM).len() > 0 {
|
||||
h265vram_encoding = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,7 +208,7 @@ impl Encoder {
|
||||
let mut h265hw_encoding = None;
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if enable_hwcodec_option() {
|
||||
let best = HwEncoder::best();
|
||||
let best = HwRamEncoder::best();
|
||||
if _all_support_h264_decoding {
|
||||
h264hw_encoding = best.h264.map_or(None, |c| Some(c.name));
|
||||
}
|
||||
@@ -217,9 +217,9 @@ impl Encoder {
|
||||
}
|
||||
}
|
||||
let h264_useable =
|
||||
_all_support_h264_decoding && (h264gpu_encoding || h264hw_encoding.is_some());
|
||||
_all_support_h264_decoding && (h264vram_encoding || h264hw_encoding.is_some());
|
||||
let h265_useable =
|
||||
_all_support_h265_decoding && (h265gpu_encoding || h265hw_encoding.is_some());
|
||||
_all_support_h265_decoding && (h265vram_encoding || h265hw_encoding.is_some());
|
||||
let mut name = ENCODE_CODEC_NAME.lock().unwrap();
|
||||
let mut preference = PreferCodec::Auto;
|
||||
let preferences: Vec<_> = decodings
|
||||
@@ -239,7 +239,8 @@ impl Encoder {
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut auto_codec = CodecName::VP9;
|
||||
if av1_useable {
|
||||
// aom is very slow for x86 sciter version on windows x64
|
||||
if av1_useable && !(cfg!(windows) && std::env::consts::ARCH == "x86") {
|
||||
auto_codec = CodecName::AV1;
|
||||
}
|
||||
let mut system = System::new();
|
||||
@@ -254,19 +255,19 @@ impl Encoder {
|
||||
PreferCodec::VP9 => CodecName::VP9,
|
||||
PreferCodec::AV1 => CodecName::AV1,
|
||||
PreferCodec::H264 => {
|
||||
if h264gpu_encoding {
|
||||
CodecName::H264GPU
|
||||
if h264vram_encoding {
|
||||
CodecName::H264VRAM
|
||||
} else if let Some(v) = h264hw_encoding {
|
||||
CodecName::H264HW(v)
|
||||
CodecName::H264RAM(v)
|
||||
} else {
|
||||
auto_codec
|
||||
}
|
||||
}
|
||||
PreferCodec::H265 => {
|
||||
if h265gpu_encoding {
|
||||
CodecName::H265GPU
|
||||
if h265vram_encoding {
|
||||
CodecName::H265VRAM
|
||||
} else if let Some(v) = h265hw_encoding {
|
||||
CodecName::H265HW(v)
|
||||
CodecName::H265RAM(v)
|
||||
} else {
|
||||
auto_codec
|
||||
}
|
||||
@@ -306,14 +307,14 @@ impl Encoder {
|
||||
};
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if enable_hwcodec_option() {
|
||||
let best = HwEncoder::best();
|
||||
let best = HwRamEncoder::best();
|
||||
encoding.h264 |= best.h264.is_some();
|
||||
encoding.h265 |= best.h265.is_some();
|
||||
}
|
||||
#[cfg(feature = "gpucodec")]
|
||||
if enable_gpucodec_option() {
|
||||
encoding.h264 |= GpuEncoder::available(CodecName::H264GPU).len() > 0;
|
||||
encoding.h265 |= GpuEncoder::available(CodecName::H265GPU).len() > 0;
|
||||
#[cfg(feature = "vram")]
|
||||
if enable_vram_option() {
|
||||
encoding.h264 |= VRamEncoder::available(CodecName::H264VRAM).len() > 0;
|
||||
encoding.h265 |= VRamEncoder::available(CodecName::H265VRAM).len() > 0;
|
||||
}
|
||||
encoding
|
||||
}
|
||||
@@ -326,21 +327,21 @@ impl Encoder {
|
||||
},
|
||||
EncoderCfg::AOM(_) => CodecName::AV1,
|
||||
#[cfg(feature = "hwcodec")]
|
||||
EncoderCfg::HW(hw) => {
|
||||
EncoderCfg::HWRAM(hw) => {
|
||||
if hw.name.to_lowercase().contains("h264") {
|
||||
CodecName::H264HW(hw.name.clone())
|
||||
CodecName::H264RAM(hw.name.clone())
|
||||
} else {
|
||||
CodecName::H265HW(hw.name.clone())
|
||||
CodecName::H265RAM(hw.name.clone())
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "gpucodec")]
|
||||
EncoderCfg::GPU(gpu) => match gpu.feature.data_format {
|
||||
gpucodec::gpu_common::DataFormat::H264 => CodecName::H264GPU,
|
||||
gpucodec::gpu_common::DataFormat::H265 => CodecName::H265GPU,
|
||||
#[cfg(feature = "vram")]
|
||||
EncoderCfg::VRAM(vram) => match vram.feature.data_format {
|
||||
hwcodec::common::DataFormat::H264 => CodecName::H264VRAM,
|
||||
hwcodec::common::DataFormat::H265 => CodecName::H265VRAM,
|
||||
_ => {
|
||||
log::error!(
|
||||
"should not reach here, gpucodec not support {:?}",
|
||||
gpu.feature.data_format
|
||||
"should not reach here, vram not support {:?}",
|
||||
vram.feature.data_format
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -365,9 +366,9 @@ impl Encoder {
|
||||
},
|
||||
EncoderCfg::AOM(_) => decodings.iter().all(|d| d.1.i444.av1),
|
||||
#[cfg(feature = "hwcodec")]
|
||||
EncoderCfg::HW(_) => false,
|
||||
#[cfg(feature = "gpucodec")]
|
||||
EncoderCfg::GPU(_) => false,
|
||||
EncoderCfg::HWRAM(_) => false,
|
||||
#[cfg(feature = "vram")]
|
||||
EncoderCfg::VRAM(_) => false,
|
||||
};
|
||||
prefer_i444 && i444_useable && !decodings.is_empty()
|
||||
}
|
||||
@@ -398,19 +399,19 @@ impl Decoder {
|
||||
..Default::default()
|
||||
};
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if enable_hwcodec_option() {
|
||||
let best = HwDecoder::best();
|
||||
{
|
||||
let best = HwRamDecoder::best();
|
||||
decoding.ability_h264 |= if best.h264.is_some() { 1 } else { 0 };
|
||||
decoding.ability_h265 |= if best.h265.is_some() { 1 } else { 0 };
|
||||
}
|
||||
#[cfg(feature = "gpucodec")]
|
||||
if enable_gpucodec_option() && _flutter {
|
||||
decoding.ability_h264 |= if GpuDecoder::available(CodecFormat::H264, _luid).len() > 0 {
|
||||
#[cfg(feature = "vram")]
|
||||
if enable_vram_option() && _flutter {
|
||||
decoding.ability_h264 |= if VRamDecoder::available(CodecFormat::H264, _luid).len() > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
decoding.ability_h265 |= if GpuDecoder::available(CodecFormat::H265, _luid).len() > 0 {
|
||||
decoding.ability_h265 |= if VRamDecoder::available(CodecFormat::H265, _luid).len() > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
@@ -449,7 +450,7 @@ impl Decoder {
|
||||
let (mut vp8, mut vp9, mut av1) = (None, None, None);
|
||||
#[cfg(feature = "hwcodec")]
|
||||
let (mut h264_ram, mut h265_ram) = (None, None);
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
let (mut h264_vram, mut h265_vram) = (None, None);
|
||||
#[cfg(feature = "mediacodec")]
|
||||
let (mut h264_media_codec, mut h265_media_codec) = (None, None);
|
||||
@@ -482,17 +483,17 @@ impl Decoder {
|
||||
valid = av1.is_some();
|
||||
}
|
||||
CodecFormat::H264 => {
|
||||
#[cfg(feature = "gpucodec")]
|
||||
if !valid && enable_gpucodec_option() && _luid.clone().unwrap_or_default() != 0 {
|
||||
match GpuDecoder::new(format, _luid) {
|
||||
#[cfg(feature = "vram")]
|
||||
if !valid && enable_vram_option() && _luid.clone().unwrap_or_default() != 0 {
|
||||
match VRamDecoder::new(format, _luid) {
|
||||
Ok(v) => h264_vram = Some(v),
|
||||
Err(e) => log::error!("create H264 vram decoder failed: {}", e),
|
||||
}
|
||||
valid = h264_vram.is_some();
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if !valid && enable_hwcodec_option() {
|
||||
match HwDecoder::new(format) {
|
||||
if !valid {
|
||||
match HwRamDecoder::new(format) {
|
||||
Ok(v) => h264_ram = Some(v),
|
||||
Err(e) => log::error!("create H264 ram decoder failed: {}", e),
|
||||
}
|
||||
@@ -508,17 +509,17 @@ impl Decoder {
|
||||
}
|
||||
}
|
||||
CodecFormat::H265 => {
|
||||
#[cfg(feature = "gpucodec")]
|
||||
if !valid && enable_gpucodec_option() && _luid.clone().unwrap_or_default() != 0 {
|
||||
match GpuDecoder::new(format, _luid) {
|
||||
#[cfg(feature = "vram")]
|
||||
if !valid && enable_vram_option() && _luid.clone().unwrap_or_default() != 0 {
|
||||
match VRamDecoder::new(format, _luid) {
|
||||
Ok(v) => h265_vram = Some(v),
|
||||
Err(e) => log::error!("create H265 vram decoder failed: {}", e),
|
||||
}
|
||||
valid = h265_vram.is_some();
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if !valid && enable_hwcodec_option() {
|
||||
match HwDecoder::new(format) {
|
||||
if !valid {
|
||||
match HwRamDecoder::new(format) {
|
||||
Ok(v) => h265_ram = Some(v),
|
||||
Err(e) => log::error!("create H265 ram decoder failed: {}", e),
|
||||
}
|
||||
@@ -550,9 +551,9 @@ impl Decoder {
|
||||
h264_ram,
|
||||
#[cfg(feature = "hwcodec")]
|
||||
h265_ram,
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
h264_vram,
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
h265_vram,
|
||||
#[cfg(feature = "mediacodec")]
|
||||
h264_media_codec,
|
||||
@@ -604,31 +605,31 @@ impl Decoder {
|
||||
bail!("av1 decoder not available");
|
||||
}
|
||||
}
|
||||
#[cfg(any(feature = "hwcodec", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "hwcodec", feature = "vram"))]
|
||||
video_frame::Union::H264s(h264s) => {
|
||||
*chroma = Some(Chroma::I420);
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
if let Some(decoder) = &mut self.h264_vram {
|
||||
*_pixelbuffer = false;
|
||||
return Decoder::handle_gpu_video_frame(decoder, h264s, _texture);
|
||||
return Decoder::handle_vram_video_frame(decoder, h264s, _texture);
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if let Some(decoder) = &mut self.h264_ram {
|
||||
return Decoder::handle_hw_video_frame(decoder, h264s, rgb, &mut self.i420);
|
||||
return Decoder::handle_hwram_video_frame(decoder, h264s, rgb, &mut self.i420);
|
||||
}
|
||||
Err(anyhow!("don't support h264!"))
|
||||
}
|
||||
#[cfg(any(feature = "hwcodec", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "hwcodec", feature = "vram"))]
|
||||
video_frame::Union::H265s(h265s) => {
|
||||
*chroma = Some(Chroma::I420);
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
if let Some(decoder) = &mut self.h265_vram {
|
||||
*_pixelbuffer = false;
|
||||
return Decoder::handle_gpu_video_frame(decoder, h265s, _texture);
|
||||
return Decoder::handle_vram_video_frame(decoder, h265s, _texture);
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if let Some(decoder) = &mut self.h265_ram {
|
||||
return Decoder::handle_hw_video_frame(decoder, h265s, rgb, &mut self.i420);
|
||||
return Decoder::handle_hwram_video_frame(decoder, h265s, rgb, &mut self.i420);
|
||||
}
|
||||
Err(anyhow!("don't support h265!"))
|
||||
}
|
||||
@@ -710,8 +711,8 @@ impl Decoder {
|
||||
|
||||
// rgb [in/out] fmt and stride must be set in ImageRgb
|
||||
#[cfg(feature = "hwcodec")]
|
||||
fn handle_hw_video_frame(
|
||||
decoder: &mut HwDecoder,
|
||||
fn handle_hwram_video_frame(
|
||||
decoder: &mut HwRamDecoder,
|
||||
frames: &EncodedVideoFrames,
|
||||
rgb: &mut ImageRgb,
|
||||
i420: &mut Vec<u8>,
|
||||
@@ -728,9 +729,9 @@ impl Decoder {
|
||||
return Ok(ret);
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
fn handle_gpu_video_frame(
|
||||
decoder: &mut GpuDecoder,
|
||||
#[cfg(feature = "vram")]
|
||||
fn handle_vram_video_frame(
|
||||
decoder: &mut VRamDecoder,
|
||||
frames: &EncodedVideoFrames,
|
||||
texture: &mut *mut c_void,
|
||||
) -> ResultType<bool> {
|
||||
@@ -791,13 +792,16 @@ impl Decoder {
|
||||
|
||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
||||
pub fn enable_hwcodec_option() -> bool {
|
||||
if let Some(v) = Config2::get().options.get("enable-hwcodec") {
|
||||
return v != "N";
|
||||
if cfg!(windows) || cfg!(target_os = "linux") || cfg!(feature = "mediacodec") {
|
||||
if let Some(v) = Config2::get().options.get("enable-hwcodec") {
|
||||
return v != "N";
|
||||
}
|
||||
return true; // default is true
|
||||
}
|
||||
return true; // default is true
|
||||
false
|
||||
}
|
||||
#[cfg(feature = "gpucodec")]
|
||||
pub fn enable_gpucodec_option() -> bool {
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn enable_vram_option() -> bool {
|
||||
if let Some(v) = Config2::get().options.get("enable-hwcodec") {
|
||||
return v != "N";
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pub mod hw {
|
||||
use super::*;
|
||||
use crate::ImageFormat;
|
||||
#[cfg(target_os = "windows")]
|
||||
use hwcodec::{ffmpeg::ffmpeg_linesize_offset_length, AVPixelFormat};
|
||||
use hwcodec::{ffmpeg::AVPixelFormat, ffmpeg_ram::ffmpeg_linesize_offset_length};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn hw_nv12_to(
|
||||
@@ -222,9 +222,7 @@ pub fn convert_to_yuv(
|
||||
);
|
||||
}
|
||||
}
|
||||
let align = |x:usize| {
|
||||
(x + 63) / 64 * 64
|
||||
};
|
||||
let align = |x: usize| (x + 63) / 64 * 64;
|
||||
|
||||
match (src_pixfmt, dst_fmt.pixfmt) {
|
||||
(crate::Pixfmt::BGRA, crate::Pixfmt::I420) | (crate::Pixfmt::RGBA, crate::Pixfmt::I420) => {
|
||||
@@ -282,7 +280,8 @@ pub fn convert_to_yuv(
|
||||
let dst_stride_u = dst_fmt.stride[1];
|
||||
let dst_stride_v = dst_fmt.stride[2];
|
||||
dst.resize(
|
||||
align(dst_fmt.h) * (align(dst_stride_y) + align(dst_stride_u) + align(dst_stride_v)),
|
||||
align(dst_fmt.h)
|
||||
* (align(dst_stride_y) + align(dst_stride_u) + align(dst_stride_v)),
|
||||
0,
|
||||
);
|
||||
let dst_y = dst.as_mut_ptr();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
use crate::AdapterDevice;
|
||||
use crate::{common::TraitCapturer, dxgi, Frame, Pixfmt};
|
||||
use std::{
|
||||
@@ -57,12 +57,12 @@ impl TraitCapturer for Capturer {
|
||||
self.inner.set_gdi()
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn device(&self) -> AdapterDevice {
|
||||
self.inner.device()
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn set_output_texture(&mut self, texture: bool) {
|
||||
self.inner.set_output_texture(texture);
|
||||
}
|
||||
@@ -197,7 +197,7 @@ impl Display {
|
||||
self.origin() == (0, 0)
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn adapter_luid(&self) -> Option<i64> {
|
||||
self.0.adapter_luid()
|
||||
}
|
||||
@@ -247,11 +247,11 @@ impl TraitCapturer for CapturerMag {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn device(&self) -> AdapterDevice {
|
||||
AdapterDevice::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn set_output_texture(&mut self, _texture: bool) {}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
use crate::{
|
||||
codec::{base_bitrate, codec_thread_num, EncoderApi, EncoderCfg, Quality as Q},
|
||||
codec::{
|
||||
base_bitrate, codec_thread_num, enable_hwcodec_option, EncoderApi, EncoderCfg, Quality as Q,
|
||||
},
|
||||
hw, CodecFormat, EncodeInput, ImageFormat, ImageRgb, Pixfmt, HW_STRIDE_ALIGN,
|
||||
};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::{anyhow, bail, Context},
|
||||
bytes::Bytes,
|
||||
config::HwCodecConfig,
|
||||
log,
|
||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
||||
ResultType,
|
||||
serde_derive::{Deserialize, Serialize},
|
||||
serde_json, ResultType,
|
||||
};
|
||||
use hwcodec::{
|
||||
decode::{DecodeContext, DecodeFrame, Decoder},
|
||||
encode::{EncodeContext, EncodeFrame, Encoder},
|
||||
ffmpeg::{CodecInfo, CodecInfos, DataFormat},
|
||||
AVPixelFormat,
|
||||
Quality::{self, *},
|
||||
RateControl::{self, *},
|
||||
common::DataFormat,
|
||||
ffmpeg::AVPixelFormat,
|
||||
ffmpeg_ram::{
|
||||
decode::{DecodeContext, DecodeFrame, Decoder},
|
||||
encode::{EncodeContext, EncodeFrame, Encoder},
|
||||
CodecInfo, CodecInfos,
|
||||
Quality::{self, *},
|
||||
RateControl::{self, *},
|
||||
},
|
||||
};
|
||||
|
||||
const CFG_KEY_ENCODER: &str = "bestHwEncoders";
|
||||
const CFG_KEY_DECODER: &str = "bestHwDecoders";
|
||||
|
||||
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_NV12;
|
||||
pub const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
|
||||
const DEFAULT_GOP: i32 = i32::MAX;
|
||||
@@ -30,7 +32,7 @@ const DEFAULT_HW_QUALITY: Quality = Quality_Default;
|
||||
const DEFAULT_RC: RateControl = RC_DEFAULT;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HwEncoderConfig {
|
||||
pub struct HwRamEncoderConfig {
|
||||
pub name: String,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
@@ -38,7 +40,7 @@ pub struct HwEncoderConfig {
|
||||
pub keyframe_interval: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct HwEncoder {
|
||||
pub struct HwRamEncoder {
|
||||
encoder: Encoder,
|
||||
name: String,
|
||||
pub format: DataFormat,
|
||||
@@ -48,13 +50,13 @@ pub struct HwEncoder {
|
||||
bitrate: u32, //kbs
|
||||
}
|
||||
|
||||
impl EncoderApi for HwEncoder {
|
||||
impl EncoderApi for HwRamEncoder {
|
||||
fn new(cfg: EncoderCfg, _i444: bool) -> ResultType<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match cfg {
|
||||
EncoderCfg::HW(config) => {
|
||||
EncoderCfg::HWRAM(config) => {
|
||||
let b = Self::convert_quality(config.quality);
|
||||
let base_bitrate = base_bitrate(config.width as _, config.height as _);
|
||||
let mut bitrate = base_bitrate * b / 100;
|
||||
@@ -85,7 +87,7 @@ impl EncoderApi for HwEncoder {
|
||||
}
|
||||
};
|
||||
match Encoder::new(ctx.clone()) {
|
||||
Ok(encoder) => Ok(HwEncoder {
|
||||
Ok(encoder) => Ok(HwRamEncoder {
|
||||
encoder,
|
||||
name: config.name,
|
||||
format,
|
||||
@@ -95,7 +97,7 @@ impl EncoderApi for HwEncoder {
|
||||
bitrate,
|
||||
}),
|
||||
Err(_) => {
|
||||
HwCodecConfig::clear();
|
||||
HwCodecConfig::clear_ram();
|
||||
Err(anyhow!(format!("Failed to create encoder")))
|
||||
}
|
||||
}
|
||||
@@ -126,6 +128,7 @@ impl EncoderApi for HwEncoder {
|
||||
match self.format {
|
||||
DataFormat::H264 => vf.set_h264s(frames),
|
||||
DataFormat::H265 => vf.set_h265s(frames),
|
||||
_ => bail!("unsupported format: {:?}", self.format),
|
||||
}
|
||||
Ok(vf)
|
||||
} else {
|
||||
@@ -160,7 +163,7 @@ impl EncoderApi for HwEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn input_texture(&self) -> bool {
|
||||
false
|
||||
}
|
||||
@@ -184,9 +187,9 @@ impl EncoderApi for HwEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
impl HwEncoder {
|
||||
impl HwRamEncoder {
|
||||
pub fn best() -> CodecInfos {
|
||||
get_config(CFG_KEY_ENCODER).unwrap_or(CodecInfos {
|
||||
get_config().map(|c| c.e).unwrap_or(CodecInfos {
|
||||
h264: None,
|
||||
h265: None,
|
||||
})
|
||||
@@ -214,28 +217,30 @@ impl HwEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HwDecoder {
|
||||
pub struct HwRamDecoder {
|
||||
decoder: Decoder,
|
||||
pub info: CodecInfo,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HwDecoders {
|
||||
pub h264: Option<HwDecoder>,
|
||||
pub h265: Option<HwDecoder>,
|
||||
}
|
||||
|
||||
impl HwDecoder {
|
||||
impl HwRamDecoder {
|
||||
pub fn best() -> CodecInfos {
|
||||
get_config(CFG_KEY_DECODER).unwrap_or(CodecInfos {
|
||||
h264: None,
|
||||
h265: None,
|
||||
})
|
||||
let mut info = CodecInfo::soft();
|
||||
if enable_hwcodec_option() {
|
||||
if let Ok(hw) = get_config().map(|c| c.d) {
|
||||
if let Some(h264) = hw.h264 {
|
||||
info.h264 = Some(h264);
|
||||
}
|
||||
if let Some(h265) = hw.h265 {
|
||||
info.h265 = Some(h265);
|
||||
}
|
||||
}
|
||||
}
|
||||
info
|
||||
}
|
||||
|
||||
pub fn new(format: CodecFormat) -> ResultType<Self> {
|
||||
log::info!("try create {format:?} ram decoder");
|
||||
let best = HwDecoder::best();
|
||||
let best = HwRamDecoder::best();
|
||||
let info = match format {
|
||||
CodecFormat::H264 => {
|
||||
if let Some(info) = best.h264 {
|
||||
@@ -259,26 +264,26 @@ impl HwDecoder {
|
||||
thread_count: codec_thread_num(16) as _,
|
||||
};
|
||||
match Decoder::new(ctx) {
|
||||
Ok(decoder) => Ok(HwDecoder { decoder, info }),
|
||||
Ok(decoder) => Ok(HwRamDecoder { decoder, info }),
|
||||
Err(_) => {
|
||||
HwCodecConfig::clear();
|
||||
HwCodecConfig::clear_ram();
|
||||
Err(anyhow!(format!("Failed to create decoder")))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<HwDecoderImage>> {
|
||||
pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<HwRamDecoderImage>> {
|
||||
match self.decoder.decode(data) {
|
||||
Ok(v) => Ok(v.iter().map(|f| HwDecoderImage { frame: f }).collect()),
|
||||
Ok(v) => Ok(v.iter().map(|f| HwRamDecoderImage { frame: f }).collect()),
|
||||
Err(e) => Err(anyhow!(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HwDecoderImage<'a> {
|
||||
pub struct HwRamDecoderImage<'a> {
|
||||
frame: &'a DecodeFrame,
|
||||
}
|
||||
|
||||
impl HwDecoderImage<'_> {
|
||||
impl HwRamDecoderImage<'_> {
|
||||
// rgb [in/out] fmt and stride must be set in ImageRgb
|
||||
pub fn to_fmt(&self, rgb: &mut ImageRgb, i420: &mut Vec<u8>) -> ResultType<()> {
|
||||
let frame = self.frame;
|
||||
@@ -332,23 +337,24 @@ impl HwDecoderImage<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config(k: &str) -> ResultType<CodecInfos> {
|
||||
let v = HwCodecConfig::load()
|
||||
.options
|
||||
.get(k)
|
||||
.unwrap_or(&"".to_owned())
|
||||
.to_owned();
|
||||
match CodecInfos::deserialize(&v) {
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
struct Available {
|
||||
e: CodecInfos,
|
||||
d: CodecInfos,
|
||||
}
|
||||
|
||||
fn get_config() -> ResultType<Available> {
|
||||
match serde_json::from_str(&HwCodecConfig::load().ram) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(_) => Err(anyhow!("Failed to get config:{}", k)),
|
||||
Err(e) => Err(anyhow!("Failed to get config:{e:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_available_hwcodec() {
|
||||
let ctx = EncodeContext {
|
||||
name: String::from(""),
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
pixfmt: DEFAULT_PIXFMT,
|
||||
align: HW_STRIDE_ALIGN as _,
|
||||
bitrate: 0,
|
||||
@@ -358,30 +364,27 @@ pub fn check_available_hwcodec() {
|
||||
rc: DEFAULT_RC,
|
||||
thread_count: 4,
|
||||
};
|
||||
let encoders = CodecInfo::score(Encoder::available_encoders(ctx));
|
||||
let decoders = CodecInfo::score(Decoder::available_decoders());
|
||||
|
||||
if let Ok(old_encoders) = get_config(CFG_KEY_ENCODER) {
|
||||
if let Ok(old_decoders) = get_config(CFG_KEY_DECODER) {
|
||||
if encoders == old_encoders && decoders == old_decoders {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "vram")]
|
||||
let vram = crate::vram::check_available_vram();
|
||||
#[cfg(not(feature = "vram"))]
|
||||
let vram = "".to_owned();
|
||||
let encoders = CodecInfo::prioritized(Encoder::available_encoders(ctx, Some(vram.clone())));
|
||||
let decoders = CodecInfo::prioritized(Decoder::available_decoders(Some(vram.clone())));
|
||||
let ram = Available {
|
||||
e: encoders,
|
||||
d: decoders,
|
||||
};
|
||||
if let Ok(ram) = serde_json::to_string_pretty(&ram) {
|
||||
HwCodecConfig { ram, vram }.store();
|
||||
}
|
||||
|
||||
if let Ok(encoders) = encoders.serialize() {
|
||||
if let Ok(decoders) = decoders.serialize() {
|
||||
let mut config = HwCodecConfig::load();
|
||||
config.options.insert(CFG_KEY_ENCODER.to_owned(), encoders);
|
||||
config.options.insert(CFG_KEY_DECODER.to_owned(), decoders);
|
||||
config.store();
|
||||
return;
|
||||
}
|
||||
}
|
||||
log::error!("Failed to serialize codec info");
|
||||
}
|
||||
|
||||
pub fn hwcodec_new_check_process() {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
pub fn start_check_process(force: bool) {
|
||||
if !force && !enable_hwcodec_option() {
|
||||
return;
|
||||
}
|
||||
use hbb_common::allow_err;
|
||||
use std::sync::Once;
|
||||
let f = || {
|
||||
// Clear to avoid checking process errors
|
||||
@@ -421,7 +424,11 @@ pub fn hwcodec_new_check_process() {
|
||||
};
|
||||
};
|
||||
static ONCE: Once = Once::new();
|
||||
ONCE.call_once(|| {
|
||||
if force && ONCE.is_completed() {
|
||||
std::thread::spawn(f);
|
||||
});
|
||||
} else {
|
||||
ONCE.call_once(|| {
|
||||
std::thread::spawn(f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +37,13 @@ cfg_if! {
|
||||
|
||||
pub mod codec;
|
||||
pub mod convert;
|
||||
#[cfg(feature = "gpucodec")]
|
||||
pub mod gpucodec;
|
||||
#[cfg(feature = "hwcodec")]
|
||||
pub mod hwcodec;
|
||||
#[cfg(feature = "mediacodec")]
|
||||
pub mod mediacodec;
|
||||
pub mod vpxcodec;
|
||||
#[cfg(feature = "vram")]
|
||||
pub mod vram;
|
||||
pub use self::convert::*;
|
||||
pub const STRIDE_ALIGN: usize = 64; // commonly used in libvpx vpx_img_alloc caller
|
||||
pub const HW_STRIDE_ALIGN: usize = 0; // recommended by av_frame_get_buffer
|
||||
@@ -111,10 +111,10 @@ pub trait TraitCapturer {
|
||||
#[cfg(windows)]
|
||||
fn set_gdi(&mut self) -> bool;
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn device(&self) -> AdapterDevice;
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn set_output_texture(&mut self, texture: bool);
|
||||
}
|
||||
|
||||
@@ -245,10 +245,10 @@ pub enum CodecName {
|
||||
VP8,
|
||||
VP9,
|
||||
AV1,
|
||||
H264HW(String),
|
||||
H265HW(String),
|
||||
H264GPU,
|
||||
H265GPU,
|
||||
H264RAM(String),
|
||||
H265RAM(String),
|
||||
H264VRAM,
|
||||
H265VRAM,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
@@ -280,8 +280,8 @@ impl From<&CodecName> for CodecFormat {
|
||||
CodecName::VP8 => Self::VP8,
|
||||
CodecName::VP9 => Self::VP9,
|
||||
CodecName::AV1 => Self::AV1,
|
||||
CodecName::H264HW(_) | CodecName::H264GPU => Self::H264,
|
||||
CodecName::H265HW(_) | CodecName::H265GPU => Self::H265,
|
||||
CodecName::H264RAM(_) | CodecName::H264VRAM => Self::H264,
|
||||
CodecName::H265RAM(_) | CodecName::H265VRAM => Self::H265,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ impl EncoderApi for VpxEncoder {
|
||||
self.yuvfmt.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn input_texture(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -5,24 +5,24 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
codec::{base_bitrate, enable_gpucodec_option, EncoderApi, EncoderCfg, Quality},
|
||||
codec::{base_bitrate, enable_vram_option, EncoderApi, EncoderCfg, Quality},
|
||||
AdapterDevice, CodecFormat, CodecName, EncodeInput, EncodeYuvFormat, Pixfmt,
|
||||
};
|
||||
use gpucodec::gpu_common::{
|
||||
self, Available, DecodeContext, DynamicContext, EncodeContext, FeatureContext, MAX_GOP,
|
||||
};
|
||||
use gpucodec::{
|
||||
decode::{self, DecodeFrame, Decoder},
|
||||
encode::{self, EncodeFrame, Encoder},
|
||||
};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::{anyhow, bail, Context},
|
||||
bytes::Bytes,
|
||||
log,
|
||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
||||
ResultType,
|
||||
};
|
||||
use hwcodec::{
|
||||
common::{DataFormat, Driver, MAX_GOP},
|
||||
native::{
|
||||
decode::{self, DecodeFrame, Decoder},
|
||||
encode::{self, EncodeFrame, Encoder},
|
||||
Available, DecodeContext, DynamicContext, EncodeContext, FeatureContext,
|
||||
},
|
||||
};
|
||||
|
||||
const OUTPUT_SHARED_HANDLE: bool = false;
|
||||
|
||||
@@ -35,31 +35,31 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GpuEncoderConfig {
|
||||
pub struct VRamEncoderConfig {
|
||||
pub device: AdapterDevice,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub quality: Quality,
|
||||
pub feature: gpucodec::gpu_common::FeatureContext,
|
||||
pub feature: FeatureContext,
|
||||
pub keyframe_interval: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct GpuEncoder {
|
||||
pub struct VRamEncoder {
|
||||
encoder: Encoder,
|
||||
pub format: gpu_common::DataFormat,
|
||||
pub format: DataFormat,
|
||||
ctx: EncodeContext,
|
||||
bitrate: u32,
|
||||
last_frame_len: usize,
|
||||
same_bad_len_counter: usize,
|
||||
}
|
||||
|
||||
impl EncoderApi for GpuEncoder {
|
||||
impl EncoderApi for VRamEncoder {
|
||||
fn new(cfg: EncoderCfg, _i444: bool) -> ResultType<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match cfg {
|
||||
EncoderCfg::GPU(config) => {
|
||||
EncoderCfg::VRAM(config) => {
|
||||
let b = Self::convert_quality(config.quality, &config.feature);
|
||||
let base_bitrate = base_bitrate(config.width as _, config.height as _);
|
||||
let mut bitrate = base_bitrate * b / 100;
|
||||
@@ -79,7 +79,7 @@ impl EncoderApi for GpuEncoder {
|
||||
},
|
||||
};
|
||||
match Encoder::new(ctx.clone()) {
|
||||
Ok(encoder) => Ok(GpuEncoder {
|
||||
Ok(encoder) => Ok(VRamEncoder {
|
||||
encoder,
|
||||
ctx,
|
||||
format: config.feature.data_format,
|
||||
@@ -88,7 +88,7 @@ impl EncoderApi for GpuEncoder {
|
||||
same_bad_len_counter: 0,
|
||||
}),
|
||||
Err(_) => {
|
||||
hbb_common::config::GpucodecConfig::clear();
|
||||
hbb_common::config::HwCodecConfig::clear_vram();
|
||||
Err(anyhow!(format!("Failed to create encoder")))
|
||||
}
|
||||
}
|
||||
@@ -138,8 +138,8 @@ impl EncoderApi for GpuEncoder {
|
||||
..Default::default()
|
||||
};
|
||||
match self.format {
|
||||
gpu_common::DataFormat::H264 => vf.set_h264s(frames),
|
||||
gpu_common::DataFormat::H265 => vf.set_h265s(frames),
|
||||
DataFormat::H264 => vf.set_h264s(frames),
|
||||
DataFormat::H265 => vf.set_h265s(frames),
|
||||
_ => bail!("{:?} not supported", self.format),
|
||||
}
|
||||
Ok(vf)
|
||||
@@ -160,7 +160,7 @@ impl EncoderApi for GpuEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn input_texture(&self) -> bool {
|
||||
true
|
||||
}
|
||||
@@ -181,11 +181,11 @@ impl EncoderApi for GpuEncoder {
|
||||
}
|
||||
|
||||
fn support_abr(&self) -> bool {
|
||||
self.ctx.f.driver != gpu_common::EncodeDriver::VPL
|
||||
self.ctx.f.driver != Driver::VPL
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuEncoder {
|
||||
impl VRamEncoder {
|
||||
pub fn try_get(device: &AdapterDevice, name: CodecName) -> Option<FeatureContext> {
|
||||
let v: Vec<_> = Self::available(name)
|
||||
.drain(..)
|
||||
@@ -201,12 +201,12 @@ impl GpuEncoder {
|
||||
pub fn available(name: CodecName) -> Vec<FeatureContext> {
|
||||
let not_use = ENOCDE_NOT_USE.lock().unwrap().clone();
|
||||
if not_use.values().any(|not_use| *not_use) {
|
||||
log::info!("currently not use gpucodec encoders: {not_use:?}");
|
||||
log::info!("currently not use vram encoders: {not_use:?}");
|
||||
return vec![];
|
||||
}
|
||||
let data_format = match name {
|
||||
CodecName::H264GPU => gpu_common::DataFormat::H264,
|
||||
CodecName::H265GPU => gpu_common::DataFormat::H265,
|
||||
CodecName::H264VRAM => DataFormat::H264,
|
||||
CodecName::H265VRAM => DataFormat::H265,
|
||||
_ => return vec![],
|
||||
};
|
||||
let Ok(displays) = crate::Display::all() else {
|
||||
@@ -252,27 +252,21 @@ impl GpuEncoder {
|
||||
pub fn convert_quality(quality: Quality, f: &FeatureContext) -> u32 {
|
||||
match quality {
|
||||
Quality::Best => {
|
||||
if f.driver == gpu_common::EncodeDriver::VPL
|
||||
&& f.data_format == gpu_common::DataFormat::H264
|
||||
{
|
||||
if f.driver == Driver::VPL && f.data_format == DataFormat::H264 {
|
||||
200
|
||||
} else {
|
||||
150
|
||||
}
|
||||
}
|
||||
Quality::Balanced => {
|
||||
if f.driver == gpu_common::EncodeDriver::VPL
|
||||
&& f.data_format == gpu_common::DataFormat::H264
|
||||
{
|
||||
if f.driver == Driver::VPL && f.data_format == DataFormat::H264 {
|
||||
150
|
||||
} else {
|
||||
100
|
||||
}
|
||||
}
|
||||
Quality::Low => {
|
||||
if f.driver == gpu_common::EncodeDriver::VPL
|
||||
&& f.data_format == gpu_common::DataFormat::H264
|
||||
{
|
||||
if f.driver == Driver::VPL && f.data_format == DataFormat::H264 {
|
||||
75
|
||||
} else {
|
||||
50
|
||||
@@ -283,7 +277,7 @@ impl GpuEncoder {
|
||||
}
|
||||
|
||||
pub fn set_not_use(display: usize, not_use: bool) {
|
||||
log::info!("set display#{display} not use gpucodec encode to {not_use}");
|
||||
log::info!("set display#{display} not use vram encode to {not_use}");
|
||||
ENOCDE_NOT_USE.lock().unwrap().insert(display, not_use);
|
||||
}
|
||||
|
||||
@@ -292,17 +286,11 @@ impl GpuEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GpuDecoder {
|
||||
pub struct VRamDecoder {
|
||||
decoder: Decoder,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GpuDecoders {
|
||||
pub h264: Option<GpuDecoder>,
|
||||
pub h265: Option<GpuDecoder>,
|
||||
}
|
||||
|
||||
impl GpuDecoder {
|
||||
impl VRamDecoder {
|
||||
pub fn try_get(format: CodecFormat, luid: Option<i64>) -> Option<DecodeContext> {
|
||||
let v: Vec<_> = Self::available(format, luid);
|
||||
if v.len() > 0 {
|
||||
@@ -315,8 +303,8 @@ impl GpuDecoder {
|
||||
pub fn available(format: CodecFormat, luid: Option<i64>) -> Vec<DecodeContext> {
|
||||
let luid = luid.unwrap_or_default();
|
||||
let data_format = match format {
|
||||
CodecFormat::H264 => gpu_common::DataFormat::H264,
|
||||
CodecFormat::H265 => gpu_common::DataFormat::H265,
|
||||
CodecFormat::H264 => DataFormat::H264,
|
||||
CodecFormat::H265 => DataFormat::H265,
|
||||
_ => return vec![],
|
||||
};
|
||||
get_available_config()
|
||||
@@ -328,15 +316,13 @@ impl GpuDecoder {
|
||||
}
|
||||
|
||||
pub fn possible_available_without_check() -> (bool, bool) {
|
||||
if !enable_gpucodec_option() {
|
||||
if !enable_vram_option() {
|
||||
return (false, false);
|
||||
}
|
||||
let v = get_available_config().map(|c| c.d).unwrap_or_default();
|
||||
(
|
||||
v.iter()
|
||||
.any(|d| d.data_format == gpu_common::DataFormat::H264),
|
||||
v.iter()
|
||||
.any(|d| d.data_format == gpu_common::DataFormat::H265),
|
||||
v.iter().any(|d| d.data_format == DataFormat::H264),
|
||||
v.iter().any(|d| d.data_format == DataFormat::H265),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -346,7 +332,7 @@ impl GpuDecoder {
|
||||
match Decoder::new(ctx) {
|
||||
Ok(decoder) => Ok(Self { decoder }),
|
||||
Err(_) => {
|
||||
hbb_common::config::GpucodecConfig::clear();
|
||||
hbb_common::config::HwCodecConfig::clear_vram();
|
||||
Err(anyhow!(format!(
|
||||
"Failed to create decoder, format: {:?}",
|
||||
format
|
||||
@@ -354,33 +340,33 @@ impl GpuDecoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<GpuDecoderImage>> {
|
||||
pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<VRamDecoderImage>> {
|
||||
match self.decoder.decode(data) {
|
||||
Ok(v) => Ok(v.iter().map(|f| GpuDecoderImage { frame: f }).collect()),
|
||||
Ok(v) => Ok(v.iter().map(|f| VRamDecoderImage { frame: f }).collect()),
|
||||
Err(e) => Err(anyhow!(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GpuDecoderImage<'a> {
|
||||
pub struct VRamDecoderImage<'a> {
|
||||
pub frame: &'a DecodeFrame,
|
||||
}
|
||||
|
||||
impl GpuDecoderImage<'_> {}
|
||||
impl VRamDecoderImage<'_> {}
|
||||
|
||||
fn get_available_config() -> ResultType<Available> {
|
||||
let available = hbb_common::config::GpucodecConfig::load().available;
|
||||
let available = hbb_common::config::HwCodecConfig::load().vram;
|
||||
match Available::deserialize(&available) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(_) => Err(anyhow!("Failed to deserialize:{}", available)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_available_gpucodec() {
|
||||
pub(crate) fn check_available_vram() -> String {
|
||||
let d = DynamicContext {
|
||||
device: None,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
kbitrate: 5000,
|
||||
framerate: 60,
|
||||
gop: MAX_GOP as _,
|
||||
@@ -391,54 +377,5 @@ pub fn check_available_gpucodec() {
|
||||
e: encoders,
|
||||
d: decoders,
|
||||
};
|
||||
|
||||
if let Ok(available) = available.serialize() {
|
||||
let mut config = hbb_common::config::GpucodecConfig::load();
|
||||
config.available = available;
|
||||
config.store();
|
||||
return;
|
||||
}
|
||||
log::error!("Failed to serialize gpucodec");
|
||||
}
|
||||
|
||||
pub fn gpucodec_new_check_process() {
|
||||
use std::sync::Once;
|
||||
|
||||
static ONCE: Once = Once::new();
|
||||
ONCE.call_once(|| {
|
||||
std::thread::spawn(move || {
|
||||
// Remove to avoid checking process errors
|
||||
// But when the program is just started, the configuration file has not been updated, and the new connection will read an empty configuration
|
||||
hbb_common::config::GpucodecConfig::clear();
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
let arg = "--check-gpucodec-config";
|
||||
if let Ok(mut child) = std::process::Command::new(exe).arg(arg).spawn() {
|
||||
// wait up to 30 seconds
|
||||
for _ in 0..30 {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(Some(_)) = child.try_wait() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
allow_err!(child.kill());
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
log::info!("Check gpucodec config, exit with: {status}")
|
||||
}
|
||||
Ok(None) => {
|
||||
log::info!(
|
||||
"Check gpucodec config, status not ready yet, let's really wait"
|
||||
);
|
||||
let res = child.wait();
|
||||
log::info!("Check gpucodec config, wait result: {res:?}");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Check gpucodec config, error attempting to wait: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
available.serialize().unwrap_or_default()
|
||||
}
|
||||
@@ -185,7 +185,7 @@ impl Capturer {
|
||||
self.gdi_capturer.take();
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn set_output_texture(&mut self, texture: bool) {
|
||||
self.output_texture = texture;
|
||||
}
|
||||
@@ -620,7 +620,7 @@ impl Display {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn adapter_luid(&self) -> Option<i64> {
|
||||
unsafe {
|
||||
if !self.adapter.is_null() {
|
||||
|
||||
143
res/devices.py
Executable file
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
|
||||
**/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">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\DefaultIcon">
|
||||
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product)",0' />
|
||||
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product).exe",0' />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell" />
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell\open" />
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell\open\command">
|
||||
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product)" "--play" "%1"' />
|
||||
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product).exe" --play "%1"' />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
@@ -36,10 +36,20 @@
|
||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell" />
|
||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell\open" />
|
||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell\open\command">
|
||||
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product)" "%1"' />
|
||||
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product).exe" "%1"' />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<!--For compatibility with registry values from previous versions-->
|
||||
<Component Id="Product.Registry.UninstallApp" Guid="FC1A3D2E-5642-FBD8-CFA6-5ECAC6DE69A8">
|
||||
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\$(var.Product)" >
|
||||
<RegistryValue Type="string" Name="BuildDate" Value="$(var.BuildDate)" />
|
||||
<RegistryValue Type="string" Name="share_rdp" Value="" />
|
||||
|
||||
<!--$ArpStart$-->
|
||||
<!--$ArpEnd$-->
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
</DirectoryRef>
|
||||
|
||||
</Fragment>
|
||||
|
||||
@@ -5,87 +5,125 @@
|
||||
<?include ../Includes.wxi?>
|
||||
|
||||
<DirectoryRef Id="INSTALLFOLDER" FileSource="$(var.BuildDir)">
|
||||
<Component Id="RustDesk.exe" Guid="620F0F69-4C17-4320-A619-495E329712A4">
|
||||
<File Id="$(var.Product).exe" Name="$(var.Product).exe" KeyPath="yes" Checksum="yes">
|
||||
<fire:FirewallException Id="RustDeskExTCPDom" Name="$(var.Product) TCP Domain" Profile="domain" Protocol="tcp" Scope="any" IgnoreFailure="yes" />
|
||||
<fire:FirewallException Id="RustDeskExTCPPriv" Name="$(var.Product) TCP Private" Profile="private" Protocol="tcp" Scope="any" IgnoreFailure="yes" />
|
||||
<fire:FirewallException Id="RustDeskExUDPDom" Name="$(var.Product) UDP Domain" Profile="domain" Protocol="udp" Scope="any" IgnoreFailure="yes" />
|
||||
<fire:FirewallException Id="RustDeskExUDPPriv" Name="$(var.Product) UDP Private" Profile="private" Protocol="udp" Scope="any" IgnoreFailure="yes" />
|
||||
<Component Id="App.exe" Guid="620F0F69-4C17-4320-A619-495E329712A4">
|
||||
<File Id="App.exe" Name="$(var.Product).exe" KeyPath="yes" Checksum="yes">
|
||||
<!--<fire:FirewallException Id="AppEx" Name="$(var.Product) Service" Scope="any" IgnoreFailure="yes" />-->
|
||||
</File>
|
||||
<ServiceInstall Id="ServiceInstaller" Type="ownProcess" Vital="yes" Name="$(var.Product)" DisplayName="!(loc.Service_DisplayName)" Description="!(loc.Service_Description)" Start="auto" Account="LocalSystem" ErrorControl="ignore" Interactive="no" Arguments="--service" />
|
||||
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="$(var.Product)" Wait="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<SetProperty Id="RestartService" Value=""net" start $(var.Product)" Before="RestartService" Sequence="execute" />
|
||||
<CustomAction Id="RestartService" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
|
||||
|
||||
<CustomAction Id="RemoveInstallFolder.SetParam" Return="check" Property="RemoveInstallFolder" Value="[INSTALLFOLDER]" />
|
||||
<CustomAction Id="AddFirewallRules.SetParam" Return="check" Property="AddFirewallRules" Value="1[INSTALLFOLDER]$(var.Product).exe" />
|
||||
<CustomAction Id="RemoveFirewallRules.SetParam" Return="check" Property="RemoveFirewallRules" Value="0[INSTALLFOLDER]$(var.Product).exe" />
|
||||
<CustomAction Id="CreateStartService.SetParam" Return="check" Property="CreateStartService" Value="$(var.Product);"[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>
|
||||
|
||||
<!--The ServiceControl element above handles starting/stopping the server on install/uninstall,
|
||||
however it also needs to be restarted after a modify or repair install. This action does
|
||||
that provided the server feature is already installed and not being uninstalled.-->
|
||||
<Custom Action="SetPropertyIsServiceRunning" After="InstallInitialize" Condition="Installed" />
|
||||
<Custom Action="SetPropertyIsServiceRunning.SetParam.AppName" Before="SetPropertyIsServiceRunning" Condition="Installed" />
|
||||
<Custom Action="SetPropertyIsServiceRunning.SetParam.PropertyName" Before="SetPropertyIsServiceRunning" Condition="Installed" />
|
||||
|
||||
<Custom Action="RestartService" Before="InstallFinalize" />
|
||||
<Custom Action="SetPropertyServiceStop" After="InstallInitialize" Condition="NOT Installed" />
|
||||
<Custom Action="SetPropertyServiceStop.SetParam.ConfigFile" Before="SetPropertyServiceStop" Condition="NOT Installed" />
|
||||
<Custom Action="SetPropertyServiceStop.SetParam.ConfigKey" Before="SetPropertyServiceStop" Condition="NOT Installed" />
|
||||
<Custom Action="SetPropertyServiceStop.SetParam.PropertyName" Before="SetPropertyServiceStop" Condition="NOT Installed" />
|
||||
|
||||
<Custom Action="CreateStartService" Before="InstallFinalize" Condition="NOT STOP_SERVICE="'Y'"" />
|
||||
<Custom Action="CreateStartService.SetParam" Before="CreateStartService" Condition="NOT STOP_SERVICE="'Y'"" />
|
||||
|
||||
<Custom Action="CustomActionHello" Before="InstallFinalize" />
|
||||
|
||||
<Custom Action="SetCmdRemoveDir" After="RemoveFiles"/>
|
||||
<Custom Action="RunCommandAsSystem" After="SetCmdRemoveDir"/>
|
||||
<!--Shortcut is in InstallValidate section. So we just let it be created, then try delete if stopping service.-->
|
||||
<Custom Action="TryDeleteStartupShortcut" After="InstallFinalize" Condition="STOP_SERVICE="'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>
|
||||
|
||||
<!-- Shortcuts -->
|
||||
<DirectoryRef Id="App.StartMenu">
|
||||
<Component Id="App.StartMenu" Guid="30F6D57A-B805-4DA4-A071-05A3B22400CA">
|
||||
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu" Type="string" Value="1" KeyPath="yes" />
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu" Type="string" Value="1" KeyPath="yes" />
|
||||
<RemoveFolder Id="Remove.App.StartMenu" On="uninstall" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="App.StartMenu">
|
||||
<Component Id="App.StartMenu.Shortcut" Guid="43ABCAC7-E47D-42D8-A408-25EC70DBB993" Condition="STARTMENUSHORTCUTS = 1">
|
||||
|
||||
<Shortcut Id="App.StartMenu.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!RustDesk.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
|
||||
<Shortcut Id="App.StartMenu.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!App.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
|
||||
<!--
|
||||
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
|
||||
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
|
||||
-->
|
||||
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
||||
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
|
||||
https://learn.microsoft.com/en-us/windows/win32/msi/ice38
|
||||
-->
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="App.StartMenu.ShortcutTray" Guid="9362C316-40BB-41C1-859C-08182AA47E8D" Condition="STARTMENUSHORTCUTS = 1">
|
||||
|
||||
<Shortcut Id="App.StartMenu.ShortcutTray" Name="!(loc.SC_Client_Tray)" Description="!(loc.SC_Client_Tray_Desc)" Target="[!RustDesk.exe]" Arguments="--tray" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
|
||||
<!--
|
||||
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
|
||||
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
|
||||
-->
|
||||
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
||||
<Component Id="App.StartMenu.ShortcutUninstall" Guid="E100D7F8-D607-4513-28DA-2C95E5EA698E" Condition="STARTMENUSHORTCUTS = 1">
|
||||
<Shortcut Id="App.StartMenu.ShortcutUninstall" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System6432Folder]msiexec.exe" Arguments="/x [ProductCode]" Icon="AppIcon" />
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.ShortcutUninstall" Type="string" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<StandardDirectory Id="DesktopFolder">
|
||||
<Component Id="App.Desktop.Shortcut" Guid="CA8FB7AA-17F7-4E36-A58A-5A016A303709" Condition="DESKTOPSHORTCUTS = 1">
|
||||
|
||||
<Shortcut Id="App.Desktop.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!RustDesk.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
|
||||
<!--
|
||||
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
|
||||
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
|
||||
-->
|
||||
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.Desktop.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
||||
<Shortcut Id="App.Desktop.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!App.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.Desktop.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</StandardDirectory>
|
||||
<StandardDirectory Id="StartupFolder">
|
||||
<Component Id="App.StartupFolder.ShortcutTray" Guid="B1D1E2BB-E53E-E159-DB7C-744D5C726A8C" Condition="STARTUPSHORTCUTS = 1">
|
||||
<Shortcut Id="App.StartupFolder.ShortcutTray" Name="!(loc.SC_Client_Tray)" Description="!(loc.SC_Client_Tray_Desc)" Target="[!App.exe]" Arguments="--tray" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartupFolder.ShortcutTray" Type="string" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</StandardDirectory>
|
||||
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<!--<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Component Id="App.UninstallShortcut" Guid="FB0F2AC7-2AE5-4C54-B860-5E472620B6B1">
|
||||
<Shortcut Id="App.UninstallShortcut" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System64Folder]msiexec.exe" Arguments="/x [ProductCode]" IconIndex="0" />
|
||||
<Shortcut Id="App.UninstallShortcut" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System6432Folder]msiexec.exe" Arguments="/x [ProductCode]" Icon="AppIcon" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
</DirectoryRef>-->
|
||||
|
||||
<ComponentGroup Id="Components" Directory="INSTALLFOLDER">
|
||||
<ComponentRef Id="RustDesk.exe" />
|
||||
<ComponentRef Id="App.exe" />
|
||||
<ComponentRef Id="App.Desktop.Shortcut" />
|
||||
<ComponentRef Id="App.UninstallShortcut" />
|
||||
<!--<ComponentRef Id="App.UninstallShortcut" />-->
|
||||
<ComponentRef Id="App.StartMenu.Shortcut" />
|
||||
<ComponentRef Id="App.StartMenu.ShortcutTray" />
|
||||
<ComponentRef Id="App.StartMenu.ShortcutUninstall" />
|
||||
<ComponentRef Id="App.StartupFolder.ShortcutTray" />
|
||||
|
||||
<!--$AutoComonentStart$-->
|
||||
<!--$AutoComponentEnd$-->
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<?include ../Includes.wxi?>
|
||||
|
||||
|
||||
<Fragment>
|
||||
|
||||
<Property Id="AddRemovePropertiesFile" Value="1" />
|
||||
|
||||
<!--STOP_SERVICE is set to 'Y'. Because the cofig value may be empty or 'Y'-->
|
||||
<Property Id="STOP_SERVICE" Value="'Y'" />
|
||||
|
||||
<!--
|
||||
Support entries shown when clicking "Click here for support information"
|
||||
in Control Panel's Add/Remove Programs http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/configuration_properties.asp
|
||||
-->
|
||||
<Property Id="ARPCOMMENTS" Value="!(loc.AR_Comment)" />
|
||||
Support entries shown when clicking "Click here for support information"
|
||||
in Control Panel's Add/Remove Programs https://learn.microsoft.com/en-us/windows/win32/msi/property-reference
|
||||
-->
|
||||
<!--<Property Id="ARPCOMMENTS" Value="!(loc.AR_Comment)" />
|
||||
<Property Id="ARPCONTACT" Value="https://github.com/rustdesk/rustdesk" />
|
||||
<Property Id="ARPHELPLINK" Value="https://github.com/rustdesk/rustdesk" />
|
||||
<Property Id="ARPREADME" Value="https://github.com/rustdesk/rustdesk" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://github.com/rustdesk/rustdesk" />
|
||||
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/rustdesk/rustdesk" />
|
||||
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/rustdesk/rustdesk" />-->
|
||||
|
||||
<Property Id="ARPPRODUCTICON" Value="AppIcon" />
|
||||
|
||||
<Property Id="RUSTDESKNATIVEINSTALL">
|
||||
<RegistrySearch Id="RustDeskNativeInstallSearch" Root="HKCR" Key="$(var.RegKeyRoot)\DefaultIcon" Type="raw" />
|
||||
</Property>
|
||||
<Property Id="RUSTDESKNATIVEINSTALLFOLDER">
|
||||
<RegistrySearch Id="RustDeskNativeInstallFolderSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="INSTALLFOLDER" Type="raw" />
|
||||
</Property>
|
||||
<!--$ArpStart$-->
|
||||
<!--$ArpEnd$-->
|
||||
|
||||
<Property Id="APP_WINDOWS_INSTALLER">
|
||||
<RegistrySearch Id="AppWindowsInstallerFolderSearch" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\$(var.Product)" Name="WindowsInstaller" Type="raw" />
|
||||
</Property>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
<?include ../Includes.wxi?>
|
||||
|
||||
<Binary Id="Custom_Actions_Dll" SourceFile="$(var.CustomActions.TargetDir)$(var.CustomActions.TargetName).CA.dll" />
|
||||
<Fragment>
|
||||
<?include ../Includes.wxi?>
|
||||
|
||||
<CustomAction Id="CustomActionHello" DllEntry="CustomActionHello" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RunCommandAsSystem" DllEntry="RunCommandAsSystem" Impersonate="no" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<Binary Id="Custom_Actions_Dll" SourceFile="$(var.CustomActions.TargetDir)$(var.CustomActions.TargetName).dll" />
|
||||
|
||||
<Property Id="CMD" Value='echo "Hello"' />
|
||||
<CustomAction Id="SetCmdRemoveDir" Property="CMD" Value='rmdir /s /q "[INSTALLFOLDER]"' />
|
||||
|
||||
<!-- Use WixQuietExec to run the commands to avoid the command window popping up. The command line to run needs to be stored
|
||||
in a property with the same id as the WixQuietExec custom action and the path to the exe needs to be quoted.
|
||||
A SetProperty action is used to allow the [SystemFolder] reference to be resolved and needs to be scheduled to run before the action.-->
|
||||
|
||||
<SetProperty Id="FirewallPortOutAdd" Value=""[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="FirewallPortOutAdd" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
|
||||
<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="FirewallPortInAdd" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
|
||||
|
||||
<SetProperty Id="FirewallPortRemove" Value=""[SystemFolder]netsh.exe" advfirewall firewall delete rule name="$(var.Product) Service"" Before="FirewallPortRemove" Sequence="execute" />
|
||||
<CustomAction Id="FirewallPortRemove" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
|
||||
|
||||
</Fragment>
|
||||
<CustomAction Id="CustomActionHello" DllEntry="CustomActionHello" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RemoveInstallFolder" DllEntry="RemoveInstallFolder" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TerminateProcesses" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TerminateBrokers" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="AddFirewallRules" DllEntry="AddFirewallRules" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RemoveFirewallRules" DllEntry="AddFirewallRules" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="SetPropertyIsServiceRunning" DllEntry="SetPropertyIsServiceRunning" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="CreateStartService" DllEntry="CreateStartService" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TryStopDeleteService" DllEntry="TryStopDeleteService" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TryDeleteStartupShortcut" DllEntry="TryDeleteStartupShortcut" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="SetPropertyServiceStop" DllEntry="SetPropertyFromConfig" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="AddRegSoftwareSASGeneration" DllEntry="AddRegSoftwareSASGeneration" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RemoveAmyuniIdd" DllEntry="RemoveAmyuniIdd" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -1,51 +1,52 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
<Fragment>
|
||||
|
||||
<?include ..\Includes.wxi?>
|
||||
<?include ..\Includes.wxi?>
|
||||
|
||||
<!--
|
||||
Properties and related actions for specifying whether to install start menu/desktop shortcuts.
|
||||
-->
|
||||
<!--
|
||||
Properties and related actions for specifying whether to install start menu/desktop shortcuts.
|
||||
-->
|
||||
|
||||
<!-- These are the actual properties that get used in conditions to determine whether to
|
||||
install start menu shortcuts, they are initialized with a default value to install shortcuts.
|
||||
They should not be set directly from the command line or registry, instead the CREATE* properties
|
||||
below should be set, then they will update these properties with their values only if set. -->
|
||||
<Property Id="STARTMENUSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<Property Id="DESKTOPSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<!-- These are the actual properties that get used in conditions to determine whether to
|
||||
install start menu shortcuts, they are initialized with a default value to install shortcuts.
|
||||
They should not be set directly from the command line or registry, instead the CREATE* properties
|
||||
below should be set, then they will update these properties with their values only if set. -->
|
||||
<Property Id="STARTMENUSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<Property Id="DESKTOPSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<Property Id="STARTUPSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
|
||||
<!-- These properties get set from either the command line, bundle or registry value,
|
||||
if set they update the properties above with their value. -->
|
||||
<Property Id="CREATESTARTMENUSHORTCUTS" Secure="yes">
|
||||
<RegistrySearch Id="CreateStartMenuShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="STARTMENUSHORTCUTS" Type="raw" />
|
||||
</Property>
|
||||
<Property Id="CREATEDESKTOPSHORTCUTS" Secure="yes">
|
||||
<RegistrySearch Id="CreateDesktopShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="DESKTOPSHORTCUTS" Type="raw" />
|
||||
</Property>
|
||||
<!-- These properties get set from either the command line, bundle or registry value,
|
||||
if set they update the properties above with their value. -->
|
||||
<Property Id="CREATESTARTMENUSHORTCUTS" Secure="yes">
|
||||
<RegistrySearch Id="CreateStartMenuShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="STARTMENUSHORTCUTS" Type="raw" />
|
||||
</Property>
|
||||
<Property Id="CREATEDESKTOPSHORTCUTS" Secure="yes">
|
||||
<RegistrySearch Id="CreateDesktopShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="DESKTOPSHORTCUTS" Type="raw" />
|
||||
</Property>
|
||||
|
||||
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Component Id="Product.Registry.PersistedShortcutProperties" Guid="1BBAD054-6EC2-4362-BF1B-E8BDE988B597">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="STARTMENUSHORTCUTS" Value="[STARTMENUSHORTCUTS]" KeyPath="yes" />
|
||||
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="[DESKTOPSHORTCUTS]" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Component Id="Product.Registry.PersistedShortcutProperties" Guid="1BBAD054-6EC2-4362-BF1B-E8BDE988B597">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="STARTMENUSHORTCUTS" Value="[STARTMENUSHORTCUTS]" KeyPath="yes" />
|
||||
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="[DESKTOPSHORTCUTS]" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- If a property value has been passed via the command line (which includes when set from the bundle), the registry search will
|
||||
overwrite the command line value, these actions temporarily store the command line value before the registry search
|
||||
is performed so they can be restored after the registry search is complete -->
|
||||
<SetProperty Id="SavedStartMenuShortcutsCmdLineValue" Value="[CREATESTARTMENUSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
|
||||
<SetProperty Id="SavedDesktopShortcutsCmdLineValue" Value="[CREATEDESKTOPSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
|
||||
<!-- If a property value has been passed via the command line (which includes when set from the bundle), the registry search will
|
||||
overwrite the command line value, these actions temporarily store the command line value before the registry search
|
||||
is performed so they can be restored after the registry search is complete -->
|
||||
<SetProperty Id="SavedStartMenuShortcutsCmdLineValue" Value="[CREATESTARTMENUSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
|
||||
<SetProperty Id="SavedDesktopShortcutsCmdLineValue" Value="[CREATEDESKTOPSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
|
||||
|
||||
<!-- If a command line value was stored, restore it after the registry search has been performed -->
|
||||
<SetProperty Action="RestoreSavedStartMenuShortcutsValue" Id="CREATESTARTMENUSHORTCUTS" Value="[SavedStartMenuShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedStartMenuShortcutsCmdLineValue" />
|
||||
<SetProperty Action="RestoreSavedDesktopShortcutsValue" Id="CREATEDESKTOPSHORTCUTS" Value="[SavedDesktopShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedDesktopShortcutsCmdLineValue" />
|
||||
<!-- If a command line value was stored, restore it after the registry search has been performed -->
|
||||
<SetProperty Action="RestoreSavedStartMenuShortcutsValue" Id="CREATESTARTMENUSHORTCUTS" Value="[SavedStartMenuShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedStartMenuShortcutsCmdLineValue" />
|
||||
<SetProperty Action="RestoreSavedDesktopShortcutsValue" Id="CREATEDESKTOPSHORTCUTS" Value="[SavedDesktopShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedDesktopShortcutsCmdLineValue" />
|
||||
|
||||
<!-- If a command line value or registry value was set, update the main properties with the value -->
|
||||
<SetProperty Id="STARTMENUSHORTCUTS" Value="[CREATESTARTMENUSHORTCUTS]" After="RestoreSavedStartMenuShortcutsValue" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
|
||||
<SetProperty Id="DESKTOPSHORTCUTS" Value="[CREATEDESKTOPSHORTCUTS]" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
|
||||
<!-- If a command line value or registry value was set, update the main properties with the value -->
|
||||
<SetProperty Id="STARTMENUSHORTCUTS" Value="[CREATESTARTMENUSHORTCUTS]" After="RestoreSavedStartMenuShortcutsValue" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
|
||||
<SetProperty Id="DESKTOPSHORTCUTS" Value="[CREATEDESKTOPSHORTCUTS]" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
|
||||
|
||||
</Fragment>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
<Fragment>
|
||||
|
||||
<Property Id="UpgradesFile" Value="1" />
|
||||
<Property Id="UpgradesFile" Value="1" />
|
||||
|
||||
<Upgrade Id="9D8E3E95-42B8-427E-B801-79F3FE7B6DD7">
|
||||
<UpgradeVersion Property="OLD_VERSION_FOUND" Minimum="2.0.0.0" Maximum="2.99.99" IncludeMinimum="yes" IncludeMaximum="yes" OnlyDetect="no" IgnoreRemoveFailure="yes" MigrateFeatures="yes" />
|
||||
</Upgrade>
|
||||
<!--$UpgradeStart$-->
|
||||
<!--$UpgradeEnd$-->
|
||||
|
||||
</Fragment>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -4,7 +4,4 @@
|
||||
<!--$PreVarsStart$-->
|
||||
<!--$PreVarsEnd$-->
|
||||
|
||||
<!-- This should NEVER be changed ! -->
|
||||
<?define UpgradeCode = "9D8E3E95-42B8-427E-B801-79F3FE7B6DD7" ?>
|
||||
|
||||
</Include>
|
||||
|
||||
@@ -47,6 +47,6 @@ This file contains the declaration of all the localizable strings.
|
||||
|
||||
<!-- User Interfaces -->
|
||||
<String Id="AnotherAppDialogTitle" Value="Cancel installation."/>
|
||||
<String Id="AnotherAppDialogDescription" Value="The application is installed by another method, please uninstall it first."/>
|
||||
<String Id="AnotherAppDialogDescription" Value="The application is installed by self-installation method, please uninstall it first."/>
|
||||
|
||||
</WixLocalization>
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CustomActions\CustomActions.csproj" />
|
||||
<ProjectReference Include="..\CustomActions\CustomActions.vcxproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
<?include Includes.wxi?>
|
||||
|
||||
<Package Name="$(var.Product)" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" Language="!(loc.ProductLanguage)" Codepage="0" UpgradeCode="$(var.UpgradeCode)" Scope="perMachine">
|
||||
<Package Name="$(var.Product)" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" Language="!(loc.ProductLanguage)" UpgradeCode="$(var.UpgradeCode)" Scope="perMachine">
|
||||
|
||||
<SummaryInformation Keywords="Installer" Description="$(var.Description)" Codepage="!(loc.SummaryCodepage)" />
|
||||
|
||||
<!--<PropertyRef Id="UpgradesFile" />-->
|
||||
|
||||
|
||||
<PropertyRef Id="AddRemovePropertiesFile" />
|
||||
|
||||
<Media Id="1" Cabinet="cab1.cab" EmbedCab="yes" CompressionLevel="high" />
|
||||
@@ -22,39 +22,20 @@
|
||||
<UIRef Id="WixUI_ErrorProgressText" />
|
||||
|
||||
<InstallUISequence>
|
||||
<!--<Custom Action="ReadCustomPathsFromExistingPathsFile" Before="CostFinalize" Condition="NOT Installed" />-->
|
||||
<Show Dialog="AnotherAppDialog" Before="WelcomeDlg" Condition="Not installed AND RUSTDESKNATIVEINSTALL AND Not RUSTDESKNATIVEINSTALLFOLDER"/>
|
||||
|
||||
<Show Dialog="AnotherAppDialog" Before="WelcomeDlg" Condition="Not installed AND APP_WINDOWS_INSTALLER="#0""/>
|
||||
</InstallUISequence>
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<!-- Stop all MP2 processes -->
|
||||
<!--<Custom Action="StopProcesses" Before="ReadCustomPathsFromExistingPathsFile" />-->
|
||||
|
||||
<!-- Reads custom paths which maybe have been changed by the user in a former installation -->
|
||||
<!--<Custom Action="ReadCustomPathsFromExistingPathsFile" Before="PrepareXmlPathVariables" Condition="(NOT Installed) AND (INSTALLTYPE_CUSTOM = 0)" />-->
|
||||
|
||||
<!--<Custom Action="PrepareXmlPathVariables" Before="FileCost" Condition="NOT Installed" />-->
|
||||
<!--<Custom Action="AttachClientToServer" After="InstallFinalize" Condition="NOT Installed" />-->
|
||||
|
||||
<Custom Action="FirewallPortRemove" Before="InstallFinalize" Condition="REMOVE~="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" />
|
||||
|
||||
<Custom Action="CloseProcesses" Before="RemoveFiles" />
|
||||
<!--Only do InstallValidate if is not Uninstall-->
|
||||
<!--<InstallValidate Condition="NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE )" />-->
|
||||
<!--Only do InstallValidate if is Install-->
|
||||
<InstallValidate Condition="NOT Installed" />
|
||||
|
||||
</InstallExecuteSequence>
|
||||
<CustomAction Id="LaunchApp" ExeCommand="" Return="asyncNoWait" FileRef="RustDesk.exe" />
|
||||
<CustomAction Id="LaunchAppTray" ExeCommand=" --tray" Return="asyncNoWait" FileRef="RustDesk.exe" />
|
||||
<CustomAction Id="CloseProcesses" ExeCommand='taskkill /F /IM "$(var.Product).exe"' Return="asyncNoWait" Directory="INSTALLFOLDER" />
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" Schedule="afterInstallInitialize" />
|
||||
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" Schedule="afterInstallInitialize" AllowSameVersionUpgrades="yes" />
|
||||
|
||||
<Feature Id="App" Level="1" AllowAdvertise="no" Display="expand" Title="!(loc.F_App)" Description="!(loc.F_App_Desc)" AllowAbsent="no">
|
||||
<ComponentGroupRef Id="Components" />
|
||||
@@ -64,8 +45,13 @@
|
||||
<ComponentRef Id="Product.Registry.CommandPlay" />
|
||||
<ComponentRef Id="Product.Registry.URLProtocol" />
|
||||
<ComponentRef Id="Product.Registry.Command" />
|
||||
<ComponentRef Id="Product.Registry.UninstallApp" />
|
||||
<ComponentRef Id="App.StartMenu" />
|
||||
<ComponentRef Id="Product.Registry.PersistedShortcutProperties" />
|
||||
</Feature>
|
||||
|
||||
<!--https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set-->
|
||||
<!--$CustomBitmapsStart$-->
|
||||
<!--$CustomBitmapsEnd$-->
|
||||
</Package>
|
||||
</Wix>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
<UI>
|
||||
<Dialog Id="AnotherAppDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)">
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="!(loc.WixUICancel)">
|
||||
<Publish Event="EndDialog" Value="ErrorAbort" />
|
||||
</Control>
|
||||
<Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="!(loc.ExitDialogBitmap)" />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogDescription)" />
|
||||
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogTitle)" />
|
||||
</Dialog>
|
||||
</UI>
|
||||
</Fragment>
|
||||
<Fragment>
|
||||
<UI>
|
||||
<Dialog Id="AnotherAppDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)">
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="!(loc.WixUICancel)">
|
||||
<Publish Event="EndDialog" Value="ErrorAbort" />
|
||||
</Control>
|
||||
<Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="!(loc.ExitDialogBitmap)" />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogDescription)" />
|
||||
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogTitle)" />
|
||||
</Dialog>
|
||||
</UI>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -4,14 +4,41 @@ Use Visual Studio 2022 to compile this project.
|
||||
|
||||
This project is mainly derived from <https://github.com/MediaPortal/MediaPortal-2.git> .
|
||||
|
||||
## Steps
|
||||
|
||||
1. `python preprocess.py`, see `python preprocess.py -h` for help.
|
||||
2. Build the .sln solution.
|
||||
|
||||
Run `msiexec /i package.msi /l*v install.log` to record the log.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Put the custom dialog bitmaps in "Resources" directory. The supported bitmaps are `['WixUIBannerBmp', 'WixUIDialogBmp', 'WixUIExclamationIco', 'WixUIInfoIco', 'WixUINewIco', 'WixUIUpIco']`.
|
||||
|
||||
## Knowledge
|
||||
|
||||
### properties
|
||||
|
||||
[wix-toolset-set-custom-action-run-only-on-uninstall](https://www.advancedinstaller.com/versus/wix-toolset/wix-toolset-set-custom-action-run-only-on-uninstall.html)
|
||||
|
||||
| Property Name | Install | Uninstall | Change | Repair | Upgrade |
|
||||
| ------ | ------ | ------ | ------ | ------ | ------ |
|
||||
| Installed | False | True | True | True | True |
|
||||
| REINSTALL | False | False | False | True | False |
|
||||
| UPGRADINGPRODUCTCODE | False | False | False | False | True |
|
||||
| REMOVE | False | True | False | False | True |
|
||||
|
||||
## TODOs
|
||||
|
||||
1. tray, uninstall shortcut
|
||||
1. launch client after installation
|
||||
1. github ci
|
||||
1. options
|
||||
1. Start menu. Uninstall
|
||||
1. custom options
|
||||
1. Custom client.
|
||||
1. firewall and tcp allow. Outgoing
|
||||
1. Custom icon. Current `Resources/icon.ico`.
|
||||
1. Show license ?
|
||||
1. Do create service. Outgoing.
|
||||
|
||||
## Refs
|
||||
|
||||
1. [windows-installer-portal](https://learn.microsoft.com/en-us/windows/win32/Msi/windows-installer-portal)
|
||||
1. [wxs](https://wixtoolset.org/docs/schema/wxs/)
|
||||
1. [wxs github](https://github.com/wixtoolset/wix)
|
||||
|
||||
@@ -4,34 +4,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
VisualStudioVersion = 17.7.34003.232
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Package", "Package\Package.wixproj", "{F403A403-CEFF-4399-B51C-CC646C8E98CF}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{95BE171E-6438-4F45-9876-0B667D9F7830} = {95BE171E-6438-4F45-9876-0B667D9F7830}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomActions", "CustomActions\CustomActions.csproj", "{95BE171E-6438-4F45-9876-0B667D9F7830}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CustomActions", "CustomActions\CustomActions.vcxproj", "{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|Any CPU.ActiveCfg = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|Any CPU.Build.0 = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|x64.ActiveCfg = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Debug|x64.Build.0 = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|Any CPU.Build.0 = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.ActiveCfg = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.Build.0 = Release|x64
|
||||
{95BE171E-6438-4F45-9876-0B667D9F7830}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{95BE171E-6438-4F45-9876-0B667D9F7830}.Debug|x64.ActiveCfg = Release|Any CPU
|
||||
{95BE171E-6438-4F45-9876-0B667D9F7830}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{95BE171E-6438-4F45-9876-0B667D9F7830}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{95BE171E-6438-4F45-9876-0B667D9F7830}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{95BE171E-6438-4F45-9876-0B667D9F7830}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.ActiveCfg = Release|x64
|
||||
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,69 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
import argparse
|
||||
import datetime
|
||||
import subprocess
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
g_indent_unit = "\t"
|
||||
g_version = ""
|
||||
g_build_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
# Replace the following links with your own in the custom arp properties.
|
||||
# https://learn.microsoft.com/en-us/windows/win32/msi/property-reference
|
||||
g_arpsystemcomponent = {
|
||||
"Comments": {
|
||||
"msi": "ARPCOMMENTS",
|
||||
"t": "string",
|
||||
"v": "!(loc.AR_Comment)",
|
||||
},
|
||||
"Contact": {
|
||||
"msi": "ARPCONTACT",
|
||||
"v": "https://github.com/rustdesk/rustdesk",
|
||||
},
|
||||
"HelpLink": {
|
||||
"msi": "ARPHELPLINK",
|
||||
"v": "https://github.com/rustdesk/rustdesk/issues/",
|
||||
},
|
||||
"ReadMe": {
|
||||
"msi": "ARPREADME",
|
||||
"v": "https://github.com/fufesou/rustdesk",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def make_parser():
|
||||
parser = argparse.ArgumentParser(description="Msi preprocess script.")
|
||||
parser.add_argument(
|
||||
"-d", "--debug", action="store_true", help="Is debug", default=False
|
||||
"-d",
|
||||
"--dist-dir",
|
||||
type=str,
|
||||
default="../../rustdesk",
|
||||
help="The dist direcotry to install.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--arp",
|
||||
action="store_true",
|
||||
help="Is ARPSYSTEMCOMPONENT",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--custom-arp",
|
||||
type=str,
|
||||
default="{}",
|
||||
help='Custom arp properties, e.g. \'["Comments": {"msi": "ARPCOMMENTS", "v": "Remote control application."}]\'',
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--custom", action="store_true", help="Is custom client", default=False
|
||||
)
|
||||
parser.add_argument(
|
||||
"-an", "--app-name", type=str, default="RustDesk", help="The app name."
|
||||
"--app-name", type=str, default="RustDesk", help="The app name."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--version", type=str, default="1.2.4", help="The app version."
|
||||
"-v", "--version", type=str, default="", help="The app version."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--manufacturer",
|
||||
type=str,
|
||||
default="Purslane Ltd",
|
||||
default="PURSLANE",
|
||||
help="The app manufacturer.",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def read_lines_and_start_index(file_path, start_tag, end_tag):
|
||||
def read_lines_and_start_index(file_path, tag_start, tag_end):
|
||||
with open(file_path, "r") as f:
|
||||
lines = f.readlines()
|
||||
start_index = -1
|
||||
end_index = -1
|
||||
index_start = -1
|
||||
index_end = -1
|
||||
for i, line in enumerate(lines):
|
||||
if start_tag in line:
|
||||
start_index = i
|
||||
if end_tag in line:
|
||||
end_index = i
|
||||
if tag_start in line:
|
||||
index_start = i
|
||||
if tag_end in line:
|
||||
index_end = i
|
||||
|
||||
if start_index == -1 or end_index == -1:
|
||||
print("Error: start or end tag not found")
|
||||
if index_start == -1:
|
||||
print(f'Error: start tag "{tag_start}" not found')
|
||||
return None, None
|
||||
return lines, start_index
|
||||
if index_end == -1:
|
||||
print(f'Error: end tag "{tag_end}" not found')
|
||||
return None, None
|
||||
return lines, index_start
|
||||
|
||||
|
||||
def insert_components_between_tags(lines, start_index, app_name, build_dir):
|
||||
def insert_components_between_tags(lines, index_start, app_name, dist_dir):
|
||||
indent = g_indent_unit * 3
|
||||
path = Path(build_dir)
|
||||
path = Path(dist_dir)
|
||||
idx = 1
|
||||
for file_path in path.glob("**/*"):
|
||||
if file_path.is_file():
|
||||
if file_path.name.lower() == f"{app_name}.exe".lower():
|
||||
continue
|
||||
|
||||
relative_file_path = file_path.relative_to(path)
|
||||
guid = uuid.uuid5(
|
||||
uuid.NAMESPACE_OID, app_name + "/" + str(relative_file_path)
|
||||
)
|
||||
|
||||
subdir = str(file_path.parent.relative_to(path))
|
||||
dir_attr = ""
|
||||
if subdir != ".":
|
||||
@@ -73,67 +115,59 @@ def insert_components_between_tags(lines, start_index, app_name, build_dir):
|
||||
# because it will cause error
|
||||
# "Error WIX0130 The primary key 'xxxx' is duplicated in table 'Directory'"
|
||||
to_insert_lines = f"""
|
||||
{indent}<Component Guid="{guid}" {dir_attr}>
|
||||
{indent}<Component Guid="{uuid.uuid4()}" {dir_attr}>
|
||||
{indent}{g_indent_unit}<File Source="{file_path.as_posix()}" KeyPath="yes" Checksum="yes" />
|
||||
{indent}</Component>
|
||||
"""
|
||||
lines.insert(start_index + 1, to_insert_lines[1:])
|
||||
start_index += 1
|
||||
lines.insert(index_start + 1, to_insert_lines[1:])
|
||||
index_start += 1
|
||||
idx += 1
|
||||
return True
|
||||
|
||||
|
||||
def gen_auto_component(app_name, build_dir):
|
||||
target_file = Path(sys.argv[0]).parent.joinpath("Package/Components/RustDesk.wxs")
|
||||
start_tag = "<!--$AutoComonentStart$-->"
|
||||
end_tag = "<!--$AutoComponentEnd$-->"
|
||||
|
||||
lines, start_index = read_lines_and_start_index(target_file, start_tag, end_tag)
|
||||
if lines is None:
|
||||
return False
|
||||
|
||||
if not insert_components_between_tags(lines, start_index, app_name, build_dir):
|
||||
return False
|
||||
|
||||
with open(target_file, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
return True
|
||||
def gen_auto_component(app_name, dist_dir):
|
||||
return gen_content_between_tags(
|
||||
"Package/Components/RustDesk.wxs",
|
||||
"<!--$AutoComonentStart$-->",
|
||||
"<!--$AutoComponentEnd$-->",
|
||||
lambda lines, index_start: insert_components_between_tags(
|
||||
lines, index_start, app_name, dist_dir
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def gen_pre_vars(args, build_dir):
|
||||
target_file = Path(sys.argv[0]).parent.joinpath("Package/Includes.wxi")
|
||||
start_tag = "<!--$PreVarsStart$-->"
|
||||
end_tag = "<!--$PreVarsEnd$-->"
|
||||
def gen_pre_vars(args, dist_dir):
|
||||
def func(lines, index_start):
|
||||
upgrade_code = uuid.uuid5(uuid.NAMESPACE_OID, app_name + ".exe")
|
||||
|
||||
lines, start_index = read_lines_and_start_index(target_file, start_tag, end_tag)
|
||||
if lines is None:
|
||||
return False
|
||||
indent = g_indent_unit * 1
|
||||
to_insert_lines = [
|
||||
f'{indent}<?define Version="{g_version}" ?>\n',
|
||||
f'{indent}<?define Manufacturer="{args.manufacturer}" ?>\n',
|
||||
f'{indent}<?define Product="{args.app_name}" ?>\n',
|
||||
f'{indent}<?define Description="{args.app_name} Installer" ?>\n',
|
||||
f'{indent}<?define ProductLower="{args.app_name.lower()}" ?>\n',
|
||||
f'{indent}<?define RegKeyRoot=".$(var.ProductLower)" ?>\n',
|
||||
f'{indent}<?define RegKeyInstall="$(var.RegKeyRoot)\\Install" ?>\n',
|
||||
f'{indent}<?define BuildDir="{dist_dir}" ?>\n',
|
||||
f'{indent}<?define BuildDate="{g_build_date}" ?>\n',
|
||||
"\n",
|
||||
f"{indent}<!-- The UpgradeCode must be consistent for each product. ! -->\n"
|
||||
f'{indent}<?define UpgradeCode = "{upgrade_code}" ?>\n',
|
||||
]
|
||||
|
||||
indent = g_indent_unit * 1
|
||||
to_insert_lines = [
|
||||
f'{indent}<?define Version="{args.version}" ?>\n',
|
||||
f'{indent}<?define Manufacturer="{args.manufacturer}" ?>\n',
|
||||
f'{indent}<?define Product="{args.app_name}" ?>\n',
|
||||
f'{indent}<?define Description="{args.app_name} Installer" ?>\n',
|
||||
f'{indent}<?define ProductLower="{args.app_name.lower()}" ?>\n',
|
||||
f'{indent}<?define RegKeyRoot=".$(var.ProductLower)" ?>\n',
|
||||
f'{indent}<?define RegKeyInstall="$(var.RegKeyRoot)\Install" ?>\n',
|
||||
f'{indent}<?define BuildDir="{build_dir}" ?>\n',
|
||||
]
|
||||
for i, line in enumerate(to_insert_lines):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
for i, line in enumerate(to_insert_lines):
|
||||
lines.insert(start_index + i + 1, line)
|
||||
|
||||
with open(target_file, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
return True
|
||||
return gen_content_between_tags(
|
||||
"Package/Includes.wxi", "<!--$PreVarsStart$-->", "<!--$PreVarsEnd$-->", func
|
||||
)
|
||||
|
||||
|
||||
def replace_app_name_in_lans(app_name):
|
||||
def replace_app_name_in_langs(app_name):
|
||||
langs_dir = Path(sys.argv[0]).parent.joinpath("Package/Language")
|
||||
for file_path in langs_dir.glob("*.wxs"):
|
||||
for file_path in langs_dir.glob("*.wxl"):
|
||||
with open(file_path, "r") as f:
|
||||
lines = f.readlines()
|
||||
for i, line in enumerate(lines):
|
||||
@@ -142,23 +176,295 @@ def replace_app_name_in_lans(app_name):
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
def gen_upgrade_info():
|
||||
def func(lines, index_start):
|
||||
indent = g_indent_unit * 3
|
||||
|
||||
vs = g_version.split(".")
|
||||
major = vs[0]
|
||||
upgrade_id = uuid.uuid4()
|
||||
to_insert_lines = [
|
||||
f'{indent}<Upgrade Id="{upgrade_id}">\n',
|
||||
f'{indent}{g_indent_unit}<UpgradeVersion Property="OLD_VERSION_FOUND" Minimum="{major}.0.0" Maximum="{major}.99.99" IncludeMinimum="yes" IncludeMaximum="yes" OnlyDetect="no" IgnoreRemoveFailure="yes" MigrateFeatures="yes" />\n',
|
||||
f"{indent}</Upgrade>\n",
|
||||
]
|
||||
|
||||
for i, line in enumerate(to_insert_lines):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Fragments/Upgrades.wxs",
|
||||
"<!--$UpgradeStart$-->",
|
||||
"<!--$UpgradeEnd$-->",
|
||||
func,
|
||||
)
|
||||
|
||||
|
||||
def gen_custom_dialog_bitmaps():
|
||||
def func(lines, index_start):
|
||||
indent = g_indent_unit * 2
|
||||
|
||||
# https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set
|
||||
vars = [
|
||||
"WixUIBannerBmp",
|
||||
"WixUIDialogBmp",
|
||||
"WixUIExclamationIco",
|
||||
"WixUIInfoIco",
|
||||
"WixUINewIco",
|
||||
"WixUIUpIco",
|
||||
]
|
||||
to_insert_lines = []
|
||||
for var in vars:
|
||||
if Path(f"Package/Resources/{var}.bmp").exists():
|
||||
to_insert_lines.append(
|
||||
f'{indent}<WixVariable Id="{var}" Value="Resources\\{var}.bmp" />\n'
|
||||
)
|
||||
|
||||
for i, line in enumerate(to_insert_lines):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Package.wxs",
|
||||
"<!--$CustomBitmapsStart$-->",
|
||||
"<!--$CustomBitmapsEnd$-->",
|
||||
func,
|
||||
)
|
||||
|
||||
|
||||
def gen_custom_ARPSYSTEMCOMPONENT_False(args):
|
||||
def func(lines, index_start):
|
||||
indent = g_indent_unit * 2
|
||||
|
||||
lines_new = []
|
||||
lines_new.append(
|
||||
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/arpsystemcomponent?redirectedfrom=MSDN-->\n"
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<!--<Property Id="ARPSYSTEMCOMPONENT" Value="1" />-->\n\n'
|
||||
)
|
||||
|
||||
lines_new.append(
|
||||
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/property-reference-->\n"
|
||||
)
|
||||
for _, v in g_arpsystemcomponent.items():
|
||||
if "msi" in v and "v" in v:
|
||||
lines_new.append(
|
||||
f'{indent}<Property Id="{v["msi"]}" Value="{v["v"]}" />\n'
|
||||
)
|
||||
|
||||
for i, line in enumerate(lines_new):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Fragments/AddRemoveProperties.wxs",
|
||||
"<!--$ArpStart$-->",
|
||||
"<!--$ArpEnd$-->",
|
||||
func,
|
||||
)
|
||||
|
||||
|
||||
def get_folder_size(folder_path):
|
||||
total_size = 0
|
||||
|
||||
folder = Path(folder_path)
|
||||
for file in folder.glob("**/*"):
|
||||
if file.is_file():
|
||||
total_size += file.stat().st_size
|
||||
|
||||
return total_size
|
||||
|
||||
|
||||
def gen_custom_ARPSYSTEMCOMPONENT_True(args, dist_dir):
|
||||
def func(lines, index_start):
|
||||
indent = g_indent_unit * 5
|
||||
|
||||
lines_new = []
|
||||
lines_new.append(
|
||||
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/property-reference-->\n"
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="DisplayName" Value="{args.app_name}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="DisplayIcon" Value="[INSTALLFOLDER]{args.app_name}.exe" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="DisplayVersion" Value="{g_version}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="Publisher" Value="{args.manufacturer}" />\n'
|
||||
)
|
||||
installDate = datetime.datetime.now().strftime("%Y%m%d")
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="InstallDate" Value="{installDate}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="InstallLocation" Value="[INSTALLFOLDER]" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="InstallSource" Value="[InstallSource]" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="Language" Value="[ProductLanguage]" />\n'
|
||||
)
|
||||
|
||||
estimated_size = get_folder_size(dist_dir)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="EstimatedSize" Value="{estimated_size}" />\n'
|
||||
)
|
||||
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="expandable" Name="ModifyPath" Value="MsiExec.exe /X [ProductCode]" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Id="NoModify" Value="1" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="expandable" Name="UninstallString" Value="MsiExec.exe /X [ProductCode]" />\n'
|
||||
)
|
||||
|
||||
vs = g_version.split(".")
|
||||
major, minor, build = vs[0], vs[1], vs[2]
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="Version" Value="{g_version}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="VersionMajor" Value="{major}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="VersionMinor" Value="{minor}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="VersionBuild" Value="{build}" />\n'
|
||||
)
|
||||
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="WindowsInstaller" Value="1" />\n'
|
||||
)
|
||||
for k, v in g_arpsystemcomponent.items():
|
||||
if "v" in v:
|
||||
t = v["t"] if "t" in v is None else "string"
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="{t}" Name="{k}" Value="{v["v"]}" />\n'
|
||||
)
|
||||
|
||||
for i, line in enumerate(lines_new):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Components/Regs.wxs",
|
||||
"<!--$ArpStart$-->",
|
||||
"<!--$ArpEnd$-->",
|
||||
func,
|
||||
)
|
||||
|
||||
|
||||
def gen_custom_ARPSYSTEMCOMPONENT(args, dist_dir):
|
||||
try:
|
||||
custom_arp = json.loads(args.custom_arp)
|
||||
g_arpsystemcomponent.update(custom_arp)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Failed to decode custom arp: {e}")
|
||||
return False
|
||||
|
||||
if args.arp:
|
||||
return gen_custom_ARPSYSTEMCOMPONENT_True(args, dist_dir)
|
||||
else:
|
||||
return gen_custom_ARPSYSTEMCOMPONENT_False(args)
|
||||
|
||||
|
||||
def gen_content_between_tags(filename, tag_start, tag_end, func):
|
||||
target_file = Path(sys.argv[0]).parent.joinpath(filename)
|
||||
lines, index_start = read_lines_and_start_index(target_file, tag_start, tag_end)
|
||||
if lines is None:
|
||||
return False
|
||||
|
||||
func(lines, index_start)
|
||||
|
||||
with open(target_file, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def init_global_vars(dist_dir, app_name, args):
|
||||
dist_app = dist_dir.joinpath(app_name + ".exe")
|
||||
|
||||
def read_process_output(args):
|
||||
process = subprocess.Popen(
|
||||
f"{dist_app} {args}",
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
shell=True,
|
||||
)
|
||||
output, _ = process.communicate()
|
||||
return output.decode("utf-8").strip()
|
||||
|
||||
global g_version
|
||||
global g_build_date
|
||||
g_version = args.version.replace("-", ".")
|
||||
if g_version == "":
|
||||
g_version = read_process_output("--version")
|
||||
version_pattern = re.compile(r"\d+\.\d+\.\d+.*")
|
||||
if not version_pattern.match(g_version):
|
||||
print(f"Error: version {g_version} not found in {dist_app}")
|
||||
return False
|
||||
|
||||
g_build_date = read_process_output("--build-date")
|
||||
build_date_pattern = re.compile(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}")
|
||||
if not build_date_pattern.match(g_build_date):
|
||||
print(f"Error: build date {g_build_date} not found in {dist_app}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def replace_component_guids_in_wxs():
|
||||
langs_dir = Path(sys.argv[0]).parent.joinpath("Package")
|
||||
for file_path in langs_dir.glob("**/*.wxs"):
|
||||
with open(file_path, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# <Component Id="Product.Registry.DefaultIcon" Guid="6DBF2690-0955-4C6A-940F-634DDA503F49">
|
||||
for i, line in enumerate(lines):
|
||||
match = re.search(r'Component.+Guid="([^"]+)"', line)
|
||||
if match:
|
||||
lines[i] = re.sub(r'Guid="[^"]+"', f'Guid="{uuid.uuid4()}"', line)
|
||||
|
||||
with open(file_path, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = make_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
app_name = args.app_name
|
||||
build_dir = (
|
||||
Path(sys.argv[0])
|
||||
.parent.joinpath(
|
||||
f'../../flutter/build/windows/x64/runner/{"Debug" if args.debug else "Release"}'
|
||||
)
|
||||
.resolve()
|
||||
)
|
||||
dist_dir = Path(sys.argv[0]).parent.joinpath(args.dist_dir).resolve()
|
||||
|
||||
if not gen_pre_vars(args, build_dir):
|
||||
if not init_global_vars(dist_dir, app_name, args):
|
||||
sys.exit(-1)
|
||||
|
||||
if not gen_auto_component(app_name, build_dir):
|
||||
if not gen_pre_vars(args, dist_dir):
|
||||
sys.exit(-1)
|
||||
|
||||
replace_app_name_in_lans(args.app_name)
|
||||
if app_name != "RustDesk":
|
||||
replace_component_guids_in_wxs()
|
||||
|
||||
if not gen_upgrade_info():
|
||||
sys.exit(-1)
|
||||
|
||||
if not gen_custom_ARPSYSTEMCOMPONENT(args, dist_dir):
|
||||
sys.exit(-1)
|
||||
|
||||
if not gen_auto_component(app_name, dist_dir):
|
||||
sys.exit(-1)
|
||||
|
||||
if not gen_custom_dialog_bitmaps():
|
||||
sys.exit(-1)
|
||||
|
||||
replace_app_name_in_langs(args.app_name)
|
||||
|
||||
@@ -1038,9 +1038,9 @@ pub struct VideoHandler {
|
||||
impl VideoHandler {
|
||||
/// Create a new video handler.
|
||||
pub fn new(format: CodecFormat, _display: usize) -> Self {
|
||||
#[cfg(all(feature = "gpucodec", feature = "flutter"))]
|
||||
#[cfg(all(feature = "vram", feature = "flutter"))]
|
||||
let luid = crate::flutter::get_adapter_luid();
|
||||
#[cfg(not(all(feature = "gpucodec", feature = "flutter")))]
|
||||
#[cfg(not(all(feature = "vram", feature = "flutter")))]
|
||||
let luid = Default::default();
|
||||
log::info!("new video handler for display #{_display}, format: {format:?}, luid: {luid:?}");
|
||||
VideoHandler {
|
||||
@@ -1097,9 +1097,9 @@ impl VideoHandler {
|
||||
|
||||
/// Reset the decoder, change format if it is Some
|
||||
pub fn reset(&mut self, format: Option<CodecFormat>) {
|
||||
#[cfg(all(feature = "flutter", feature = "gpucodec"))]
|
||||
#[cfg(all(feature = "flutter", feature = "vram"))]
|
||||
let luid = crate::flutter::get_adapter_luid();
|
||||
#[cfg(not(all(feature = "flutter", feature = "gpucodec")))]
|
||||
#[cfg(not(all(feature = "flutter", feature = "vram")))]
|
||||
let luid = None;
|
||||
let format = format.unwrap_or(self.decoder.format());
|
||||
self.decoder = Decoder::new(format, luid);
|
||||
@@ -1564,22 +1564,13 @@ impl LoginConfigHandler {
|
||||
///
|
||||
/// * `ignore_default` - If `true`, ignore the default value of the option.
|
||||
fn get_option_message(&self, ignore_default: bool) -> Option<OptionMessage> {
|
||||
if self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP) {
|
||||
if self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP) || self.conn_type.eq(&ConnType::FILE_TRANSFER) {
|
||||
return None;
|
||||
}
|
||||
let mut n = 0;
|
||||
let mut msg = OptionMessage::new();
|
||||
// Version 1.2.5 can remove this, and OptionMessage is not needed for file transfer
|
||||
msg.support_windows_specific_session = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
|
||||
if self.conn_type.eq(&ConnType::FILE_TRANSFER) {
|
||||
return Some(msg);
|
||||
}
|
||||
let q = self.image_quality.clone();
|
||||
if let Some(q) = self.get_image_quality_enum(&q, ignore_default) {
|
||||
msg.image_quality = q.into();
|
||||
n += 1;
|
||||
} else if q == "custom" {
|
||||
let config = self.load_config();
|
||||
let allow_more = !crate::using_public_server() || self.direct == Some(true);
|
||||
@@ -1602,32 +1593,25 @@ impl LoginConfigHandler {
|
||||
msg.custom_fps = custom_fps;
|
||||
*self.custom_fps.lock().unwrap() = Some(custom_fps as _);
|
||||
}
|
||||
n += 1;
|
||||
}
|
||||
let view_only = self.get_toggle_option("view-only");
|
||||
if view_only {
|
||||
msg.disable_keyboard = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
}
|
||||
if view_only || self.get_toggle_option("show-remote-cursor") {
|
||||
msg.show_remote_cursor = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
}
|
||||
if !view_only && self.get_toggle_option("lock-after-session-end") {
|
||||
msg.lock_after_session_end = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
}
|
||||
if self.get_toggle_option("disable-audio") {
|
||||
msg.disable_audio = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
}
|
||||
if !view_only && self.get_toggle_option("enable-file-transfer") {
|
||||
msg.enable_file_transfer = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
}
|
||||
if view_only || self.get_toggle_option("disable-clipboard") {
|
||||
msg.disable_clipboard = BoolOption::Yes.into();
|
||||
n += 1;
|
||||
}
|
||||
msg.supported_decoding =
|
||||
hbb_common::protobuf::MessageField::some(Decoder::supported_decodings(
|
||||
@@ -1636,12 +1620,7 @@ impl LoginConfigHandler {
|
||||
self.adapter_luid,
|
||||
&self.mark_unsupported,
|
||||
));
|
||||
n += 1;
|
||||
if n > 0 {
|
||||
Some(msg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(msg)
|
||||
}
|
||||
|
||||
pub fn get_option_message_after_login(&self) -> Option<OptionMessage> {
|
||||
|
||||
@@ -1304,8 +1304,14 @@ pub async fn get_next_nonkeyexchange_msg(
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn check_process(arg: &str, same_uid: bool) -> bool {
|
||||
pub fn check_process(arg: &str, mut same_uid: bool) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
if !crate::platform::is_root() && !same_uid {
|
||||
log::warn!("Can not get other process's command line arguments on macos without root");
|
||||
same_uid = true;
|
||||
}
|
||||
use hbb_common::sysinfo::System;
|
||||
let mut sys = System::new();
|
||||
sys.refresh_processes();
|
||||
@@ -1332,8 +1338,13 @@ pub fn check_process(arg: &str, same_uid: bool) -> bool {
|
||||
if same_uid && p.user_id() != my_uid {
|
||||
continue;
|
||||
}
|
||||
// on mac, p.cmd() get "/Applications/RustDesk.app/Contents/MacOS/RustDesk", "XPC_SERVICE_NAME=com.carriez.RustDesk_server"
|
||||
let parg = if p.cmd().len() <= 1 { "" } else { &p.cmd()[1] };
|
||||
if arg == parg {
|
||||
if arg.is_empty() {
|
||||
if !parg.starts_with("--") {
|
||||
return true;
|
||||
}
|
||||
} else if arg == parg {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,9 +114,14 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
if args.contains(&"--noinstall".to_string()) {
|
||||
args.clear();
|
||||
}
|
||||
if args.len() > 0 && args[0] == "--version" {
|
||||
println!("{}", crate::VERSION);
|
||||
return None;
|
||||
if args.len() > 0 {
|
||||
if args[0] == "--version" {
|
||||
println!("{}", crate::VERSION);
|
||||
return None;
|
||||
} else if args[0] == "--build-date" {
|
||||
println!("{}", crate::BUILD_DATE);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -208,34 +213,16 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
.show()
|
||||
.ok();
|
||||
return None;
|
||||
} else if args[0] == "--install-cert" {
|
||||
#[cfg(windows)]
|
||||
hbb_common::allow_err!(crate::platform::windows::install_cert(
|
||||
crate::platform::windows::DRIVER_CERT_FILE
|
||||
));
|
||||
if args.len() > 1 && args[1] == "silent" {
|
||||
return None;
|
||||
}
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
if crate::virtual_display_manager::is_virtual_display_supported() {
|
||||
hbb_common::allow_err!(crate::virtual_display_manager::install_update_driver());
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "--uninstall-cert" {
|
||||
#[cfg(windows)]
|
||||
hbb_common::allow_err!(crate::platform::windows::uninstall_cert());
|
||||
return None;
|
||||
} else if args[0] == "--install-idd" {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// It's ok to install cert multiple times.
|
||||
hbb_common::allow_err!(crate::platform::windows::install_cert(
|
||||
crate::platform::windows::DRIVER_CERT_FILE
|
||||
));
|
||||
}
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
if crate::virtual_display_manager::is_virtual_display_supported() {
|
||||
hbb_common::allow_err!(crate::virtual_display_manager::install_update_driver());
|
||||
hbb_common::allow_err!(
|
||||
crate::virtual_display_manager::rustdesk_idd::install_update_driver()
|
||||
);
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "--portable-service" {
|
||||
@@ -245,6 +232,12 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
_is_run_as_system,
|
||||
);
|
||||
return None;
|
||||
} else if args[0] == "--uninstall-amyuni-idd" {
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
hbb_common::allow_err!(
|
||||
crate::virtual_display_manager::amyuni_idd::uninstall_driver()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
if args[0] == "--remove" {
|
||||
@@ -274,7 +267,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
} else if args[0] == "--server" {
|
||||
log::info!("start --server with user {}", crate::username());
|
||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||
crate::privacy_mode::restore_reg_connectivity();
|
||||
crate::privacy_mode::restore_reg_connectivity(true);
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
{
|
||||
crate::start_server(true);
|
||||
@@ -430,10 +423,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
#[cfg(feature = "hwcodec")]
|
||||
scrap::hwcodec::check_available_hwcodec();
|
||||
return None;
|
||||
} else if args[0] == "--check-gpucodec-config" {
|
||||
#[cfg(feature = "gpucodec")]
|
||||
scrap::gpucodec::check_available_gpucodec();
|
||||
return None;
|
||||
} else if args[0] == "--cm" {
|
||||
// call connection manager to establish connections
|
||||
// meanwhile, return true to call flutter window to show control panel
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
ui_session_interface::{io_loop, InvokeUiSession, Session},
|
||||
};
|
||||
use flutter_rust_bridge::StreamSink;
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||
use hbb_common::dlopen::{
|
||||
symbor::{Library, Symbol},
|
||||
Error as LibError,
|
||||
@@ -16,7 +16,7 @@ use hbb_common::{
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use std::{
|
||||
@@ -63,7 +63,7 @@ lazy_static::lazy_static! {
|
||||
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open_self();
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "windows", feature = "gpucodec"))]
|
||||
#[cfg(all(target_os = "windows", feature = "vram"))]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref TEXTURE_GPU_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("flutter_gpu_texture_renderer_plugin.dll");
|
||||
}
|
||||
@@ -168,15 +168,15 @@ pub unsafe extern "C" fn get_rustdesk_app_name(buffer: *mut u16, length: i32) ->
|
||||
#[derive(Default)]
|
||||
struct SessionHandler {
|
||||
event_stream: Option<StreamSink<EventToUI>>,
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||
renderer: VideoRenderer,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
enum RenderType {
|
||||
PixelBuffer,
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
Texture,
|
||||
}
|
||||
|
||||
@@ -214,41 +214,41 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
|
||||
dst_rgba_stride: c_int,
|
||||
);
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
pub type FlutterGpuTextureRendererPluginCApiSetTexture =
|
||||
unsafe extern "C" fn(output: *mut c_void, texture: *mut c_void);
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
pub type FlutterGpuTextureRendererPluginCApiGetAdapterLuid = unsafe extern "C" fn() -> i64;
|
||||
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
pub(super) type TextureRgbaPtr = usize;
|
||||
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||
struct DisplaySessionInfo {
|
||||
// TextureRgba pointer in flutter native.
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
texture_rgba_ptr: TextureRgbaPtr,
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
size: (usize, usize),
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
gpu_output_ptr: usize,
|
||||
notify_render_type: Option<RenderType>,
|
||||
}
|
||||
|
||||
// Video Texture Renderer in Flutter
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||
#[derive(Clone)]
|
||||
struct VideoRenderer {
|
||||
is_support_multi_ui_session: bool,
|
||||
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
on_texture_func: Option<Symbol<'static, FlutterGpuTextureRendererPluginCApiSetTexture>>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||
impl Default for VideoRenderer {
|
||||
fn default() -> Self {
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
@@ -270,7 +270,7 @@ impl Default for VideoRenderer {
|
||||
None
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
let on_texture_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
||||
Ok(lib) => {
|
||||
let find_sym_res = unsafe {
|
||||
@@ -297,13 +297,13 @@ impl Default for VideoRenderer {
|
||||
is_support_multi_ui_session: false,
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
on_rgba_func,
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
on_texture_func,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "gpucodec"))]
|
||||
#[cfg(any(feature = "flutter_texture_render", feature = "vram"))]
|
||||
impl VideoRenderer {
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter_texture_render")]
|
||||
@@ -318,7 +318,7 @@ impl VideoRenderer {
|
||||
DisplaySessionInfo {
|
||||
texture_rgba_ptr: usize::default(),
|
||||
size: (width, height),
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
gpu_output_ptr: usize::default(),
|
||||
notify_render_type: None,
|
||||
},
|
||||
@@ -345,7 +345,7 @@ impl VideoRenderer {
|
||||
DisplaySessionInfo {
|
||||
texture_rgba_ptr: ptr as _,
|
||||
size: (0, 0),
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
gpu_output_ptr: usize::default(),
|
||||
notify_render_type: None,
|
||||
},
|
||||
@@ -355,7 +355,7 @@ impl VideoRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn register_gpu_output(&self, display: usize, ptr: usize) {
|
||||
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||
if ptr == 0 {
|
||||
@@ -434,7 +434,7 @@ impl VideoRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn on_texture(&self, display: usize, texture: *mut c_void) -> bool {
|
||||
let mut write_lock = self.map_display_sessions.write().unwrap();
|
||||
let opt_info = if !self.is_support_multi_ui_session {
|
||||
@@ -793,7 +793,7 @@ impl InvokeUiSession for FlutterHandler {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
fn on_texture(&self, display: usize, texture: *mut c_void) {
|
||||
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
||||
if session.renderer.on_texture(display, texture) {
|
||||
@@ -1073,9 +1073,9 @@ pub fn session_add(
|
||||
Some(switch_uuid.to_string())
|
||||
};
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
let adapter_luid = get_adapter_luid();
|
||||
#[cfg(not(feature = "gpucodec"))]
|
||||
#[cfg(not(feature = "vram"))]
|
||||
let adapter_luid = None;
|
||||
|
||||
session.lc.write().unwrap().initialize(
|
||||
@@ -1106,7 +1106,7 @@ pub fn session_start_(
|
||||
) -> ResultType<()> {
|
||||
// is_connected is used to indicate whether to start a peer connection. For two cases:
|
||||
// 1. "Move tab to new window"
|
||||
// 2. multi ui session within the same peer connnection.
|
||||
// 2. multi ui session within the same peer connection.
|
||||
let mut is_connected = false;
|
||||
let mut is_found = false;
|
||||
for s in sessions::get_sessions() {
|
||||
@@ -1453,7 +1453,7 @@ pub fn session_register_pixelbuffer_texture(_session_id: SessionID, _display: us
|
||||
|
||||
#[inline]
|
||||
pub fn session_register_gpu_texture(_session_id: SessionID, _display: usize, _output_ptr: usize) {
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
for s in sessions::get_sessions() {
|
||||
if let Some(h) = s
|
||||
.ui_handler
|
||||
@@ -1468,7 +1468,7 @@ pub fn session_register_gpu_texture(_session_id: SessionID, _display: usize, _ou
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpucodec")]
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn get_adapter_luid() -> Option<i64> {
|
||||
let get_adapter_luid_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
||||
Ok(lib) => {
|
||||
|
||||
@@ -39,11 +39,15 @@ lazy_static::lazy_static! {
|
||||
static ref TEXTURE_RENDER_KEY: Arc<AtomicI32> = Arc::new(AtomicI32::new(0));
|
||||
}
|
||||
|
||||
fn initialize(app_dir: &str) {
|
||||
fn initialize(app_dir: &str, custom_client_config: &str) {
|
||||
flutter::async_tasks::start_flutter_async_runner();
|
||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||
// core_main's load_custom_client does not work for flutter since it is only applied to its load_library in main.c
|
||||
crate::load_custom_client();
|
||||
if custom_client_config.is_empty() {
|
||||
crate::load_custom_client();
|
||||
} else {
|
||||
crate::read_custom_client(custom_client_config);
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
// flexi_logger can't work when android_logger initialized.
|
||||
@@ -1282,8 +1286,8 @@ pub fn cm_get_clients_length() -> usize {
|
||||
crate::ui_cm_interface::get_clients_length()
|
||||
}
|
||||
|
||||
pub fn main_init(app_dir: String) {
|
||||
initialize(&app_dir);
|
||||
pub fn main_init(app_dir: String, custom_client_config: String) {
|
||||
initialize(&app_dir, &custom_client_config);
|
||||
}
|
||||
|
||||
pub fn main_device_id(id: String) {
|
||||
@@ -1302,8 +1306,8 @@ pub fn main_has_hwcodec() -> SyncReturn<bool> {
|
||||
SyncReturn(has_hwcodec())
|
||||
}
|
||||
|
||||
pub fn main_has_gpucodec() -> SyncReturn<bool> {
|
||||
SyncReturn(has_gpucodec())
|
||||
pub fn main_has_vram() -> SyncReturn<bool> {
|
||||
SyncReturn(has_vram())
|
||||
}
|
||||
|
||||
pub fn main_supported_hwdecodings() -> SyncReturn<String> {
|
||||
@@ -1789,7 +1793,7 @@ pub fn main_has_file_clipboard() -> SyncReturn<bool> {
|
||||
}
|
||||
|
||||
pub fn main_has_gpu_texture_render() -> SyncReturn<bool> {
|
||||
SyncReturn(cfg!(feature = "gpucodec"))
|
||||
SyncReturn(cfg!(feature = "vram"))
|
||||
}
|
||||
|
||||
pub fn cm_init() {
|
||||
@@ -2103,6 +2107,16 @@ pub fn main_get_hard_option(key: String) -> SyncReturn<String> {
|
||||
SyncReturn(get_hard_option(key))
|
||||
}
|
||||
|
||||
pub fn main_check_hwcodec() {
|
||||
check_hwcodec()
|
||||
}
|
||||
|
||||
pub fn session_request_new_display_init_msgs(session_id: SessionID, display: usize) {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.request_init_msgs(display);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use hbb_common::{config, log};
|
||||
@@ -2115,31 +2129,35 @@ pub mod server_side {
|
||||
use crate::start_server;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer(
|
||||
pub unsafe extern "system" fn Java_ffi_FFI_startServer(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
app_dir: JString,
|
||||
custom_client_config: JString,
|
||||
) {
|
||||
log::debug!("startServer from jvm");
|
||||
let mut env = env;
|
||||
if let Ok(app_dir) = env.get_string(&app_dir) {
|
||||
*config::APP_DIR.write().unwrap() = app_dir.into();
|
||||
}
|
||||
if let Ok(custom_client_config) = env.get_string(&custom_client_config) {
|
||||
if !custom_client_config.is_empty() {
|
||||
let custom_client_config: String = custom_client_config.into();
|
||||
crate::read_custom_client(&custom_client_config);
|
||||
}
|
||||
}
|
||||
std::thread::spawn(move || start_server(true));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startService(
|
||||
_env: JNIEnv,
|
||||
_class: JClass,
|
||||
) {
|
||||
pub unsafe extern "system" fn Java_ffi_FFI_startService(_env: JNIEnv, _class: JClass) {
|
||||
log::debug!("startService from jvm");
|
||||
config::Config::set_option("stop-service".into(), "".into());
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale(
|
||||
pub unsafe extern "system" fn Java_ffi_FFI_translateLocale(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
locale: JString,
|
||||
@@ -2158,10 +2176,7 @@ pub mod server_side {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_refreshScreen(
|
||||
_env: JNIEnv,
|
||||
_class: JClass,
|
||||
) {
|
||||
pub unsafe extern "system" fn Java_ffi_FFI_refreshScreen(_env: JNIEnv, _class: JClass) {
|
||||
crate::server::video_service::refresh()
|
||||
}
|
||||
}
|
||||
|
||||
15
src/ipc.rs
15
src/ipc.rs
@@ -232,6 +232,7 @@ pub enum Data {
|
||||
#[cfg(windows)]
|
||||
ControlledSessionCount(usize),
|
||||
CmErr(String),
|
||||
CheckHwcodec,
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
@@ -502,6 +503,14 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
.await
|
||||
);
|
||||
}
|
||||
Data::CheckHwcodec =>
|
||||
{
|
||||
#[cfg(feature = "hwcodec")]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
if crate::platform::is_root() {
|
||||
scrap::hwcodec::start_check_process(true);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -929,6 +938,12 @@ pub async fn connect_to_user_session(usid: Option<u32>) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn notify_server_to_check_hwcodec() -> ResultType<()> {
|
||||
connect(1_000, "").await?.send(&&Data::CheckHwcodec).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@@ -16,6 +16,7 @@ mod et;
|
||||
mod fa;
|
||||
mod fr;
|
||||
mod he;
|
||||
mod hr;
|
||||
mod hu;
|
||||
mod id;
|
||||
mod it;
|
||||
@@ -81,6 +82,7 @@ pub const LANGS: &[(&str, &str)] = &[
|
||||
("lv", "Latviešu"),
|
||||
("ar", "العربية"),
|
||||
("he", "עברית"),
|
||||
("hr", "Hrvatski"),
|
||||
];
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -152,6 +154,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
|
||||
"ar" => ar::T.deref(),
|
||||
"bg" => bg::T.deref(),
|
||||
"he" => he::T.deref(),
|
||||
"hr" => hr::T.deref(),
|
||||
_ => en::T.deref(),
|
||||
};
|
||||
let (name, placeholder_value) = extract_placeholder(&name);
|
||||
|
||||
@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("share_warning_tip", ""),
|
||||
("Everyone", ""),
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("share_warning_tip", ""),
|
||||
("Everyone", ""),
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("share_warning_tip", ""),
|
||||
("Everyone", ""),
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -590,7 +590,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("powered_by_me", "由 RustDesk 提供支持"),
|
||||
("outgoing_only_desk_tip", "当前版本的软件是定制版本。\n您可以连接至其他设备,但是其他设备无法连接至您的设备。"),
|
||||
("preset_password_warning", "此定制版本附有预设密码。 任何知晓此密码的人都能完全控制您的设备。如果这不是您所预期的,请立即卸载此软件。"),
|
||||
("Security Alert", ""),
|
||||
("Security Alert", "安全警告"),
|
||||
("My address book", "我的地址簿"),
|
||||
("Personal", "个人的"),
|
||||
("Owner", "所有者"),
|
||||
@@ -602,5 +602,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("share_warning_tip", "上述的字段為共享且对其他人可见。"),
|
||||
("Everyone", "所有人"),
|
||||
("ab_web_console_tip", "打开 Web 控制台以执行更多操作"),
|
||||
("allow-only-conn-window-open-tip", "仅当 RustDesk 窗口打开时允许连接"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "没有物理显示器,没必要使用隐私模式。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -601,5 +601,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("share_warning_tip", "Výše uvedená pole jsou sdílená a viditelná pro ostatní."),
|
||||
("Everyone", "Každý"),
|
||||
("ab_web_console_tip", "Více na webové konzoli"),
|
||||
("allow-only-conn-window-open-tip", "Povolit připojení pouze v případě, že je otevřené okno RustDesk"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Žádné fyzické displeje, není třeba používat režim soukromí."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user