mirror of
https://github.com/wx-chevalier/screen-sharing-rdp.git
synced 2025-12-24 13:08:12 +08:00
feat:临时添加rustdesk的代码
This commit is contained in:
4
rust-rdp/rust-desk/.cargo/config.toml
Normal file
4
rust-rdp/rust-desk/.cargo/config.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[target.'cfg(target_os="macos")']
|
||||
rustflags = [
|
||||
"-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null",
|
||||
]
|
||||
1
rust-rdp/rust-desk/.gitattributes
vendored
Normal file
1
rust-rdp/rust-desk/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto
|
||||
2
rust-rdp/rust-desk/.github/FUNDING.yml
vendored
Normal file
2
rust-rdp/rust-desk/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
github: [rustdesk]
|
||||
ko_fi: rustdesk
|
||||
32
rust-rdp/rust-desk/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
rust-rdp/rust-desk/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug (English only, Please).
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Hey there, thank you for creating an issue! -->
|
||||
|
||||
**Describe the bug you encountered:**
|
||||
|
||||
...
|
||||
|
||||
**What did you expect to happen instead?**
|
||||
|
||||
...
|
||||
|
||||
|
||||
**How did you install `RustDesk`?**
|
||||
|
||||
<!-- GitHub release, build from source, Windows portable version, etc. -->
|
||||
|
||||
---
|
||||
|
||||
**RustDesk version and environment**
|
||||
|
||||
<!--
|
||||
In order to reproduce your issue, please add some information about the environment
|
||||
in which you're running RustDesk.
|
||||
-->
|
||||
2
rust-rdp/rust-desk/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
2
rust-rdp/rust-desk/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
blank_issues_enabled: true
|
||||
|
||||
10
rust-rdp/rust-desk/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
10
rust-rdp/rust-desk/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project ((English only, Please).
|
||||
title: ''
|
||||
labels: feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
10
rust-rdp/rust-desk/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
10
rust-rdp/rust-desk/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about 'RustDesk' (English only, Please).
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
181
rust-rdp/rust-desk/.github/workflows/ci.yml
vendored
Normal file
181
rust-rdp/rust-desk/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
name: CI
|
||||
|
||||
# env:
|
||||
# MIN_SUPPORTED_RUST_VERSION: "1.46.0"
|
||||
# CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
# ensure_cargo_fmt:
|
||||
# name: Ensure 'cargo fmt' has been run
|
||||
# runs-on: ubuntu-20.04
|
||||
# steps:
|
||||
# - uses: actions-rs/toolchain@v1
|
||||
# with:
|
||||
# toolchain: stable
|
||||
# default: true
|
||||
# profile: minimal
|
||||
# components: rustfmt
|
||||
# - uses: actions/checkout@v2
|
||||
# - run: cargo fmt -- --check
|
||||
|
||||
# min_version:
|
||||
# name: Minimum supported rust version
|
||||
# runs-on: ubuntu-20.04
|
||||
# steps:
|
||||
# - name: Checkout source code
|
||||
# uses: actions/checkout@v2
|
||||
|
||||
# - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
|
||||
# uses: actions-rs/toolchain@v1
|
||||
# with:
|
||||
# toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }}
|
||||
# default: true
|
||||
# profile: minimal # minimal component installation (ie, no documentation)
|
||||
# components: clippy
|
||||
# - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
|
||||
# uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: clippy
|
||||
# args: --locked --all-targets --all-features -- --allow clippy::unknown_clippy_lints
|
||||
# - name: Run tests
|
||||
# uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: test
|
||||
# args: --locked
|
||||
|
||||
build:
|
||||
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
# - { 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-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-unknown-linux-gnu , os: ubuntu-20.04 }
|
||||
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
case ${{ matrix.job.target }} in
|
||||
x86_64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake ;;
|
||||
# arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
|
||||
# aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
|
||||
esac
|
||||
|
||||
- name: Restore from cache and install vcpkg
|
||||
uses: lukka/run-vcpkg@v7
|
||||
with:
|
||||
setupOnly: true
|
||||
vcpkgGitCommitId: '1d4128f08e30cec31b94500840c7eca8ebc579cb'
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
run: |
|
||||
$VCPKG_ROOT/vcpkg install libvpx libyuv opus
|
||||
shell: bash
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.job.target }}
|
||||
override: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
|
||||
- name: Show version information (Rust, cargo, GCC)
|
||||
shell: bash
|
||||
run: |
|
||||
gcc --version || true
|
||||
rustup -V
|
||||
rustup toolchain list
|
||||
rustup default
|
||||
cargo -V
|
||||
rustc -V
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.job.use-cross }}
|
||||
command: build
|
||||
args: --locked --release --target=${{ matrix.job.target }}
|
||||
|
||||
# - name: Strip debug information from executable
|
||||
# id: strip
|
||||
# shell: bash
|
||||
# run: |
|
||||
# # Figure out suffix of binary
|
||||
# EXE_suffix=""
|
||||
# case ${{ matrix.job.target }} in
|
||||
# *-pc-windows-*) EXE_suffix=".exe" ;;
|
||||
# esac;
|
||||
|
||||
# # Figure out what strip tool to use if any
|
||||
# STRIP="strip"
|
||||
# case ${{ matrix.job.target }} in
|
||||
# arm-unknown-linux-*) STRIP="arm-linux-gnueabihf-strip" ;;
|
||||
# aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;;
|
||||
# *-pc-windows-msvc) STRIP="" ;;
|
||||
# esac;
|
||||
|
||||
# # Setup paths
|
||||
# BIN_DIR="${{ env.CICD_INTERMEDIATES_DIR }}/stripped-release-bin/"
|
||||
# mkdir -p "${BIN_DIR}"
|
||||
# BIN_NAME="${{ env.PROJECT_NAME }}${EXE_suffix}"
|
||||
# BIN_PATH="${BIN_DIR}/${BIN_NAME}"
|
||||
|
||||
# # Copy the release build binary to the result location
|
||||
# cp "target/${{ matrix.job.target }}/release/${BIN_NAME}" "${BIN_DIR}"
|
||||
|
||||
# # Also strip if possible
|
||||
# if [ -n "${STRIP}" ]; then
|
||||
# "${STRIP}" "${BIN_PATH}"
|
||||
# fi
|
||||
|
||||
# # Let subsequent steps know where to find the (stripped) bin
|
||||
# echo ::set-output name=BIN_PATH::${BIN_PATH}
|
||||
# echo ::set-output name=BIN_NAME::${BIN_NAME}
|
||||
|
||||
- name: Set testing options
|
||||
id: test-options
|
||||
shell: bash
|
||||
run: |
|
||||
# test only library unit tests and binary for arm-type targets
|
||||
unset CARGO_TEST_OPTIONS
|
||||
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${PROJECT_NAME}" ;; esac;
|
||||
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
|
||||
|
||||
- name: Build tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.job.use-cross }}
|
||||
command: build
|
||||
args: --locked --tests --target=${{ matrix.job.target }}
|
||||
|
||||
# - name: Run tests
|
||||
# uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# use-cross: ${{ matrix.job.use-cross }}
|
||||
# command: test
|
||||
# args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
|
||||
10
rust-rdp/rust-desk/.gitignore
vendored
Normal file
10
rust-rdp/rust-desk/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/target
|
||||
.vscode
|
||||
.idea
|
||||
.DS_Store
|
||||
src/ui/inline.rs
|
||||
extractor
|
||||
__pycache__
|
||||
src/version.rs
|
||||
*dmg
|
||||
sciter.dll
|
||||
BIN
rust-rdp/rust-desk/128x128.png
Normal file
BIN
rust-rdp/rust-desk/128x128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
BIN
rust-rdp/rust-desk/128x128@2x.png
Normal file
BIN
rust-rdp/rust-desk/128x128@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
rust-rdp/rust-desk/32x32.png
Normal file
BIN
rust-rdp/rust-desk/32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
46
rust-rdp/rust-desk/CONTRIBUTING.md
Normal file
46
rust-rdp/rust-desk/CONTRIBUTING.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributing to RustDesk
|
||||
|
||||
RustDesk welcomes contribution from everyone. Here are the guidelines if you are
|
||||
thinking of helping us:
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions to RustDesk or its dependencies should be made in the form of GitHub
|
||||
pull requests. Each pull request will be reviewed by a core contributor
|
||||
(someone with permission to land patches) and either landed in the main tree or
|
||||
given feedback for changes that would be required. All contributions should
|
||||
follow this format, even those from core contributors.
|
||||
|
||||
Should you wish to work on an issue, please claim it first by commenting on
|
||||
the GitHub issue that you want to work on it. This is to prevent duplicated
|
||||
efforts from contributors on the same issue.
|
||||
|
||||
## Pull Request Checklist
|
||||
|
||||
- Branch from the master branch and, if needed, rebase to the current master
|
||||
branch before submitting your pull request. If it doesn't merge cleanly with
|
||||
master you may be asked to rebase your changes.
|
||||
|
||||
- Commits should be as small as possible, while ensuring that each commit is
|
||||
correct independently (i.e., each commit should compile and pass tests).
|
||||
|
||||
- Commits should be accompanied by a Developer Certificate of Origin
|
||||
(http://developercertificate.org) sign-off, which indicates that you (and
|
||||
your employer if applicable) agree to be bound by the terms of the
|
||||
[project license](LICENSE). In git, this is the `-s` option to `git commit`
|
||||
|
||||
- If your patch is not getting reviewed or you need a specific person to review
|
||||
it, you can @-reply a reviewer asking for a review in the pull request or a
|
||||
comment, or you can ask for a review via [email](mailto:info@rustdesk.com).
|
||||
|
||||
- Add tests relevant to the fixed bug or new feature.
|
||||
|
||||
For specific git instructions, see [GitHub workflow 101](https://github.com/servo/servo/wiki/Github-workflow).
|
||||
|
||||
## Conduct
|
||||
|
||||
We follow the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct).
|
||||
|
||||
## Communication
|
||||
|
||||
RustDesk contributors frequent the [Discord](https://discord.gg/nDceKgxnkV).
|
||||
4519
rust-rdp/rust-desk/Cargo.lock
generated
Normal file
4519
rust-rdp/rust-desk/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
117
rust-rdp/rust-desk/Cargo.toml
Normal file
117
rust-rdp/rust-desk/Cargo.toml
Normal file
@@ -0,0 +1,117 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.1.8"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2018"
|
||||
build= "build.rs"
|
||||
description = "A remote control software."
|
||||
|
||||
[features]
|
||||
inline = []
|
||||
cli = []
|
||||
use_samplerate = ["samplerate"]
|
||||
use_rubato = ["rubato"]
|
||||
use_dasp = ["dasp"]
|
||||
default = ["use_dasp"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
whoami = "1.2"
|
||||
scrap = { path = "libs/scrap" }
|
||||
hbb_common = { path = "libs/hbb_common" }
|
||||
enigo = { path = "libs/enigo" }
|
||||
sys-locale = "0.1"
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
cfg-if = "1.0"
|
||||
lazy_static = "1.4"
|
||||
sha2 = "0.10"
|
||||
repng = "0.2"
|
||||
libc = "0.2"
|
||||
parity-tokio-ipc = { git = "https://github.com/open-trade/parity-tokio-ipc" }
|
||||
flexi_logger = "0.22"
|
||||
runas = "0.2"
|
||||
magnum-opus = { git = "https://github.com/open-trade/magnum-opus" }
|
||||
dasp = { version = "0.11", features = ["signal", "interpolate-linear", "interpolate"], optional = true }
|
||||
rubato = { version = "0.10", optional = true }
|
||||
samplerate = { version = "0.2", optional = true }
|
||||
async-trait = "0.1"
|
||||
crc32fast = "1.3"
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
clap = "3.0"
|
||||
rpassword = "5.0"
|
||||
base64 = "0.13"
|
||||
sysinfo = "0.23"
|
||||
num_cpus = "1.13"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android")))'.dependencies]
|
||||
cpal = { git = "https://github.com/open-trade/cpal" }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
machine-uid = "0.2"
|
||||
mac_address = "1.1"
|
||||
sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn" }
|
||||
ctrlc = "3.2"
|
||||
arboard = "2.0"
|
||||
clipboard-master = "3.1"
|
||||
#rdev = { path = "../rdev" }
|
||||
rdev = { git = "https://github.com/open-trade/rdev" }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
systray = { git = "https://github.com/liyue201/systray-rs" }
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
winreg = "0.10"
|
||||
windows-service = "0.4"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2"
|
||||
cocoa = "0.24"
|
||||
dispatch = "0.2"
|
||||
core-foundation = "0.9"
|
||||
core-graphics = "0.22"
|
||||
notify = "4.0.17"
|
||||
include_dir = "0.7.2"
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libpulse-simple-binding = "2.24"
|
||||
libpulse-binding = "2.25"
|
||||
rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.10"
|
||||
|
||||
[workspace]
|
||||
members = ["libs/scrap", "libs/hbb_common", "libs/enigo"]
|
||||
|
||||
[package.metadata.winres]
|
||||
LegalCopyright = "Copyright © 2020"
|
||||
# this FileDescription overrides package.description
|
||||
FileDescription = "RustDesk"
|
||||
|
||||
[target.'cfg(target_os="windows")'.build-dependencies]
|
||||
winres = "0.1"
|
||||
winapi = { version = "0.3", features = [ "winnt" ] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
hbb_common = { path = "libs/hbb_common" }
|
||||
|
||||
[dev-dependencies]
|
||||
hound = "3.4"
|
||||
|
||||
[package.metadata.bundle]
|
||||
name = "RustDesk"
|
||||
identifier = "com.carriez.rustdesk"
|
||||
icon = ["32x32.png", "128x128.png", "128x128@2x.png"]
|
||||
deb_depends = ["libgtk-3-0", "libxcb-randr0", "libxdo3", "libxfixes3", "libxcb-shape0", "libxcb-xfixes0", "libasound2", "libsystemd0", "pulseaudio"]
|
||||
osx_minimum_system_version = "10.14"
|
||||
|
||||
#https://github.com/johnthagen/min-sized-rust
|
||||
#!!! rembember call "strip target/release/rustdesk"
|
||||
# which reduce binary size a lot
|
||||
[profile.release]
|
||||
#lto = true
|
||||
#codegen-units = 1
|
||||
#panic = 'abort'
|
||||
#opt-level = 'z' # only have smaller size after strip
|
||||
20
rust-rdp/rust-desk/Dockerfile
Normal file
20
rust-rdp/rust-desk/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM debian
|
||||
|
||||
WORKDIR /
|
||||
RUN apt update -y && apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake unzip zip sudo
|
||||
|
||||
RUN git clone https://github.com/microsoft/vcpkg && cd vcpkg && git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82
|
||||
RUN /vcpkg/bootstrap-vcpkg.sh -disableMetrics
|
||||
RUN /vcpkg/vcpkg --disable-metrics install libvpx libyuv opus
|
||||
|
||||
RUN groupadd -r user && useradd -r -g user user --home /home/user && mkdir -p /home/user && chown user /home/user && echo "user ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/user
|
||||
WORKDIR /home/user
|
||||
RUN wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
USER user
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh
|
||||
RUN chmod +x rustup.sh
|
||||
RUN ./rustup.sh -y
|
||||
|
||||
USER root
|
||||
COPY ./entrypoint /
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
674
rust-rdp/rust-desk/LICENSE
Normal file
674
rust-rdp/rust-desk/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
162
rust-rdp/rust-desk/README-DE.md
Normal file
162
rust-rdp/rust-desk/README-DE.md
Normal file
@@ -0,0 +1,162 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#kostenlose-öffentliche-server">Server</a> •
|
||||
<a href="#die-groben-schritte-zum-kompilieren">Kompilieren</a> •
|
||||
<a href="#auf-docker-kompilieren">Docker</a> •
|
||||
<a href="#dateistruktur">Dateistruktur</a> •
|
||||
<a href="#screenshots">Screenshots</a><br>
|
||||
[<a href="README.md">English</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||
<b>Wir brauchen deine Hilfe um diese README Datei zu verbessern und aktualisieren</b>
|
||||
</p>
|
||||
|
||||
Rede mit uns: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Das hier ist ein Programm was man nutzen kann, um einen Computer fernzusteuern, es wurde in Rust geschrieben. Es funktioniert ohne Konfiguration oder ähnliches, man kann es einfach direkt nutzen. Du hast volle Kontrolle über deine Daten und brauchst dir daher auch keine Sorgen um die Sicherheit dieser Daten zu machen. Du kannst unseren rendezvous/relay Server nutzen, [einen eigenen Server eröffnen](https://rustdesk.com/blog/id-relay-set/) oder [einen neuen eigenen Server programmieren](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
RustDesk heißt jegliche Mitarbeit willkommen. Schau dir [`CONTRIBUTING.md`](CONTRIBUTING.md) an, wenn du Hilfe brauchst für den Start.
|
||||
|
||||
[**PROGRAMM DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## Kostenlose öffentliche Server
|
||||
|
||||
Hier sind die Server die du kostenlos nutzen kannst, es kann sein das sich diese Liste immer mal wieder ändert. Falls du nicht in der Nähe einer dieser Server bist, kann es sein, dass deine Verbindung langsam sein wird.
|
||||
|
||||
| Standort | Serverart | Spezifikationen | Kommentare |
|
||||
| --------- | ------------- | ------------------ | ---------- |
|
||||
| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | |
|
||||
| Singapore | Vultr | 1 VCPU / 1GB RAM | |
|
||||
| Dallas | Vultr | 1 VCPU / 1GB RAM | |
|
||||
|
||||
## Abhängigkeiten
|
||||
|
||||
Die Desktop Versionen nutzen [Sciter](https://sciter.com/) für die Oberfläche, bitte lade die dynamische Sciter Bibliothek selbst herunter.
|
||||
|
||||
[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)
|
||||
|
||||
## Die groben Schritte zum Kompilieren
|
||||
|
||||
- Bereite deine Rust Entwicklungsumgebung und C++ Entwicklungsumgebung vor
|
||||
|
||||
- Installiere [vcpkg](https://github.com/microsoft/vcpkg) und füge die `VCPKG_ROOT` Systemumgebungsvariable hinzu
|
||||
|
||||
- Windows: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static`
|
||||
- Linux/MacOS: `vcpkg install libvpx libyuv opus`
|
||||
|
||||
- Nutze `cargo run`
|
||||
|
||||
## Kompilieren auf Linux
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### vcpkg installieren
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### libvpx reparieren (Für Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Kompilieren
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Ändere Wayland zu X11 (Xorg)
|
||||
|
||||
RustDesk unterstützt "Wayland" nicht. Siehe [hier](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) um Xorg als Standard GNOME Session zu nutzen.
|
||||
|
||||
## Auf Docker Kompilieren
|
||||
|
||||
Beginne damit das Repository zu klonen und den Docker Container zu bauen:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Jedes Mal, wenn du das Programm Kompilieren musst, nutze diesen Befehl:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Bedenke, dass das erste Mal Kompilieren länger dauern kann, da die Abhängigkeiten erst kompiliert werden müssen bevor sie zwischengespeichert werden können. Darauf folgende Kompiliervorgänge werden schneller sein. Falls du zusätzliche oder andere Argumente für den Kompilierbefehl angeben musst, kannst du diese am Ende des Befehls an der `<OPTIONAL-ARGS>` Position machen. Wenn du zum Beispiel eine optimierte Releaseversion kompilieren willst, kannst du das tun indem du `--release` am Ende des Befehls anhängst. Das daraus entstehende Programm kannst du im “target” Ordner auf deinem System finden. Du kannst es mit folgenden Befehlen ausführen:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Oder, wenn du eine Releaseversion benutzt:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Bitte gehe sicher, dass du diese Befehle vom Stammverzeichnis vom RustDesk Repository nutzt, sonst kann es passieren, dass das Programm die Ressourcen nicht finden kann. Bitte bedenke auch, dass Unterbefehle von Cargo, wie z.B. `install` oder `run` aktuell noch nicht unterstützt werden, da sie das Programm innerhalb des Containers starten oder installieren würden, anstatt auf deinem eigentlichen System.
|
||||
|
||||
## Dateistruktur
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: Video Codec, Konfiguration, TCP/UDP Wrapper, Protokoll Puffer, fs Funktionen für Dateitransfer, und ein paar andere nützliche Funktionen
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: Bildschirmaufnahme
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: Plattformspezifische Maus und Tastatur Steuerung
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: Audio/Zwischenablage/Eingabe/Videodienste und Netzwerk Verbindungen
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: Starten einer Peer-Verbindung
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Mit [rustdesk-server](https://github.com/rustdesk/rustdesk-server) kommunizieren, für Verbindung von außen warten, direkt (TCP hole punching) oder weitergeleitet
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: Plattformspezifischer Code
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
160
rust-rdp/rust-desk/README-ES.md
Normal file
160
rust-rdp/rust-desk/README-ES.md
Normal file
@@ -0,0 +1,160 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#servidores-gratis-de-uso-público">Servidores</a> •
|
||||
<a href="#pasos-para-compilar-desde-el-inicio">Compilar</a> •
|
||||
<a href="#como-compilar-con-docker">Docker</a> •
|
||||
<a href="#estructura-de-archivos">Estructura</a> •
|
||||
<a href="#captura-de-pantalla">Captura de pantalla</a><br>
|
||||
[<a href="README.md">English</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||
<b>Necesitamos tu ayuda para traducir este README a tu idioma</b>
|
||||
</p>
|
||||
|
||||
Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Otro software de escritorio remoto, escrito en Rust. Funciona de forma inmediata, sin necesidad de configuración. Tienes el control total de sus datos, sin preocupaciones sobre la seguridad. Puedes utilizar nuestro servidor de rendezvous/relay, [set up your own](https://rustdesk.com/blog/id-relay-set/), o [escribir tu propio servidor rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
RustDesk agradece la contribución de todo el mundo. Ve [`CONTRIBUTING.md`](CONTRIBUTING.md) para ayuda inicial.
|
||||
|
||||
[**DESCARGA DE BINARIOS**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## Servidores gratis de uso público
|
||||
|
||||
A continuación se muestran los servidores que está utilizando de forma gratuita, puede cambiar en algún momento. Si no estás cerca de uno de ellos, tu red puede ser lenta.
|
||||
|
||||
- Seoul, AWS lightsail, 1 VCPU/0.5G RAM
|
||||
- Singapore, Vultr, 1 VCPU/1G RAM
|
||||
- Dallas, Vultr, 1 VCPU/1G RAM
|
||||
|
||||
## Dependencies
|
||||
|
||||
La versión Desktop usa [sciter](https://sciter.com/) para GUI, por favor bajate la librería sciter tu mismo..
|
||||
|
||||
[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)
|
||||
|
||||
## Pasos para compilar desde el inicio
|
||||
|
||||
- Prepara el entono de desarrollode Rust y el entorno de compilación de C++ y Rust.
|
||||
|
||||
- Instala [vcpkg](https://github.com/microsoft/vcpkg), y configura la variable de entono `VCPKG_ROOT` correctamente.
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/Osx: vcpkg install libvpx libyuv opus
|
||||
|
||||
- run `cargo run`
|
||||
|
||||
## Como compilar en linux
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### Install vcpkg
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### Soluciona libvpx (For Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Compila
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Cambia Wayland a X11 (Xorg)
|
||||
|
||||
RustDesk no soporta Wayland. Comprueba [aquí](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) para configurar Xorg en la sesión por defecto de GNOME.
|
||||
|
||||
## Como compilar con Docker
|
||||
|
||||
Empieza clonando el repositorio y compilando el contenedor de docker:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Entonces, cada vez que necesites compilar una modificación, ejecuta el siguiente comando:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Ten en cuenta que la primera compilación puede tardar más tiempo antes de que las dependencias se almacenen en la caché, las siguientes compilaciones serán más rápidas. Además, si necesitas especificar diferentes argumentos a la orden de compilación, puede hacerlo al final de la linea de comandos en el apartado`<OPTIONAL-ARGS>`. Por ejemplo, si desea compilar una versión optimizada para publicación, deberá ejecutar el comando anterior seguido de `---release`. El ejecutable resultante estará disponible en la carpeta de destino en su sistema, y puede ser ejecutado con:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
O si estas ejecutando una versión para su publicación:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Por favor, asegurate de que estás ejecutando estos comandos desde la raíz del repositorio de RustDesk, de lo contrario la aplicación puede ser incapaz de encontrar los recursos necesarios. También hay que tener en cuenta que otros subcomandos de carga como `install` o `run` no estan actualmente soportados via este metodo y podrían requerir ser instalados dentro del contenedor y no en el host.
|
||||
|
||||
## Estructura de archivos
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, configuración, tcp/udp wrapper, protobuf, fs funciones para transferencia de ficheros, y alguna función de utilidad.
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: captura de pantalla
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: control específico por cada plataforma para el teclado/ratón
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: sonido/portapapeles/entrada/servicios de video, y conexiones de red
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: iniciar una conexión "peer to peer"
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Comunicación con [rustdesk-server](https://github.com/rustdesk/rustdesk-server), esperar la conexión remota directa ("TCP hole punching") o conexión indirecta ("relayed")
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: código específico de cada plataforma
|
||||
|
||||
## Captura de pantalla
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
161
rust-rdp/rust-desk/README-FI.md
Normal file
161
rust-rdp/rust-desk/README-FI.md
Normal file
@@ -0,0 +1,161 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Etätyöpöytäsi"><br>
|
||||
<a href="#free-public-servers">Palvelimet</a> •
|
||||
<a href="#raw-steps-to-build">Rakenna</a> •
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Rakenne</a> •
|
||||
<a href="#snapshot">Tilannevedos</a><br>
|
||||
[<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>] | [<a href="README-FI.md">Suomi</a>]<br>
|
||||
<b>Tarvitsemme apua tämän README-tiedoston kääntämiseksi äidinkielellesi</b>
|
||||
</p>
|
||||
|
||||
Juttele meidän kanssa: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Vielä yksi etätyöpöytäohjelmisto, ohjelmoitu Rust-kielellä. Toimii suoraan pakkauksesta, ei tarvitse asetuksia. Hallitset täysin tietojasi, ei tarvitse murehtia turvallisuutta. Voit käyttää meidän rendezvous/relay-palvelinta, [aseta omasi](https://rustdesk.com/blog/id-relay-set/), tai [kirjoita oma rendezvous/relay-palvelin](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
RustDesk toivottaa avustukset tervetulleiksi kaikilta. Katso lisätietoja [`CONTRIBUTING.md`](CONTRIBUTING.md) avun saamiseksi.
|
||||
|
||||
[**BINAARILATAUS**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## Vapaita julkisia palvelimia
|
||||
|
||||
Alla on palvelimia, joita voit käyttää ilmaiseksi, ne saattavat muuttua ajan mittaan. Jos et ole lähellä yhtä näistä, verkkosi voi olla hidas.
|
||||
| Sijainti | Myyjä | Määrittely |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM |
|
||||
| Singapore | Vultr | 1 VCPU / 1GB RAM |
|
||||
| Dallas | Vultr | 1 VCPU / 1GB RAM | |
|
||||
|
||||
## Riippuvuudet
|
||||
|
||||
Desktop-versiot käyttävät [sciter](https://sciter.com/) graafisena käyttöliittymänä, lataa sciter-dynaaminen kirjasto itsellesi.
|
||||
|
||||
[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)
|
||||
|
||||
## Rakentamisaskeleet harppoen
|
||||
|
||||
- Valmistele Rust-kehitysympäristö ja C++-rakentamisympäristö
|
||||
|
||||
- Asenna [vcpkg](https://github.com/microsoft/vcpkg), ja aseta `VCPKG_ROOT`-ympäristömuuttuja oikein
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus
|
||||
|
||||
- aja `cargo run`
|
||||
|
||||
## Kuinka rakentaa Linuxissa
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### Asenna vcpkg
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### Korjaa libvpx (Fedora-linux-versiota varten)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Rakenna
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Vaihda Wayland-ympäristö X11 (Xorg)-ympäristöön
|
||||
|
||||
RustDesk ei tue Waylandia. Tarkista [tämä](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) asettamaan Xorg oletus GNOME-istuntona.
|
||||
|
||||
## Kuinka rakennetaan Dockerin kanssa
|
||||
|
||||
Aloita kloonaamalla tietovarasto ja rakentamalla docker-säiliö:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Sitten, joka kerta kun sinun on rakennettava sovellus, aja seuraava komento:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Huomaa, että ensimmäinen rakentaminen saattaa kestää pitempään ennen kuin riippuvuudet on siirretty välimuistiin, seuraavat rakentamiset ovat nopeampia. Lisäksi, jos sinun on määritettävä eri argumentteja rakentamiskomennolle, saatat tehdä sen niin, että komennon lopussa <OPTIONAL-ARGS>`-kohdassa. Esimerkiksi, jos haluat rakentaa optimoidun julkaisuversion, sinun on ajettava komento yllä siten, että sitä seuraa argumentti `---release`. Suoritettava tiedosto on saatavilla järjestelmäsi kohdehakemistossa, ja se voidaan suorittaa seuraavan kera:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Tai, jos olet suorittamassa jakeluversion suoritettavaa tiedostoa:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Varmista, että suoritat näitä komentoja RustDesktop-tietovaraston juurihakemistossa, muutoin sovellus ei ehkä löydä vaadittuja resursseja. Huomaa myös, että muita cargo-alikomentoja kuten `install` tai `run` ei nykyisin tueta tässä menetelmässä, koska ne asentavat tai suorittavat ohjelman säiliön sisällä eikä isäntäohjelman sisällä.
|
||||
|
||||
## Tiedostorakenne
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs-funktiot tiedostosiirtoon, ja jotkut muut apuohjelmafunktiot
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: näyttökaappaukset
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform specific keyboard/mouse control
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: Graafinen käyttöliittymä
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/clipboard/input/video services, and network connections
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start a peer connection
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Communicate with [rustdesk-server](https://github.com/rustdesk/rustdesk-server), wait for remote direct (TCP hole punching) or relayed connection
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform specific code
|
||||
|
||||
## Tilannekuvat
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
160
rust-rdp/rust-desk/README-FR.md
Normal file
160
rust-rdp/rust-desk/README-FR.md
Normal file
@@ -0,0 +1,160 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#serveurs-publics-libres">Serveurs</a> -
|
||||
<a href="#étapes-brutes-de-la-compilationbuild">Build</a> -
|
||||
<a href="#comment-construire-avec-docker">Docker</a> -
|
||||
<a href="#structure-du-projet">Structure</a> -
|
||||
<a href="#images">Images</a><br>
|
||||
[<a href="README.md">English</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-ES.md">Española</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||
<b>Nous avons besoin de votre aide pour traduire ce README dans votre langue maternelle</b>.
|
||||
</p>
|
||||
|
||||
Chattez avec nous : [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Encore un autre logiciel de bureau à distance, écrit en Rust. Fonctionne directement, aucune configuration n'est nécessaire. Vous avez le contrôle total de vos données, sans aucun souci de sécurité. Vous pouvez utiliser notre serveur de rendez-vous/relais, [configurer le vôtre](https://rustdesk.com/blog/id-relay-set/), ou [écrire votre propre serveur de rendez-vous/relais](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
RustDesk accueille les contributions de tout le monde. Voir [`CONTRIBUTING.md`](CONTRIBUTING.md) pour plus d'informations.
|
||||
|
||||
[**TÉLÉCHARGEMENT BINAIRE**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## Serveurs publics libres
|
||||
|
||||
Ci-dessous se trouvent les serveurs que vous utilisez gratuitement, cela peut changer au fil du temps. Si vous n'êtes pas proche de l'un d'entre eux, votre réseau peut être lent.
|
||||
|
||||
- Séoul, AWS lightsail, 1 VCPU/0.5G RAM
|
||||
- Singapour, Vultr, 1 VCPU/1G RAM
|
||||
- Dallas, Vultr, 1 VCPU/1G RAM
|
||||
|
||||
## Dépendances
|
||||
|
||||
Les versions de bureau utilisent [sciter](https://sciter.com/) pour l'interface graphique, veuillez télécharger la bibliothèque dynamique sciter vous-même.
|
||||
|
||||
[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)
|
||||
|
||||
## Étapes brutes de la compilation/build
|
||||
|
||||
- Préparez votre environnement de développement Rust et votre environnement de compilation C++.
|
||||
|
||||
- Installez [vcpkg](https://github.com/microsoft/vcpkg), et définissez correctement la variable d'environnement `VCPKG_ROOT`.
|
||||
|
||||
- Windows : vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/Osx : vcpkg install libvpx libyuv opus
|
||||
|
||||
- Exécuter `cargo run`
|
||||
|
||||
## Comment compiler/build sous Linux
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### Installer vcpkg
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### Corriger libvpx (Pour Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Construire
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p cible/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
Exécution du cargo
|
||||
```
|
||||
|
||||
### Changer Wayland en X11 (Xorg)
|
||||
|
||||
RustDesk ne supporte pas Wayland. Lisez [cela](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) pour configurer Xorg comme la session GNOME par défaut.
|
||||
|
||||
## Comment construire avec Docker
|
||||
|
||||
Commencez par cloner le dépôt et construire le conteneur Docker :
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Ensuite, chaque fois que vous devez build le logiciel, exécutez la commande suivante :
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Notez que le premier build peut prendre plus de temps avant que les dépendances ne soient mises en cache, les constructions suivantes seront plus rapides. De plus, si vous devez spécifier différents arguments à la commande de compilation, vous pouvez le faire à la fin de la commande dans la position `<OPTIONAL-ARGS>`. Par exemple, si vous voulez construire une version optimisée de la version release, vous devez exécuter la commande ci-dessus suivie de `---release`. L'exécutable résultant sera disponible dans le dossier cible sur votre système, et peut être lancé avec :
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Ou, si vous exécutez un exécutable provenant d'une release :
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Veuillez vous assurer que vous exécutez ces commandes à partir de la racine du référentiel RustDesk, sinon l'application ne pourra pas trouver les ressources requises. Notez également que les autres sous-commandes de cargo telles que `install` ou `run` ne sont pas actuellement supportées par cette méthode car elles installeraient ou exécuteraient le programme à l'intérieur du conteneur au lieu de l'hôte.
|
||||
|
||||
## Structure du projet
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)** : codec vidéo, config, wrapper tcp/udp, protobuf, fonctions fs pour le transfert de fichiers, et quelques autres fonctions utilitaires.
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)** : capture d'écran
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)** : contrôle clavier/souris spécifique à la plate-forme
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)** : INTERFACE GRAPHIQUE
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)** : services audio/clipboard/input/vidéo, et connexions réseau
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)** : démarrer une connexion entre pairs
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)** : Communiquer avec [rustdesk-server](https://github.com/rustdesk/rustdesk-server), attendre une connexion distante directe (TCP hole punching) ou relayée.
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)** : code spécifique à la plateforme
|
||||
|
||||
## Images
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
164
rust-rdp/rust-desk/README-JP.md
Normal file
164
rust-rdp/rust-desk/README-JP.md
Normal file
@@ -0,0 +1,164 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#free-public-servers">Servers</a> •
|
||||
<a href="#raw-steps-to-build">Build</a> •
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||
<b>このREADMEをあなたの母国語に翻訳するために、あなたの助けが必要です。</b>
|
||||
</p>
|
||||
|
||||
Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Rustで書かれた、設定不要ですぐに使えるリモートデスクトップソフトウェアです。自分のデータを完全にコントロールでき、セキュリティの心配もありません。私たちのランデブー/リレーサーバを使うことも、[自分で設定する](https://rustdesk.com/blog/id-relay-set/) ことも、 [自分でランデブー/リレーサーバを書くこともできます。](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
RustDeskは誰からの貢献も歓迎します。 貢献するには [`CONTRIBUTING.md`](CONTRIBUTING.md) を参照してください。
|
||||
|
||||
[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## 無料のパブリックサーバー
|
||||
|
||||
下記のサーバーは、無料で使用できますが、後々変更されることがあります。これらのサーバーから遠い場合、接続が遅い可能性があります。
|
||||
| Location | Vendor | Specification |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM |
|
||||
| Singapore | Vultr | 1 VCPU / 1GB RAM |
|
||||
| Dallas | Vultr | 1 VCPU / 1GB RAM | |
|
||||
|
||||
## 依存関係
|
||||
|
||||
デスクトップ版ではGUIに [sciter](https://sciter.com/) が使われています。 sciter dynamic library をダウンロードしてください。
|
||||
|
||||
[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)
|
||||
|
||||
## ビルド手順
|
||||
|
||||
- Rust開発環境とC ++ビルド環境を準備します
|
||||
|
||||
- [vcpkg](https://github.com/microsoft/vcpkg), をインストールし、 `VCPKG_ROOT` 環境変数を正しく設定します。
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus
|
||||
|
||||
- run `cargo run`
|
||||
|
||||
## Linuxでのビルド手順
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### Install vcpkg
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### Fix libvpx (For Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Wayland の場合、X11(Xorg)に変更します
|
||||
|
||||
RustDeskはWaylandをサポートしていません。
|
||||
[こちら](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) を確認して、XorgをデフォルトのGNOMEセッションとして構成します。
|
||||
|
||||
## Dockerでビルドする方法
|
||||
|
||||
リポジトリのクローンを作成し、Dockerコンテナを構築することから始めます。
|
||||
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
その後、アプリケーションをビルドする必要があるたびに、以下のコマンドを実行します。
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
なお、最初のビルドでは、依存関係がキャッシュされるまで時間がかかることがありますが、その後のビルドではより速くなります。さらに、ビルドコマンドに別の引数を指定する必要がある場合は、コマンドの最後にある `<OPTIONAL-ARGS>` の位置で指定することができます。例えば、最適化されたリリースバージョンをビルドしたい場合は、上記のコマンドの後に
|
||||
`---release` を実行します。できあがった実行ファイルは、システムのターゲット・フォルダに格納され、次のコマンドで実行できます。
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
あるいは、リリース用の実行ファイルを実行している場合:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
これらのコマンドをRustDeskリポジトリのルートから実行していることを確認してください。そうしないと、アプリケーションが必要なリソースを見つけられない可能性があります。また、 `install` や `run` などの他の cargo サブコマンドは、ホストではなくコンテナ内にプログラムをインストールまたは実行するため、現在この方法ではサポートされていないことに注意してください。
|
||||
|
||||
## File Structure
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: ビデオコーデック、コンフィグ、tcp/udpラッパー、protobuf、ファイル転送用のfs関数、その他のユーティリティ関数
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: スクリーンキャプチャ
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: プラットフォーム固有のキーボード/マウスコントロール
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: オーディオ/クリップボード/入力/ビデオサービス、ネットワーク接続
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: ピア接続の開始
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server), と通信し、リモートダイレクト (TCP hole punching) または中継接続を待つ。
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: プラットフォーム固有のコード
|
||||
|
||||
## Snapshot
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
161
rust-rdp/rust-desk/README-ML.md
Normal file
161
rust-rdp/rust-desk/README-ML.md
Normal file
@@ -0,0 +1,161 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#free-public-servers">Servers</a> •
|
||||
<a href="#raw-steps-to-build">Build</a> •
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||
<b>ഈ README നിങ്ങളുടെ മാതൃഭാഷയിലേക്ക് വിവർത്തനം ചെയ്യാൻ ഞങ്ങൾക്ക് നിങ്ങളുടെ സഹായം ആവശ്യമാണ്</b>
|
||||
</p>
|
||||
|
||||
ഞങ്ങളുമായി ചാറ്റ് ചെയ്യുക: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
റസ്റ്റിൽ എഴുതിയ മറ്റൊരു റിമോട്ട് ഡെസ്ക്ടോപ്പ് സോഫ്റ്റ്വെയർ. ബോക്സിന് പുറത്ത് പ്രവർത്തിക്കുന്നു, കോൺഫിഗറേഷൻ ആവശ്യമില്ല. സുരക്ഷയെക്കുറിച്ച് ആശങ്കകളൊന്നുമില്ലാതെ, നിങ്ങളുടെ ഡാറ്റയുടെ പൂർണ്ണ നിയന്ത്രണം നിങ്ങൾക്കുണ്ട്. നിങ്ങൾക്ക് ഞങ്ങളുടെ rendezvous/relay സെർവർ ഉപയോഗിക്കാം, [സ്വന്തമായി സജ്ജീകരിക്കുക](https://rustdesk.com/blog/id-relay-set/), അല്ലെങ്കിൽ [നിങ്ങളുടെ സ്വന്തം rendezvous/relay സെർവർ എഴുതുക](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
എല്ലാവരുടെയും സംഭാവനയെ RustDesk സ്വാഗതം ചെയ്യുന്നു. ആരംഭിക്കുന്നതിനുള്ള സഹായത്തിന് [`CONTRIBUTING.md`](CONTRIBUTING.md) കാണുക.
|
||||
|
||||
[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## സൗജന്യ പൊതു സെർവറുകൾ
|
||||
|
||||
നിങ്ങൾ സൗജന്യമായി ഉപയോഗിക്കുന്ന സെർവറുകൾ ചുവടെയുണ്ട്, അത് സമയത്തിനനുസരിച്ച് മാറിയേക്കാം. നിങ്ങൾ ഇവയിലൊന്നിനോട് അടുത്തല്ലെങ്കിൽ, നിങ്ങളുടെ നെറ്റ്വർക്ക് സ്ലോ ആയേക്കാം.
|
||||
| സ്ഥാനം | കച്ചവടക്കാരൻ | വിവരണം |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM |
|
||||
| Singapore | Vultr | 1 VCPU / 1GB RAM |
|
||||
| Dallas | Vultr | 1 VCPU / 1GB RAM | |
|
||||
|
||||
## ഡിപെൻഡൻസികൾ
|
||||
|
||||
ഡെസ്ക്ടോപ്പ് പതിപ്പുകൾ GUI-യ്ക്കായി [sciter](https://sciter.com/) ഉപയോഗിക്കുന്നു, ദയവായി സ്സൈറ്റർ ഡൈനാമിക് ലൈബ്രറി സ്വയം ഡൗൺലോഡ് ചെയ്യുക.
|
||||
|
||||
[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)
|
||||
|
||||
## നിർമ്മിക്കാനുള്ള അസംസ്കൃത പടികൾ
|
||||
|
||||
- നിങ്ങളുടെ Rust development envയും and C++ build envയും തയ്യാറാക്കുക
|
||||
|
||||
- [vcpkg](https://github.com/microsoft/vcpkg) ഇൻസ്റ്റാൾ ചെയ്ത് `VCPKG_ROOT` env വേരിയബിൾ ശരിയായി സജ്ജമാക്കുക
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus
|
||||
|
||||
- run `cargo run`
|
||||
|
||||
## ലിനക്സിൽ എങ്ങനെ നിർമ്മിക്കാം
|
||||
|
||||
### ഉബുണ്ടു 18 (ഡെബിയൻ 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### ഫെഡോറ 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### ആർച് (മഞ്ചാരോ)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### vcpkg ഇൻസ്റ്റാൾ ചെയ്യുക
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### libvpx പരിഹരിക്കുക (ഫെഡോറയ്ക്ക്)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### നിർമാണം
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### വേലാൻഡ് X11 (Xorg) ആയി മാറ്റുക
|
||||
|
||||
RustDesk Wayland-നെ പിന്തുണയ്ക്കുന്നില്ല. സ്ഥിരസ്ഥിതി ഗ്നോം സെഷനായി Xorg കോൺഫിഗർ ചെയ്യുന്നതിന് [ഇത്](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) പരിശോധിക്കുക.
|
||||
|
||||
## ഡോക്കർ ഉപയോഗിച്ച് എങ്ങനെ നിർമ്മിക്കാം
|
||||
|
||||
റെപ്പോസിറ്റോറി ക്ലോണുചെയ്ത് ഡോക്കർ കണ്ടെയ്നർ നിർമ്മിക്കുന്നതിലൂടെ ആരംഭിക്കുക:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
തുടർന്ന്, ഓരോ തവണയും നിങ്ങൾ ആപ്ലിക്കേഷൻ നിർമ്മിക്കേണ്ടതുണ്ട്, ഇനിപ്പറയുന്ന കമാൻഡ് പ്രവർത്തിപ്പിക്കുക:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
ഡിപൻഡൻസികൾ കാഷെ ചെയ്യുന്നതിനുമുമ്പ് ആദ്യ ബിൽഡ് കൂടുതൽ സമയമെടുത്തേക്കാം, തുടർന്നുള്ള ബിൽഡുകൾ വേഗത്തിലാകും. കൂടാതെ, നിങ്ങൾക്ക് ബിൽഡ് കമാൻഡിലേക്ക് വ്യത്യസ്ത ആർഗ്യുമെന്റുകൾ വ്യക്തമാക്കണമെങ്കിൽ, കമാൻഡിന്റെ അവസാനം `<OPTIONAL-ARGS>` സ്ഥാനത്ത് നിങ്ങൾക്ക് അങ്ങനെ ചെയ്യാം. ഉദാഹരണത്തിന്, നിങ്ങൾ ഒരു ഒപ്റ്റിമൈസ് ചെയ്ത റിലീസ് പതിപ്പ് നിർമ്മിക്കാൻ ആഗ്രഹിക്കുന്നുവെങ്കിൽ, മുകളിലുള്ള കമാൻഡ് തുടർന്ന് `---release` നിങ്ങൾ പ്രവർത്തിപ്പിക്കും. തത്ഫലമായുണ്ടാകുന്ന എക്സിക്യൂട്ടബിൾ നിങ്ങളുടെ സിസ്റ്റത്തിലെ ടാർഗെറ്റ് ഫോൾഡറിൽ ലഭ്യമാകും, കൂടാതെ ഇത് ഉപയോഗിച്ച് പ്രവർത്തിപ്പിക്കാം:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
അല്ലെങ്കിൽ, നിങ്ങൾ ഒരു റിലീസ് എക്സിക്യൂട്ടബിൾ പ്രവർത്തിപ്പിക്കുകയാണെങ്കിൽ:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
RustDesk റിപ്പോസിറ്ററിയുടെ റൂട്ടിൽ നിന്നാണ് നിങ്ങൾ ഈ കമാൻഡുകൾ പ്രവർത്തിപ്പിക്കുന്നതെന്ന് ദയവായി ഉറപ്പാക്കുക, അല്ലാത്തപക്ഷം ആപ്ലിക്കേഷന് ആവശ്യമായ ഉറവിടങ്ങൾ കണ്ടെത്താൻ കഴിഞ്ഞേക്കില്ല. ഹോസ്റ്റിന് പകരം കണ്ടെയ്നറിനുള്ളിൽ പ്രോഗ്രാം ഇൻസ്റ്റാൾ ചെയ്യുകയോ പ്രവർത്തിപ്പിക്കുകയോ ചെയ്യുന്നതിനാൽ, `install` അല്ലെങ്കിൽ `run` പോലുള്ള മറ്റ് കാർഗോ സബ്കമാൻഡുകൾ നിലവിൽ ഈ രീതിയെ പിന്തുണയ്ക്കുന്നില്ല എന്നതും ശ്രദ്ധിക്കുക.
|
||||
|
||||
## ഫയൽ ഘടന
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs functions for file transfer, and some other utility functions
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: screen capture
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform specific keyboard/mouse control
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/clipboard/input/video services, and network connections
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start a peer connection
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Communicate with [rustdesk-server](https://github.com/rustdesk/rustdesk-server), wait for remote direct (TCP hole punching) or relayed connection
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform specific code
|
||||
|
||||
## സ്നാപ്പ്ഷോട്ടുകൾ
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
163
rust-rdp/rust-desk/README-NL.md
Normal file
163
rust-rdp/rust-desk/README-NL.md
Normal file
@@ -0,0 +1,163 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Jouw verbinding op afstand"><br>
|
||||
<a href="#free-public-servers">Servers</a> •
|
||||
<a href="#raw-steps-to-build">Bouwen</a> •
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structuur</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||
<b>We hebben je hulp nodig om deze README te vertalen naar jouw moedertaal</b>
|
||||
</p>
|
||||
|
||||
Praat met ons: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Nog weer een applicatie voor toegang op afstand, geschreven in Rust. Werkt meteen, geen configuratie nodig. Je hebt volledig beheer over je data, zonder na te hoeven denken over veiligheid. Je kunt onze rendez-vous/relay-server gebruiken, [je eigen server opzetten](https://rustdesk.com/blog/id-relay-set), of [je eigen rendez-vous/relay-server schrijven](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
RustDesk verwelkomt bijdragen van iedereen. Zie [`CONTRIBUTING.md`](CONTRIBUTING.md) om te lezen hoe je van start kunt gaan.
|
||||
|
||||
[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## Gratis openbare servers
|
||||
|
||||
Onderstaande servers zijn de servers die je gratis kunt gebruiken, ze kunnen op termijn veranderen. Als je niet fysiek dichtbij een van deze servers bent, kan je verbinding traag werken.
|
||||
| Locatie | Aanbieder | Specificaties |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM |
|
||||
| Singapore | Vultr | 1 VCPU / 1GB RAM |
|
||||
| Dallas | Vultr | 1 VCPU / 1GB RAM | |
|
||||
|
||||
## Afhankelijkheden
|
||||
|
||||
Desktopversies gebruiken [sciter](https://sciter.com/) voor de grafische schil. Gelieve zelf de sciter-library te downloaden.
|
||||
|
||||
[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)
|
||||
|
||||
## Handmatige bouwinstructies
|
||||
|
||||
- Bereid je Rust-ontwikkelomgeving en C++-bouwomgeving voor.
|
||||
|
||||
- Installeer [vcpkg](https://github.com/microsoft/vcpkg) en configureer de `VCPKG_ROOT` omgevingsvariabele op de juiste manier:
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus
|
||||
|
||||
- Voer uit: `cargo run`
|
||||
|
||||
## Bouwen op Linux
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### Installatie van vcpkg
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### Fix voor libvpx (voor Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Bouwen
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Wissel van Wayland naar X11 (Xorg)
|
||||
|
||||
RustDesk ondersteunt Wayland niet. Lees [hier](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) hoe je Xorg als standaardsessie kunt instellen voor GNOME.
|
||||
|
||||
## Bouwen met Docker
|
||||
|
||||
Kloon eerst deze repository en bouw de Docker-container:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Voer vervolgens de volgende commando's uit iedere keer dat je de applicatie opnieuw moet bouwen:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Let op dat de eerste build langer kan duren omdat de dependencies nog niet zijn gecached; latere builds zullen sneller zijn. Als je extra command line arguments wilt toevoegen aan het build-commando, dan kun je dat doen aan het einde van de opdrachtregel in plaats van `<OPTIONAL-ARGS>`. Bijvoorbeeld: als je een geoptimaliseerde releaseversie wilt bouwen, draai dan het bovenstaande commando gevolgd door `---release`.
|
||||
|
||||
Het uitvoerbare bestand, in debug-modus, zal verschijnen in de target-map, en kan als volgt worden uitgevoerd:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Als je een release-versie hebt gebouwd, is het commando als volgt:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Zorg ervoor dat je deze commando's van de root van de RustDesk-repository uitvoert, anders kan het programma de nodige afhankelijkheden mogelijk niet vinden. Let ook op dat andere cargo-subcommando's zoals `install` en `run` zijn momenteel niet ondersteund, aangezien deze zouden worden uitgevoerd in een container in plaats van op de host.
|
||||
|
||||
## Bestandsstructuur
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: videocodec, configuratie, TCP/UDP-wrapper, protobuf, bestandssysteemfuncties voor bestandsoverdracht en nog wat andere nuttige functies
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: schermopname
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platformspecifieke muis- en toetsenbordbeheer
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: geluids-, klembord-, invoer- en video-services, netwerkverbindingen
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: voor het opzetten van peer-verbindingen
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Communicatie met [rustdesk-server](https://github.com/rustdesk/rustdesk-server), afwachten van redirect op afstand (TCP hole punching) of een relayed verbinding
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platformspecifieke code
|
||||
|
||||
## Snapshot
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
161
rust-rdp/rust-desk/README-PL.md
Normal file
161
rust-rdp/rust-desk/README-PL.md
Normal file
@@ -0,0 +1,161 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#darmowe-serwery-publiczne">Serwery</a> •
|
||||
<a href="#podstawowe-kroki-do-kompilacji">Kompilacja</a> •
|
||||
<a href="#jak-kompilować-za-pomocą-dockera">Docker</a> •
|
||||
<a href="#struktura-plików">Struktura</a> •
|
||||
<a href="#migawkisnapshoty">Snapshot</a><br>
|
||||
[<a href="README.md">English</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-ES.md">Española</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||
<b>Potrzebujemy twojej pomocy w tłumaczeniu README na twój ojczysty język</b>
|
||||
</p>
|
||||
|
||||
Porozmawiaj z nami na: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Kolejny program do zdalnego pulpitu, napisany w Rust. Działa od samego początku, nie wymaga konfiguracji. Masz pełną kontrolę nad swoimi danymi, bez obaw o bezpieczeństwo. Możesz skorzystać z naszego darmowego serwera publicznego , [skonfigurować własny](https://rustdesk.com/blog/id-relay-set/), lub [napisać własny serwer rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
RustDesk zaprasza do współpracy każdego. Zobacz [`CONTRIBUTING.md`](CONTRIBUTING.md) pomoc w uruchomieniu programu.
|
||||
|
||||
[**POBIERZ KOMPILACJE**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## Darmowe Serwery Publiczne
|
||||
|
||||
Poniżej znajdują się serwery, z których można korzystać za darmo, może się to zmienić z upływem czasu. Jeśli nie znajdujesz się w pobliżu jednego z nich, Twoja prędkość połączenia może być niska.
|
||||
| Lokalizacja | Dostawca | Specyfikacja |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seul | AWS lightsail | 1 VCPU / 0.5GB RAM |
|
||||
| Singapur | Vultr | 1 VCPU / 1GB RAM |
|
||||
| Dallas | Vultr | 1 VCPU / 1GB RAM | |
|
||||
|
||||
## Zależności
|
||||
|
||||
Wersje desktopowe używają [sciter](https://sciter.com/) dla GUI, proszę pobrać bibliotekę dynamiczną sciter samodzielnie.
|
||||
|
||||
[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)
|
||||
|
||||
## Podstawowe kroki do kompilacji.
|
||||
|
||||
- Przygotuj środowisko programistyczne Rust i środowisko programowania C++
|
||||
|
||||
- Zainstaluj [vcpkg](https://github.com/microsoft/vcpkg), i ustaw `VCPKG_ROOT` env zmienną prawidłowo
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus
|
||||
|
||||
- uruchom `cargo run`
|
||||
|
||||
## Jak Kompilować na Linuxie
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### Zainstaluj vcpkg
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### Fix libvpx (For Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Kompilacja
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
cargo run
|
||||
```
|
||||
|
||||
### Zmień Wayland na X11 (Xorg)
|
||||
|
||||
RustDesk nie obsługuje Waylanda. Sprawdź [this](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) by skonfigurować Xorg jako domyślną sesję GNOME.
|
||||
|
||||
## Jak kompilować za pomocą Dockera
|
||||
|
||||
Rozpocznij od sklonowania repozytorium i stworzenia kontenera docker:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Następnie, za każdym razem, gdy potrzebujesz skompilować aplikację, uruchom następujące polecenie:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Zauważ, że pierwsza kompilacja może potrwać dłużej zanim zależności zostaną zbuforowane, kolejne będą szybsze. Dodatkowo, jeśli potrzebujesz określić inne argumenty dla polecenia budowania, możesz to zrobić na końcu komendy w miejscu `<OPTIONAL-ARGS>`. Na przykład, jeśli chciałbyś zbudować zoptymalizowaną wersję wydania, uruchomiłbyś powyższą komendę a następnie `---release`. Powstały plik wykonywalny będzie dostępny w folderze docelowym w twoim systemie, i może być uruchomiony z:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Lub, jeśli uruchamiasz plik wykonywalny wersji:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Upewnij się, że uruchamiasz te polecenia z katalogu głównego repozytorium RustDesk, w przeciwnym razie aplikacja może nie być w stanie znaleźć wymaganych zasobów. Należy również pamiętać, że inne podpolecenia ładowania, takie jak `install` lub `run` nie są obecnie obsługiwane za pomocą tej metody, ponieważ instalowałyby lub uruchamiały program wewnątrz kontenera zamiast na hoście.
|
||||
|
||||
## Struktura plików
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: kodek wideo, config, wrapper tcp/udp, protobuf, funkcje fs do transferu plików i kilka innych funkcji użytkowych
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: przechwytywanie ekranu
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: specyficzne dla danej platformy sterowanie klawiaturą/myszą
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/schowek/wejście(input)/wideo oraz połączenia sieciowe
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: uruchamia połączenie peer
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Komunikacja z [rustdesk-server](https://github.com/rustdesk/rustdesk-server), wait for remote direct (TCP hole punching) or relayed connection
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: specyficzny dla danej platformy kod
|
||||
|
||||
## Migawki(Snapshoty)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
213
rust-rdp/rust-desk/README-ZH.md
Normal file
213
rust-rdp/rust-desk/README-ZH.md
Normal file
@@ -0,0 +1,213 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#免费公共服务器">服务器</a> •
|
||||
<a href="#基本构建步骤">编译</a> •
|
||||
<a href="#使用Docker编译">Docker</a> •
|
||||
<a href="#文件结构">结构</a> •
|
||||
<a href="#截图">截图</a><br>
|
||||
[<a href="README.md">English</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||
</p>
|
||||
|
||||
Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
远程桌面软件,开箱即用,无需任何配置。您完全掌控数据,不用担心安全问题。您可以使用我们的注册/中继服务器,
|
||||
或者[自己设置](https://rustdesk.com/blog/id-relay-set/),
|
||||
亦或者[开发您的版本](https://github.com/rustdesk/rustdesk-server-demo)。
|
||||
|
||||
欢迎大家贡献代码, 请看 [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
||||
|
||||
[**可执行程序下载**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## 免费公共服务器
|
||||
|
||||
以下是您免费使用的服务器,它可能会随着时间的推移而变化。如果您不靠近其中之一,您的网络可能会很慢。
|
||||
|
||||
- 首尔, AWS lightsail, 1 VCPU/0.5G RAM
|
||||
- 新加坡, Vultr, 1 VCPU/1G RAM
|
||||
- 达拉斯, Vultr, 1 VCPU/1G RAM
|
||||
|
||||
## 依赖
|
||||
|
||||
桌面版本界面使用[sciter](https://sciter.com/), 请自行下载。
|
||||
|
||||
[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)
|
||||
|
||||
## 基本构建步骤
|
||||
|
||||
- 请准备好 Rust 开发环境和 C++编译环境
|
||||
|
||||
- 安装[vcpkg](https://github.com/microsoft/vcpkg), 正确设置`VCPKG_ROOT`环境变量
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/Osx: vcpkg install libvpx libyuv opus
|
||||
|
||||
- 运行 `cargo run`
|
||||
|
||||
## 在 Linux 上编译
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### 安装 vcpkg
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### 修复 libvpx (仅仅针对 Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### 构建
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
cargo run
|
||||
```
|
||||
|
||||
### 把 Wayland 修改成 X11 (Xorg)
|
||||
|
||||
RustDesk 暂时不支持 Wayland,不过正在积极开发中.
|
||||
请查看[this](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/)配置 X11.
|
||||
|
||||
## 使用 Docker 编译
|
||||
|
||||
首先克隆存储库并构建 docker 容器:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
针对国内网络访问问题,可以做以下几点优化:
|
||||
|
||||
1. Dockerfile 中修改系统的源到国内镜像
|
||||
|
||||
```
|
||||
在Dockerfile的RUN apt update之前插入两行:
|
||||
|
||||
RUN sed -i "s/deb.debian.org/mirrors.163.com/g" /etc/apt/sources.list
|
||||
RUN sed -i "s/security.debian.org/mirrors.163.com/g" /etc/apt/sources.list
|
||||
```
|
||||
|
||||
2. 修改容器系统中的 cargo 源,在`RUN ./rustup.sh -y`后插入下面代码:
|
||||
|
||||
```
|
||||
RUN echo '[source.crates-io]' > ~/.cargo/config \
|
||||
&& echo 'registry = "https://github.com/rust-lang/crates.io-index"' >> ~/.cargo/config \
|
||||
&& echo '# 替换成你偏好的镜像源' >> ~/.cargo/config \
|
||||
&& echo "replace-with = 'sjtu'" >> ~/.cargo/config \
|
||||
&& echo '# 上海交通大学' >> ~/.cargo/config \
|
||||
&& echo '[source.sjtu]' >> ~/.cargo/config \
|
||||
&& echo 'registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"' >> ~/.cargo/config \
|
||||
&& echo '' >> ~/.cargo/config
|
||||
```
|
||||
|
||||
3. Dockerfile 中加入代理的 env
|
||||
|
||||
```
|
||||
在User root后插入两行
|
||||
|
||||
ENV http_proxy=http://host:port
|
||||
ENV https_proxy=http://host:port
|
||||
```
|
||||
|
||||
4. docker build 命令后面加上 proxy 参数
|
||||
```
|
||||
docker build -t "rustdesk-builder" . --build-arg http_proxy=http://host:port --build-arg https_proxy=http://host:port
|
||||
```
|
||||
|
||||
然后,每次需要构建应用程序时,运行以下命令:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
运行若遇到无权限问题,出现以下提示:
|
||||
|
||||
```
|
||||
usermod: user user is currently used by process 1
|
||||
groupmod: Permission denied.
|
||||
groupmod: cannot lock /etc/group; try again later.
|
||||
```
|
||||
|
||||
可以尝试把`-e PUID="$(id -u)" -e PGID="$(id -g)"`参数去掉。(出现这一问题的原因是容器中的 entrypoint 脚本中判定 uid 和 gid 与给定的环境变量不一致时会修改 user 的 uid 和 gid 重新运行,但是重新运行时取不到环境变量中的 uid 和 gid 了,会再次进入 uid 与 gid 与给定值不一致的逻辑分支)
|
||||
|
||||
请注意,第一次构建可能需要比较长的时间,因为需要缓存依赖项(国内网络经常出现拉取失败,可多尝试几次),后续构建会更快。此外,如果您需要为构建命令指定不同的参数,
|
||||
您可以在命令末尾的 `<OPTIONAL-ARGS>` 位置执行此操作。例如,如果你想构建一个优化的发布版本,你可以在命令后跟 `---release`。
|
||||
将在 target 下产生可执行程序,请通过以下方式运行调试版本:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
或者运行发布版本:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
请确保您从 RustDesk 存储库的根目录运行这些命令,否则应用程序可能无法找到所需的资源。另请注意,此方法当前不支持其他`Cargo`子命令,
|
||||
例如 `install` 或 `run`,因为运行在容器里,而不是宿主机上。
|
||||
|
||||
## 文件结构
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 视频编解码, 配置, tcp/udp 封装, protobuf, 文件传输相关文件系统操作函数, 以及一些其他实用函数
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 截屏
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: 平台相关的鼠标键盘输入
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 被控端服务,audio/clipboard/input/video 服务, 已经连接实现
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: 控制端
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: 与[rustdesk-server](https://github.com/rustdesk/rustdesk-server)保持 UDP 通讯, 等待远程连接(通过打洞直连或者中继)
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 平台服务相关代码
|
||||
|
||||
## 截图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
161
rust-rdp/rust-desk/README.md
Normal file
161
rust-rdp/rust-desk/README.md
Normal file
@@ -0,0 +1,161 @@
|
||||
<p align="center">
|
||||
<img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#free-public-servers">Servers</a> •
|
||||
<a href="#raw-steps-to-build">Build</a> •
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">Structure</a> •
|
||||
<a href="#snapshot">Snapshot</a><br>
|
||||
[<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</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-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-RU.md">Русский</a>] | [<a href="README-PT.md">Português</a>]<br>
|
||||
<b>We need your help to translate this README and <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> to your native language</b>
|
||||
</p>
|
||||
|
||||
Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Yet another remote desktop software, written in Rust. Works out of the box, no configuration required. You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, [set up your own](https://rustdesk.com/blog/id-relay-set/), or [write your own rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
RustDesk welcomes contribution from everyone. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for help getting started.
|
||||
|
||||
[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases)
|
||||
|
||||
## Free Public Servers
|
||||
|
||||
Below are the servers you are using for free, it may change along the time. If you are not close to one of these, your network may be slow.
|
||||
| Location | Vendor | Specification |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM |
|
||||
| Singapore | Vultr | 1 VCPU / 1GB RAM |
|
||||
| Dallas | Vultr | 1 VCPU / 1GB RAM | |
|
||||
|
||||
## Dependencies
|
||||
|
||||
Desktop versions use [sciter](https://sciter.com/) for GUI, please download sciter dynamic library yourself.
|
||||
|
||||
[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)
|
||||
|
||||
## Raw steps to build
|
||||
|
||||
- Prepare your Rust development env and C++ build env
|
||||
|
||||
- Install [vcpkg](https://github.com/microsoft/vcpkg), and set `VCPKG_ROOT` env variable correctly
|
||||
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus
|
||||
|
||||
- run `cargo run`
|
||||
|
||||
## How to build on Linux
|
||||
|
||||
### Ubuntu 18 (Debian 10)
|
||||
|
||||
```sh
|
||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
||||
```
|
||||
|
||||
### Fedora 28 (CentOS 8)
|
||||
|
||||
```sh
|
||||
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
|
||||
```
|
||||
|
||||
### Arch (Manjaro)
|
||||
|
||||
```sh
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
|
||||
```
|
||||
|
||||
### Install vcpkg
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
cd vcpkg
|
||||
git checkout 2021.12.01
|
||||
cd ..
|
||||
vcpkg/bootstrap-vcpkg.sh
|
||||
export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus
|
||||
```
|
||||
|
||||
### Fix libvpx (For Fedora)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
cd *
|
||||
./configure
|
||||
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||
make
|
||||
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
mkdir -p target/debug
|
||||
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||
mv libsciter-gtk.so target/debug
|
||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
```
|
||||
|
||||
### Change Wayland to X11 (Xorg)
|
||||
|
||||
RustDesk does not support Wayland. Check [this](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) to configuring Xorg as the default GNOME session.
|
||||
|
||||
## How to build with Docker
|
||||
|
||||
Begin by cloning the repository and building the docker container:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
Then, each time you need to build the application, run the following command:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Note that the first build may take longer before dependencies are cached, subsequent builds will be faster. Additionally, if you need to specify different arguments to the build command, you may do so at the end of the command in the `<OPTIONAL-ARGS>` position. For instance, if you wanted to build an optimized release version, you would run the command above followed by `---release`. The resulting executable will be available in the target folder on your system, and can be run with:
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
```
|
||||
|
||||
Or, if you're running a release executable:
|
||||
|
||||
```sh
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
Please ensure that you are running these commands from the root of the RustDesk repository, otherwise the application may be unable to find the required resources. Also note that other cargo subcommands such as `install` or `run` are not currently supported via this method as they would install or run the program inside the container instead of the host.
|
||||
|
||||
## File Structure
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs functions for file transfer, and some other utility functions
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: screen capture
|
||||
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform specific keyboard/mouse control
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/clipboard/input/video services, and network connections
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start a peer connection
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Communicate with [rustdesk-server](https://github.com/rustdesk/rustdesk-server), wait for remote direct (TCP hole punching) or relayed connection
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform specific code
|
||||
|
||||
## Snapshot
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
13
rust-rdp/rust-desk/SECURITY.md
Normal file
13
rust-rdp/rust-desk/SECURITY.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| --------- | ------------------ |
|
||||
| 1.1.x | :white_check_mark: |
|
||||
| 1.x | :white_check_mark: |
|
||||
| Below 1.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Here we should write what to do in case of a security vulnerability
|
||||
74
rust-rdp/rust-desk/build.rs
Normal file
74
rust-rdp/rust-desk/build.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
#[cfg(windows)]
|
||||
fn build_windows() {
|
||||
cc::Build::new().file("src/windows.cc").compile("windows");
|
||||
println!("cargo:rustc-link-lib=WtsApi32");
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=windows.cc");
|
||||
}
|
||||
|
||||
#[cfg(all(windows, feature = "inline"))]
|
||||
fn build_manifest() {
|
||||
use std::io::Write;
|
||||
if std::env::var("PROFILE").unwrap() == "release" {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("icon.ico")
|
||||
.set_language(winapi::um::winnt::MAKELANGID(
|
||||
winapi::um::winnt::LANG_ENGLISH,
|
||||
winapi::um::winnt::SUBLANG_ENGLISH_US,
|
||||
))
|
||||
.set_manifest_file("manifest.xml");
|
||||
match res.compile() {
|
||||
Err(e) => {
|
||||
write!(std::io::stderr(), "{}", e).unwrap();
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn install_oboe() {
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
if target_os != "android" {
|
||||
return;
|
||||
}
|
||||
let mut target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
if target_arch == "x86_64" {
|
||||
target_arch = "x64".to_owned();
|
||||
} else if target_arch == "aarch64" {
|
||||
target_arch = "arm64".to_owned();
|
||||
} else {
|
||||
target_arch = "arm".to_owned();
|
||||
}
|
||||
let target = format!("{}-android-static", target_arch);
|
||||
let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap();
|
||||
let mut path: std::path::PathBuf = vcpkg_root.into();
|
||||
path.push("installed");
|
||||
path.push(target);
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"cargo:rustc-link-search={}",
|
||||
path.join("lib").to_str().unwrap()
|
||||
)
|
||||
);
|
||||
println!("cargo:rustc-link-lib=oboe");
|
||||
println!("cargo:rustc-link-lib=c++");
|
||||
println!("cargo:rustc-link-lib=OpenSLES");
|
||||
// I always got some strange link error with oboe, so as workaround, put oboe.cc into oboe src: src/common/AudioStreamBuilder.cpp
|
||||
// also to avoid libc++_shared not found issue, cp ndk's libc++_shared.so to jniLibs, e.g.
|
||||
// ./flutter_hbb/android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so
|
||||
// let include = path.join("include");
|
||||
//cc::Build::new().file("oboe.cc").include(include).compile("oboe_wrapper");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(all(windows, feature = "inline"))]
|
||||
build_manifest();
|
||||
#[cfg(windows)]
|
||||
build_windows();
|
||||
#[cfg(target_os = "macos")]
|
||||
println!("cargo:rustc-link-lib=framework=ApplicationServices");
|
||||
hbb_common::gen_version();
|
||||
install_oboe();
|
||||
}
|
||||
34
rust-rdp/rust-desk/entrypoint
Executable file
34
rust-rdp/rust-desk/entrypoint
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd $HOME/rustdesk
|
||||
. $HOME/.cargo/env
|
||||
|
||||
argv=$@
|
||||
|
||||
while test $# -gt 0; do
|
||||
case "$1" in
|
||||
--release)
|
||||
mkdir -p target/release
|
||||
test -f target/release/libsciter-gtk.so || cp $HOME/libsciter-gtk.so target/release/
|
||||
release=1
|
||||
shift
|
||||
;;
|
||||
--target)
|
||||
shift
|
||||
if test $# -gt 0; then
|
||||
rustup target add $1
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z $release ]; then
|
||||
mkdir -p target/debug
|
||||
test -f target/debug/libsciter-gtk.so || cp $HOME/libsciter-gtk.so target/debug/
|
||||
fi
|
||||
|
||||
VCPKG_ROOT=/vcpkg cargo build $argv
|
||||
25
rust-rdp/rust-desk/libs/enigo/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
25
rust-rdp/rust-desk/libs/enigo/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug, needs investigation
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps or a minimal code example to reproduce the behavior.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS: [e.g. Linux, Windows, macOS ..]
|
||||
- Rust [e.g. rustc --version]
|
||||
- Library Version [e.g. enigo 0.0.13 or commit hash fa448be ]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
rust-rdp/rust-desk/libs/enigo/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
rust-rdp/rust-desk/libs/enigo/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement, needs investigation
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
19
rust-rdp/rust-desk/libs/enigo/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
19
rust-rdp/rust-desk/libs/enigo/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask your Question here
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe your Question**
|
||||
A clear and concise description of what you want to know.
|
||||
|
||||
**Describe your Goal**
|
||||
A clear and concise description of what you want to achieve. Consider the [XYProblem](http://xyproblem.info/)
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS: [e.g. Linux, Windows, macOS ..]
|
||||
- Rust [e.g. rustc --version]
|
||||
- Library Version [e.g. enigo 0.0.13 or commit hash fa448be ]
|
||||
14
rust-rdp/rust-desk/libs/enigo/.gitignore
vendored
Normal file
14
rust-rdp/rust-desk/libs/enigo/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
.DS_Store
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
|
||||
Cargo.lock
|
||||
|
||||
# RustFmt files
|
||||
**/*.rs.bk
|
||||
|
||||
# intellij
|
||||
.idea
|
||||
15
rust-rdp/rust-desk/libs/enigo/.travis.yml
Normal file
15
rust-rdp/rust-desk/libs/enigo/.travis.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
before_install:
|
||||
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -qq update; fi
|
||||
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get install -y libxdo-dev; fi
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
41
rust-rdp/rust-desk/libs/enigo/Cargo.toml
Normal file
41
rust-rdp/rust-desk/libs/enigo/Cargo.toml
Normal file
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "enigo"
|
||||
version = "0.0.14"
|
||||
authors = ["Dustin Bensing <dustin.bensing@googlemail.com>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
description = "Enigo lets you control your mouse and keyboard in an abstract way on different operating systems (currently only Linux, macOS, Win – Redox and *BSD planned)"
|
||||
documentation = "https://docs.rs/enigo/"
|
||||
homepage = "https://github.com/enigo-rs/enigo"
|
||||
repository = "https://github.com/enigo-rs/enigo"
|
||||
readme = "README.md"
|
||||
keywords = ["input", "mouse", "testing", "keyboard", "automation"]
|
||||
categories = ["development-tools::testing", "api-bindings", "hardware-support"]
|
||||
license = "MIT"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "enigo-rs/enigo" }
|
||||
appveyor = { repository = "pythoneer/enigo-85xiy" }
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", optional = true }
|
||||
serde_derive = { version = "1.0", optional = true }
|
||||
log = "0.4"
|
||||
|
||||
[features]
|
||||
with_serde = ["serde", "serde_derive"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser", "winbase"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.22"
|
||||
objc = "0.2"
|
||||
unicode-segmentation = "1.6"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
pkg-config = "0.3"
|
||||
21
rust-rdp/rust-desk/libs/enigo/LICENSE
Normal file
21
rust-rdp/rust-desk/libs/enigo/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 pythoneer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
46
rust-rdp/rust-desk/libs/enigo/README.md
Normal file
46
rust-rdp/rust-desk/libs/enigo/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
[](https://travis-ci.org/enigo-rs/enigo)
|
||||
[](https://ci.appveyor.com/project/pythoneer/enigo-85xiy)
|
||||
[](https://dependencyci.com/github/pythoneer/enigo)
|
||||
[](https://docs.rs/enigo)
|
||||
[](https://crates.io/crates/enigo)
|
||||
[](https://discord.gg/Eb8CsnN)
|
||||
[](https://gitter.im/enigo-rs/Lobby)
|
||||
|
||||
|
||||
# enigo
|
||||
Cross platform input simulation in Rust!
|
||||
|
||||
- [x] Linux (X11) mouse
|
||||
- [x] Linux (X11) text
|
||||
- [ ] Linux (Wayland) mouse
|
||||
- [ ] Linux (Wayland) text
|
||||
- [x] MacOS mouse
|
||||
- [x] MacOS text
|
||||
- [x] Win mouse
|
||||
- [x] Win text
|
||||
- [x] Custom Parser
|
||||
|
||||
|
||||
```Rust
|
||||
let mut enigo = Enigo::new();
|
||||
|
||||
enigo.mouse_move_to(500, 200);
|
||||
enigo.mouse_click(MouseButton::Left);
|
||||
enigo.key_sequence_parse("{+CTRL}a{-CTRL}{+SHIFT}Hello World{-SHIFT}");
|
||||
```
|
||||
|
||||
for more look at examples
|
||||
|
||||
Runtime dependencies
|
||||
--------------------
|
||||
|
||||
Linux users may have to install libxdo-dev. For example, on Ubuntu:
|
||||
|
||||
```Bash
|
||||
apt install libxdo-dev
|
||||
```
|
||||
On Arch:
|
||||
|
||||
```Bash
|
||||
pacman -S xdotool
|
||||
```
|
||||
121
rust-rdp/rust-desk/libs/enigo/appveyor.yml
Normal file
121
rust-rdp/rust-desk/libs/enigo/appveyor.yml
Normal file
@@ -0,0 +1,121 @@
|
||||
# Appveyor configuration template for Rust using rustup for Rust installation
|
||||
# https://github.com/starkat99/appveyor-rust
|
||||
|
||||
## Operating System (VM environment) ##
|
||||
|
||||
# Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets.
|
||||
os: Visual Studio 2015
|
||||
|
||||
## Build Matrix ##
|
||||
|
||||
# This configuration will setup a build for each channel & target combination (12 windows
|
||||
# combinations in all).
|
||||
#
|
||||
# There are 3 channels: stable, beta, and nightly.
|
||||
#
|
||||
# Alternatively, the full version may be specified for the channel to build using that specific
|
||||
# version (e.g. channel: 1.5.0)
|
||||
#
|
||||
# The values for target are the set of windows Rust build targets. Each value is of the form
|
||||
#
|
||||
# ARCH-pc-windows-TOOLCHAIN
|
||||
#
|
||||
# Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker
|
||||
# toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for
|
||||
# a description of the toolchain differences.
|
||||
# See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of
|
||||
# toolchains and host triples.
|
||||
#
|
||||
# Comment out channel/target combos you do not wish to build in CI.
|
||||
#
|
||||
# You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands
|
||||
# and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly
|
||||
# channels to enable unstable features when building for nightly. Or you could add additional
|
||||
# matrix entries to test different combinations of features.
|
||||
environment:
|
||||
matrix:
|
||||
|
||||
### MSVC Toolchains ###
|
||||
|
||||
# Stable 64-bit MSVC
|
||||
- channel: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
# Stable 32-bit MSVC
|
||||
- channel: stable
|
||||
target: i686-pc-windows-msvc
|
||||
# Beta 64-bit MSVC
|
||||
- channel: beta
|
||||
target: x86_64-pc-windows-msvc
|
||||
# Beta 32-bit MSVC
|
||||
- channel: beta
|
||||
target: i686-pc-windows-msvc
|
||||
# Nightly 64-bit MSVC
|
||||
- channel: nightly
|
||||
target: x86_64-pc-windows-msvc
|
||||
#cargoflags: --features "unstable"
|
||||
# Nightly 32-bit MSVC
|
||||
- channel: nightly
|
||||
target: i686-pc-windows-msvc
|
||||
#cargoflags: --features "unstable"
|
||||
|
||||
### GNU Toolchains ###
|
||||
|
||||
# Stable 64-bit GNU
|
||||
- channel: stable
|
||||
target: x86_64-pc-windows-gnu
|
||||
# Stable 32-bit GNU
|
||||
- channel: stable
|
||||
target: i686-pc-windows-gnu
|
||||
# Beta 64-bit GNU
|
||||
- channel: beta
|
||||
target: x86_64-pc-windows-gnu
|
||||
# Beta 32-bit GNU
|
||||
- channel: beta
|
||||
target: i686-pc-windows-gnu
|
||||
# Nightly 64-bit GNU
|
||||
- channel: nightly
|
||||
target: x86_64-pc-windows-gnu
|
||||
#cargoflags: --features "unstable"
|
||||
# Nightly 32-bit GNU
|
||||
- channel: nightly
|
||||
target: i686-pc-windows-gnu
|
||||
#cargoflags: --features "unstable"
|
||||
|
||||
### Allowed failures ###
|
||||
|
||||
# See Appveyor documentation for specific details. In short, place any channel or targets you wish
|
||||
# to allow build failures on (usually nightly at least is a wise choice). This will prevent a build
|
||||
# or test failure in the matching channels/targets from failing the entire build.
|
||||
matrix:
|
||||
allow_failures:
|
||||
- channel: nightly
|
||||
|
||||
# If you only care about stable channel build failures, uncomment the following line:
|
||||
#- channel: beta
|
||||
|
||||
## Install Script ##
|
||||
|
||||
# This is the most important part of the Appveyor configuration. This installs the version of Rust
|
||||
# specified by the 'channel' and 'target' environment variables from the build matrix. This uses
|
||||
# rustup to install Rust.
|
||||
#
|
||||
# For simple configurations, instead of using the build matrix, you can simply set the
|
||||
# default-toolchain and default-host manually here.
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init -yv --default-toolchain %channel% --default-host %target%
|
||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
|
||||
## Build Script ##
|
||||
|
||||
# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents
|
||||
# the "directory does not contain a project or solution file" error.
|
||||
build: false
|
||||
|
||||
# Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs
|
||||
#directly or perform other testing commands. Rust will automatically be placed in the PATH
|
||||
# environment variable.
|
||||
test_script:
|
||||
- cargo test --verbose %cargoflags%
|
||||
61
rust-rdp/rust-desk/libs/enigo/build.rs
Normal file
61
rust-rdp/rust-desk/libs/enigo/build.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
fn main() {}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main() {}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use pkg_config;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::env;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::fs::File;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::io::Write;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn main() {
|
||||
let libraries = [
|
||||
"xext",
|
||||
"gl",
|
||||
"xcursor",
|
||||
"xxf86vm",
|
||||
"xft",
|
||||
"xinerama",
|
||||
"xi",
|
||||
"x11",
|
||||
"xlib_xcb",
|
||||
"xmu",
|
||||
"xrandr",
|
||||
"xtst",
|
||||
"xrender",
|
||||
"xscrnsaver",
|
||||
"xt",
|
||||
];
|
||||
|
||||
let mut config = String::new();
|
||||
for lib in libraries.iter() {
|
||||
let libdir = match pkg_config::get_variable(lib, "libdir") {
|
||||
Ok(libdir) => format!("Some(\"{}\")", libdir),
|
||||
Err(_) => "None".to_string(),
|
||||
};
|
||||
config.push_str(&format!(
|
||||
"pub const {}: Option<&'static str> = {};\n",
|
||||
lib, libdir
|
||||
));
|
||||
}
|
||||
let config = format!("pub mod config {{ pub mod libdir {{\n{}}}\n}}", config);
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let dest_path = Path::new(&out_dir).join("config.rs");
|
||||
let mut f = File::create(&dest_path).unwrap();
|
||||
f.write_all(&config.into_bytes()).unwrap();
|
||||
|
||||
let target = env::var("TARGET").unwrap();
|
||||
if target.contains("linux") {
|
||||
println!("cargo:rustc-link-lib=dl");
|
||||
} else if target.contains("freebsd") || target.contains("dragonfly") {
|
||||
println!("cargo:rustc-link-lib=c");
|
||||
}
|
||||
}
|
||||
11
rust-rdp/rust-desk/libs/enigo/examples/dsl.rs
Normal file
11
rust-rdp/rust-desk/libs/enigo/examples/dsl.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use enigo::{Enigo, KeyboardControllable};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
let mut enigo = Enigo::new();
|
||||
|
||||
// write text and select all
|
||||
enigo.key_sequence_parse("{+UNICODE}{{Hello World!}} ❤️{-UNICODE}{+CTRL}a{-CTRL}");
|
||||
}
|
||||
12
rust-rdp/rust-desk/libs/enigo/examples/key.rs
Normal file
12
rust-rdp/rust-desk/libs/enigo/examples/key.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use enigo::{Enigo, Key, KeyboardControllable};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
let mut enigo = Enigo::new();
|
||||
|
||||
enigo.key_down(Key::Layout('a')).ok();
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
enigo.key_up(Key::Layout('a'));
|
||||
}
|
||||
16
rust-rdp/rust-desk/libs/enigo/examples/keyboard.rs
Normal file
16
rust-rdp/rust-desk/libs/enigo/examples/keyboard.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use enigo::{Enigo, Key, KeyboardControllable};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
let mut enigo = Enigo::new();
|
||||
|
||||
// write text
|
||||
enigo.key_sequence("Hello World! here is a lot of text ❤️");
|
||||
|
||||
// select all
|
||||
enigo.key_down(Key::Control).ok();
|
||||
enigo.key_click(Key::Layout('a'));
|
||||
enigo.key_up(Key::Control);
|
||||
}
|
||||
37
rust-rdp/rust-desk/libs/enigo/examples/mouse.rs
Normal file
37
rust-rdp/rust-desk/libs/enigo/examples/mouse.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use enigo::{Enigo, MouseButton, MouseControllable};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let wait_time = Duration::from_secs(2);
|
||||
let mut enigo = Enigo::new();
|
||||
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_move_to(500, 200);
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_down(MouseButton::Left).ok();
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_move_relative(100, 100);
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_up(MouseButton::Left);
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_click(MouseButton::Left);
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_scroll_x(2);
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_scroll_x(-2);
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_scroll_y(2);
|
||||
thread::sleep(wait_time);
|
||||
|
||||
enigo.mouse_scroll_y(-2);
|
||||
thread::sleep(wait_time);
|
||||
}
|
||||
22
rust-rdp/rust-desk/libs/enigo/examples/timer.rs
Normal file
22
rust-rdp/rust-desk/libs/enigo/examples/timer.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use enigo::{Enigo, Key, KeyboardControllable};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
fn main() {
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
let mut enigo = Enigo::new();
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
// write text
|
||||
enigo.key_sequence("Hello World! ❤️");
|
||||
|
||||
let time = now.elapsed();
|
||||
println!("{:?}", time);
|
||||
|
||||
// select all
|
||||
enigo.key_down(Key::Control).ok();
|
||||
enigo.key_click(Key::Layout('a'));
|
||||
enigo.key_up(Key::Control);
|
||||
}
|
||||
1
rust-rdp/rust-desk/libs/enigo/rustfmt.toml
Normal file
1
rust-rdp/rust-desk/libs/enigo/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
wrap_comments = true
|
||||
184
rust-rdp/rust-desk/libs/enigo/src/dsl.rs
Normal file
184
rust-rdp/rust-desk/libs/enigo/src/dsl.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
use crate::{Key, KeyboardControllable};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
/// An error that can occur when parsing DSL
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseError {
|
||||
/// When a tag doesn't exist.
|
||||
/// Example: {+TEST}{-TEST}
|
||||
/// ^^^^ ^^^^
|
||||
UnknownTag(String),
|
||||
|
||||
/// When a { is encountered inside a {TAG}.
|
||||
/// Example: {+HELLO{WORLD}
|
||||
/// ^
|
||||
UnexpectedOpen,
|
||||
|
||||
/// When a { is never matched with a }.
|
||||
/// Example: {+SHIFT}Hello{-SHIFT
|
||||
/// ^
|
||||
UnmatchedOpen,
|
||||
|
||||
/// Opposite of UnmatchedOpen.
|
||||
/// Example: +SHIFT}Hello{-SHIFT}
|
||||
/// ^
|
||||
UnmatchedClose,
|
||||
}
|
||||
impl Error for ParseError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
ParseError::UnknownTag(_) => "Unknown tag",
|
||||
ParseError::UnexpectedOpen => "Unescaped open bracket ({) found inside tag name",
|
||||
ParseError::UnmatchedOpen => "Unmatched open bracket ({). No matching close (})",
|
||||
ParseError::UnmatchedClose => "Unmatched close bracket (}). No previous open ({)",
|
||||
}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate the DSL. This tokenizes the input and presses the keys.
|
||||
pub fn eval<K>(enigo: &mut K, input: &str) -> Result<(), ParseError>
|
||||
where
|
||||
K: KeyboardControllable,
|
||||
{
|
||||
for token in tokenize(input)? {
|
||||
match token {
|
||||
Token::Sequence(buffer) => {
|
||||
for key in buffer.chars() {
|
||||
enigo.key_click(Key::Layout(key));
|
||||
}
|
||||
}
|
||||
Token::Unicode(buffer) => enigo.key_sequence(&buffer),
|
||||
Token::KeyUp(key) => enigo.key_up(key),
|
||||
Token::KeyDown(key) => enigo.key_down(key).unwrap_or(()),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Token {
|
||||
Sequence(String),
|
||||
Unicode(String),
|
||||
KeyUp(Key),
|
||||
KeyDown(Key),
|
||||
}
|
||||
|
||||
fn tokenize(input: &str) -> Result<Vec<Token>, ParseError> {
|
||||
let mut unicode = false;
|
||||
|
||||
let mut tokens = Vec::new();
|
||||
let mut buffer = String::new();
|
||||
let mut iter = input.chars().peekable();
|
||||
|
||||
fn flush(tokens: &mut Vec<Token>, buffer: String, unicode: bool) {
|
||||
if !buffer.is_empty() {
|
||||
if unicode {
|
||||
tokens.push(Token::Unicode(buffer));
|
||||
} else {
|
||||
tokens.push(Token::Sequence(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(c) = iter.next() {
|
||||
if c == '{' {
|
||||
match iter.next() {
|
||||
Some('{') => buffer.push('{'),
|
||||
Some(mut c) => {
|
||||
flush(&mut tokens, buffer, unicode);
|
||||
buffer = String::new();
|
||||
|
||||
let mut tag = String::new();
|
||||
loop {
|
||||
tag.push(c);
|
||||
match iter.next() {
|
||||
Some('{') => match iter.peek() {
|
||||
Some(&'{') => {
|
||||
iter.next();
|
||||
c = '{'
|
||||
}
|
||||
_ => return Err(ParseError::UnexpectedOpen),
|
||||
},
|
||||
Some('}') => match iter.peek() {
|
||||
Some(&'}') => {
|
||||
iter.next();
|
||||
c = '}'
|
||||
}
|
||||
_ => break,
|
||||
},
|
||||
Some(new) => c = new,
|
||||
None => return Err(ParseError::UnmatchedOpen),
|
||||
}
|
||||
}
|
||||
match &*tag {
|
||||
"+UNICODE" => unicode = true,
|
||||
"-UNICODE" => unicode = false,
|
||||
"+SHIFT" => tokens.push(Token::KeyDown(Key::Shift)),
|
||||
"-SHIFT" => tokens.push(Token::KeyUp(Key::Shift)),
|
||||
"+CTRL" => tokens.push(Token::KeyDown(Key::Control)),
|
||||
"-CTRL" => tokens.push(Token::KeyUp(Key::Control)),
|
||||
"+META" => tokens.push(Token::KeyDown(Key::Meta)),
|
||||
"-META" => tokens.push(Token::KeyUp(Key::Meta)),
|
||||
"+ALT" => tokens.push(Token::KeyDown(Key::Alt)),
|
||||
"-ALT" => tokens.push(Token::KeyUp(Key::Alt)),
|
||||
_ => return Err(ParseError::UnknownTag(tag)),
|
||||
}
|
||||
}
|
||||
None => return Err(ParseError::UnmatchedOpen),
|
||||
}
|
||||
} else if c == '}' {
|
||||
match iter.next() {
|
||||
Some('}') => buffer.push('}'),
|
||||
_ => return Err(ParseError::UnmatchedClose),
|
||||
}
|
||||
} else {
|
||||
buffer.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
flush(&mut tokens, buffer, unicode);
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn success() {
|
||||
assert_eq!(
|
||||
tokenize("{{Hello World!}} {+CTRL}hi{-CTRL}"),
|
||||
Ok(vec![
|
||||
Token::Sequence("{Hello World!} ".into()),
|
||||
Token::KeyDown(Key::Control),
|
||||
Token::Sequence("hi".into()),
|
||||
Token::KeyUp(Key::Control)
|
||||
])
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn unexpected_open() {
|
||||
assert_eq!(tokenize("{hello{}world}"), Err(ParseError::UnexpectedOpen));
|
||||
}
|
||||
#[test]
|
||||
fn unmatched_open() {
|
||||
assert_eq!(
|
||||
tokenize("{this is going to fail"),
|
||||
Err(ParseError::UnmatchedOpen)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn unmatched_close() {
|
||||
assert_eq!(
|
||||
tokenize("{+CTRL}{{this}} is going to fail}"),
|
||||
Err(ParseError::UnmatchedClose)
|
||||
);
|
||||
}
|
||||
}
|
||||
529
rust-rdp/rust-desk/libs/enigo/src/lib.rs
Normal file
529
rust-rdp/rust-desk/libs/enigo/src/lib.rs
Normal file
@@ -0,0 +1,529 @@
|
||||
//! Enigo lets you simulate mouse and keyboard input-events as if they were
|
||||
//! made by the actual hardware. The goal is to make it available on different
|
||||
//! operating systems like Linux, macOS and Windows – possibly many more but
|
||||
//! [Redox](https://redox-os.org/) and *BSD are planned. Please see the
|
||||
//! [Repo](https://github.com/enigo-rs/enigo) for the current status.
|
||||
//!
|
||||
//! I consider this library in an early alpha status, the API will change in
|
||||
//! in the future. The keyboard handling is far from being very usable. I plan
|
||||
//! to build a simple
|
||||
//! [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)
|
||||
//! that will resemble something like:
|
||||
//!
|
||||
//! `"hello {+SHIFT}world{-SHIFT} and break line{ENTER}"`
|
||||
//!
|
||||
//! The current status is that you can just print
|
||||
//! [unicode](http://unicode.org/)
|
||||
//! characters like [emoji](http://getemoji.com/) without the `{+SHIFT}`
|
||||
//! [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)
|
||||
//! or any other "special" key on the Linux, macOS and Windows operating system.
|
||||
//!
|
||||
//! Possible use cases could be for testing user interfaces on different
|
||||
//! plattforms,
|
||||
//! building remote control applications or just automating tasks for user
|
||||
//! interfaces unaccessible by a public API or scripting laguage.
|
||||
//!
|
||||
//! For the keyboard there are currently two modes you can use. The first mode
|
||||
//! is represented by the [key_sequence]() function
|
||||
//! its purpose is to simply write unicode characters. This is independent of
|
||||
//! the keyboardlayout. Please note that
|
||||
//! you're not be able to use modifier keys like Control
|
||||
//! to influence the outcome. If you want to use modifier keys to e.g.
|
||||
//! copy/paste
|
||||
//! use the Layout variant. Please note that this is indeed layout dependent.
|
||||
|
||||
//! # Examples
|
||||
//! ```no_run
|
||||
//! use enigo::*;
|
||||
//! let mut enigo = Enigo::new();
|
||||
//! //paste
|
||||
//! enigo.key_down(Key::Control);
|
||||
//! enigo.key_click(Key::Layout('v'));
|
||||
//! enigo.key_up(Key::Control);
|
||||
//! ```
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use enigo::*;
|
||||
//! let mut enigo = Enigo::new();
|
||||
//! enigo.mouse_move_to(500, 200);
|
||||
//! enigo.mouse_down(MouseButton::Left);
|
||||
//! enigo.mouse_move_relative(100, 100);
|
||||
//! enigo.mouse_up(MouseButton::Left);
|
||||
//! enigo.key_sequence("hello world");
|
||||
//! ```
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
|
||||
// TODO(dustin) use interior mutability not &mut self
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod win;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use win::Enigo;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::Enigo;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use crate::linux::Enigo;
|
||||
|
||||
/// DSL parser module
|
||||
pub mod dsl;
|
||||
|
||||
#[cfg(feature = "with_serde")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[cfg(feature = "with_serde")]
|
||||
extern crate serde;
|
||||
|
||||
///
|
||||
pub type ResultType = std::result::Result<(), Box<dyn std::error::Error>>;
|
||||
|
||||
#[cfg_attr(feature = "with_serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
/// MouseButton represents a mouse button,
|
||||
/// and is used in for example
|
||||
/// [mouse_click](trait.MouseControllable.html#tymethod.mouse_click).
|
||||
/// WARNING: Types with the prefix Scroll
|
||||
/// IS NOT intended to be used, and may not work on
|
||||
/// all operating systems.
|
||||
pub enum MouseButton {
|
||||
/// Left mouse button
|
||||
Left,
|
||||
/// Middle mouse button
|
||||
Middle,
|
||||
/// Right mouse button
|
||||
Right,
|
||||
|
||||
/// Scroll up button
|
||||
ScrollUp,
|
||||
/// Left right button
|
||||
ScrollDown,
|
||||
/// Left right button
|
||||
ScrollLeft,
|
||||
/// Left right button
|
||||
ScrollRight,
|
||||
}
|
||||
|
||||
/// Representing an interface and a set of mouse functions every
|
||||
/// operating system implementation _should_ implement.
|
||||
pub trait MouseControllable {
|
||||
/// Lets the mouse cursor move to the specified x and y coordinates.
|
||||
///
|
||||
/// The topleft corner of your monitor screen is x=0 y=0. Move
|
||||
/// the cursor down the screen by increasing the y and to the right
|
||||
/// by increasing x coordinate.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut enigo = Enigo::new();
|
||||
/// enigo.mouse_move_to(500, 200);
|
||||
/// ```
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32);
|
||||
|
||||
/// Lets the mouse cursor move the specified amount in the x and y
|
||||
/// direction.
|
||||
///
|
||||
/// The amount specified in the x and y parameters are added to the
|
||||
/// current location of the mouse cursor. A positive x values lets
|
||||
/// the mouse cursor move an amount of `x` pixels to the right. A negative
|
||||
/// value for `x` lets the mouse cursor go to the left. A positive value
|
||||
/// of y
|
||||
/// lets the mouse cursor go down, a negative one lets the mouse cursor go
|
||||
/// up.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut enigo = Enigo::new();
|
||||
/// enigo.mouse_move_relative(100, 100);
|
||||
/// ```
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32);
|
||||
|
||||
/// Push down one of the mouse buttons
|
||||
///
|
||||
/// Push down the mouse button specified by the parameter `button` of
|
||||
/// type [MouseButton](enum.MouseButton.html)
|
||||
/// and holds it until it is released by
|
||||
/// [mouse_up](trait.MouseControllable.html#tymethod.mouse_up).
|
||||
/// Calls to [mouse_move_to](trait.MouseControllable.html#tymethod.
|
||||
/// mouse_move_to) or
|
||||
/// [mouse_move_relative](trait.MouseControllable.html#tymethod.
|
||||
/// mouse_move_relative)
|
||||
/// will work like expected and will e.g. drag widgets or highlight text.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut enigo = Enigo::new();
|
||||
/// enigo.mouse_down(MouseButton::Left);
|
||||
/// ```
|
||||
fn mouse_down(&mut self, button: MouseButton) -> ResultType;
|
||||
|
||||
/// Lift up a pushed down mouse button
|
||||
///
|
||||
/// Lift up a previously pushed down button (by invoking
|
||||
/// [mouse_down](trait.MouseControllable.html#tymethod.mouse_down)).
|
||||
/// If the button was not pushed down or consecutive calls without
|
||||
/// invoking [mouse_down](trait.MouseControllable.html#tymethod.mouse_down)
|
||||
/// will emit lift up events. It depends on the
|
||||
/// operating system whats actually happening – my guess is it will just
|
||||
/// get ignored.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut enigo = Enigo::new();
|
||||
/// enigo.mouse_up(MouseButton::Right);
|
||||
/// ```
|
||||
fn mouse_up(&mut self, button: MouseButton);
|
||||
|
||||
/// Click a mouse button
|
||||
///
|
||||
/// it's esentially just a consecutive invokation of
|
||||
/// [mouse_down](trait.MouseControllable.html#tymethod.mouse_down) followed
|
||||
/// by a [mouse_up](trait.MouseControllable.html#tymethod.mouse_up). Just
|
||||
/// for
|
||||
/// convenience.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut enigo = Enigo::new();
|
||||
/// enigo.mouse_click(MouseButton::Right);
|
||||
/// ```
|
||||
fn mouse_click(&mut self, button: MouseButton);
|
||||
|
||||
/// Scroll the mouse (wheel) left or right
|
||||
///
|
||||
/// Positive numbers for length lets the mouse wheel scroll to the right
|
||||
/// and negative ones to the left. The value that is specified translates
|
||||
/// to `lines` defined by the operating system and is essentially one 15°
|
||||
/// (click)rotation on the mouse wheel. How many lines it moves depends
|
||||
/// on the current setting in the operating system.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut enigo = Enigo::new();
|
||||
/// enigo.mouse_scroll_x(2);
|
||||
/// ```
|
||||
fn mouse_scroll_x(&mut self, length: i32);
|
||||
|
||||
/// Scroll the mouse (wheel) up or down
|
||||
///
|
||||
/// Positive numbers for length lets the mouse wheel scroll down
|
||||
/// and negative ones up. The value that is specified translates
|
||||
/// to `lines` defined by the operating system and is essentially one 15°
|
||||
/// (click)rotation on the mouse wheel. How many lines it moves depends
|
||||
/// on the current setting in the operating system.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut enigo = Enigo::new();
|
||||
/// enigo.mouse_scroll_y(2);
|
||||
/// ```
|
||||
fn mouse_scroll_y(&mut self, length: i32);
|
||||
}
|
||||
|
||||
/// A key on the keyboard.
|
||||
/// For alphabetical keys, use Key::Layout for a system independent key.
|
||||
/// If a key is missing, you can use the raw keycode with Key::Raw.
|
||||
#[cfg_attr(feature = "with_serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Key {
|
||||
/// alt key on Linux and Windows (option key on macOS)
|
||||
Alt,
|
||||
/// backspace key
|
||||
Backspace,
|
||||
/// caps lock key
|
||||
CapsLock,
|
||||
#[deprecated(since = "0.0.12", note = "now renamed to Meta")]
|
||||
/// command key on macOS (super key on Linux, windows key on Windows)
|
||||
Command,
|
||||
/// control key
|
||||
Control,
|
||||
/// delete key
|
||||
Delete,
|
||||
/// down arrow key
|
||||
DownArrow,
|
||||
/// end key
|
||||
End,
|
||||
/// escape key (esc)
|
||||
Escape,
|
||||
/// F1 key
|
||||
F1,
|
||||
/// F10 key
|
||||
F10,
|
||||
/// F11 key
|
||||
F11,
|
||||
/// F12 key
|
||||
F12,
|
||||
/// F2 key
|
||||
F2,
|
||||
/// F3 key
|
||||
F3,
|
||||
/// F4 key
|
||||
F4,
|
||||
/// F5 key
|
||||
F5,
|
||||
/// F6 key
|
||||
F6,
|
||||
/// F7 key
|
||||
F7,
|
||||
/// F8 key
|
||||
F8,
|
||||
/// F9 key
|
||||
F9,
|
||||
/// home key
|
||||
Home,
|
||||
/// left arrow key
|
||||
LeftArrow,
|
||||
/// meta key (also known as "windows", "super", and "command")
|
||||
Meta,
|
||||
/// option key on macOS (alt key on Linux and Windows)
|
||||
Option, // deprecated, use Alt instead
|
||||
/// page down key
|
||||
PageDown,
|
||||
/// page up key
|
||||
PageUp,
|
||||
/// return key
|
||||
Return,
|
||||
/// right arrow key
|
||||
RightArrow,
|
||||
/// shift key
|
||||
Shift,
|
||||
/// space key
|
||||
Space,
|
||||
#[deprecated(since = "0.0.12", note = "now renamed to Meta")]
|
||||
/// super key on linux (command key on macOS, windows key on Windows)
|
||||
Super,
|
||||
/// tab key (tabulator)
|
||||
Tab,
|
||||
/// up arrow key
|
||||
UpArrow,
|
||||
#[deprecated(since = "0.0.12", note = "now renamed to Meta")]
|
||||
/// windows key on Windows (super key on Linux, command key on macOS)
|
||||
Windows,
|
||||
///
|
||||
Numpad0,
|
||||
///
|
||||
Numpad1,
|
||||
///
|
||||
Numpad2,
|
||||
///
|
||||
Numpad3,
|
||||
///
|
||||
Numpad4,
|
||||
///
|
||||
Numpad5,
|
||||
///
|
||||
Numpad6,
|
||||
///
|
||||
Numpad7,
|
||||
///
|
||||
Numpad8,
|
||||
///
|
||||
Numpad9,
|
||||
///
|
||||
Cancel,
|
||||
///
|
||||
Clear,
|
||||
///
|
||||
Pause,
|
||||
///
|
||||
Kana,
|
||||
///
|
||||
Hangul,
|
||||
///
|
||||
Junja,
|
||||
///
|
||||
Final,
|
||||
///
|
||||
Hanja,
|
||||
///
|
||||
Kanji,
|
||||
///
|
||||
Convert,
|
||||
///
|
||||
Select,
|
||||
///
|
||||
Print,
|
||||
///
|
||||
Execute,
|
||||
///
|
||||
Snapshot,
|
||||
///
|
||||
Insert,
|
||||
///
|
||||
Help,
|
||||
///
|
||||
Sleep,
|
||||
///
|
||||
Separator,
|
||||
///
|
||||
VolumeUp,
|
||||
///
|
||||
VolumeDown,
|
||||
///
|
||||
Mute,
|
||||
///
|
||||
Scroll,
|
||||
/// scroll lock
|
||||
NumLock,
|
||||
///
|
||||
RWin,
|
||||
///
|
||||
Apps,
|
||||
///
|
||||
Multiply,
|
||||
///
|
||||
Add,
|
||||
///
|
||||
Subtract,
|
||||
///
|
||||
Decimal,
|
||||
///
|
||||
Divide,
|
||||
///
|
||||
Equals,
|
||||
///
|
||||
NumpadEnter,
|
||||
///
|
||||
RightShift,
|
||||
///
|
||||
RightControl,
|
||||
///
|
||||
RightAlt,
|
||||
///
|
||||
/// Function, /// mac
|
||||
/// keyboard layout dependent key
|
||||
Layout(char),
|
||||
/// raw keycode eg 0x38
|
||||
Raw(u16),
|
||||
}
|
||||
|
||||
/// Representing an interface and a set of keyboard functions every
|
||||
/// operating system implementation _should_ implement.
|
||||
pub trait KeyboardControllable {
|
||||
/// Types the string parsed with DSL.
|
||||
///
|
||||
/// Typing {+SHIFT}hello{-SHIFT} becomes HELLO.
|
||||
/// TODO: Full documentation
|
||||
fn key_sequence_parse(&mut self, sequence: &str)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.key_sequence_parse_try(sequence)
|
||||
.expect("Could not parse sequence");
|
||||
}
|
||||
/// Same as key_sequence_parse except returns any errors
|
||||
fn key_sequence_parse_try(&mut self, sequence: &str) -> Result<(), dsl::ParseError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
dsl::eval(self, sequence)
|
||||
}
|
||||
|
||||
/// Types the string
|
||||
///
|
||||
/// Emits keystrokes such that the given string is inputted.
|
||||
///
|
||||
/// You can use many unicode here like: ❤️. This works
|
||||
/// regadless of the current keyboardlayout.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut enigo = Enigo::new();
|
||||
/// enigo.key_sequence("hello world ❤️");
|
||||
/// ```
|
||||
fn key_sequence(&mut self, sequence: &str);
|
||||
|
||||
/// presses a given key down
|
||||
fn key_down(&mut self, key: Key) -> ResultType;
|
||||
|
||||
/// release a given key formally pressed down by
|
||||
/// [key_down](trait.KeyboardControllable.html#tymethod.key_down)
|
||||
fn key_up(&mut self, key: Key);
|
||||
|
||||
/// Much like the
|
||||
/// [key_down](trait.KeyboardControllable.html#tymethod.key_down) and
|
||||
/// [key_up](trait.KeyboardControllable.html#tymethod.key_up)
|
||||
/// function they're just invoked consecutively
|
||||
fn key_click(&mut self, key: Key);
|
||||
|
||||
///
|
||||
fn get_key_state(&mut self, key: Key) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
struct Enigo;
|
||||
|
||||
impl Enigo {
|
||||
/// Constructs a new `Enigo` instance.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut enigo = Enigo::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return Enigo {};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Debug for Enigo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Enigo")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_get_key_state() {
|
||||
let mut enigo = Enigo::new();
|
||||
let keys = [Key::CapsLock, Key::NumLock];
|
||||
for k in keys.iter() {
|
||||
enigo.key_click(k.clone());
|
||||
let a = enigo.get_key_state(k.clone());
|
||||
enigo.key_click(k.clone());
|
||||
let b = enigo.get_key_state(k.clone());
|
||||
assert!(a != b);
|
||||
}
|
||||
let keys = [Key::Control, Key::Alt, Key::Shift];
|
||||
for k in keys.iter() {
|
||||
enigo.key_down(k.clone()).ok();
|
||||
let a = enigo.get_key_state(k.clone());
|
||||
enigo.key_up(k.clone());
|
||||
let b = enigo.get_key_state(k.clone());
|
||||
assert!(a != b);
|
||||
}
|
||||
}
|
||||
}
|
||||
363
rust-rdp/rust-desk/libs/enigo/src/linux.rs
Normal file
363
rust-rdp/rust-desk/libs/enigo/src/linux.rs
Normal file
@@ -0,0 +1,363 @@
|
||||
use libc;
|
||||
|
||||
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
|
||||
use self::libc::{c_char, c_int, c_void, useconds_t};
|
||||
use std::{borrow::Cow, ffi::CString, ptr};
|
||||
|
||||
const CURRENT_WINDOW: c_int = 0;
|
||||
const DEFAULT_DELAY: u64 = 12000;
|
||||
type Window = c_int;
|
||||
type Xdo = *const c_void;
|
||||
|
||||
#[link(name = "xdo")]
|
||||
extern "C" {
|
||||
fn xdo_free(xdo: Xdo);
|
||||
fn xdo_new(display: *const c_char) -> Xdo;
|
||||
|
||||
fn xdo_click_window(xdo: Xdo, window: Window, button: c_int) -> c_int;
|
||||
fn xdo_mouse_down(xdo: Xdo, window: Window, button: c_int) -> c_int;
|
||||
fn xdo_mouse_up(xdo: Xdo, window: Window, button: c_int) -> c_int;
|
||||
fn xdo_move_mouse(xdo: Xdo, x: c_int, y: c_int, screen: c_int) -> c_int;
|
||||
fn xdo_move_mouse_relative(xdo: Xdo, x: c_int, y: c_int) -> c_int;
|
||||
|
||||
fn xdo_enter_text_window(
|
||||
xdo: Xdo,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int;
|
||||
fn xdo_send_keysequence_window(
|
||||
xdo: Xdo,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int;
|
||||
fn xdo_send_keysequence_window_down(
|
||||
xdo: Xdo,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int;
|
||||
fn xdo_send_keysequence_window_up(
|
||||
xdo: Xdo,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
) -> c_int;
|
||||
fn xdo_get_input_state(xdo: Xdo) -> u32;
|
||||
}
|
||||
|
||||
fn mousebutton(button: MouseButton) -> c_int {
|
||||
match button {
|
||||
MouseButton::Left => 1,
|
||||
MouseButton::Middle => 2,
|
||||
MouseButton::Right => 3,
|
||||
MouseButton::ScrollUp => 4,
|
||||
MouseButton::ScrollDown => 5,
|
||||
MouseButton::ScrollLeft => 6,
|
||||
MouseButton::ScrollRight => 7,
|
||||
}
|
||||
}
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
pub struct Enigo {
|
||||
xdo: Xdo,
|
||||
delay: u64,
|
||||
}
|
||||
// This is safe, we have a unique pointer.
|
||||
// TODO: use Unique<c_char> once stable.
|
||||
unsafe impl Send for Enigo {}
|
||||
|
||||
impl Default for Enigo {
|
||||
/// Create a new Enigo instance
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
xdo: unsafe { xdo_new(ptr::null()) },
|
||||
delay: DEFAULT_DELAY,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Enigo {
|
||||
/// Get the delay per keypress.
|
||||
/// Default value is 12000.
|
||||
/// This is Linux-specific.
|
||||
pub fn delay(&self) -> u64 {
|
||||
self.delay
|
||||
}
|
||||
/// Set the delay per keypress.
|
||||
/// This is Linux-specific.
|
||||
pub fn set_delay(&mut self, delay: u64) {
|
||||
self.delay = delay;
|
||||
}
|
||||
}
|
||||
impl Drop for Enigo {
|
||||
fn drop(&mut self) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_free(self.xdo);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MouseControllable for Enigo {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_move_mouse(self.xdo, x as c_int, y as c_int, 0);
|
||||
}
|
||||
}
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_move_mouse_relative(self.xdo, x as c_int, y as c_int);
|
||||
}
|
||||
}
|
||||
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
|
||||
if self.xdo.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
unsafe {
|
||||
xdo_mouse_down(self.xdo, CURRENT_WINDOW, mousebutton(button));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn mouse_up(&mut self, button: MouseButton) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_mouse_up(self.xdo, CURRENT_WINDOW, mousebutton(button));
|
||||
}
|
||||
}
|
||||
fn mouse_click(&mut self, button: MouseButton) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
xdo_click_window(self.xdo, CURRENT_WINDOW, mousebutton(button));
|
||||
}
|
||||
}
|
||||
fn mouse_scroll_x(&mut self, length: i32) {
|
||||
let button;
|
||||
let mut length = length;
|
||||
|
||||
if length < 0 {
|
||||
button = MouseButton::ScrollLeft;
|
||||
} else {
|
||||
button = MouseButton::ScrollRight;
|
||||
}
|
||||
|
||||
if length < 0 {
|
||||
length = -length;
|
||||
}
|
||||
|
||||
for _ in 0..length {
|
||||
self.mouse_click(button);
|
||||
}
|
||||
}
|
||||
fn mouse_scroll_y(&mut self, length: i32) {
|
||||
let button;
|
||||
let mut length = length;
|
||||
|
||||
if length < 0 {
|
||||
button = MouseButton::ScrollUp;
|
||||
} else {
|
||||
button = MouseButton::ScrollDown;
|
||||
}
|
||||
|
||||
if length < 0 {
|
||||
length = -length;
|
||||
}
|
||||
|
||||
for _ in 0..length {
|
||||
self.mouse_click(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn keysequence<'a>(key: Key) -> Cow<'a, str> {
|
||||
if let Key::Layout(c) = key {
|
||||
return Cow::Owned(format!("U{:X}", c as u32));
|
||||
}
|
||||
if let Key::Raw(k) = key {
|
||||
return Cow::Owned(format!("{}", k as u16));
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
// I mean duh, we still need to support deprecated keys until they're removed
|
||||
// https://www.rubydoc.info/gems/xdo/XDo/Keyboard
|
||||
// https://gitlab.com/cunidev/gestures/-/wikis/xdotool-list-of-key-codes
|
||||
Cow::Borrowed(match key {
|
||||
Key::Alt => "Alt",
|
||||
Key::Backspace => "BackSpace",
|
||||
Key::CapsLock => "Caps_Lock",
|
||||
Key::Control => "Control",
|
||||
Key::Delete => "Delete",
|
||||
Key::DownArrow => "Down",
|
||||
Key::End => "End",
|
||||
Key::Escape => "Escape",
|
||||
Key::F1 => "F1",
|
||||
Key::F10 => "F10",
|
||||
Key::F11 => "F11",
|
||||
Key::F12 => "F12",
|
||||
Key::F2 => "F2",
|
||||
Key::F3 => "F3",
|
||||
Key::F4 => "F4",
|
||||
Key::F5 => "F5",
|
||||
Key::F6 => "F6",
|
||||
Key::F7 => "F7",
|
||||
Key::F8 => "F8",
|
||||
Key::F9 => "F9",
|
||||
Key::Home => "Home",
|
||||
//Key::Layout(_) => unreachable!(),
|
||||
Key::LeftArrow => "Left",
|
||||
Key::Option => "Option",
|
||||
Key::PageDown => "Page_Down",
|
||||
Key::PageUp => "Page_Up",
|
||||
//Key::Raw(_) => unreachable!(),
|
||||
Key::Return => "Return",
|
||||
Key::RightArrow => "Right",
|
||||
Key::Shift => "Shift",
|
||||
Key::Space => "space",
|
||||
Key::Tab => "Tab",
|
||||
Key::UpArrow => "Up",
|
||||
Key::Numpad0 => "U30", //"KP_0",
|
||||
Key::Numpad1 => "U31", //"KP_1",
|
||||
Key::Numpad2 => "U32", //"KP_2",
|
||||
Key::Numpad3 => "U33", //"KP_3",
|
||||
Key::Numpad4 => "U34", //"KP_4",
|
||||
Key::Numpad5 => "U35", //"KP_5",
|
||||
Key::Numpad6 => "U36", //"KP_6",
|
||||
Key::Numpad7 => "U37", //"KP_7",
|
||||
Key::Numpad8 => "U38", //"KP_8",
|
||||
Key::Numpad9 => "U39", //"KP_9",
|
||||
Key::Decimal => "U2E", //"KP_Decimal",
|
||||
Key::Cancel => "Cancel",
|
||||
Key::Clear => "Clear",
|
||||
Key::Pause => "Pause",
|
||||
Key::Kana => "Kana",
|
||||
Key::Hangul => "Hangul",
|
||||
Key::Junja => "",
|
||||
Key::Final => "",
|
||||
Key::Hanja => "Hanja",
|
||||
Key::Kanji => "Kanji",
|
||||
Key::Convert => "",
|
||||
Key::Select => "Select",
|
||||
Key::Print => "Print",
|
||||
Key::Execute => "Execute",
|
||||
Key::Snapshot => "3270_PrintScreen",
|
||||
Key::Insert => "Insert",
|
||||
Key::Help => "Help",
|
||||
Key::Sleep => "",
|
||||
Key::Separator => "KP_Separator",
|
||||
Key::VolumeUp => "",
|
||||
Key::VolumeDown => "",
|
||||
Key::Mute => "",
|
||||
Key::Scroll => "Scroll_Lock",
|
||||
Key::NumLock => "Num_Lock",
|
||||
Key::RWin => "Super_R",
|
||||
Key::Apps => "Menu",
|
||||
Key::Multiply => "KP_Multiply",
|
||||
Key::Add => "KP_Add",
|
||||
Key::Subtract => "KP_Subtract",
|
||||
Key::Divide => "KP_Divide",
|
||||
Key::Equals => "KP_Equal",
|
||||
Key::NumpadEnter => "KP_Enter",
|
||||
Key::RightShift => "Shift_R",
|
||||
Key::RightControl => "Control_R",
|
||||
Key::RightAlt => "Alt_R",
|
||||
|
||||
Key::Command | Key::Super | Key::Windows | Key::Meta => "Super",
|
||||
|
||||
_ => "",
|
||||
})
|
||||
}
|
||||
impl KeyboardControllable for Enigo {
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
if self.xdo.is_null() {
|
||||
return false;
|
||||
}
|
||||
let mod_shift = 1 << 0;
|
||||
let mod_lock = 1 << 1;
|
||||
let mod_control = 1 << 2;
|
||||
let mod_alt = 1 << 3;
|
||||
let mod_numlock = 1 << 4;
|
||||
let mod_meta = 1 << 6;
|
||||
let mask = unsafe { xdo_get_input_state(self.xdo) };
|
||||
// println!("{:b}", mask);
|
||||
match key {
|
||||
Key::Shift => mask & mod_shift != 0,
|
||||
Key::CapsLock => mask & mod_lock != 0,
|
||||
Key::Control => mask & mod_control != 0,
|
||||
Key::Alt => mask & mod_alt != 0,
|
||||
Key::NumLock => mask & mod_numlock != 0,
|
||||
Key::Meta => mask & mod_meta != 0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn key_sequence(&mut self, sequence: &str) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Ok(string) = CString::new(sequence) {
|
||||
unsafe {
|
||||
xdo_enter_text_window(
|
||||
self.xdo,
|
||||
CURRENT_WINDOW,
|
||||
string.as_ptr(),
|
||||
self.delay as useconds_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
if self.xdo.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
let string = CString::new(&*keysequence(key))?;
|
||||
unsafe {
|
||||
xdo_send_keysequence_window_down(
|
||||
self.xdo,
|
||||
CURRENT_WINDOW,
|
||||
string.as_ptr(),
|
||||
self.delay as useconds_t,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn key_up(&mut self, key: Key) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Ok(string) = CString::new(&*keysequence(key)) {
|
||||
unsafe {
|
||||
xdo_send_keysequence_window_up(
|
||||
self.xdo,
|
||||
CURRENT_WINDOW,
|
||||
string.as_ptr(),
|
||||
self.delay as useconds_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn key_click(&mut self, key: Key) {
|
||||
if self.xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Ok(string) = CString::new(&*keysequence(key)) {
|
||||
unsafe {
|
||||
xdo_send_keysequence_window(
|
||||
self.xdo,
|
||||
CURRENT_WINDOW,
|
||||
string.as_ptr(),
|
||||
self.delay as useconds_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
120
rust-rdp/rust-desk/libs/enigo/src/macos/keycodes.rs
Normal file
120
rust-rdp/rust-desk/libs/enigo/src/macos/keycodes.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
// https://stackoverflow.com/questions/3202629/where-can-i-find-a-list-of-mac-virtual-key-codes
|
||||
|
||||
/* keycodes for keys that are independent of keyboard layout */
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub const kVK_Return: u16 = 0x24;
|
||||
pub const kVK_Tab: u16 = 0x30;
|
||||
pub const kVK_Space: u16 = 0x31;
|
||||
pub const kVK_Delete: u16 = 0x33;
|
||||
pub const kVK_Escape: u16 = 0x35;
|
||||
pub const kVK_Command: u16 = 0x37;
|
||||
pub const kVK_Shift: u16 = 0x38;
|
||||
pub const kVK_CapsLock: u16 = 0x39;
|
||||
pub const kVK_Option: u16 = 0x3A;
|
||||
pub const kVK_Control: u16 = 0x3B;
|
||||
pub const kVK_RightShift: u16 = 0x3C;
|
||||
pub const kVK_RightOption: u16 = 0x3D;
|
||||
pub const kVK_RightControl: u16 = 0x3E;
|
||||
pub const kVK_Function: u16 = 0x3F;
|
||||
pub const kVK_F17: u16 = 0x40;
|
||||
pub const kVK_VolumeUp: u16 = 0x48;
|
||||
pub const kVK_VolumeDown: u16 = 0x49;
|
||||
pub const kVK_Mute: u16 = 0x4A;
|
||||
pub const kVK_F18: u16 = 0x4F;
|
||||
pub const kVK_F19: u16 = 0x50;
|
||||
pub const kVK_F20: u16 = 0x5A;
|
||||
pub const kVK_F5: u16 = 0x60;
|
||||
pub const kVK_F6: u16 = 0x61;
|
||||
pub const kVK_F7: u16 = 0x62;
|
||||
pub const kVK_F3: u16 = 0x63;
|
||||
pub const kVK_F8: u16 = 0x64;
|
||||
pub const kVK_F9: u16 = 0x65;
|
||||
pub const kVK_F11: u16 = 0x67;
|
||||
pub const kVK_F13: u16 = 0x69;
|
||||
pub const kVK_F16: u16 = 0x6A;
|
||||
pub const kVK_F14: u16 = 0x6B;
|
||||
pub const kVK_F10: u16 = 0x6D;
|
||||
pub const kVK_F12: u16 = 0x6F;
|
||||
pub const kVK_F15: u16 = 0x71;
|
||||
pub const kVK_Help: u16 = 0x72;
|
||||
pub const kVK_Home: u16 = 0x73;
|
||||
pub const kVK_PageUp: u16 = 0x74;
|
||||
pub const kVK_ForwardDelete: u16 = 0x75;
|
||||
pub const kVK_F4: u16 = 0x76;
|
||||
pub const kVK_End: u16 = 0x77;
|
||||
pub const kVK_F2: u16 = 0x78;
|
||||
pub const kVK_PageDown: u16 = 0x79;
|
||||
pub const kVK_F1: u16 = 0x7A;
|
||||
pub const kVK_LeftArrow: u16 = 0x7B;
|
||||
pub const kVK_RightArrow: u16 = 0x7C;
|
||||
pub const kVK_DownArrow: u16 = 0x7D;
|
||||
pub const kVK_UpArrow: u16 = 0x7E;
|
||||
pub const kVK_ANSI_Keypad0: u16 = 0x52;
|
||||
pub const kVK_ANSI_Keypad1: u16 = 0x53;
|
||||
pub const kVK_ANSI_Keypad2: u16 = 0x54;
|
||||
pub const kVK_ANSI_Keypad3: u16 = 0x55;
|
||||
pub const kVK_ANSI_Keypad4: u16 = 0x56;
|
||||
pub const kVK_ANSI_Keypad5: u16 = 0x57;
|
||||
pub const kVK_ANSI_Keypad6: u16 = 0x58;
|
||||
pub const kVK_ANSI_Keypad7: u16 = 0x59;
|
||||
pub const kVK_ANSI_Keypad8: u16 = 0x5B;
|
||||
pub const kVK_ANSI_Keypad9: u16 = 0x5C;
|
||||
pub const kVK_ANSI_KeypadClear: u16 = 0x47;
|
||||
pub const kVK_ANSI_KeypadDecimal: u16 = 0x41;
|
||||
pub const kVK_ANSI_KeypadMultiply: u16 = 0x43;
|
||||
pub const kVK_ANSI_KeypadPlus: u16 = 0x45;
|
||||
pub const kVK_ANSI_KeypadDivide: u16 = 0x4B;
|
||||
pub const kVK_ANSI_KeypadEnter: u16 = 0x4C;
|
||||
pub const kVK_ANSI_KeypadMinus: u16 = 0x4E;
|
||||
pub const kVK_ANSI_KeypadEquals: u16 = 0x51;
|
||||
pub const kVK_RIGHT_COMMAND: u16 = 0x36;
|
||||
pub const kVK_ANSI_A : u16 = 0x00;
|
||||
pub const kVK_ANSI_S : u16 = 0x01;
|
||||
pub const kVK_ANSI_D : u16 = 0x02;
|
||||
pub const kVK_ANSI_F : u16 = 0x03;
|
||||
pub const kVK_ANSI_H : u16 = 0x04;
|
||||
pub const kVK_ANSI_G : u16 = 0x05;
|
||||
pub const kVK_ANSI_Z : u16 = 0x06;
|
||||
pub const kVK_ANSI_X : u16 = 0x07;
|
||||
pub const kVK_ANSI_C : u16 = 0x08;
|
||||
pub const kVK_ANSI_V : u16 = 0x09;
|
||||
pub const kVK_ANSI_B : u16 = 0x0B;
|
||||
pub const kVK_ANSI_Q : u16 = 0x0C;
|
||||
pub const kVK_ANSI_W : u16 = 0x0D;
|
||||
pub const kVK_ANSI_E : u16 = 0x0E;
|
||||
pub const kVK_ANSI_R : u16 = 0x0F;
|
||||
pub const kVK_ANSI_Y : u16 = 0x10;
|
||||
pub const kVK_ANSI_T : u16 = 0x11;
|
||||
pub const kVK_ANSI_1 : u16 = 0x12;
|
||||
pub const kVK_ANSI_2 : u16 = 0x13;
|
||||
pub const kVK_ANSI_3 : u16 = 0x14;
|
||||
pub const kVK_ANSI_4 : u16 = 0x15;
|
||||
pub const kVK_ANSI_6 : u16 = 0x16;
|
||||
pub const kVK_ANSI_5 : u16 = 0x17;
|
||||
pub const kVK_ANSI_Equal : u16 = 0x18;
|
||||
pub const kVK_ANSI_9 : u16 = 0x19;
|
||||
pub const kVK_ANSI_7 : u16 = 0x1A;
|
||||
pub const kVK_ANSI_Minus : u16 = 0x1B;
|
||||
pub const kVK_ANSI_8 : u16 = 0x1C;
|
||||
pub const kVK_ANSI_0 : u16 = 0x1D;
|
||||
pub const kVK_ANSI_RightBracket : u16 = 0x1E;
|
||||
pub const kVK_ANSI_O : u16 = 0x1F;
|
||||
pub const kVK_ANSI_U : u16 = 0x20;
|
||||
pub const kVK_ANSI_LeftBracket : u16 = 0x21;
|
||||
pub const kVK_ANSI_I : u16 = 0x22;
|
||||
pub const kVK_ANSI_P : u16 = 0x23;
|
||||
pub const kVK_ANSI_L : u16 = 0x25;
|
||||
pub const kVK_ANSI_J : u16 = 0x26;
|
||||
pub const kVK_ANSI_Quote : u16 = 0x27;
|
||||
pub const kVK_ANSI_K : u16 = 0x28;
|
||||
pub const kVK_ANSI_Semicolon : u16 = 0x29;
|
||||
pub const kVK_ANSI_Backslash : u16 = 0x2A;
|
||||
pub const kVK_ANSI_Comma : u16 = 0x2B;
|
||||
pub const kVK_ANSI_Slash : u16 = 0x2C;
|
||||
pub const kVK_ANSI_N : u16 = 0x2D;
|
||||
pub const kVK_ANSI_M : u16 = 0x2E;
|
||||
pub const kVK_ANSI_Period : u16 = 0x2F;
|
||||
pub const kVK_ANSI_Grave : u16 = 0x32;
|
||||
667
rust-rdp/rust-desk/libs/enigo/src/macos/macos_impl.rs
Normal file
667
rust-rdp/rust-desk/libs/enigo/src/macos/macos_impl.rs
Normal file
@@ -0,0 +1,667 @@
|
||||
use core_graphics;
|
||||
|
||||
// TODO(dustin): use only the things i need
|
||||
|
||||
use self::core_graphics::display::*;
|
||||
use self::core_graphics::event::*;
|
||||
use self::core_graphics::event_source::*;
|
||||
use std::collections::HashMap as Map;
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::*;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use crate::macos::keycodes::*;
|
||||
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use objc::runtime::Class;
|
||||
|
||||
struct MyCGEvent;
|
||||
type TISInputSourceRef = *mut c_void;
|
||||
type CFDataRef = *const c_void;
|
||||
type OptionBits = u32;
|
||||
type OSStatus = i32;
|
||||
type UniChar = u16;
|
||||
type UniCharCount = usize;
|
||||
type Boolean = c_uchar;
|
||||
type CFStringEncoding = u32;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct __CFString([u8; 0]);
|
||||
type CFStringRef = *const __CFString;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kCFStringEncodingUTF8: u32 = 134_217_984;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kUCKeyActionDisplay: u16 = 3;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kUCKeyTranslateDeadKeysBit: OptionBits = 1 << 31;
|
||||
const BUF_LEN: usize = 4;
|
||||
|
||||
#[allow(improper_ctypes)]
|
||||
#[allow(non_snake_case)]
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
fn CFDataGetBytePtr(theData: CFDataRef) -> *const u8;
|
||||
fn TISCopyCurrentKeyboardInputSource() -> TISInputSourceRef;
|
||||
fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
|
||||
fn TISCopyCurrentASCIICapableKeyboardLayoutInputSource() -> TISInputSourceRef;
|
||||
static kTISPropertyUnicodeKeyLayoutData: *mut c_void;
|
||||
static kTISPropertyInputSourceID: *mut c_void;
|
||||
fn UCKeyTranslate(
|
||||
keyLayoutPtr: *const u8, //*const UCKeyboardLayout,
|
||||
virtualKeyCode: u16,
|
||||
keyAction: u16,
|
||||
modifierKeyState: u32,
|
||||
keyboardType: u32,
|
||||
keyTranslateOptions: OptionBits,
|
||||
deadKeyState: *mut u32,
|
||||
maxStringLength: UniCharCount,
|
||||
actualStringLength: *mut UniCharCount,
|
||||
unicodeString: *mut [UniChar; BUF_LEN],
|
||||
) -> OSStatus;
|
||||
fn LMGetKbdType() -> u8;
|
||||
fn CFStringGetCString(
|
||||
theString: CFStringRef,
|
||||
buffer: *mut c_char,
|
||||
bufferSize: CFIndex,
|
||||
encoding: CFStringEncoding,
|
||||
) -> Boolean;
|
||||
|
||||
fn CGEventPost(tapLocation: CGEventTapLocation, event: *mut MyCGEvent);
|
||||
// Actually return CFDataRef which is const here, but for coding convienence, return *mut c_void
|
||||
fn TISGetInputSourceProperty(source: TISInputSourceRef, property: *const c_void)
|
||||
-> *mut c_void;
|
||||
// not present in servo/core-graphics
|
||||
fn CGEventCreateScrollWheelEvent(
|
||||
source: &CGEventSourceRef,
|
||||
units: ScrollUnit,
|
||||
wheelCount: u32,
|
||||
wheel1: i32,
|
||||
...
|
||||
) -> *mut MyCGEvent;
|
||||
fn CGEventSourceKeyState(stateID: i32, key: u16) -> bool;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct NSPoint {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
// not present in servo/core-graphics
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
enum ScrollUnit {
|
||||
Pixel = 0,
|
||||
Line = 1,
|
||||
}
|
||||
// hack
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
pub struct Enigo {
|
||||
event_source: Option<CGEventSource>,
|
||||
double_click_interval: u32,
|
||||
last_click_time: Option<std::time::Instant>,
|
||||
multiple_click: i64,
|
||||
flags: CGEventFlags,
|
||||
char_to_vkey_map: Map<String, Map<char, CGKeyCode>>,
|
||||
}
|
||||
|
||||
impl Enigo {
|
||||
///
|
||||
pub fn reset_flag(&mut self) {
|
||||
self.flags = CGEventFlags::CGEventFlagNull;
|
||||
}
|
||||
|
||||
///
|
||||
pub fn add_flag(&mut self, key: &Key) {
|
||||
let flag = match key {
|
||||
&Key::CapsLock => CGEventFlags::CGEventFlagAlphaShift,
|
||||
&Key::Shift => CGEventFlags::CGEventFlagShift,
|
||||
&Key::Control => CGEventFlags::CGEventFlagControl,
|
||||
&Key::Alt => CGEventFlags::CGEventFlagAlternate,
|
||||
&Key::Meta => CGEventFlags::CGEventFlagCommand,
|
||||
&Key::NumLock => CGEventFlags::CGEventFlagNumericPad,
|
||||
_ => CGEventFlags::CGEventFlagNull,
|
||||
};
|
||||
self.flags |= flag;
|
||||
}
|
||||
|
||||
fn post(&self, event: CGEvent) {
|
||||
event.set_flags(self.flags);
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Enigo {
|
||||
fn default() -> Self {
|
||||
let mut double_click_interval = 500;
|
||||
if let Some(ns_event) = Class::get("NSEvent") {
|
||||
let tm: f64 = unsafe { msg_send![ns_event, doubleClickInterval] };
|
||||
if tm > 0. {
|
||||
double_click_interval = (tm * 1000.) as u32;
|
||||
log::info!("double click interval: {}ms", double_click_interval);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
// TODO(dustin): return error rather than panic here
|
||||
event_source: if let Ok(src) =
|
||||
CGEventSource::new(CGEventSourceStateID::CombinedSessionState)
|
||||
{
|
||||
Some(src)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
double_click_interval,
|
||||
multiple_click: 1,
|
||||
last_click_time: None,
|
||||
flags: CGEventFlags::CGEventFlagNull,
|
||||
char_to_vkey_map: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseControllable for Enigo {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
let pressed = Self::pressed_buttons();
|
||||
|
||||
let event_type = if pressed & 1 > 0 {
|
||||
CGEventType::LeftMouseDragged
|
||||
} else if pressed & 2 > 0 {
|
||||
CGEventType::RightMouseDragged
|
||||
} else {
|
||||
CGEventType::MouseMoved
|
||||
};
|
||||
|
||||
let dest = CGPoint::new(x as f64, y as f64);
|
||||
if let Some(src) = self.event_source.as_ref() {
|
||||
if let Ok(event) =
|
||||
CGEvent::new_mouse_event(src.clone(), event_type, dest, CGMouseButton::Left)
|
||||
{
|
||||
self.post(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32) {
|
||||
let (display_width, display_height) = Self::main_display_size();
|
||||
let (current_x, y_inv) = Self::mouse_location_raw_coords();
|
||||
let current_y = (display_height as i32) - y_inv;
|
||||
let new_x = current_x + x;
|
||||
let new_y = current_y + y;
|
||||
|
||||
if new_x < 0
|
||||
|| new_x as usize > display_width
|
||||
|| new_y < 0
|
||||
|| new_y as usize > display_height
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.mouse_move_to(new_x, new_y);
|
||||
}
|
||||
|
||||
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
|
||||
let now = std::time::Instant::now();
|
||||
if let Some(t) = self.last_click_time {
|
||||
if t.elapsed().as_millis() as u32 <= self.double_click_interval {
|
||||
self.multiple_click += 1;
|
||||
} else {
|
||||
self.multiple_click = 1;
|
||||
}
|
||||
}
|
||||
self.last_click_time = Some(now);
|
||||
let (current_x, current_y) = Self::mouse_location();
|
||||
let (button, event_type) = match button {
|
||||
MouseButton::Left => (CGMouseButton::Left, CGEventType::LeftMouseDown),
|
||||
MouseButton::Middle => (CGMouseButton::Center, CGEventType::OtherMouseDown),
|
||||
MouseButton::Right => (CGMouseButton::Right, CGEventType::RightMouseDown),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
let dest = CGPoint::new(current_x as f64, current_y as f64);
|
||||
if let Some(src) = self.event_source.as_ref() {
|
||||
if let Ok(event) = CGEvent::new_mouse_event(src.clone(), event_type, dest, button) {
|
||||
if self.multiple_click > 1 {
|
||||
event.set_integer_value_field(
|
||||
EventField::MOUSE_EVENT_CLICK_STATE,
|
||||
self.multiple_click,
|
||||
);
|
||||
}
|
||||
self.post(event);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mouse_up(&mut self, button: MouseButton) {
|
||||
let (current_x, current_y) = Self::mouse_location();
|
||||
let (button, event_type) = match button {
|
||||
MouseButton::Left => (CGMouseButton::Left, CGEventType::LeftMouseUp),
|
||||
MouseButton::Middle => (CGMouseButton::Center, CGEventType::OtherMouseUp),
|
||||
MouseButton::Right => (CGMouseButton::Right, CGEventType::RightMouseUp),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
let dest = CGPoint::new(current_x as f64, current_y as f64);
|
||||
if let Some(src) = self.event_source.as_ref() {
|
||||
if let Ok(event) = CGEvent::new_mouse_event(src.clone(), event_type, dest, button) {
|
||||
if self.multiple_click > 1 {
|
||||
event.set_integer_value_field(
|
||||
EventField::MOUSE_EVENT_CLICK_STATE,
|
||||
self.multiple_click,
|
||||
);
|
||||
}
|
||||
self.post(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_click(&mut self, button: MouseButton) {
|
||||
self.mouse_down(button).ok();
|
||||
self.mouse_up(button);
|
||||
}
|
||||
|
||||
fn mouse_scroll_x(&mut self, length: i32) {
|
||||
let mut scroll_direction = -1; // 1 left -1 right;
|
||||
let mut length = length;
|
||||
|
||||
if length < 0 {
|
||||
length *= -1;
|
||||
scroll_direction *= -1;
|
||||
}
|
||||
|
||||
if let Some(src) = self.event_source.as_ref() {
|
||||
for _ in 0..length {
|
||||
unsafe {
|
||||
let mouse_ev = CGEventCreateScrollWheelEvent(
|
||||
&src,
|
||||
ScrollUnit::Line,
|
||||
2, // CGWheelCount 1 = y 2 = xy 3 = xyz
|
||||
0,
|
||||
scroll_direction,
|
||||
);
|
||||
|
||||
CGEventPost(CGEventTapLocation::HID, mouse_ev);
|
||||
CFRelease(mouse_ev as *const std::ffi::c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_scroll_y(&mut self, length: i32) {
|
||||
let mut scroll_direction = -1; // 1 left -1 right;
|
||||
let mut length = length;
|
||||
|
||||
if length < 0 {
|
||||
length *= -1;
|
||||
scroll_direction *= -1;
|
||||
}
|
||||
|
||||
if let Some(src) = self.event_source.as_ref() {
|
||||
for _ in 0..length {
|
||||
unsafe {
|
||||
let mouse_ev = CGEventCreateScrollWheelEvent(
|
||||
&src,
|
||||
ScrollUnit::Line,
|
||||
1, // CGWheelCount 1 = y 2 = xy 3 = xyz
|
||||
scroll_direction,
|
||||
);
|
||||
|
||||
CGEventPost(CGEventTapLocation::HID, mouse_ev);
|
||||
CFRelease(mouse_ev as *const std::ffi::c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.
|
||||
// com/questions/1918841/how-to-convert-ascii-character-to-cgkeycode
|
||||
|
||||
impl KeyboardControllable for Enigo {
|
||||
fn key_sequence(&mut self, sequence: &str) {
|
||||
// NOTE(dustin): This is a fix for issue https://github.com/enigo-rs/enigo/issues/68
|
||||
// TODO(dustin): This could be improved by aggregating 20 bytes worth of graphemes at a time
|
||||
// but i am unsure what would happen for grapheme clusters greater than 20 bytes ...
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
let clusters = UnicodeSegmentation::graphemes(sequence, true).collect::<Vec<&str>>();
|
||||
for cluster in clusters {
|
||||
if let Some(src) = self.event_source.as_ref() {
|
||||
if let Ok(event) = CGEvent::new_keyboard_event(src.clone(), 0, true) {
|
||||
event.set_string(cluster);
|
||||
self.post(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_click(&mut self, key: Key) {
|
||||
let keycode = self.key_to_keycode(key);
|
||||
if keycode == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(src) = self.event_source.as_ref() {
|
||||
if let Ok(event) = CGEvent::new_keyboard_event(src.clone(), keycode, true) {
|
||||
self.post(event);
|
||||
}
|
||||
|
||||
if let Ok(event) = CGEvent::new_keyboard_event(src.clone(), keycode, false) {
|
||||
self.post(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
if let Some(src) = self.event_source.as_ref() {
|
||||
if let Ok(event) =
|
||||
CGEvent::new_keyboard_event(src.clone(), self.key_to_keycode(key), true)
|
||||
{
|
||||
self.post(event);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_up(&mut self, key: Key) {
|
||||
if let Some(src) = self.event_source.as_ref() {
|
||||
if let Ok(event) =
|
||||
CGEvent::new_keyboard_event(src.clone(), self.key_to_keycode(key), false)
|
||||
{
|
||||
self.post(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
let keycode = self.key_to_keycode(key);
|
||||
unsafe { CGEventSourceKeyState(1, keycode) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Enigo {
|
||||
fn pressed_buttons() -> usize {
|
||||
if let Some(ns_event) = Class::get("NSEvent") {
|
||||
unsafe { msg_send![ns_event, pressedMouseButtons] }
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the `(width, height)` in pixels of the main display
|
||||
pub fn main_display_size() -> (usize, usize) {
|
||||
let display_id = unsafe { CGMainDisplayID() };
|
||||
let width = unsafe { CGDisplayPixelsWide(display_id) };
|
||||
let height = unsafe { CGDisplayPixelsHigh(display_id) };
|
||||
(width, height)
|
||||
}
|
||||
|
||||
/// Returns the current mouse location in Cocoa coordinates which have Y
|
||||
/// inverted from the Carbon coordinates used in the rest of the API.
|
||||
/// This function exists so that mouse_move_relative only has to fetch
|
||||
/// the screen size once.
|
||||
fn mouse_location_raw_coords() -> (i32, i32) {
|
||||
if let Some(ns_event) = Class::get("NSEvent") {
|
||||
let pt: NSPoint = unsafe { msg_send![ns_event, mouseLocation] };
|
||||
(pt.x as i32, pt.y as i32)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The mouse coordinates in points, only works on the main display
|
||||
pub fn mouse_location() -> (i32, i32) {
|
||||
let (x, y_inv) = Self::mouse_location_raw_coords();
|
||||
let (_, display_height) = Self::main_display_size();
|
||||
(x, (display_height as i32) - y_inv)
|
||||
}
|
||||
|
||||
fn key_to_keycode(&mut self, key: Key) -> CGKeyCode {
|
||||
#[allow(deprecated)]
|
||||
// I mean duh, we still need to support deprecated keys until they're removed
|
||||
match key {
|
||||
Key::Alt => kVK_Option,
|
||||
Key::Backspace => kVK_Delete,
|
||||
Key::CapsLock => kVK_CapsLock,
|
||||
Key::Control => kVK_Control,
|
||||
Key::Delete => kVK_ForwardDelete,
|
||||
Key::DownArrow => kVK_DownArrow,
|
||||
Key::End => kVK_End,
|
||||
Key::Escape => kVK_Escape,
|
||||
Key::F1 => kVK_F1,
|
||||
Key::F10 => kVK_F10,
|
||||
Key::F11 => kVK_F11,
|
||||
Key::F12 => kVK_F12,
|
||||
Key::F2 => kVK_F2,
|
||||
Key::F3 => kVK_F3,
|
||||
Key::F4 => kVK_F4,
|
||||
Key::F5 => kVK_F5,
|
||||
Key::F6 => kVK_F6,
|
||||
Key::F7 => kVK_F7,
|
||||
Key::F8 => kVK_F8,
|
||||
Key::F9 => kVK_F9,
|
||||
Key::Home => kVK_Home,
|
||||
Key::LeftArrow => kVK_LeftArrow,
|
||||
Key::Option => kVK_Option,
|
||||
Key::PageDown => kVK_PageDown,
|
||||
Key::PageUp => kVK_PageUp,
|
||||
Key::Return => kVK_Return,
|
||||
Key::RightArrow => kVK_RightArrow,
|
||||
Key::Shift => kVK_Shift,
|
||||
Key::Space => kVK_Space,
|
||||
Key::Tab => kVK_Tab,
|
||||
Key::UpArrow => kVK_UpArrow,
|
||||
Key::Numpad0 => kVK_ANSI_Keypad0,
|
||||
Key::Numpad1 => kVK_ANSI_Keypad1,
|
||||
Key::Numpad2 => kVK_ANSI_Keypad2,
|
||||
Key::Numpad3 => kVK_ANSI_Keypad3,
|
||||
Key::Numpad4 => kVK_ANSI_Keypad4,
|
||||
Key::Numpad5 => kVK_ANSI_Keypad5,
|
||||
Key::Numpad6 => kVK_ANSI_Keypad6,
|
||||
Key::Numpad7 => kVK_ANSI_Keypad7,
|
||||
Key::Numpad8 => kVK_ANSI_Keypad8,
|
||||
Key::Numpad9 => kVK_ANSI_Keypad9,
|
||||
Key::Mute => kVK_Mute,
|
||||
Key::VolumeDown => kVK_VolumeUp,
|
||||
Key::VolumeUp => kVK_VolumeDown,
|
||||
Key::Help => kVK_Help,
|
||||
Key::Snapshot => kVK_F13,
|
||||
Key::Clear => kVK_ANSI_KeypadClear,
|
||||
Key::Decimal => kVK_ANSI_KeypadDecimal,
|
||||
Key::Multiply => kVK_ANSI_KeypadMultiply,
|
||||
Key::Add => kVK_ANSI_KeypadPlus,
|
||||
Key::Divide => kVK_ANSI_KeypadDivide,
|
||||
Key::NumpadEnter => kVK_ANSI_KeypadEnter,
|
||||
Key::Subtract => kVK_ANSI_KeypadMinus,
|
||||
Key::Equals => kVK_ANSI_KeypadEquals,
|
||||
Key::NumLock => kVK_ANSI_KeypadClear,
|
||||
Key::RWin => kVK_RIGHT_COMMAND,
|
||||
Key::RightShift => kVK_RightShift,
|
||||
Key::RightControl => kVK_RightControl,
|
||||
Key::RightAlt => kVK_RightOption,
|
||||
|
||||
Key::Raw(raw_keycode) => raw_keycode,
|
||||
Key::Layout(c) => self.map_key_board(c),
|
||||
|
||||
Key::Super | Key::Command | Key::Windows | Key::Meta => kVK_Command,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn map_key_board(&mut self, ch: char) -> CGKeyCode {
|
||||
let mut code = 0;
|
||||
unsafe {
|
||||
let (keyboard, layout) = get_layout();
|
||||
if !keyboard.is_null() && !layout.is_null() {
|
||||
let name_ref = TISGetInputSourceProperty(keyboard, kTISPropertyInputSourceID);
|
||||
if !name_ref.is_null() {
|
||||
let name = get_string(name_ref as _);
|
||||
if let Some(name) = name {
|
||||
if let Some(m) = self.char_to_vkey_map.get(&name) {
|
||||
code = *m.get(&ch).unwrap_or(&0);
|
||||
} else {
|
||||
let m = get_map(&name, layout);
|
||||
code = *m.get(&ch).unwrap_or(&0);
|
||||
self.char_to_vkey_map.insert(name.clone(), m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
}
|
||||
if code > 0 {
|
||||
return code;
|
||||
}
|
||||
match ch {
|
||||
'a' => kVK_ANSI_A,
|
||||
'b' => kVK_ANSI_B,
|
||||
'c' => kVK_ANSI_C,
|
||||
'd' => kVK_ANSI_D,
|
||||
'e' => kVK_ANSI_E,
|
||||
'f' => kVK_ANSI_F,
|
||||
'g' => kVK_ANSI_G,
|
||||
'h' => kVK_ANSI_H,
|
||||
'i' => kVK_ANSI_I,
|
||||
'j' => kVK_ANSI_J,
|
||||
'k' => kVK_ANSI_K,
|
||||
'l' => kVK_ANSI_L,
|
||||
'm' => kVK_ANSI_M,
|
||||
'n' => kVK_ANSI_N,
|
||||
'o' => kVK_ANSI_O,
|
||||
'p' => kVK_ANSI_P,
|
||||
'q' => kVK_ANSI_Q,
|
||||
'r' => kVK_ANSI_R,
|
||||
's' => kVK_ANSI_S,
|
||||
't' => kVK_ANSI_T,
|
||||
'u' => kVK_ANSI_U,
|
||||
'v' => kVK_ANSI_V,
|
||||
'w' => kVK_ANSI_W,
|
||||
'x' => kVK_ANSI_X,
|
||||
'y' => kVK_ANSI_Y,
|
||||
'z' => kVK_ANSI_Z,
|
||||
'0' => kVK_ANSI_0,
|
||||
'1' => kVK_ANSI_1,
|
||||
'2' => kVK_ANSI_2,
|
||||
'3' => kVK_ANSI_3,
|
||||
'4' => kVK_ANSI_4,
|
||||
'5' => kVK_ANSI_5,
|
||||
'6' => kVK_ANSI_6,
|
||||
'7' => kVK_ANSI_7,
|
||||
'8' => kVK_ANSI_8,
|
||||
'9' => kVK_ANSI_9,
|
||||
'-' => kVK_ANSI_Minus,
|
||||
'=' => kVK_ANSI_Equal,
|
||||
'[' => kVK_ANSI_LeftBracket,
|
||||
']' => kVK_ANSI_RightBracket,
|
||||
'\\' => kVK_ANSI_Backslash,
|
||||
';' => kVK_ANSI_Semicolon,
|
||||
'\'' => kVK_ANSI_Quote,
|
||||
',' => kVK_ANSI_Comma,
|
||||
'.' => kVK_ANSI_Period,
|
||||
'/' => kVK_ANSI_Slash,
|
||||
'`' => kVK_ANSI_Grave,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_string(cf_string: CFStringRef) -> Option<String> {
|
||||
if !cf_string.is_null() {
|
||||
let mut buf: [i8; 255] = [0; 255];
|
||||
let success = CFStringGetCString(
|
||||
cf_string,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as _,
|
||||
kCFStringEncodingUTF8,
|
||||
);
|
||||
if success != 0 {
|
||||
let name: &CStr = CStr::from_ptr(buf.as_ptr());
|
||||
if let Ok(name) = name.to_str() {
|
||||
return Some(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_layout() -> (TISInputSourceRef, *const u8) {
|
||||
let mut keyboard = TISCopyCurrentKeyboardInputSource();
|
||||
let mut layout = null_mut();
|
||||
if !keyboard.is_null() {
|
||||
layout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);
|
||||
}
|
||||
if layout.is_null() {
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
// https://github.com/microsoft/vscode/issues/23833
|
||||
keyboard = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
if !keyboard.is_null() {
|
||||
layout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);
|
||||
}
|
||||
}
|
||||
if layout.is_null() {
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
keyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
|
||||
if !keyboard.is_null() {
|
||||
layout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData);
|
||||
}
|
||||
}
|
||||
if layout.is_null() {
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
return (null_mut(), null_mut());
|
||||
}
|
||||
let layout_ptr = CFDataGetBytePtr(layout as _);
|
||||
if layout_ptr.is_null() {
|
||||
if !keyboard.is_null() {
|
||||
CFRelease(keyboard);
|
||||
}
|
||||
return (null_mut(), null_mut());
|
||||
}
|
||||
(keyboard, layout_ptr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_map(name: &str, layout: *const u8) -> Map<char, CGKeyCode> {
|
||||
log::info!("Create keyboard map for {}", name);
|
||||
let mut keys_down: u32 = 0;
|
||||
let mut map = Map::new();
|
||||
for keycode in 0..128 {
|
||||
let mut buff = [0_u16; BUF_LEN];
|
||||
let kb_type = unsafe { LMGetKbdType() };
|
||||
let mut length: UniCharCount = 0;
|
||||
let _retval = unsafe {
|
||||
UCKeyTranslate(
|
||||
layout,
|
||||
keycode,
|
||||
kUCKeyActionDisplay as _,
|
||||
0,
|
||||
kb_type as _,
|
||||
kUCKeyTranslateDeadKeysBit as _,
|
||||
&mut keys_down,
|
||||
BUF_LEN,
|
||||
&mut length,
|
||||
&mut buff,
|
||||
)
|
||||
};
|
||||
if length > 0 {
|
||||
if let Ok(str) = String::from_utf16(&buff[..length]) {
|
||||
if let Some(chr) = str.chars().next() {
|
||||
map.insert(chr, keycode as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
unsafe impl Send for Enigo {}
|
||||
4
rust-rdp/rust-desk/libs/enigo/src/macos/mod.rs
Normal file
4
rust-rdp/rust-desk/libs/enigo/src/macos/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
mod macos_impl;
|
||||
|
||||
pub mod keycodes;
|
||||
pub use self::macos_impl::Enigo;
|
||||
81
rust-rdp/rust-desk/libs/enigo/src/win/keycodes.rs
Normal file
81
rust-rdp/rust-desk/libs/enigo/src/win/keycodes.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731
|
||||
//
|
||||
// JP/KR mapping https://github.com/TigerVNC/tigervnc/blob/1a008c1380305648ab50f1d99e73439747e9d61d/vncviewer/win32.c#L267
|
||||
// altgr handle: https://github.com/TigerVNC/tigervnc/blob/dccb95f345f7a9c5aa785a19d1bfa3fdecd8f8e0/vncviewer/Viewport.cxx#L1066
|
||||
|
||||
pub const EVK_RETURN: u16 = 0x0D;
|
||||
pub const EVK_TAB: u16 = 0x09;
|
||||
pub const EVK_SPACE: u16 = 0x20;
|
||||
pub const EVK_BACK: u16 = 0x08;
|
||||
pub const EVK_ESCAPE: u16 = 0x1b;
|
||||
pub const EVK_LWIN: u16 = 0x5b;
|
||||
pub const EVK_SHIFT: u16 = 0x10;
|
||||
//pub const EVK_LSHIFT: u16 = 0xa0;
|
||||
pub const EVK_RSHIFT: u16 = 0xa1;
|
||||
//pub const EVK_LMENU: u16 = 0xa4;
|
||||
pub const EVK_RMENU: u16 = 0xa5;
|
||||
pub const EVK_CAPITAL: u16 = 0x14;
|
||||
pub const EVK_MENU: u16 = 0x12;
|
||||
pub const EVK_LCONTROL: u16 = 0xa2;
|
||||
pub const EVK_RCONTROL: u16 = 0xa3;
|
||||
pub const EVK_HOME: u16 = 0x24;
|
||||
pub const EVK_PRIOR: u16 = 0x21;
|
||||
pub const EVK_NEXT: u16 = 0x22;
|
||||
pub const EVK_END: u16 = 0x23;
|
||||
pub const EVK_LEFT: u16 = 0x25;
|
||||
pub const EVK_RIGHT: u16 = 0x27;
|
||||
pub const EVK_UP: u16 = 0x26;
|
||||
pub const EVK_DOWN: u16 = 0x28;
|
||||
pub const EVK_DELETE: u16 = 0x2E;
|
||||
pub const EVK_F1: u16 = 0x70;
|
||||
pub const EVK_F2: u16 = 0x71;
|
||||
pub const EVK_F3: u16 = 0x72;
|
||||
pub const EVK_F4: u16 = 0x73;
|
||||
pub const EVK_F5: u16 = 0x74;
|
||||
pub const EVK_F6: u16 = 0x75;
|
||||
pub const EVK_F7: u16 = 0x76;
|
||||
pub const EVK_F8: u16 = 0x77;
|
||||
pub const EVK_F9: u16 = 0x78;
|
||||
pub const EVK_F10: u16 = 0x79;
|
||||
pub const EVK_F11: u16 = 0x7a;
|
||||
pub const EVK_F12: u16 = 0x7b;
|
||||
pub const EVK_NUMPAD0: u16 = 0x60;
|
||||
pub const EVK_NUMPAD1: u16 = 0x61;
|
||||
pub const EVK_NUMPAD2: u16 = 0x62;
|
||||
pub const EVK_NUMPAD3: u16 = 0x63;
|
||||
pub const EVK_NUMPAD4: u16 = 0x64;
|
||||
pub const EVK_NUMPAD5: u16 = 0x65;
|
||||
pub const EVK_NUMPAD6: u16 = 0x66;
|
||||
pub const EVK_NUMPAD7: u16 = 0x67;
|
||||
pub const EVK_NUMPAD8: u16 = 0x68;
|
||||
pub const EVK_NUMPAD9: u16 = 0x69;
|
||||
pub const EVK_CANCEL: u16 = 0x03;
|
||||
pub const EVK_CLEAR: u16 = 0x0C;
|
||||
pub const EVK_PAUSE: u16 = 0x13;
|
||||
pub const EVK_KANA: u16 = 0x15;
|
||||
pub const EVK_HANGUL: u16 = 0x15;
|
||||
pub const EVK_JUNJA: u16 = 0x17;
|
||||
pub const EVK_FINAL: u16 = 0x18;
|
||||
pub const EVK_HANJA: u16 = 0x19;
|
||||
pub const EVK_KANJI: u16 = 0x19;
|
||||
pub const EVK_CONVERT: u16 = 0x1C;
|
||||
pub const EVK_SELECT: u16 = 0x29;
|
||||
pub const EVK_PRINT: u16 = 0x2A;
|
||||
pub const EVK_EXECUTE: u16 = 0x2B;
|
||||
pub const EVK_SNAPSHOT: u16 = 0x2C;
|
||||
pub const EVK_INSERT: u16 = 0x2D;
|
||||
pub const EVK_HELP: u16 = 0x2F;
|
||||
pub const EVK_SLEEP: u16 = 0x5F;
|
||||
pub const EVK_SEPARATOR: u16 = 0x6C;
|
||||
pub const EVK_VOLUME_MUTE: u16 = 0xAD;
|
||||
pub const EVK_VOLUME_DOWN: u16 = 0xAE;
|
||||
pub const EVK_VOLUME_UP: u16 = 0xAF;
|
||||
pub const EVK_NUMLOCK: u16 = 0x90;
|
||||
pub const EVK_SCROLL: u16 = 0x91;
|
||||
pub const EVK_RWIN: u16 = 0x5C;
|
||||
pub const EVK_APPS: u16 = 0x5D;
|
||||
pub const EVK_ADD: u16 = 0x6B;
|
||||
pub const EVK_MULTIPLY: u16 = 0x6A;
|
||||
pub const EVK_SUBTRACT: u16 = 0x6D;
|
||||
pub const EVK_DECIMAL: u16 = 0x6E;
|
||||
pub const EVK_DIVIDE: u16 = 0x6F;
|
||||
4
rust-rdp/rust-desk/libs/enigo/src/win/mod.rs
Normal file
4
rust-rdp/rust-desk/libs/enigo/src/win/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
mod win_impl;
|
||||
|
||||
pub mod keycodes;
|
||||
pub use self::win_impl::Enigo;
|
||||
378
rust-rdp/rust-desk/libs/enigo/src/win/win_impl.rs
Normal file
378
rust-rdp/rust-desk/libs/enigo/src/win/win_impl.rs
Normal file
@@ -0,0 +1,378 @@
|
||||
use winapi;
|
||||
|
||||
use self::winapi::ctypes::c_int;
|
||||
use self::winapi::shared::{minwindef::*, windef::*};
|
||||
use self::winapi::um::winbase::*;
|
||||
use self::winapi::um::winuser::*;
|
||||
|
||||
use crate::win::keycodes::*;
|
||||
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use std::mem::*;
|
||||
|
||||
extern "system" {
|
||||
pub fn GetLastError() -> DWORD;
|
||||
}
|
||||
|
||||
/// The main struct for handling the event emitting
|
||||
#[derive(Default)]
|
||||
pub struct Enigo;
|
||||
static mut LAYOUT: HKL = std::ptr::null_mut();
|
||||
|
||||
fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) -> DWORD {
|
||||
let mut input = INPUT {
|
||||
type_: INPUT_MOUSE,
|
||||
u: unsafe {
|
||||
transmute(MOUSEINPUT {
|
||||
dx,
|
||||
dy,
|
||||
mouseData: data,
|
||||
dwFlags: flags,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
})
|
||||
},
|
||||
};
|
||||
unsafe { SendInput(1, &mut input as LPINPUT, size_of::<INPUT>() as c_int) }
|
||||
}
|
||||
|
||||
fn keybd_event(flags: u32, vk: u16, scan: u16) -> DWORD {
|
||||
let mut scan = scan;
|
||||
unsafe {
|
||||
// https://github.com/rustdesk/rustdesk/issues/366
|
||||
if scan == 0 {
|
||||
if LAYOUT.is_null() {
|
||||
let current_window_thread_id =
|
||||
GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut());
|
||||
LAYOUT = GetKeyboardLayout(current_window_thread_id);
|
||||
}
|
||||
scan = MapVirtualKeyExW(vk as _, 0, LAYOUT) as _;
|
||||
}
|
||||
}
|
||||
let mut input = INPUT {
|
||||
type_: INPUT_KEYBOARD,
|
||||
u: unsafe {
|
||||
transmute_copy(&KEYBDINPUT {
|
||||
wVk: vk,
|
||||
wScan: scan,
|
||||
dwFlags: flags,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
})
|
||||
},
|
||||
};
|
||||
unsafe { SendInput(1, &mut input as LPINPUT, size_of::<INPUT>() as c_int) }
|
||||
}
|
||||
|
||||
fn get_error() -> String {
|
||||
unsafe {
|
||||
let buff_size = 256;
|
||||
let mut buff: Vec<u16> = Vec::with_capacity(buff_size);
|
||||
buff.resize(buff_size, 0);
|
||||
let errno = GetLastError();
|
||||
let chars_copied = FormatMessageW(
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS
|
||||
| FORMAT_MESSAGE_FROM_SYSTEM
|
||||
| FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
||||
std::ptr::null(),
|
||||
errno,
|
||||
0,
|
||||
buff.as_mut_ptr(),
|
||||
(buff_size + 1) as u32,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
if chars_copied == 0 {
|
||||
return "".to_owned();
|
||||
}
|
||||
let mut curr_char: usize = chars_copied as usize;
|
||||
while curr_char > 0 {
|
||||
let ch = buff[curr_char];
|
||||
|
||||
if ch >= ' ' as u16 {
|
||||
break;
|
||||
}
|
||||
curr_char -= 1;
|
||||
}
|
||||
let sl = std::slice::from_raw_parts(buff.as_ptr(), curr_char);
|
||||
let err_msg = String::from_utf16(sl);
|
||||
return err_msg.unwrap_or("".to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseControllable for Enigo {
|
||||
fn mouse_move_to(&mut self, x: i32, y: i32) {
|
||||
mouse_event(
|
||||
MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK,
|
||||
0,
|
||||
(x - unsafe { GetSystemMetrics(SM_XVIRTUALSCREEN) }) * 65535
|
||||
/ unsafe { GetSystemMetrics(SM_CXVIRTUALSCREEN) },
|
||||
(y - unsafe { GetSystemMetrics(SM_YVIRTUALSCREEN) }) * 65535
|
||||
/ unsafe { GetSystemMetrics(SM_CYVIRTUALSCREEN) },
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_move_relative(&mut self, x: i32, y: i32) {
|
||||
mouse_event(MOUSEEVENTF_MOVE, 0, x, y);
|
||||
}
|
||||
|
||||
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
|
||||
let res = mouse_event(
|
||||
match button {
|
||||
MouseButton::Left => MOUSEEVENTF_LEFTDOWN,
|
||||
MouseButton::Middle => MOUSEEVENTF_MIDDLEDOWN,
|
||||
MouseButton::Right => MOUSEEVENTF_RIGHTDOWN,
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
if res == 0 {
|
||||
let err = get_error();
|
||||
if !err.is_empty() {
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mouse_up(&mut self, button: MouseButton) {
|
||||
mouse_event(
|
||||
match button {
|
||||
MouseButton::Left => MOUSEEVENTF_LEFTUP,
|
||||
MouseButton::Middle => MOUSEEVENTF_MIDDLEUP,
|
||||
MouseButton::Right => MOUSEEVENTF_RIGHTUP,
|
||||
_ => unimplemented!(),
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_click(&mut self, button: MouseButton) {
|
||||
self.mouse_down(button).ok();
|
||||
self.mouse_up(button);
|
||||
}
|
||||
|
||||
fn mouse_scroll_x(&mut self, length: i32) {
|
||||
mouse_event(MOUSEEVENTF_HWHEEL, unsafe { transmute(length * 120) }, 0, 0);
|
||||
}
|
||||
|
||||
fn mouse_scroll_y(&mut self, length: i32) {
|
||||
mouse_event(MOUSEEVENTF_WHEEL, unsafe { transmute(length * 120) }, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardControllable for Enigo {
|
||||
fn key_sequence(&mut self, sequence: &str) {
|
||||
let mut buffer = [0; 2];
|
||||
|
||||
for c in sequence.chars() {
|
||||
// Windows uses uft-16 encoding. We need to check
|
||||
// for variable length characters. As such some
|
||||
// characters can be 32 bit long and those are
|
||||
// encoded in such called hight and low surrogates
|
||||
// each 16 bit wide that needs to be send after
|
||||
// another to the SendInput function without
|
||||
// being interrupted by "keyup"
|
||||
let result = c.encode_utf16(&mut buffer);
|
||||
if result.len() == 1 {
|
||||
self.unicode_key_click(result[0]);
|
||||
} else {
|
||||
for utf16_surrogate in result {
|
||||
self.unicode_key_down(utf16_surrogate.clone());
|
||||
}
|
||||
// do i need to produce a keyup?
|
||||
// self.unicode_key_up(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_click(&mut self, key: Key) {
|
||||
let vk = self.key_to_keycode(key);
|
||||
keybd_event(0, vk, 0);
|
||||
keybd_event(KEYEVENTF_KEYUP, vk, 0);
|
||||
}
|
||||
|
||||
fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||
let res = keybd_event(0, self.key_to_keycode(key), 0);
|
||||
if res == 0 {
|
||||
let err = get_error();
|
||||
if !err.is_empty() {
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_up(&mut self, key: Key) {
|
||||
keybd_event(KEYEVENTF_KEYUP, self.key_to_keycode(key), 0);
|
||||
}
|
||||
|
||||
fn get_key_state(&mut self, key: Key) -> bool {
|
||||
let keycode = self.key_to_keycode(key);
|
||||
let x = unsafe { GetKeyState(keycode as _) };
|
||||
if key == Key::CapsLock || key == Key::NumLock || key == Key::Scroll {
|
||||
return (x & 0x1) == 0x1;
|
||||
}
|
||||
return (x as u16 & 0x8000) == 0x8000;
|
||||
}
|
||||
}
|
||||
|
||||
impl Enigo {
|
||||
/// Gets the (width, height) of the main display in screen coordinates (pixels).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut size = Enigo::main_display_size();
|
||||
/// ```
|
||||
pub fn main_display_size() -> (usize, usize) {
|
||||
let w = unsafe { GetSystemMetrics(SM_CXSCREEN) as usize };
|
||||
let h = unsafe { GetSystemMetrics(SM_CYSCREEN) as usize };
|
||||
(w, h)
|
||||
}
|
||||
|
||||
/// Gets the location of mouse in screen coordinates (pixels).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use enigo::*;
|
||||
/// let mut location = Enigo::mouse_location();
|
||||
/// ```
|
||||
pub fn mouse_location() -> (i32, i32) {
|
||||
let mut point = POINT { x: 0, y: 0 };
|
||||
let result = unsafe { GetCursorPos(&mut point) };
|
||||
if result != 0 {
|
||||
(point.x, point.y)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn unicode_key_click(&self, unicode_char: u16) {
|
||||
self.unicode_key_down(unicode_char);
|
||||
self.unicode_key_up(unicode_char);
|
||||
}
|
||||
|
||||
fn unicode_key_down(&self, unicode_char: u16) {
|
||||
keybd_event(KEYEVENTF_UNICODE, 0, unicode_char);
|
||||
}
|
||||
|
||||
fn unicode_key_up(&self, unicode_char: u16) {
|
||||
keybd_event(KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, 0, unicode_char);
|
||||
}
|
||||
|
||||
fn key_to_keycode(&self, key: Key) -> u16 {
|
||||
unsafe {
|
||||
LAYOUT = std::ptr::null_mut();
|
||||
}
|
||||
// do not use the codes from crate winapi they're
|
||||
// wrongly typed with i32 instead of i16 use the
|
||||
// ones provided by win/keycodes.rs that are prefixed
|
||||
// with an 'E' infront of the original name
|
||||
#[allow(deprecated)]
|
||||
// I mean duh, we still need to support deprecated keys until they're removed
|
||||
match key {
|
||||
Key::Alt => EVK_MENU,
|
||||
Key::Backspace => EVK_BACK,
|
||||
Key::CapsLock => EVK_CAPITAL,
|
||||
Key::Control => EVK_LCONTROL,
|
||||
Key::Delete => EVK_DELETE,
|
||||
Key::DownArrow => EVK_DOWN,
|
||||
Key::End => EVK_END,
|
||||
Key::Escape => EVK_ESCAPE,
|
||||
Key::F1 => EVK_F1,
|
||||
Key::F10 => EVK_F10,
|
||||
Key::F11 => EVK_F11,
|
||||
Key::F12 => EVK_F12,
|
||||
Key::F2 => EVK_F2,
|
||||
Key::F3 => EVK_F3,
|
||||
Key::F4 => EVK_F4,
|
||||
Key::F5 => EVK_F5,
|
||||
Key::F6 => EVK_F6,
|
||||
Key::F7 => EVK_F7,
|
||||
Key::F8 => EVK_F8,
|
||||
Key::F9 => EVK_F9,
|
||||
Key::Home => EVK_HOME,
|
||||
Key::LeftArrow => EVK_LEFT,
|
||||
Key::Option => EVK_MENU,
|
||||
Key::PageDown => EVK_NEXT,
|
||||
Key::PageUp => EVK_PRIOR,
|
||||
Key::Return => EVK_RETURN,
|
||||
Key::RightArrow => EVK_RIGHT,
|
||||
Key::Shift => EVK_SHIFT,
|
||||
Key::Space => EVK_SPACE,
|
||||
Key::Tab => EVK_TAB,
|
||||
Key::UpArrow => EVK_UP,
|
||||
Key::Numpad0 => EVK_NUMPAD0,
|
||||
Key::Numpad1 => EVK_NUMPAD1,
|
||||
Key::Numpad2 => EVK_NUMPAD2,
|
||||
Key::Numpad3 => EVK_NUMPAD3,
|
||||
Key::Numpad4 => EVK_NUMPAD4,
|
||||
Key::Numpad5 => EVK_NUMPAD5,
|
||||
Key::Numpad6 => EVK_NUMPAD6,
|
||||
Key::Numpad7 => EVK_NUMPAD7,
|
||||
Key::Numpad8 => EVK_NUMPAD8,
|
||||
Key::Numpad9 => EVK_NUMPAD9,
|
||||
Key::Cancel => EVK_CANCEL,
|
||||
Key::Clear => EVK_CLEAR,
|
||||
Key::Pause => EVK_PAUSE,
|
||||
Key::Kana => EVK_KANA,
|
||||
Key::Hangul => EVK_HANGUL,
|
||||
Key::Junja => EVK_JUNJA,
|
||||
Key::Final => EVK_FINAL,
|
||||
Key::Hanja => EVK_HANJA,
|
||||
Key::Kanji => EVK_KANJI,
|
||||
Key::Convert => EVK_CONVERT,
|
||||
Key::Select => EVK_SELECT,
|
||||
Key::Print => EVK_PRINT,
|
||||
Key::Execute => EVK_EXECUTE,
|
||||
Key::Snapshot => EVK_SNAPSHOT,
|
||||
Key::Insert => EVK_INSERT,
|
||||
Key::Help => EVK_HELP,
|
||||
Key::Sleep => EVK_SLEEP,
|
||||
Key::Separator => EVK_SEPARATOR,
|
||||
Key::Mute => EVK_VOLUME_MUTE,
|
||||
Key::VolumeDown => EVK_VOLUME_DOWN,
|
||||
Key::VolumeUp => EVK_VOLUME_UP,
|
||||
Key::Scroll => EVK_SCROLL,
|
||||
Key::NumLock => EVK_NUMLOCK,
|
||||
Key::RWin => EVK_RWIN,
|
||||
Key::Apps => EVK_APPS,
|
||||
Key::Add => EVK_ADD,
|
||||
Key::Multiply => EVK_MULTIPLY,
|
||||
Key::Decimal => EVK_DECIMAL,
|
||||
Key::Subtract => EVK_SUBTRACT,
|
||||
Key::Divide => EVK_DIVIDE,
|
||||
Key::NumpadEnter => EVK_RETURN,
|
||||
Key::Equals => '=' as _,
|
||||
Key::RightShift => EVK_RSHIFT,
|
||||
Key::RightControl => EVK_RCONTROL,
|
||||
Key::RightAlt => EVK_RMENU,
|
||||
|
||||
Key::Raw(raw_keycode) => raw_keycode,
|
||||
Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()),
|
||||
Key::Super | Key::Command | Key::Windows | Key::Meta => EVK_LWIN,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_layoutdependent_keycode(&self, string: String) -> u16 {
|
||||
// get the first char from the string ignore the rest
|
||||
// ensure its not a multybyte char
|
||||
if let Some(chr) = string.chars().nth(0) {
|
||||
// NOTE VkKeyScanW uses the current keyboard LAYOUT
|
||||
// to specify a LAYOUT use VkKeyScanExW and GetKeyboardLayout
|
||||
// or load one with LoadKeyboardLayoutW
|
||||
let current_window_thread_id =
|
||||
unsafe { GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut()) };
|
||||
unsafe { LAYOUT = GetKeyboardLayout(current_window_thread_id) };
|
||||
let keycode_and_shiftstate = unsafe { VkKeyScanExW(chr as _, LAYOUT) };
|
||||
keycode_and_shiftstate as _
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
4
rust-rdp/rust-desk/libs/hbb_common/.gitignore
vendored
Normal file
4
rust-rdp/rust-desk/libs/hbb_common/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
src/protos/
|
||||
48
rust-rdp/rust-desk/libs/hbb_common/Cargo.toml
Normal file
48
rust-rdp/rust-desk/libs/hbb_common/Cargo.toml
Normal file
@@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "hbb_common"
|
||||
version = "0.1.0"
|
||||
authors = ["rustdesk<info@rustdesk.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
protobuf = "3.0.0-alpha.2"
|
||||
tokio = { version = "1.15", features = ["full"] }
|
||||
tokio-util = { version = "0.6", features = ["full"] }
|
||||
futures = "0.3"
|
||||
bytes = "1.1"
|
||||
log = "0.4"
|
||||
env_logger = "0.9"
|
||||
socket2 = { version = "0.3", features = ["reuseport"] }
|
||||
zstd = "0.9"
|
||||
quinn = {version = "0.8", optional = true }
|
||||
anyhow = "1.0"
|
||||
futures-util = "0.3"
|
||||
directories-next = "2.0"
|
||||
rand = "0.8"
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
lazy_static = "1.4"
|
||||
confy = { git = "https://github.com/open-trade/confy" }
|
||||
dirs-next = "2.0"
|
||||
filetime = "0.2"
|
||||
sodiumoxide = "0.2"
|
||||
regex = "1.4"
|
||||
tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
mac_address = "1.1"
|
||||
|
||||
[features]
|
||||
quic = ["quinn"]
|
||||
|
||||
[build-dependencies]
|
||||
protobuf-codegen-pure = "3.0.0-alpha.2"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3", features = ["winuser"] }
|
||||
|
||||
[dev-dependencies]
|
||||
toml = "0.5"
|
||||
serde_json = "1.0"
|
||||
9
rust-rdp/rust-desk/libs/hbb_common/build.rs
Normal file
9
rust-rdp/rust-desk/libs/hbb_common/build.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
fn main() {
|
||||
std::fs::create_dir_all("src/protos").unwrap();
|
||||
protobuf_codegen_pure::Codegen::new()
|
||||
.out_dir("src/protos")
|
||||
.inputs(&["protos/rendezvous.proto", "protos/message.proto"])
|
||||
.include("protos")
|
||||
.run()
|
||||
.expect("Codegen failed.");
|
||||
}
|
||||
409
rust-rdp/rust-desk/libs/hbb_common/protos/message.proto
Normal file
409
rust-rdp/rust-desk/libs/hbb_common/protos/message.proto
Normal file
@@ -0,0 +1,409 @@
|
||||
syntax = "proto3";
|
||||
package hbb;
|
||||
|
||||
message VP9 {
|
||||
bytes data = 1;
|
||||
bool key = 2;
|
||||
int64 pts = 3;
|
||||
}
|
||||
|
||||
message VP9s { repeated VP9 frames = 1; }
|
||||
|
||||
message RGB { bool compress = 1; }
|
||||
|
||||
// planes data send directly in binary for better use arraybuffer on web
|
||||
message YUV {
|
||||
bool compress = 1;
|
||||
int32 stride = 2;
|
||||
}
|
||||
|
||||
message VideoFrame {
|
||||
oneof union {
|
||||
VP9s vp9s = 6;
|
||||
RGB rgb = 7;
|
||||
YUV yuv = 8;
|
||||
}
|
||||
}
|
||||
|
||||
message DisplayInfo {
|
||||
sint32 x = 1;
|
||||
sint32 y = 2;
|
||||
int32 width = 3;
|
||||
int32 height = 4;
|
||||
string name = 5;
|
||||
bool online = 6;
|
||||
}
|
||||
|
||||
message PortForward {
|
||||
string host = 1;
|
||||
int32 port = 2;
|
||||
}
|
||||
|
||||
message FileTransfer {
|
||||
string dir = 1;
|
||||
bool show_hidden = 2;
|
||||
}
|
||||
|
||||
message LoginRequest {
|
||||
string username = 1;
|
||||
bytes password = 2;
|
||||
string my_id = 4;
|
||||
string my_name = 5;
|
||||
OptionMessage option = 6;
|
||||
oneof union {
|
||||
FileTransfer file_transfer = 7;
|
||||
PortForward port_forward = 8;
|
||||
}
|
||||
bool video_ack_required = 9;
|
||||
}
|
||||
|
||||
message ChatMessage { string text = 1; }
|
||||
|
||||
message PeerInfo {
|
||||
string username = 1;
|
||||
string hostname = 2;
|
||||
string platform = 3;
|
||||
repeated DisplayInfo displays = 4;
|
||||
int32 current_display = 5;
|
||||
bool sas_enabled = 6;
|
||||
string version = 7;
|
||||
}
|
||||
|
||||
message LoginResponse {
|
||||
oneof union {
|
||||
string error = 1;
|
||||
PeerInfo peer_info = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message MouseEvent {
|
||||
int32 mask = 1;
|
||||
sint32 x = 2;
|
||||
sint32 y = 3;
|
||||
repeated ControlKey modifiers = 4;
|
||||
}
|
||||
|
||||
enum ControlKey {
|
||||
Unknown = 0;
|
||||
Alt = 1;
|
||||
Backspace = 2;
|
||||
CapsLock = 3;
|
||||
Control = 4;
|
||||
Delete = 5;
|
||||
DownArrow = 6;
|
||||
End = 7;
|
||||
Escape = 8;
|
||||
F1 = 9;
|
||||
F10 = 10;
|
||||
F11 = 11;
|
||||
F12 = 12;
|
||||
F2 = 13;
|
||||
F3 = 14;
|
||||
F4 = 15;
|
||||
F5 = 16;
|
||||
F6 = 17;
|
||||
F7 = 18;
|
||||
F8 = 19;
|
||||
F9 = 20;
|
||||
Home = 21;
|
||||
LeftArrow = 22;
|
||||
/// meta key (also known as "windows"; "super"; and "command")
|
||||
Meta = 23;
|
||||
/// option key on macOS (alt key on Linux and Windows)
|
||||
Option = 24; // deprecated, use Alt instead
|
||||
PageDown = 25;
|
||||
PageUp = 26;
|
||||
Return = 27;
|
||||
RightArrow = 28;
|
||||
Shift = 29;
|
||||
Space = 30;
|
||||
Tab = 31;
|
||||
UpArrow = 32;
|
||||
Numpad0 = 33;
|
||||
Numpad1 = 34;
|
||||
Numpad2 = 35;
|
||||
Numpad3 = 36;
|
||||
Numpad4 = 37;
|
||||
Numpad5 = 38;
|
||||
Numpad6 = 39;
|
||||
Numpad7 = 40;
|
||||
Numpad8 = 41;
|
||||
Numpad9 = 42;
|
||||
Cancel = 43;
|
||||
Clear = 44;
|
||||
Menu = 45; // deprecated, use Alt instead
|
||||
Pause = 46;
|
||||
Kana = 47;
|
||||
Hangul = 48;
|
||||
Junja = 49;
|
||||
Final = 50;
|
||||
Hanja = 51;
|
||||
Kanji = 52;
|
||||
Convert = 53;
|
||||
Select = 54;
|
||||
Print = 55;
|
||||
Execute = 56;
|
||||
Snapshot = 57;
|
||||
Insert = 58;
|
||||
Help = 59;
|
||||
Sleep = 60;
|
||||
Separator = 61;
|
||||
Scroll = 62;
|
||||
NumLock = 63;
|
||||
RWin = 64;
|
||||
Apps = 65;
|
||||
Multiply = 66;
|
||||
Add = 67;
|
||||
Subtract = 68;
|
||||
Decimal = 69;
|
||||
Divide = 70;
|
||||
Equals = 71;
|
||||
NumpadEnter = 72;
|
||||
RShift = 73;
|
||||
RControl = 74;
|
||||
RAlt = 75;
|
||||
CtrlAltDel = 100;
|
||||
LockScreen = 101;
|
||||
}
|
||||
|
||||
message KeyEvent {
|
||||
bool down = 1;
|
||||
bool press = 2;
|
||||
oneof union {
|
||||
ControlKey control_key = 3;
|
||||
uint32 chr = 4;
|
||||
uint32 unicode = 5;
|
||||
string seq = 6;
|
||||
}
|
||||
repeated ControlKey modifiers = 8;
|
||||
}
|
||||
|
||||
message CursorData {
|
||||
uint64 id = 1;
|
||||
sint32 hotx = 2;
|
||||
sint32 hoty = 3;
|
||||
int32 width = 4;
|
||||
int32 height = 5;
|
||||
bytes colors = 6;
|
||||
}
|
||||
|
||||
message CursorPosition {
|
||||
sint32 x = 1;
|
||||
sint32 y = 2;
|
||||
}
|
||||
|
||||
message Hash {
|
||||
string salt = 1;
|
||||
string challenge = 2;
|
||||
}
|
||||
|
||||
message Clipboard {
|
||||
bool compress = 1;
|
||||
bytes content = 2;
|
||||
}
|
||||
|
||||
enum FileType {
|
||||
Dir = 0;
|
||||
DirLink = 2;
|
||||
DirDrive = 3;
|
||||
File = 4;
|
||||
FileLink = 5;
|
||||
}
|
||||
|
||||
message FileEntry {
|
||||
FileType entry_type = 1;
|
||||
string name = 2;
|
||||
bool is_hidden = 3;
|
||||
uint64 size = 4;
|
||||
uint64 modified_time = 5;
|
||||
}
|
||||
|
||||
message FileDirectory {
|
||||
int32 id = 1;
|
||||
string path = 2;
|
||||
repeated FileEntry entries = 3;
|
||||
}
|
||||
|
||||
message ReadDir {
|
||||
string path = 1;
|
||||
bool include_hidden = 2;
|
||||
}
|
||||
|
||||
message ReadAllFiles {
|
||||
int32 id = 1;
|
||||
string path = 2;
|
||||
bool include_hidden = 3;
|
||||
}
|
||||
|
||||
message FileAction {
|
||||
oneof union {
|
||||
ReadDir read_dir = 1;
|
||||
FileTransferSendRequest send = 2;
|
||||
FileTransferReceiveRequest receive = 3;
|
||||
FileDirCreate create = 4;
|
||||
FileRemoveDir remove_dir = 5;
|
||||
FileRemoveFile remove_file = 6;
|
||||
ReadAllFiles all_files = 7;
|
||||
FileTransferCancel cancel = 8;
|
||||
}
|
||||
}
|
||||
|
||||
message FileTransferCancel { int32 id = 1; }
|
||||
|
||||
message FileResponse {
|
||||
oneof union {
|
||||
FileDirectory dir = 1;
|
||||
FileTransferBlock block = 2;
|
||||
FileTransferError error = 3;
|
||||
FileTransferDone done = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message FileTransferBlock {
|
||||
int32 id = 1;
|
||||
sint32 file_num = 2;
|
||||
bytes data = 3;
|
||||
bool compressed = 4;
|
||||
}
|
||||
|
||||
message FileTransferError {
|
||||
int32 id = 1;
|
||||
string error = 2;
|
||||
sint32 file_num = 3;
|
||||
}
|
||||
|
||||
message FileTransferSendRequest {
|
||||
int32 id = 1;
|
||||
string path = 2;
|
||||
bool include_hidden = 3;
|
||||
}
|
||||
|
||||
message FileTransferDone {
|
||||
int32 id = 1;
|
||||
sint32 file_num = 2;
|
||||
}
|
||||
|
||||
message FileTransferReceiveRequest {
|
||||
int32 id = 1;
|
||||
string path = 2; // path written to
|
||||
repeated FileEntry files = 3;
|
||||
}
|
||||
|
||||
message FileRemoveDir {
|
||||
int32 id = 1;
|
||||
string path = 2;
|
||||
bool recursive = 3;
|
||||
}
|
||||
|
||||
message FileRemoveFile {
|
||||
int32 id = 1;
|
||||
string path = 2;
|
||||
sint32 file_num = 3;
|
||||
}
|
||||
|
||||
message FileDirCreate {
|
||||
int32 id = 1;
|
||||
string path = 2;
|
||||
}
|
||||
|
||||
message SwitchDisplay {
|
||||
int32 display = 1;
|
||||
sint32 x = 2;
|
||||
sint32 y = 3;
|
||||
int32 width = 4;
|
||||
int32 height = 5;
|
||||
}
|
||||
|
||||
message PermissionInfo {
|
||||
enum Permission {
|
||||
Keyboard = 0;
|
||||
Clipboard = 2;
|
||||
Audio = 3;
|
||||
}
|
||||
|
||||
Permission permission = 1;
|
||||
bool enabled = 2;
|
||||
}
|
||||
|
||||
enum ImageQuality {
|
||||
NotSet = 0;
|
||||
Low = 2;
|
||||
Balanced = 3;
|
||||
Best = 4;
|
||||
}
|
||||
|
||||
message OptionMessage {
|
||||
enum BoolOption {
|
||||
NotSet = 0;
|
||||
No = 1;
|
||||
Yes = 2;
|
||||
}
|
||||
ImageQuality image_quality = 1;
|
||||
BoolOption lock_after_session_end = 2;
|
||||
BoolOption show_remote_cursor = 3;
|
||||
BoolOption privacy_mode = 4;
|
||||
BoolOption block_input = 5;
|
||||
int32 custom_image_quality = 6;
|
||||
BoolOption disable_audio = 7;
|
||||
BoolOption disable_clipboard = 8;
|
||||
}
|
||||
|
||||
message OptionResponse {
|
||||
OptionMessage opt = 1;
|
||||
string error = 2;
|
||||
}
|
||||
|
||||
message TestDelay {
|
||||
int64 time = 1;
|
||||
bool from_client = 2;
|
||||
}
|
||||
|
||||
message PublicKey {
|
||||
bytes asymmetric_value = 1;
|
||||
bytes symmetric_value = 2;
|
||||
}
|
||||
|
||||
message SignedId { bytes id = 1; }
|
||||
|
||||
message AudioFormat {
|
||||
uint32 sample_rate = 1;
|
||||
uint32 channels = 2;
|
||||
}
|
||||
|
||||
message AudioFrame { bytes data = 1; }
|
||||
|
||||
message Misc {
|
||||
oneof union {
|
||||
ChatMessage chat_message = 4;
|
||||
SwitchDisplay switch_display = 5;
|
||||
PermissionInfo permission_info = 6;
|
||||
OptionMessage option = 7;
|
||||
AudioFormat audio_format = 8;
|
||||
string close_reason = 9;
|
||||
bool refresh_video = 10;
|
||||
OptionResponse option_response = 11;
|
||||
bool video_received = 12;
|
||||
}
|
||||
}
|
||||
|
||||
message Message {
|
||||
oneof union {
|
||||
SignedId signed_id = 3;
|
||||
PublicKey public_key = 4;
|
||||
TestDelay test_delay = 5;
|
||||
VideoFrame video_frame = 6;
|
||||
LoginRequest login_request = 7;
|
||||
LoginResponse login_response = 8;
|
||||
Hash hash = 9;
|
||||
MouseEvent mouse_event = 10;
|
||||
AudioFrame audio_frame = 11;
|
||||
CursorData cursor_data = 12;
|
||||
CursorPosition cursor_position = 13;
|
||||
uint64 cursor_id = 14;
|
||||
KeyEvent key_event = 15;
|
||||
Clipboard clipboard = 16;
|
||||
FileAction file_action = 17;
|
||||
FileResponse file_response = 18;
|
||||
Misc misc = 19;
|
||||
}
|
||||
}
|
||||
166
rust-rdp/rust-desk/libs/hbb_common/protos/rendezvous.proto
Normal file
166
rust-rdp/rust-desk/libs/hbb_common/protos/rendezvous.proto
Normal file
@@ -0,0 +1,166 @@
|
||||
syntax = "proto3";
|
||||
package hbb;
|
||||
|
||||
message RegisterPeer {
|
||||
string id = 1;
|
||||
int32 serial = 2;
|
||||
}
|
||||
|
||||
enum ConnType {
|
||||
DEFAULT_CONN = 0;
|
||||
FILE_TRANSFER = 1;
|
||||
PORT_FORWARD = 2;
|
||||
RDP = 3;
|
||||
}
|
||||
|
||||
message RegisterPeerResponse { bool request_pk = 2; }
|
||||
|
||||
message PunchHoleRequest {
|
||||
string id = 1;
|
||||
NatType nat_type = 2;
|
||||
ConnType conn_type = 4;
|
||||
}
|
||||
|
||||
message PunchHole {
|
||||
bytes socket_addr = 1;
|
||||
string relay_server = 2;
|
||||
NatType nat_type = 3;
|
||||
}
|
||||
|
||||
message TestNatRequest {
|
||||
int32 serial = 1;
|
||||
}
|
||||
|
||||
// per my test, uint/int has no difference in encoding, int not good for negative, use sint for negative
|
||||
message TestNatResponse {
|
||||
int32 port = 1;
|
||||
ConfigUpdate cu = 2; // for mobile
|
||||
}
|
||||
|
||||
enum NatType {
|
||||
UNKNOWN_NAT = 0;
|
||||
ASYMMETRIC = 1;
|
||||
SYMMETRIC = 2;
|
||||
}
|
||||
|
||||
message PunchHoleSent {
|
||||
bytes socket_addr = 1;
|
||||
string id = 2;
|
||||
string relay_server = 3;
|
||||
NatType nat_type = 4;
|
||||
string version = 5;
|
||||
}
|
||||
|
||||
message RegisterPk {
|
||||
string id = 1;
|
||||
bytes uuid = 2;
|
||||
bytes pk = 3;
|
||||
}
|
||||
|
||||
message RegisterPkResponse {
|
||||
enum Result {
|
||||
OK = 0;
|
||||
UUID_MISMATCH = 2;
|
||||
ID_EXISTS = 3;
|
||||
TOO_FREQUENT = 4;
|
||||
INVALID_ID_FORMAT = 5;
|
||||
NOT_SUPPORT = 6;
|
||||
SERVER_ERROR = 7;
|
||||
}
|
||||
Result result = 1;
|
||||
}
|
||||
|
||||
message PunchHoleResponse {
|
||||
bytes socket_addr = 1;
|
||||
bytes pk = 2;
|
||||
enum Failure {
|
||||
ID_NOT_EXIST = 0;
|
||||
OFFLINE = 2;
|
||||
LICENSE_MISMATCH = 3;
|
||||
LICENSE_OVERUSE = 4;
|
||||
}
|
||||
Failure failure = 3;
|
||||
string relay_server = 4;
|
||||
oneof union {
|
||||
NatType nat_type = 5;
|
||||
bool is_local = 6;
|
||||
}
|
||||
string other_failure = 7;
|
||||
}
|
||||
|
||||
message ConfigUpdate {
|
||||
int32 serial = 1;
|
||||
repeated string rendezvous_servers = 2;
|
||||
}
|
||||
|
||||
message RequestRelay {
|
||||
string id = 1;
|
||||
string uuid = 2;
|
||||
bytes socket_addr = 3;
|
||||
string relay_server = 4;
|
||||
bool secure = 5;
|
||||
ConnType conn_type = 7;
|
||||
}
|
||||
|
||||
message RelayResponse {
|
||||
bytes socket_addr = 1;
|
||||
string uuid = 2;
|
||||
string relay_server = 3;
|
||||
oneof union {
|
||||
string id = 4;
|
||||
bytes pk = 5;
|
||||
}
|
||||
string refuse_reason = 6;
|
||||
string version = 7;
|
||||
}
|
||||
|
||||
message SoftwareUpdate { string url = 1; }
|
||||
|
||||
// if in same intranet, punch hole won't work both for udp and tcp,
|
||||
// even some router has below connection error if we connect itself,
|
||||
// { kind: Other, error: "could not resolve to any address" },
|
||||
// so we request local address to connect.
|
||||
message FetchLocalAddr {
|
||||
bytes socket_addr = 1;
|
||||
string relay_server = 2;
|
||||
}
|
||||
|
||||
message LocalAddr {
|
||||
bytes socket_addr = 1;
|
||||
bytes local_addr = 2;
|
||||
string relay_server = 3;
|
||||
string id = 4;
|
||||
string version = 5;
|
||||
}
|
||||
|
||||
message PeerDiscovery {
|
||||
string cmd = 1;
|
||||
string mac = 2;
|
||||
string id = 3;
|
||||
string username = 4;
|
||||
string hostname = 5;
|
||||
string platform = 6;
|
||||
string misc = 7;
|
||||
}
|
||||
|
||||
message RendezvousMessage {
|
||||
oneof union {
|
||||
RegisterPeer register_peer = 6;
|
||||
RegisterPeerResponse register_peer_response = 7;
|
||||
PunchHoleRequest punch_hole_request = 8;
|
||||
PunchHole punch_hole = 9;
|
||||
PunchHoleSent punch_hole_sent = 10;
|
||||
PunchHoleResponse punch_hole_response = 11;
|
||||
FetchLocalAddr fetch_local_addr = 12;
|
||||
LocalAddr local_addr = 13;
|
||||
ConfigUpdate configure_update = 14;
|
||||
RegisterPk register_pk = 15;
|
||||
RegisterPkResponse register_pk_response = 16;
|
||||
SoftwareUpdate software_update = 17;
|
||||
RequestRelay request_relay = 18;
|
||||
RelayResponse relay_response = 19;
|
||||
TestNatRequest test_nat_request = 20;
|
||||
TestNatResponse test_nat_response = 21;
|
||||
PeerDiscovery peer_discovery = 22;
|
||||
}
|
||||
}
|
||||
274
rust-rdp/rust-desk/libs/hbb_common/src/bytes_codec.rs
Normal file
274
rust-rdp/rust-desk/libs/hbb_common/src/bytes_codec.rs
Normal file
@@ -0,0 +1,274 @@
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use std::io;
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BytesCodec {
|
||||
state: DecodeState,
|
||||
raw: bool,
|
||||
max_packet_length: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum DecodeState {
|
||||
Head,
|
||||
Data(usize),
|
||||
}
|
||||
|
||||
impl BytesCodec {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: DecodeState::Head,
|
||||
raw: false,
|
||||
max_packet_length: usize::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_raw(&mut self) {
|
||||
self.raw = true;
|
||||
}
|
||||
|
||||
pub fn set_max_packet_length(&mut self, n: usize) {
|
||||
self.max_packet_length = n;
|
||||
}
|
||||
|
||||
fn decode_head(&mut self, src: &mut BytesMut) -> io::Result<Option<usize>> {
|
||||
if src.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let head_len = ((src[0] & 0x3) + 1) as usize;
|
||||
if src.len() < head_len {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut n = src[0] as usize;
|
||||
if head_len > 1 {
|
||||
n |= (src[1] as usize) << 8;
|
||||
}
|
||||
if head_len > 2 {
|
||||
n |= (src[2] as usize) << 16;
|
||||
}
|
||||
if head_len > 3 {
|
||||
n |= (src[3] as usize) << 24;
|
||||
}
|
||||
n >>= 2;
|
||||
if n > self.max_packet_length {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "Too big packet"));
|
||||
}
|
||||
src.advance(head_len);
|
||||
src.reserve(n);
|
||||
return Ok(Some(n));
|
||||
}
|
||||
|
||||
fn decode_data(&self, n: usize, src: &mut BytesMut) -> io::Result<Option<BytesMut>> {
|
||||
if src.len() < n {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(src.split_to(n)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for BytesCodec {
|
||||
type Item = BytesMut;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<BytesMut>, io::Error> {
|
||||
if self.raw {
|
||||
if !src.is_empty() {
|
||||
let len = src.len();
|
||||
return Ok(Some(src.split_to(len)));
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
let n = match self.state {
|
||||
DecodeState::Head => match self.decode_head(src)? {
|
||||
Some(n) => {
|
||||
self.state = DecodeState::Data(n);
|
||||
n
|
||||
}
|
||||
None => return Ok(None),
|
||||
},
|
||||
DecodeState::Data(n) => n,
|
||||
};
|
||||
|
||||
match self.decode_data(n, src)? {
|
||||
Some(data) => {
|
||||
self.state = DecodeState::Head;
|
||||
Ok(Some(data))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder<Bytes> for BytesCodec {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, data: Bytes, buf: &mut BytesMut) -> Result<(), io::Error> {
|
||||
if self.raw {
|
||||
buf.reserve(data.len());
|
||||
buf.put(data);
|
||||
return Ok(());
|
||||
}
|
||||
if data.len() <= 0x3F {
|
||||
buf.put_u8((data.len() << 2) as u8);
|
||||
} else if data.len() <= 0x3FFF {
|
||||
buf.put_u16_le((data.len() << 2) as u16 | 0x1);
|
||||
} else if data.len() <= 0x3FFFFF {
|
||||
let h = (data.len() << 2) as u32 | 0x2;
|
||||
buf.put_u16_le((h & 0xFFFF) as u16);
|
||||
buf.put_u8((h >> 16) as u8);
|
||||
} else if data.len() <= 0x3FFFFFFF {
|
||||
buf.put_u32_le((data.len() << 2) as u32 | 0x3);
|
||||
} else {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidInput, "Overflow"));
|
||||
}
|
||||
buf.extend(data);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_codec1() {
|
||||
let mut codec = BytesCodec::new();
|
||||
let mut buf = BytesMut::new();
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
bytes.resize(0x3F, 1);
|
||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
||||
let buf_saved = buf.clone();
|
||||
assert_eq!(buf.len(), 0x3F + 1);
|
||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
||||
assert_eq!(res.len(), 0x3F);
|
||||
assert_eq!(res[0], 1);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
let mut codec2 = BytesCodec::new();
|
||||
let mut buf2 = BytesMut::new();
|
||||
if let Ok(None) = codec2.decode(&mut buf2) {
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
buf2.extend(&buf_saved[0..1]);
|
||||
if let Ok(None) = codec2.decode(&mut buf2) {
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
buf2.extend(&buf_saved[1..]);
|
||||
if let Ok(Some(res)) = codec2.decode(&mut buf2) {
|
||||
assert_eq!(res.len(), 0x3F);
|
||||
assert_eq!(res[0], 1);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_codec2() {
|
||||
let mut codec = BytesCodec::new();
|
||||
let mut buf = BytesMut::new();
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
assert!(!codec.encode("".into(), &mut buf).is_err());
|
||||
assert_eq!(buf.len(), 1);
|
||||
bytes.resize(0x3F + 1, 2);
|
||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
||||
assert_eq!(buf.len(), 0x3F + 2 + 2);
|
||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
||||
assert_eq!(res.len(), 0);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
||||
assert_eq!(res.len(), 0x3F + 1);
|
||||
assert_eq!(res[0], 2);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_codec3() {
|
||||
let mut codec = BytesCodec::new();
|
||||
let mut buf = BytesMut::new();
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
bytes.resize(0x3F - 1, 3);
|
||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
||||
assert_eq!(buf.len(), 0x3F + 1 - 1);
|
||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
||||
assert_eq!(res.len(), 0x3F - 1);
|
||||
assert_eq!(res[0], 3);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_codec4() {
|
||||
let mut codec = BytesCodec::new();
|
||||
let mut buf = BytesMut::new();
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
bytes.resize(0x3FFF, 4);
|
||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
||||
assert_eq!(buf.len(), 0x3FFF + 2);
|
||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
||||
assert_eq!(res.len(), 0x3FFF);
|
||||
assert_eq!(res[0], 4);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_codec5() {
|
||||
let mut codec = BytesCodec::new();
|
||||
let mut buf = BytesMut::new();
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
bytes.resize(0x3FFFFF, 5);
|
||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
||||
assert_eq!(buf.len(), 0x3FFFFF + 3);
|
||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
||||
assert_eq!(res.len(), 0x3FFFFF);
|
||||
assert_eq!(res[0], 5);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_codec6() {
|
||||
let mut codec = BytesCodec::new();
|
||||
let mut buf = BytesMut::new();
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
bytes.resize(0x3FFFFF + 1, 6);
|
||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
||||
let buf_saved = buf.clone();
|
||||
assert_eq!(buf.len(), 0x3FFFFF + 4 + 1);
|
||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
||||
assert_eq!(res.len(), 0x3FFFFF + 1);
|
||||
assert_eq!(res[0], 6);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
let mut codec2 = BytesCodec::new();
|
||||
let mut buf2 = BytesMut::new();
|
||||
buf2.extend(&buf_saved[0..1]);
|
||||
if let Ok(None) = codec2.decode(&mut buf2) {
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
buf2.extend(&buf_saved[1..6]);
|
||||
if let Ok(None) = codec2.decode(&mut buf2) {
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
buf2.extend(&buf_saved[6..]);
|
||||
if let Ok(Some(res)) = codec2.decode(&mut buf2) {
|
||||
assert_eq!(res.len(), 0x3FFFFF + 1);
|
||||
assert_eq!(res[0], 6);
|
||||
} else {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
rust-rdp/rust-desk/libs/hbb_common/src/compress.rs
Normal file
50
rust-rdp/rust-desk/libs/hbb_common/src/compress.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::cell::RefCell;
|
||||
use zstd::block::{Compressor, Decompressor};
|
||||
|
||||
thread_local! {
|
||||
static COMPRESSOR: RefCell<Compressor> = RefCell::new(Compressor::new());
|
||||
static DECOMPRESSOR: RefCell<Decompressor> = RefCell::new(Decompressor::new());
|
||||
}
|
||||
|
||||
/// The library supports regular compression levels from 1 up to ZSTD_maxCLevel(),
|
||||
/// which is currently 22. Levels >= 20
|
||||
/// Default level is ZSTD_CLEVEL_DEFAULT==3.
|
||||
/// value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT
|
||||
pub fn compress(data: &[u8], level: i32) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
COMPRESSOR.with(|c| {
|
||||
if let Ok(mut c) = c.try_borrow_mut() {
|
||||
match c.compress(data, level) {
|
||||
Ok(res) => out = res,
|
||||
Err(err) => {
|
||||
crate::log::debug!("Failed to compress: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
out
|
||||
}
|
||||
|
||||
pub fn decompress(data: &[u8]) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
DECOMPRESSOR.with(|d| {
|
||||
if let Ok(mut d) = d.try_borrow_mut() {
|
||||
const MAX: usize = 1024 * 1024 * 64;
|
||||
const MIN: usize = 1024 * 1024;
|
||||
let mut n = 30 * data.len();
|
||||
if n > MAX {
|
||||
n = MAX;
|
||||
}
|
||||
if n < MIN {
|
||||
n = MIN;
|
||||
}
|
||||
match d.decompress(data, n) {
|
||||
Ok(res) => out = res,
|
||||
Err(err) => {
|
||||
crate::log::debug!("Failed to decompress: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
out
|
||||
}
|
||||
898
rust-rdp/rust-desk/libs/hbb_common/src/config.rs
Normal file
898
rust-rdp/rust-desk/libs/hbb_common/src/config.rs
Normal file
@@ -0,0 +1,898 @@
|
||||
use crate::log;
|
||||
use directories_next::ProjectDirs;
|
||||
use rand::Rng;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sodiumoxide::crypto::sign;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
pub const APP_NAME: &str = "RustDesk";
|
||||
pub const RENDEZVOUS_TIMEOUT: u64 = 12_000;
|
||||
pub const CONNECT_TIMEOUT: u64 = 18_000;
|
||||
pub const REG_INTERVAL: i64 = 12_000;
|
||||
pub const COMPRESS_LEVEL: i32 = 3;
|
||||
const SERIAL: i32 = 1;
|
||||
// 128x128
|
||||
#[cfg(target_os = "macos")] // 128x128 on 160x160 canvas, then shrink to 128, mac looks better with padding
|
||||
pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAyVBMVEUAAAAAcf8Acf8Acf8Acv8Acf8Acf8Acf8Acf8AcP8Acf8Ab/8AcP8Acf////8AaP/z+f/o8v/k7v/5/v/T5f8AYP/u9v/X6f+hx/+Kuv95pP8Aef/B1/+TwP9xoP8BdP/g6P+Irv9ZmP8Bgf/E3f98q/9sn/+01f+Es/9nm/9Jif8hhv8off/M4P+syP+avP86iP/c7f+xy/9yqf9Om/9hk/9Rjv+60P99tv9fpf88lv8yjf8Tgf8deP+kvP8BiP8NeP8hkP80gP8oj2VLAAAADXRSTlMA7o7qLvnaxZ1FOxYPjH9HWgAABHJJREFUeNrtm+tW4jAQgBfwuu7MtIUWsOUiCCioIIgLiqvr+z/UHq/LJKVkmwTcc/r9E2nzlU4mSTP9lpGRkZGR8VX5cZjfL+yCEXYL+/nDH//U/Pd8DgyTy39Xbv7oIAcWyB0cqbW/sweW2NtRaj8H1sgpGOwUIAH7Bkd7YJW9dXFwAJY5WNP/cmCZQnJvzIN18on5LwfWySXlxEPYAIcad8D6PdiHDbCfIFCADVBIENiFDbCbIACKPPXrZ+cP8E6/0znvP4EymgIEravIRcTxu8HxNSJ60a8W0AYECKrlAN+YwAthCd9wm1Ug6wKzIn5SgRduXfwkqDasCjx0XFzi9PV6zwNcIuhcWBOg+ikySq8C9UD4dEKWBCoOcspvAuLHTo9sCDQiFPHotRM48j8G5gVur1FdAN2uaYEuiz7xFsgEJ2RUoMUakXuBTHHoGxQYOBhHjeUBAefEnMAowFhaLBOKuOemBBbxLRQrH2PBCgMvNCPQGMeevTb9zLrPxz2Mo+QbEaijzPUcOOHMQZkKGRAIPem39+bypREMPTkQW/oCfk866zAkiIFG4yIKRE/aAnfiSd0WrORY6pFdXQEqi9mvAQm0RIOSnoCcZ8vJoz3diCnjRk+g8VP4/fuQDJ2Lxr6WwG0gXs9aTpDzW0vgDBlVUpixR8gYk44AD8FrUKHr8JQJGgIDnoDqoALxmWPQSi9AVVzm8gKUuEPGr/QCvptwJkbSYT/TC4S8C96DGjTj86aHtAI0x2WaBIq0eSYYpRa4EsdWVVwWu9O0Aj6f6dyBMnwEraeOgSYu0wZlauzA47QCbT7DgAQSE+hZWoEBF/BBmWOewNMK3BsSqKUW4MGcWqCSVmDkbvkXGKQOwg6PAUO9oL3xXhA20yaiCjuwYygRVQlUOTWTCf2SuNJTxeFjgaHByGuAIvd8ItdPLTDhS7IuqEE1YSKVOgbayLhSFQhMzYh8hwfBs1r7c505YVIQYEdNoKwxK06MJiyrpUFHiF0NAfCQUVHoiRclIXJIR6C2fqG37pBHvcWpgwzvAtYwkR5UGV2e42UISdBJETl3mg8ouo54Rcnti1/vaT+iuUQBt500Cgo4U10BeHSkk57FB0JjWkKRMWgLUA0lLodtImAQdaMiiri3+gIAPZQoutHNsgKF1aaDMhMyIdBf8Th+Bh8MTjGWCpl5Wv43tDmnF+IUVMrcZgRoiAxhtrloYizNkZaAnF5leglbNhj0wYCAbCDvGb0mP4nib7O7ZlcYQ2m1gPtIZgVgGNNMeaVAaWR+57TrqgtUnm3sHQ+kYeE6fufUubG1ez50FXbPnWgBlgSABmN3TTcsRl2yWkHRrwbiunvk/W2+Mg1hPZplPDeXRbZzStFH15s1QIVd3UImP5z/bHpeeQLvRJ7XLFUffQIlCvqlXETQbgN9/rlYABGosv+Vi9m2Xs639YLGrZd0br+odetlvdsvbN56abfd4vbCzv9Q3v/ygoOV21A4OPpfXvH4Ai+5ZGRkZGRkbJA/t/I0QMzoMiEAAAAASUVORK5CYII=
|
||||
";
|
||||
#[cfg(windows)] // windows, 32x32, bigger very ugly after shrink
|
||||
pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAolBMVEUAAAAAcf8Acf8Acf8AcP8Acf8Acf8Acf8AcP8Acf/////9/v/7/f+gyv9wr/8Ld/8GdP/A3P+nzv+Qwf9mqv8lhv8UfP+Kvv88k/8zjv/x+P/Y6f/U5//J4f/F3/+x1P+pz/+izP+Cuv9Zov9Qnv9FmP8gg//f7f+42P96tv9fpv8ui//1+f/q8//o8v/Q5f+ax/+Zxv+VxP+Fu/9rrf8rif+x1o3FAAAACXRSTlMAv/RPTPKHioRsIqhAAAABNklEQVQ4y4WT6XaCQAyFB7S2GUD2RaxSKGDV7sv7v1on4djEluL9Ncn5biaZk1FKzSwbRmVbM2V0DRNaGD9Maq6sacBS9jRwpUTw1Ww7SEOvkwQDaeVofQeJ1nrXjgD3pTaqCDCg/xs4OBrlwUGTHvxzoHAx69Y9+Hk5oGdAsCNXSoHfEN1JYEme4KcfvO9WAmtMBNx6jIaCgSPGLbAyvKRhAEcrAxAKTSZiACu+gNSbyWwZeKdQKj91yRXWIOWZTMgATun6EtjgczNQaKMEWD0+xJ6B4AnvECVqdKwYoJ50hIPyw25AANkzpsLB00cYOIUEoHU0uVaQVcMxBwlgVZJ3Orz+3ahH6gP2tBgfYzsZmdo1fGIzRx5Irn2WxKaHeJnKtb/4cS5/PbWACd0oo/n/39/4vwGFYSxtSYV4OAAAAABJRU5ErkJggg==
|
||||
";
|
||||
#[cfg(target_os = "linux")] // 128x128 no padding
|
||||
pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAA7VBMVEUAAAAAcf8Acf8Acf8Adf8Acf8Acf8AcP8Acv8AcP8Acf8Acf8Acf8Acv8Acf8Acf8Ab/8AcP8Acf8Acf8Acf/////7/f8Dc/8TfP/1+f/n8v9Hmf/u9v+Uw//Q5f9hp/8Yfv8Qev8Ld/+52P+z1f+s0f81j/8wjP8Hdf/3+/8mh/8fg//x9//h7//H4P9xsP9rrf9oq/8rif/r9P/D3v+92/+Duv9bpP/d7f/U5/9NnP8/lP8jhP/L4v/B3P+OwP9+t/95tf9Rn/8bgf/Z6v+Zx/90sv9lqf85kf+hy/9UoP+Wxf+kzP+dyP+Lvv/H4q8IAAAAFHRSTlMA+u6bB6x5XR4V0+S4i4k5N+a81W8MiAQAAAVcSURBVHjazdvpWtpAGIbhgEutdW3fL2GHsMsiq4KI+66t5384XahF/GbizJAy3j/1Ah5CJhNCxpm1vbryLRrBfxKJrq+sbjtSa5u7WIDdzTVH5PNSBAsSWfrsMJ+iWKDoJ2fW8hIWbGl55vW/YuE2XhUsb8CCr9OCJVix9G//gyWf/o6/KCyJfrbwAfAPYS0CayK/j4mbsGjrV8AXWLTrONuwasdZhVWrzgqsWnG+wap1Jwqrok4EVkUcmKhdVvBaOVnzYEY/oJpMD4mo6ONF/ZSIUsX2FZjQA7xRqUET+y/v2W/Sy59u62DCDMgdJmhqgIk7eqWQBBNWwPhmj147w8QTzTjKVsGEEBBLuzSrhIkivTF8DD/Aa6forQNMHBD/VyXkgHGfuBN5ALln1TADOnESyGCiT8L/1kILqD6Q0BEm9kkofhdSwNUJiV1jQvZ/SnthBNSaJJGZbgGJUnX+gEqCZPpsJ2T2Y/MGVBrE8eOAvCA/X8A4QXLnmEhTgIPqPAG5IQU4fhmkFOT7HAFenwIU8Jd/TUEODQIUtu1eOj/dUD9cknOTpgEDkup3YrOfVStDUomcWcBVisTiNxVw3TPpgCl4RgFFybZ/9iHmn8uS2yYBA8m7qUEu9oOEejH9gHxC+PazCHbcFM8K+gGHJNAs4z2xgnAkVHQDcnG1IzvnCSfvom7AM3EZ9voah4+KXoAvGFJHMSgqEfegF3BBTKoOVfkMMXFfJ8AT7MuXUDeOE9PWCUiKBpKOlmAP1gngH2LChw7vhJgr9YD8Hnt0BxrE27CtHnDJR4AHTX1+KFAP4Ef0LHTxN9HwlAMSbAjmoavKZ8ayakDXYAhwN3wzqgZk2UPvwRjshmeqATeCT09f3mWnEqoBGf4NxAB/moRqADuOtmDiid6KqQVcsQeOYOKW3uqqBRwL5nITj/yrlFpAVrDpTJT5llQLaLMHwshY7UDgvD+VujDC96WWWsBtSAE5FnChFnAeUkDMdAvw88EqTNT5SYXpTlgPaRQM1AIGorkolNnoUS1gJHigCX48SaoF3Asuspg4Mz0U8+FTgIkCG01V09kwBQP8xG5ofD5AXeirkPEJSUlwSVIfP5ykVQNaggvz+k7prTvVgDKF8BnUXP4kqgEe/257E8Ig7EE1gA8g2stBTz7FLxqrB3SIeYaeQ2IG6gE5l2+Cmt5MGOfP4KsGiH8DOYWOoujnDY2ALHF3810goZFOQDVBTFx9Uj7eI6bp6QTgnLjeGGq6KeJuoRUQixN3pDYWyz1Rva8XIL5UPFQZCsmG3gV7R+dieS+Jd3iHLglce7oBuCOhp3zwHLxPQpfQDvBOSKjZqUIml3ZJ6AD6AajFSZJwewWR8ZPsEY26SQDaJOMeZP23w6bTJ6kBjAJQILm9hzqm7otu4G+nhgGxIQUlPLKzL7GhbxqAboMCuN2XXd+lAL0ajAMwclV+FD6jAPEy5ghAlhfwX2FODX445gHKxyN++fs64PUHmDMAbbYN2DlKk2QaScwdgMs4SZxMv4OJJSoIIQBl2Qtk3gk4qiOUANRPJQHB+0A6j5AC4J27QQEZ4eZPAsYBXFk0N/YD7iUrxRBqALxOTzoMC3x8lCFlfkMjuz8iLfk6fzQCQgjg8q3ZEd8RzUVuKelBh96Nzcc3qelL1V+2zfRv1xc56Ino3tpdPT7cd//MspfTrD/7R6p4W4O2qLMObfnyIHvvYcrPtkZjDybW7d/eb32Bg/UlHnYXuXz5CMt8rC90sr7Uy/5iN+vL/ewveLS/5NNKwcbyR1r2a3/h8wdY+v3L2tZC5oUvW2uO1M7qyvp/Xv6/48z4CTxjJEfyjEaMAAAAAElFTkSuQmCC
|
||||
";
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const ORG: &str = "com.carriez";
|
||||
|
||||
type Size = (i32, i32, i32, i32);
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::load()));
|
||||
static ref CONFIG2: Arc<RwLock<Config2>> = Arc::new(RwLock::new(Config2::load()));
|
||||
pub static ref ONLINE: Arc<Mutex<HashMap<String, i64>>> = Default::default();
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref APP_DIR: Arc<RwLock<String>> = Default::default();
|
||||
}
|
||||
const CHARS: &'static [char] = &[
|
||||
'2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
|
||||
'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
];
|
||||
|
||||
pub const RENDEZVOUS_SERVERS: &'static [&'static str] = &[
|
||||
"rs-ny.rustdesk.com",
|
||||
"rs-sg.rustdesk.com",
|
||||
"rs-cn.rustdesk.com",
|
||||
];
|
||||
pub const RENDEZVOUS_PORT: i32 = 21116;
|
||||
pub const RELAY_PORT: i32 = 21117;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum NetworkType {
|
||||
Direct,
|
||||
ProxySocks,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
id: String,
|
||||
#[serde(default)]
|
||||
password: String,
|
||||
#[serde(default)]
|
||||
salt: String,
|
||||
#[serde(default)]
|
||||
key_pair: (Vec<u8>, Vec<u8>), // sk, pk
|
||||
#[serde(default)]
|
||||
key_confirmed: bool,
|
||||
#[serde(default)]
|
||||
keys_confirmed: HashMap<String, bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
|
||||
pub struct Socks5Server {
|
||||
#[serde(default)]
|
||||
pub proxy: String,
|
||||
#[serde(default)]
|
||||
pub username: String,
|
||||
#[serde(default)]
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
// more variable configs
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct Config2 {
|
||||
#[serde(default)]
|
||||
remote_id: String, // latest used one
|
||||
#[serde(default)]
|
||||
size: Size,
|
||||
#[serde(default)]
|
||||
rendezvous_server: String,
|
||||
#[serde(default)]
|
||||
nat_type: i32,
|
||||
#[serde(default)]
|
||||
serial: i32,
|
||||
|
||||
#[serde(default)]
|
||||
socks: Option<Socks5Server>,
|
||||
|
||||
// the other scalar value must before this
|
||||
#[serde(default)]
|
||||
pub options: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct PeerConfig {
|
||||
#[serde(default)]
|
||||
pub password: Vec<u8>,
|
||||
#[serde(default)]
|
||||
pub size: Size,
|
||||
#[serde(default)]
|
||||
pub size_ft: Size,
|
||||
#[serde(default)]
|
||||
pub size_pf: Size,
|
||||
#[serde(default)]
|
||||
pub view_style: String, // original (default), scale
|
||||
#[serde(default)]
|
||||
pub image_quality: String,
|
||||
#[serde(default)]
|
||||
pub custom_image_quality: Vec<i32>,
|
||||
#[serde(default)]
|
||||
pub show_remote_cursor: bool,
|
||||
#[serde(default)]
|
||||
pub lock_after_session_end: bool,
|
||||
#[serde(default)]
|
||||
pub privacy_mode: bool,
|
||||
#[serde(default)]
|
||||
pub port_forwards: Vec<(i32, String, i32)>,
|
||||
#[serde(default)]
|
||||
pub direct_failures: i32,
|
||||
#[serde(default)]
|
||||
pub disable_audio: bool,
|
||||
#[serde(default)]
|
||||
pub disable_clipboard: bool,
|
||||
|
||||
// the other scalar value must before this
|
||||
#[serde(default)]
|
||||
pub options: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub info: PeerInfoSerde,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct PeerInfoSerde {
|
||||
#[serde(default)]
|
||||
pub username: String,
|
||||
#[serde(default)]
|
||||
pub hostname: String,
|
||||
#[serde(default)]
|
||||
pub platform: String,
|
||||
}
|
||||
|
||||
fn patch(path: PathBuf) -> PathBuf {
|
||||
if let Some(_tmp) = path.to_str() {
|
||||
#[cfg(windows)]
|
||||
return _tmp
|
||||
.replace(
|
||||
"system32\\config\\systemprofile",
|
||||
"ServiceProfiles\\LocalService",
|
||||
)
|
||||
.into();
|
||||
#[cfg(target_os = "macos")]
|
||||
return _tmp.replace("Application Support", "Preferences").into();
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if _tmp == "/root" {
|
||||
if let Ok(output) = std::process::Command::new("whoami").output() {
|
||||
let user = String::from_utf8_lossy(&output.stdout)
|
||||
.to_string()
|
||||
.trim()
|
||||
.to_owned();
|
||||
if user != "root" {
|
||||
return format!("/home/{}", user).into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
impl Config2 {
|
||||
fn load() -> Config2 {
|
||||
Config::load_::<Config2>("2")
|
||||
}
|
||||
|
||||
fn reload(&mut self) {
|
||||
let new_config = Config2::load();
|
||||
*self = new_config;
|
||||
}
|
||||
|
||||
fn store(&self) {
|
||||
Config::store_(self, "2");
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn load_<T: serde::Serialize + serde::de::DeserializeOwned + Default + std::fmt::Debug>(
|
||||
suffix: &str,
|
||||
) -> T {
|
||||
let file = Self::file_(suffix);
|
||||
log::debug!("Configuration path: {}", file.display());
|
||||
let cfg = match confy::load_path(&file) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
log::error!("Failed to load config: {}", err);
|
||||
T::default()
|
||||
}
|
||||
};
|
||||
if suffix.is_empty() {
|
||||
log::debug!("{:?}", cfg);
|
||||
}
|
||||
cfg
|
||||
}
|
||||
|
||||
fn reload(&mut self) {
|
||||
let new_config = Config::load();
|
||||
*self = new_config;
|
||||
}
|
||||
|
||||
fn store_<T: serde::Serialize>(config: &T, suffix: &str) {
|
||||
let file = Self::file_(suffix);
|
||||
if let Err(err) = confy::store_path(file, config) {
|
||||
log::error!("Failed to store config: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn load() -> Config {
|
||||
Config::load_::<Config>("")
|
||||
}
|
||||
|
||||
fn store(&self) {
|
||||
Config::store_(self, "");
|
||||
}
|
||||
|
||||
pub fn file() -> PathBuf {
|
||||
Self::file_("")
|
||||
}
|
||||
|
||||
pub fn import(from: &str) {
|
||||
log::info!("import {}", from);
|
||||
// load first to create path
|
||||
Self::load();
|
||||
crate::allow_err!(std::fs::copy(from, Self::file()));
|
||||
crate::allow_err!(std::fs::copy(
|
||||
from.replace(".toml", "2.toml"),
|
||||
Self::file_("2")
|
||||
));
|
||||
}
|
||||
|
||||
pub fn save_tmp() -> String {
|
||||
let _lock = CONFIG.read().unwrap(); // do not use let _, which will be dropped immediately
|
||||
let path = Self::file_("2").to_str().unwrap_or("").to_owned();
|
||||
let path2 = format!("{}_tmp", path);
|
||||
crate::allow_err!(std::fs::copy(&path, &path2));
|
||||
let path = Self::file().to_str().unwrap_or("").to_owned();
|
||||
let path2 = format!("{}_tmp", path);
|
||||
crate::allow_err!(std::fs::copy(&path, &path2));
|
||||
path2
|
||||
}
|
||||
|
||||
fn file_(suffix: &str) -> PathBuf {
|
||||
let name = format!("{}{}", APP_NAME, suffix);
|
||||
Self::path(name).with_extension("toml")
|
||||
}
|
||||
|
||||
pub fn get_home() -> PathBuf {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return Self::path("");
|
||||
if let Some(path) = dirs_next::home_dir() {
|
||||
patch(path)
|
||||
} else if let Ok(path) = std::env::current_dir() {
|
||||
path
|
||||
} else {
|
||||
std::env::temp_dir()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path<P: AsRef<Path>>(p: P) -> PathBuf {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
let mut path: PathBuf = APP_DIR.read().unwrap().clone().into();
|
||||
path.push(p);
|
||||
return path;
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let org = "";
|
||||
#[cfg(target_os = "macos")]
|
||||
let org = ORG;
|
||||
// /var/root for root
|
||||
if let Some(project) = ProjectDirs::from("", org, APP_NAME) {
|
||||
let mut path = patch(project.config_dir().to_path_buf());
|
||||
path.push(p);
|
||||
return path;
|
||||
}
|
||||
return "".into();
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
pub fn log_path() -> PathBuf {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Some(path) = dirs_next::home_dir().as_mut() {
|
||||
path.push(format!("Library/Logs/{}", APP_NAME));
|
||||
return path.clone();
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let mut path = Self::get_home();
|
||||
path.push(format!(".local/share/logs/{}", APP_NAME));
|
||||
std::fs::create_dir_all(&path).ok();
|
||||
return path;
|
||||
}
|
||||
if let Some(path) = Self::path("").parent() {
|
||||
let mut path: PathBuf = path.into();
|
||||
path.push("log");
|
||||
return path;
|
||||
}
|
||||
"".into()
|
||||
}
|
||||
|
||||
pub fn ipc_path(postfix: &str) -> String {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// \\ServerName\pipe\PipeName
|
||||
// where ServerName is either the name of a remote computer or a period, to specify the local computer.
|
||||
// https://docs.microsoft.com/en-us/windows/win32/ipc/pipe-names
|
||||
format!("\\\\.\\pipe\\{}\\query{}", APP_NAME, postfix)
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut path: PathBuf = format!("/tmp/{}", APP_NAME).into();
|
||||
fs::create_dir(&path).ok();
|
||||
fs::set_permissions(&path, fs::Permissions::from_mode(0o0777)).ok();
|
||||
path.push(format!("ipc{}", postfix));
|
||||
path.to_str().unwrap_or("").to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon_path() -> PathBuf {
|
||||
let mut path = Self::path("icons");
|
||||
if fs::create_dir_all(&path).is_err() {
|
||||
path = std::env::temp_dir();
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_any_listen_addr() -> SocketAddr {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
|
||||
}
|
||||
|
||||
pub fn get_rendezvous_server() -> String {
|
||||
let mut rendezvous_server = Self::get_option("custom-rendezvous-server");
|
||||
if rendezvous_server.is_empty() {
|
||||
rendezvous_server = CONFIG2.write().unwrap().rendezvous_server.clone();
|
||||
}
|
||||
if rendezvous_server.is_empty() {
|
||||
rendezvous_server = Self::get_rendezvous_servers()
|
||||
.drain(..)
|
||||
.next()
|
||||
.unwrap_or("".to_owned());
|
||||
}
|
||||
if !rendezvous_server.contains(":") {
|
||||
rendezvous_server = format!("{}:{}", rendezvous_server, RENDEZVOUS_PORT);
|
||||
}
|
||||
rendezvous_server
|
||||
}
|
||||
|
||||
pub fn get_rendezvous_servers() -> Vec<String> {
|
||||
let s = Self::get_option("custom-rendezvous-server");
|
||||
if !s.is_empty() {
|
||||
return vec![s];
|
||||
}
|
||||
let serial_obsolute = CONFIG2.read().unwrap().serial > SERIAL;
|
||||
if serial_obsolute {
|
||||
let ss: Vec<String> = Self::get_option("rendezvous-servers")
|
||||
.split(",")
|
||||
.filter(|x| x.contains("."))
|
||||
.map(|x| x.to_owned())
|
||||
.collect();
|
||||
if !ss.is_empty() {
|
||||
return ss;
|
||||
}
|
||||
}
|
||||
return RENDEZVOUS_SERVERS.iter().map(|x| x.to_string()).collect();
|
||||
}
|
||||
|
||||
pub fn reset_online() {
|
||||
*ONLINE.lock().unwrap() = Default::default();
|
||||
}
|
||||
|
||||
pub fn update_latency(host: &str, latency: i64) {
|
||||
ONLINE.lock().unwrap().insert(host.to_owned(), latency);
|
||||
let mut host = "".to_owned();
|
||||
let mut delay = i64::MAX;
|
||||
for (tmp_host, tmp_delay) in ONLINE.lock().unwrap().iter() {
|
||||
if tmp_delay > &0 && tmp_delay < &delay {
|
||||
delay = tmp_delay.clone();
|
||||
host = tmp_host.to_string();
|
||||
}
|
||||
}
|
||||
if !host.is_empty() {
|
||||
let mut config = CONFIG2.write().unwrap();
|
||||
if host != config.rendezvous_server {
|
||||
log::debug!("Update rendezvous_server in config to {}", host);
|
||||
log::debug!("{:?}", *ONLINE.lock().unwrap());
|
||||
config.rendezvous_server = host;
|
||||
config.store();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_id(id: &str) {
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
if id == config.id {
|
||||
return;
|
||||
}
|
||||
config.id = id.into();
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn set_nat_type(nat_type: i32) {
|
||||
let mut config = CONFIG2.write().unwrap();
|
||||
if nat_type == config.nat_type {
|
||||
return;
|
||||
}
|
||||
config.nat_type = nat_type;
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_nat_type() -> i32 {
|
||||
CONFIG2.read().unwrap().nat_type
|
||||
}
|
||||
|
||||
pub fn set_serial(serial: i32) {
|
||||
let mut config = CONFIG2.write().unwrap();
|
||||
if serial == config.serial {
|
||||
return;
|
||||
}
|
||||
config.serial = serial;
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_serial() -> i32 {
|
||||
std::cmp::max(CONFIG2.read().unwrap().serial, SERIAL)
|
||||
}
|
||||
|
||||
fn get_auto_id() -> Option<String> {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
return None;
|
||||
let mut id = 0u32;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(Some(ma)) = mac_address::get_mac_address() {
|
||||
for x in &ma.bytes()[2..] {
|
||||
id = (id << 8) | (*x as u32);
|
||||
}
|
||||
id = id & 0x1FFFFFFF;
|
||||
Some(id.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_auto_password() -> String {
|
||||
let mut rng = rand::thread_rng();
|
||||
(0..6)
|
||||
.map(|_| CHARS[rng.gen::<usize>() % CHARS.len()])
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_key_confirmed() -> bool {
|
||||
CONFIG.read().unwrap().key_confirmed
|
||||
}
|
||||
|
||||
pub fn set_key_confirmed(v: bool) {
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
if config.key_confirmed == v {
|
||||
return;
|
||||
}
|
||||
config.key_confirmed = v;
|
||||
if !v {
|
||||
config.keys_confirmed = Default::default();
|
||||
}
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_host_key_confirmed(host: &str) -> bool {
|
||||
if let Some(true) = CONFIG.read().unwrap().keys_confirmed.get(host) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_host_key_confirmed(host: &str, v: bool) {
|
||||
if Self::get_host_key_confirmed(host) == v {
|
||||
return;
|
||||
}
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
config.keys_confirmed.insert(host.to_owned(), v);
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn set_key_pair(pair: (Vec<u8>, Vec<u8>)) {
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
if config.key_pair == pair {
|
||||
return;
|
||||
}
|
||||
config.key_pair = pair;
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_key_pair() -> (Vec<u8>, Vec<u8>) {
|
||||
// lock here to make sure no gen_keypair more than once
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
if config.key_pair.0.is_empty() {
|
||||
let (pk, sk) = sign::gen_keypair();
|
||||
config.key_pair = (sk.0.to_vec(), pk.0.into());
|
||||
config.store();
|
||||
}
|
||||
config.key_pair.clone()
|
||||
}
|
||||
|
||||
pub fn get_id() -> String {
|
||||
let mut id = CONFIG.read().unwrap().id.clone();
|
||||
if id.is_empty() {
|
||||
if let Some(tmp) = Config::get_auto_id() {
|
||||
id = tmp;
|
||||
Config::set_id(&id);
|
||||
}
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get_options() -> HashMap<String, String> {
|
||||
CONFIG2.read().unwrap().options.clone()
|
||||
}
|
||||
|
||||
pub fn set_options(v: HashMap<String, String>) {
|
||||
let mut config = CONFIG2.write().unwrap();
|
||||
if config.options == v {
|
||||
return;
|
||||
}
|
||||
config.options = v;
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_option(k: &str) -> String {
|
||||
if let Some(v) = CONFIG2.read().unwrap().options.get(k) {
|
||||
v.clone()
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_option(k: String, v: String) {
|
||||
let mut config = CONFIG2.write().unwrap();
|
||||
let v2 = if v.is_empty() { None } else { Some(&v) };
|
||||
if v2 != config.options.get(&k) {
|
||||
if v2.is_none() {
|
||||
config.options.remove(&k);
|
||||
} else {
|
||||
config.options.insert(k, v);
|
||||
}
|
||||
config.store();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_id() {
|
||||
// to-do: how about if one ip register a lot of ids?
|
||||
let id = Self::get_id();
|
||||
let mut rng = rand::thread_rng();
|
||||
let new_id = rng.gen_range(1_000_000_000..2_000_000_000).to_string();
|
||||
Config::set_id(&new_id);
|
||||
log::info!("id updated from {} to {}", id, new_id);
|
||||
}
|
||||
|
||||
pub fn set_password(password: &str) {
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
if password == config.password {
|
||||
return;
|
||||
}
|
||||
config.password = password.into();
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_password() -> String {
|
||||
let mut password = CONFIG.read().unwrap().password.clone();
|
||||
if password.is_empty() {
|
||||
password = Config::get_auto_password();
|
||||
Config::set_password(&password);
|
||||
}
|
||||
password
|
||||
}
|
||||
|
||||
pub fn set_salt(salt: &str) {
|
||||
let mut config = CONFIG.write().unwrap();
|
||||
if salt == config.salt {
|
||||
return;
|
||||
}
|
||||
config.salt = salt.into();
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_salt() -> String {
|
||||
let mut salt = CONFIG.read().unwrap().salt.clone();
|
||||
if salt.is_empty() {
|
||||
salt = Config::get_auto_password();
|
||||
Config::set_salt(&salt);
|
||||
}
|
||||
salt
|
||||
}
|
||||
|
||||
pub fn get_size() -> Size {
|
||||
CONFIG2.read().unwrap().size
|
||||
}
|
||||
|
||||
pub fn set_size(x: i32, y: i32, w: i32, h: i32) {
|
||||
let mut config = CONFIG2.write().unwrap();
|
||||
let size = (x, y, w, h);
|
||||
if size == config.size || size.2 < 300 || size.3 < 300 {
|
||||
return;
|
||||
}
|
||||
config.size = size;
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn set_remote_id(remote_id: &str) {
|
||||
let mut config = CONFIG2.write().unwrap();
|
||||
if remote_id == config.remote_id {
|
||||
return;
|
||||
}
|
||||
config.remote_id = remote_id.into();
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_remote_id() -> String {
|
||||
CONFIG2.read().unwrap().remote_id.clone()
|
||||
}
|
||||
|
||||
pub fn set_socks(socks: Option<Socks5Server>) {
|
||||
let mut config = CONFIG2.write().unwrap();
|
||||
if config.socks == socks {
|
||||
return;
|
||||
}
|
||||
config.socks = socks;
|
||||
config.store();
|
||||
}
|
||||
|
||||
pub fn get_socks() -> Option<Socks5Server> {
|
||||
CONFIG2.read().unwrap().socks.clone()
|
||||
}
|
||||
|
||||
pub fn get_network_type() -> NetworkType {
|
||||
match &CONFIG2.read().unwrap().socks {
|
||||
None => NetworkType::Direct,
|
||||
Some(_) => NetworkType::ProxySocks,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_config_to_user<P: AsRef<Path>>(target_username: String, to_dir: P) -> bool {
|
||||
let config1_root_file_path = Config::file_("");
|
||||
let config1_filename = config1_root_file_path.file_name();
|
||||
|
||||
let config2_root_file_path = Config::file_("2");
|
||||
let config2_filename = config2_root_file_path.file_name();
|
||||
|
||||
let config1_to_file_path = to_dir
|
||||
.as_ref()
|
||||
.join(PathBuf::from(&config1_filename.unwrap()));
|
||||
let config2_to_file_path = to_dir
|
||||
.as_ref()
|
||||
.join(PathBuf::from(&config2_filename.unwrap()));
|
||||
|
||||
log::info!(
|
||||
"config1_root_path:{}",
|
||||
&config1_root_file_path.as_path().to_str().unwrap()
|
||||
);
|
||||
log::info!(
|
||||
"config2_root_path:{}",
|
||||
&config2_root_file_path.as_path().to_str().unwrap()
|
||||
);
|
||||
log::info!(
|
||||
"config1_to_path:{}",
|
||||
&config1_to_file_path.as_path().to_str().unwrap()
|
||||
);
|
||||
log::info!(
|
||||
"config2_to_path:{}",
|
||||
&config2_to_file_path.as_path().to_str().unwrap()
|
||||
);
|
||||
|
||||
match std::fs::copy(&config1_root_file_path, &config1_to_file_path) {
|
||||
Err(e) => log::error!(
|
||||
"copy config {} to user failed: {}",
|
||||
config1_filename.unwrap().to_str().unwrap(),
|
||||
e
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match std::fs::copy(&config2_root_file_path, &config2_to_file_path) {
|
||||
Err(e) => log::error!(
|
||||
"copy config {} to user failed: {}",
|
||||
config2_filename.unwrap().to_str().unwrap(),
|
||||
e
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let success = std::process::Command::new("chown")
|
||||
.arg(&target_username.to_string())
|
||||
.arg(&config1_to_file_path.to_str().unwrap().to_string())
|
||||
.arg(&config2_to_file_path.to_str().unwrap().to_string())
|
||||
.spawn()
|
||||
.is_ok();
|
||||
|
||||
if success {
|
||||
CONFIG.write().unwrap().reload();
|
||||
CONFIG2.write().unwrap().reload();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
pub fn sync_config_to_root<P: AsRef<Path>>(from_file_path: P) -> bool {
|
||||
if let Some(filename) = from_file_path.as_ref().file_name() {
|
||||
let to = Config::path(filename);
|
||||
return match std::fs::copy(from_file_path, &to) {
|
||||
Ok(count) => {
|
||||
if count > 0 {
|
||||
return std::process::Command::new("chown")
|
||||
.arg("root")
|
||||
.arg(&to.to_str().unwrap().to_string())
|
||||
.spawn()
|
||||
.is_ok();
|
||||
}
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("sync_config_to_root failed: {}", e);
|
||||
false
|
||||
}
|
||||
};
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
const PEERS: &str = "peers";
|
||||
|
||||
impl PeerConfig {
|
||||
pub fn load(id: &str) -> PeerConfig {
|
||||
let _ = CONFIG.read().unwrap(); // for lock
|
||||
match confy::load_path(&Self::path(id)) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
log::error!("Failed to load config: {}", err);
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store(&self, id: &str) {
|
||||
let _ = CONFIG.read().unwrap(); // for lock
|
||||
if let Err(err) = confy::store_path(Self::path(id), self) {
|
||||
log::error!("Failed to store config: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(id: &str) {
|
||||
fs::remove_file(&Self::path(id)).ok();
|
||||
}
|
||||
|
||||
fn path(id: &str) -> PathBuf {
|
||||
let path: PathBuf = [PEERS, id].iter().collect();
|
||||
Config::path(path).with_extension("toml")
|
||||
}
|
||||
|
||||
pub fn peers() -> Vec<(String, SystemTime, PeerConfig)> {
|
||||
if let Ok(peers) = Config::path(PEERS).read_dir() {
|
||||
if let Ok(peers) = peers
|
||||
.map(|res| res.map(|e| e.path()))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
{
|
||||
let mut peers: Vec<_> = peers
|
||||
.iter()
|
||||
.filter(|p| {
|
||||
p.is_file()
|
||||
&& p.extension().map(|p| p.to_str().unwrap_or("")) == Some("toml")
|
||||
})
|
||||
.map(|p| {
|
||||
let t = fs::metadata(p)
|
||||
.map(|m| m.modified().unwrap_or(SystemTime::UNIX_EPOCH))
|
||||
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||
let id = p
|
||||
.file_stem()
|
||||
.map(|p| p.to_str().unwrap_or(""))
|
||||
.unwrap_or("")
|
||||
.to_owned();
|
||||
let c = PeerConfig::load(&id);
|
||||
if c.info.platform.is_empty() {
|
||||
fs::remove_file(&p).ok();
|
||||
}
|
||||
(id, t, c)
|
||||
})
|
||||
.filter(|p| !p.2.info.platform.is_empty())
|
||||
.collect();
|
||||
peers.sort_unstable_by(|a, b| b.1.cmp(&a.1));
|
||||
return peers;
|
||||
}
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct Fav {
|
||||
#[serde(default)]
|
||||
pub peers: Vec<String>,
|
||||
}
|
||||
|
||||
impl Fav {
|
||||
pub fn load() -> Fav {
|
||||
let _ = CONFIG.read().unwrap(); // for lock
|
||||
match confy::load_path(&Config::file_("_fav")) {
|
||||
Ok(fav) => fav,
|
||||
Err(err) => {
|
||||
log::error!("Failed to load fav: {}", err);
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store(peers: Vec<String>) {
|
||||
let f = Fav { peers };
|
||||
if let Err(err) = confy::store_path(Config::file_("_fav"), f) {
|
||||
log::error!("Failed to store fav: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct LanPeers {
|
||||
#[serde(default)]
|
||||
pub peers: String,
|
||||
}
|
||||
|
||||
impl LanPeers {
|
||||
pub fn load() -> LanPeers {
|
||||
let _ = CONFIG.read().unwrap(); // for lock
|
||||
match confy::load_path(&Config::file_("_lan_peers")) {
|
||||
Ok(peers) => peers,
|
||||
Err(err) => {
|
||||
log::error!("Failed to load lan peers: {}", err);
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store(peers: String) {
|
||||
let f = LanPeers { peers };
|
||||
if let Err(err) = confy::store_path(Config::file_("_lan_peers"), f) {
|
||||
log::error!("Failed to store lan peers: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modify_time() -> crate::ResultType<u64> {
|
||||
let p = Config::file_("_lan_peers");
|
||||
Ok(fs::metadata(p)?
|
||||
.modified()?
|
||||
.duration_since(SystemTime::UNIX_EPOCH)?
|
||||
.as_millis() as _)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_serialize() {
|
||||
let cfg: Config = Default::default();
|
||||
let res = toml::to_string_pretty(&cfg);
|
||||
assert!(res.is_ok());
|
||||
let cfg: PeerConfig = Default::default();
|
||||
let res = toml::to_string_pretty(&cfg);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
}
|
||||
554
rust-rdp/rust-desk/libs/hbb_common/src/fs.rs
Normal file
554
rust-rdp/rust-desk/libs/hbb_common/src/fs.rs
Normal file
@@ -0,0 +1,554 @@
|
||||
use crate::{bail, message_proto::*, ResultType};
|
||||
use std::path::{Path, PathBuf};
|
||||
// https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html
|
||||
use crate::{
|
||||
compress::{compress, decompress},
|
||||
config::{Config, COMPRESS_LEVEL},
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::prelude::*;
|
||||
use tokio::{fs::File, io::*};
|
||||
|
||||
pub fn read_dir(path: &PathBuf, include_hidden: bool) -> ResultType<FileDirectory> {
|
||||
let mut dir = FileDirectory {
|
||||
path: get_string(&path),
|
||||
..Default::default()
|
||||
};
|
||||
#[cfg(windows)]
|
||||
if "/" == &get_string(&path) {
|
||||
let drives = unsafe { winapi::um::fileapi::GetLogicalDrives() };
|
||||
for i in 0..32 {
|
||||
if drives & (1 << i) != 0 {
|
||||
let name = format!(
|
||||
"{}:",
|
||||
std::char::from_u32('A' as u32 + i as u32).unwrap_or('A')
|
||||
);
|
||||
dir.entries.push(FileEntry {
|
||||
name,
|
||||
entry_type: FileType::DirDrive.into(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
return Ok(dir);
|
||||
}
|
||||
for entry in path.read_dir()? {
|
||||
if let Ok(entry) = entry {
|
||||
let p = entry.path();
|
||||
let name = p
|
||||
.file_name()
|
||||
.map(|p| p.to_str().unwrap_or(""))
|
||||
.unwrap_or("")
|
||||
.to_owned();
|
||||
if name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut is_hidden = false;
|
||||
let meta;
|
||||
if let Ok(tmp) = std::fs::symlink_metadata(&p) {
|
||||
meta = tmp;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
// docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
||||
#[cfg(windows)]
|
||||
if meta.file_attributes() & 0x2 != 0 {
|
||||
is_hidden = true;
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
if name.find('.').unwrap_or(usize::MAX) == 0 {
|
||||
is_hidden = true;
|
||||
}
|
||||
if is_hidden && !include_hidden {
|
||||
continue;
|
||||
}
|
||||
let (entry_type, size) = {
|
||||
if p.is_dir() {
|
||||
if meta.file_type().is_symlink() {
|
||||
(FileType::DirLink.into(), 0)
|
||||
} else {
|
||||
(FileType::Dir.into(), 0)
|
||||
}
|
||||
} else {
|
||||
if meta.file_type().is_symlink() {
|
||||
(FileType::FileLink.into(), 0)
|
||||
} else {
|
||||
(FileType::File.into(), meta.len())
|
||||
}
|
||||
}
|
||||
};
|
||||
let modified_time = meta
|
||||
.modified()
|
||||
.map(|x| {
|
||||
x.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
.map(|x| x.as_secs())
|
||||
.unwrap_or(0)
|
||||
})
|
||||
.unwrap_or(0) as u64;
|
||||
dir.entries.push(FileEntry {
|
||||
name: get_file_name(&p),
|
||||
entry_type,
|
||||
is_hidden,
|
||||
size,
|
||||
modified_time,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_file_name(p: &PathBuf) -> String {
|
||||
p.file_name()
|
||||
.map(|p| p.to_str().unwrap_or(""))
|
||||
.unwrap_or("")
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_string(path: &PathBuf) -> String {
|
||||
path.to_str().unwrap_or("").to_owned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_path(path: &str) -> PathBuf {
|
||||
Path::new(path).to_path_buf()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_home_as_string() -> String {
|
||||
get_string(&Config::get_home())
|
||||
}
|
||||
|
||||
fn read_dir_recursive(
|
||||
path: &PathBuf,
|
||||
prefix: &PathBuf,
|
||||
include_hidden: bool,
|
||||
) -> ResultType<Vec<FileEntry>> {
|
||||
let mut files = Vec::new();
|
||||
if path.is_dir() {
|
||||
// to-do: symbol link handling, cp the link rather than the content
|
||||
// to-do: file mode, for unix
|
||||
let fd = read_dir(&path, include_hidden)?;
|
||||
for entry in fd.entries.iter() {
|
||||
match entry.entry_type.enum_value() {
|
||||
Ok(FileType::File) => {
|
||||
let mut entry = entry.clone();
|
||||
entry.name = get_string(&prefix.join(entry.name));
|
||||
files.push(entry);
|
||||
}
|
||||
Ok(FileType::Dir) => {
|
||||
if let Ok(mut tmp) = read_dir_recursive(
|
||||
&path.join(&entry.name),
|
||||
&prefix.join(&entry.name),
|
||||
include_hidden,
|
||||
) {
|
||||
for entry in tmp.drain(0..) {
|
||||
files.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(files)
|
||||
} else if path.is_file() {
|
||||
let (size, modified_time) = if let Ok(meta) = std::fs::metadata(&path) {
|
||||
(
|
||||
meta.len(),
|
||||
meta.modified()
|
||||
.map(|x| {
|
||||
x.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
.map(|x| x.as_secs())
|
||||
.unwrap_or(0)
|
||||
})
|
||||
.unwrap_or(0) as u64,
|
||||
)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
files.push(FileEntry {
|
||||
entry_type: FileType::File.into(),
|
||||
size,
|
||||
modified_time,
|
||||
..Default::default()
|
||||
});
|
||||
Ok(files)
|
||||
} else {
|
||||
bail!("Not exists");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_recursive_files(path: &str, include_hidden: bool) -> ResultType<Vec<FileEntry>> {
|
||||
read_dir_recursive(&get_path(path), &get_path(""), include_hidden)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TransferJob {
|
||||
id: i32,
|
||||
path: PathBuf,
|
||||
files: Vec<FileEntry>,
|
||||
file_num: i32,
|
||||
file: Option<File>,
|
||||
total_size: u64,
|
||||
finished_size: u64,
|
||||
transferred: u64,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_ext(name: &str) -> &str {
|
||||
if let Some(i) = name.rfind(".") {
|
||||
return &name[i + 1..];
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_compressed_file(name: &str) -> bool {
|
||||
let ext = get_ext(name);
|
||||
ext == "xz"
|
||||
|| ext == "gz"
|
||||
|| ext == "zip"
|
||||
|| ext == "7z"
|
||||
|| ext == "rar"
|
||||
|| ext == "bz2"
|
||||
|| ext == "tgz"
|
||||
|| ext == "png"
|
||||
|| ext == "jpg"
|
||||
}
|
||||
|
||||
impl TransferJob {
|
||||
pub fn new_write(id: i32, path: String, files: Vec<FileEntry>) -> Self {
|
||||
let total_size = files.iter().map(|x| x.size as u64).sum();
|
||||
Self {
|
||||
id,
|
||||
path: get_path(&path),
|
||||
files,
|
||||
total_size,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_read(id: i32, path: String, include_hidden: bool) -> ResultType<Self> {
|
||||
let files = get_recursive_files(&path, include_hidden)?;
|
||||
let total_size = files.iter().map(|x| x.size as u64).sum();
|
||||
Ok(Self {
|
||||
id,
|
||||
path: get_path(&path),
|
||||
files,
|
||||
total_size,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn files(&self) -> &Vec<FileEntry> {
|
||||
&self.files
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_files(&mut self, files: Vec<FileEntry>) {
|
||||
self.files = files;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> i32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn total_size(&self) -> u64 {
|
||||
self.total_size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn finished_size(&self) -> u64 {
|
||||
self.finished_size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transferred(&self) -> u64 {
|
||||
self.transferred
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn file_num(&self) -> i32 {
|
||||
self.file_num
|
||||
}
|
||||
|
||||
pub fn modify_time(&self) {
|
||||
let file_num = self.file_num as usize;
|
||||
if file_num < self.files.len() {
|
||||
let entry = &self.files[file_num];
|
||||
let path = self.join(&entry.name);
|
||||
let download_path = format!("{}.download", get_string(&path));
|
||||
std::fs::rename(&download_path, &path).ok();
|
||||
filetime::set_file_mtime(
|
||||
&path,
|
||||
filetime::FileTime::from_unix_time(entry.modified_time as _, 0),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_download_file(&self) {
|
||||
let file_num = self.file_num as usize;
|
||||
if file_num < self.files.len() {
|
||||
let entry = &self.files[file_num];
|
||||
let path = self.join(&entry.name);
|
||||
let download_path = format!("{}.download", get_string(&path));
|
||||
std::fs::remove_file(&download_path).ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn write(&mut self, block: FileTransferBlock) -> ResultType<()> {
|
||||
if block.id != self.id {
|
||||
bail!("Wrong id");
|
||||
}
|
||||
let file_num = block.file_num as usize;
|
||||
if file_num >= self.files.len() {
|
||||
bail!("Wrong file number");
|
||||
}
|
||||
if file_num != self.file_num as usize || self.file.is_none() {
|
||||
self.modify_time();
|
||||
if let Some(file) = self.file.as_mut() {
|
||||
file.sync_all().await?;
|
||||
}
|
||||
self.file_num = block.file_num;
|
||||
let entry = &self.files[file_num];
|
||||
let path = self.join(&entry.name);
|
||||
if let Some(p) = path.parent() {
|
||||
std::fs::create_dir_all(p).ok();
|
||||
}
|
||||
let path = format!("{}.download", get_string(&path));
|
||||
self.file = Some(File::create(&path).await?);
|
||||
}
|
||||
if block.compressed {
|
||||
let tmp = decompress(&block.data);
|
||||
self.file.as_mut().unwrap().write_all(&tmp).await?;
|
||||
self.finished_size += tmp.len() as u64;
|
||||
} else {
|
||||
self.file.as_mut().unwrap().write_all(&block.data).await?;
|
||||
self.finished_size += block.data.len() as u64;
|
||||
}
|
||||
self.transferred += block.data.len() as u64;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn join(&self, name: &str) -> PathBuf {
|
||||
if name.is_empty() {
|
||||
self.path.clone()
|
||||
} else {
|
||||
self.path.join(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read(&mut self) -> ResultType<Option<FileTransferBlock>> {
|
||||
let file_num = self.file_num as usize;
|
||||
if file_num >= self.files.len() {
|
||||
self.file.take();
|
||||
return Ok(None);
|
||||
}
|
||||
let name = &self.files[file_num].name;
|
||||
if self.file.is_none() {
|
||||
match File::open(self.join(&name)).await {
|
||||
Ok(file) => {
|
||||
self.file = Some(file);
|
||||
}
|
||||
Err(err) => {
|
||||
self.file_num += 1;
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
const BUF_SIZE: usize = 128 * 1024;
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(BUF_SIZE);
|
||||
unsafe {
|
||||
buf.set_len(BUF_SIZE);
|
||||
}
|
||||
let mut compressed = false;
|
||||
let mut offset: usize = 0;
|
||||
loop {
|
||||
match self.file.as_mut().unwrap().read(&mut buf[offset..]).await {
|
||||
Err(err) => {
|
||||
self.file_num += 1;
|
||||
self.file = None;
|
||||
return Err(err.into());
|
||||
}
|
||||
Ok(n) => {
|
||||
offset += n;
|
||||
if n == 0 || offset == BUF_SIZE {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { buf.set_len(offset) };
|
||||
if offset == 0 {
|
||||
self.file_num += 1;
|
||||
self.file = None;
|
||||
} else {
|
||||
self.finished_size += offset as u64;
|
||||
if !is_compressed_file(name) {
|
||||
let tmp = compress(&buf, COMPRESS_LEVEL);
|
||||
if tmp.len() < buf.len() {
|
||||
buf = tmp;
|
||||
compressed = true;
|
||||
}
|
||||
}
|
||||
self.transferred += buf.len() as u64;
|
||||
}
|
||||
Ok(Some(FileTransferBlock {
|
||||
id: self.id,
|
||||
file_num: file_num as _,
|
||||
data: buf.into(),
|
||||
compressed,
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_error<T: std::string::ToString>(id: i32, err: T, file_num: i32) -> Message {
|
||||
let mut resp = FileResponse::new();
|
||||
resp.set_error(FileTransferError {
|
||||
id,
|
||||
error: err.to_string(),
|
||||
file_num,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_file_response(resp);
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_dir(id: i32, files: Vec<FileEntry>) -> Message {
|
||||
let mut resp = FileResponse::new();
|
||||
resp.set_dir(FileDirectory {
|
||||
id,
|
||||
entries: files.into(),
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_file_response(resp);
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_block(block: FileTransferBlock) -> Message {
|
||||
let mut resp = FileResponse::new();
|
||||
resp.set_block(block);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_file_response(resp);
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_receive(id: i32, path: String, files: Vec<FileEntry>) -> Message {
|
||||
let mut action = FileAction::new();
|
||||
action.set_receive(FileTransferReceiveRequest {
|
||||
id,
|
||||
path,
|
||||
files: files.into(),
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_file_action(action);
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_send(id: i32, path: String, include_hidden: bool) -> Message {
|
||||
let mut action = FileAction::new();
|
||||
action.set_send(FileTransferSendRequest {
|
||||
id,
|
||||
path,
|
||||
include_hidden,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_file_action(action);
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_done(id: i32, file_num: i32) -> Message {
|
||||
let mut resp = FileResponse::new();
|
||||
resp.set_done(FileTransferDone {
|
||||
id,
|
||||
file_num,
|
||||
..Default::default()
|
||||
});
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_file_response(resp);
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove_job(id: i32, jobs: &mut Vec<TransferJob>) {
|
||||
*jobs = jobs.drain(0..).filter(|x| x.id() != id).collect();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_job(id: i32, jobs: &mut Vec<TransferJob>) -> Option<&mut TransferJob> {
|
||||
jobs.iter_mut().filter(|x| x.id() == id).next()
|
||||
}
|
||||
|
||||
pub async fn handle_read_jobs(
|
||||
jobs: &mut Vec<TransferJob>,
|
||||
stream: &mut crate::Stream,
|
||||
) -> ResultType<()> {
|
||||
let mut finished = Vec::new();
|
||||
for job in jobs.iter_mut() {
|
||||
match job.read().await {
|
||||
Err(err) => {
|
||||
stream
|
||||
.send(&new_error(job.id(), err, job.file_num()))
|
||||
.await?;
|
||||
}
|
||||
Ok(Some(block)) => {
|
||||
stream.send(&new_block(block)).await?;
|
||||
}
|
||||
Ok(None) => {
|
||||
finished.push(job.id());
|
||||
stream.send(&new_done(job.id(), job.file_num())).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in finished {
|
||||
remove_job(id, jobs);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_all_empty_dir(path: &PathBuf) -> ResultType<()> {
|
||||
let fd = read_dir(path, true)?;
|
||||
for entry in fd.entries.iter() {
|
||||
match entry.entry_type.enum_value() {
|
||||
Ok(FileType::Dir) => {
|
||||
remove_all_empty_dir(&path.join(&entry.name)).ok();
|
||||
}
|
||||
Ok(FileType::DirLink) | Ok(FileType::FileLink) => {
|
||||
std::fs::remove_file(&path.join(&entry.name)).ok();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
std::fs::remove_dir(path).ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove_file(file: &str) -> ResultType<()> {
|
||||
std::fs::remove_file(get_path(file))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_dir(dir: &str) -> ResultType<()> {
|
||||
std::fs::create_dir_all(get_path(dir))?;
|
||||
Ok(())
|
||||
}
|
||||
197
rust-rdp/rust-desk/libs/hbb_common/src/lib.rs
Normal file
197
rust-rdp/rust-desk/libs/hbb_common/src/lib.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
pub mod compress;
|
||||
#[path = "./protos/message.rs"]
|
||||
pub mod message_proto;
|
||||
#[path = "./protos/rendezvous.rs"]
|
||||
pub mod rendezvous_proto;
|
||||
pub use bytes;
|
||||
pub use futures;
|
||||
pub use protobuf;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufRead},
|
||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
path::Path,
|
||||
time::{self, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
pub use tokio;
|
||||
pub use tokio_util;
|
||||
pub mod socket_client;
|
||||
pub mod tcp;
|
||||
pub mod udp;
|
||||
pub use env_logger;
|
||||
pub use log;
|
||||
pub mod bytes_codec;
|
||||
#[cfg(feature = "quic")]
|
||||
pub mod quic;
|
||||
pub use anyhow::{self, bail};
|
||||
pub use futures_util;
|
||||
pub mod config;
|
||||
pub mod fs;
|
||||
pub use rand;
|
||||
pub use regex;
|
||||
pub use sodiumoxide;
|
||||
pub use tokio_socks;
|
||||
pub use tokio_socks::IntoTargetAddr;
|
||||
pub use tokio_socks::TargetAddr;
|
||||
pub use mac_address;
|
||||
|
||||
#[cfg(feature = "quic")]
|
||||
pub type Stream = quic::Connection;
|
||||
#[cfg(not(feature = "quic"))]
|
||||
pub type Stream = tcp::FramedStream;
|
||||
|
||||
#[inline]
|
||||
pub async fn sleep(sec: f32) {
|
||||
tokio::time::sleep(time::Duration::from_secs_f32(sec)).await;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! allow_err {
|
||||
($e:expr) => {
|
||||
if let Err(err) = $e {
|
||||
log::debug!(
|
||||
"{:?}, {}:{}:{}:{}",
|
||||
err,
|
||||
module_path!(),
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
} else {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn timeout<T: std::future::Future>(ms: u64, future: T) -> tokio::time::Timeout<T> {
|
||||
tokio::time::timeout(std::time::Duration::from_millis(ms), future)
|
||||
}
|
||||
|
||||
pub type ResultType<F, E = anyhow::Error> = anyhow::Result<F, E>;
|
||||
|
||||
/// Certain router and firewalls scan the packet and if they
|
||||
/// find an IP address belonging to their pool that they use to do the NAT mapping/translation, so here we mangle the ip address
|
||||
|
||||
pub struct AddrMangle();
|
||||
|
||||
impl AddrMangle {
|
||||
pub fn encode(addr: SocketAddr) -> Vec<u8> {
|
||||
match addr {
|
||||
SocketAddr::V4(addr_v4) => {
|
||||
let tm = (SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_micros() as u32) as u128;
|
||||
let ip = u32::from_le_bytes(addr_v4.ip().octets()) as u128;
|
||||
let port = addr.port() as u128;
|
||||
let v = ((ip + tm) << 49) | (tm << 17) | (port + (tm & 0xFFFF));
|
||||
let bytes = v.to_le_bytes();
|
||||
let mut n_padding = 0;
|
||||
for i in bytes.iter().rev() {
|
||||
if i == &0u8 {
|
||||
n_padding += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
bytes[..(16 - n_padding)].to_vec()
|
||||
}
|
||||
_ => {
|
||||
panic!("Only support ipv4");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode(bytes: &[u8]) -> SocketAddr {
|
||||
let mut padded = [0u8; 16];
|
||||
padded[..bytes.len()].copy_from_slice(&bytes);
|
||||
let number = u128::from_le_bytes(padded);
|
||||
let tm = (number >> 17) & (u32::max_value() as u128);
|
||||
let ip = (((number >> 49) - tm) as u32).to_le_bytes();
|
||||
let port = (number & 0xFFFFFF) - (tm & 0xFFFF);
|
||||
SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]),
|
||||
port as u16,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_version_from_url(url: &str) -> String {
|
||||
let n = url.chars().count();
|
||||
let a = url
|
||||
.chars()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.filter(|(_, x)| x == &'-')
|
||||
.next()
|
||||
.map(|(i, _)| i);
|
||||
if let Some(a) = a {
|
||||
let b = url
|
||||
.chars()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.filter(|(_, x)| x == &'.')
|
||||
.next()
|
||||
.map(|(i, _)| i);
|
||||
if let Some(b) = b {
|
||||
if a > b {
|
||||
if url
|
||||
.chars()
|
||||
.skip(n - b)
|
||||
.collect::<String>()
|
||||
.parse::<i32>()
|
||||
.is_ok()
|
||||
{
|
||||
return url.chars().skip(n - a).collect();
|
||||
} else {
|
||||
return url.chars().skip(n - a).take(a - b - 1).collect();
|
||||
}
|
||||
} else {
|
||||
return url.chars().skip(n - a).collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
"".to_owned()
|
||||
}
|
||||
|
||||
pub fn gen_version() {
|
||||
let mut file = File::create("./src/version.rs").unwrap();
|
||||
for line in read_lines("Cargo.toml").unwrap() {
|
||||
if let Ok(line) = line {
|
||||
let ab: Vec<&str> = line.split("=").map(|x| x.trim()).collect();
|
||||
if ab.len() == 2 && ab[0] == "version" {
|
||||
use std::io::prelude::*;
|
||||
file.write_all(format!("pub const VERSION: &str = {};", ab[1]).as_bytes())
|
||||
.ok();
|
||||
file.sync_all().ok();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let file = File::open(filename)?;
|
||||
Ok(io::BufReader::new(file).lines())
|
||||
}
|
||||
|
||||
pub fn get_version_number(v: &str) -> i64 {
|
||||
let mut n = 0;
|
||||
for x in v.split(".") {
|
||||
n = n * 1000 + x.parse::<i64>().unwrap_or(0);
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_mangle() {
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 16, 32), 21116));
|
||||
assert_eq!(addr, AddrMangle::decode(&AddrMangle::encode(addr)));
|
||||
}
|
||||
}
|
||||
135
rust-rdp/rust-desk/libs/hbb_common/src/quic.rs
Normal file
135
rust-rdp/rust-desk/libs/hbb_common/src/quic.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use crate::{allow_err, anyhow::anyhow, ResultType};
|
||||
use protobuf::Message;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use tokio::{self, stream::StreamExt, sync::mpsc};
|
||||
|
||||
const QUIC_HBB: &[&[u8]] = &[b"hbb"];
|
||||
const SERVER_NAME: &str = "hbb";
|
||||
|
||||
type Sender = mpsc::UnboundedSender<Value>;
|
||||
type Receiver = mpsc::UnboundedReceiver<Value>;
|
||||
|
||||
pub fn new_server(socket: std::net::UdpSocket) -> ResultType<(Server, SocketAddr)> {
|
||||
let mut transport_config = quinn::TransportConfig::default();
|
||||
transport_config.stream_window_uni(0);
|
||||
let mut server_config = quinn::ServerConfig::default();
|
||||
server_config.transport = Arc::new(transport_config);
|
||||
let mut server_config = quinn::ServerConfigBuilder::new(server_config);
|
||||
server_config.protocols(QUIC_HBB);
|
||||
// server_config.enable_keylog();
|
||||
// server_config.use_stateless_retry(true);
|
||||
let mut endpoint = quinn::Endpoint::builder();
|
||||
endpoint.listen(server_config.build());
|
||||
let (end, incoming) = endpoint.with_socket(socket)?;
|
||||
Ok((Server { incoming }, end.local_addr()?))
|
||||
}
|
||||
|
||||
pub async fn new_client(local_addr: &SocketAddr, peer: &SocketAddr) -> ResultType<Connection> {
|
||||
let mut endpoint = quinn::Endpoint::builder();
|
||||
let mut client_config = quinn::ClientConfigBuilder::default();
|
||||
client_config.protocols(QUIC_HBB);
|
||||
//client_config.enable_keylog();
|
||||
endpoint.default_client_config(client_config.build());
|
||||
let (endpoint, _) = endpoint.bind(local_addr)?;
|
||||
let new_conn = endpoint.connect(peer, SERVER_NAME)?.await?;
|
||||
Connection::new_for_client(new_conn.connection).await
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
incoming: quinn::Incoming,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
#[inline]
|
||||
pub async fn next(&mut self) -> ResultType<Option<Connection>> {
|
||||
Connection::new_for_server(&mut self.incoming).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Connection {
|
||||
conn: quinn::Connection,
|
||||
tx: quinn::SendStream,
|
||||
rx: Receiver,
|
||||
}
|
||||
|
||||
type Value = ResultType<Vec<u8>>;
|
||||
|
||||
impl Connection {
|
||||
async fn new_for_server(incoming: &mut quinn::Incoming) -> ResultType<Option<Self>> {
|
||||
if let Some(conn) = incoming.next().await {
|
||||
let quinn::NewConnection {
|
||||
connection: conn,
|
||||
// uni_streams,
|
||||
mut bi_streams,
|
||||
..
|
||||
} = conn.await?;
|
||||
let (tx, rx) = mpsc::unbounded_channel::<Value>();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let stream = bi_streams.next().await;
|
||||
if let Some(stream) = stream {
|
||||
let stream = match stream {
|
||||
Err(e) => {
|
||||
tx.send(Err(e.into())).ok();
|
||||
break;
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
let cloned = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
allow_err!(handle_request(stream.1, cloned).await);
|
||||
});
|
||||
} else {
|
||||
tx.send(Err(anyhow!("Reset by the peer"))).ok();
|
||||
break;
|
||||
}
|
||||
}
|
||||
log::info!("Exit connection outer loop");
|
||||
});
|
||||
let tx = conn.open_uni().await?;
|
||||
Ok(Some(Self { conn, tx, rx }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn new_for_client(conn: quinn::Connection) -> ResultType<Self> {
|
||||
let (tx, rx_quic) = conn.open_bi().await?;
|
||||
let (tx_mpsc, rx) = mpsc::unbounded_channel::<Value>();
|
||||
tokio::spawn(async move {
|
||||
allow_err!(handle_request(rx_quic, tx_mpsc).await);
|
||||
});
|
||||
Ok(Self { conn, tx, rx })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn next(&mut self) -> Option<Value> {
|
||||
// None is returned when all Sender halves have dropped,
|
||||
// indicating that no further values can be sent on the channel.
|
||||
self.rx.recv().await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remote_address(&self) -> SocketAddr {
|
||||
self.conn.remote_address()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn send_raw(&mut self, bytes: &[u8]) -> ResultType<()> {
|
||||
self.tx.write_all(bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn send(&mut self, msg: &dyn Message) -> ResultType<()> {
|
||||
match msg.write_to_bytes() {
|
||||
Ok(bytes) => self.send_raw(&bytes).await?,
|
||||
err => allow_err!(err),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_request(rx: quinn::RecvStream, tx: Sender) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
91
rust-rdp/rust-desk/libs/hbb_common/src/socket_client.rs
Normal file
91
rust-rdp/rust-desk/libs/hbb_common/src/socket_client.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
use crate::{
|
||||
config::{Config, NetworkType},
|
||||
tcp::FramedStream,
|
||||
udp::FramedSocket,
|
||||
ResultType,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::ToSocketAddrs;
|
||||
use tokio_socks::{IntoTargetAddr, TargetAddr};
|
||||
|
||||
fn to_socket_addr(host: &str) -> ResultType<SocketAddr> {
|
||||
use std::net::ToSocketAddrs;
|
||||
host.to_socket_addrs()?.next().context("Failed to solve")
|
||||
}
|
||||
|
||||
pub fn get_target_addr(host: &str) -> ResultType<TargetAddr<'static>> {
|
||||
let addr = match Config::get_network_type() {
|
||||
NetworkType::Direct => to_socket_addr(&host)?.into_target_addr()?,
|
||||
NetworkType::ProxySocks => host.into_target_addr()?,
|
||||
}
|
||||
.to_owned();
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
pub fn test_if_valid_server(host: &str) -> String {
|
||||
let mut host = host.to_owned();
|
||||
if !host.contains(":") {
|
||||
host = format!("{}:{}", host, 0);
|
||||
}
|
||||
|
||||
match Config::get_network_type() {
|
||||
NetworkType::Direct => match to_socket_addr(&host) {
|
||||
Err(err) => err.to_string(),
|
||||
Ok(_) => "".to_owned(),
|
||||
},
|
||||
NetworkType::ProxySocks => match &host.into_target_addr() {
|
||||
Err(err) => err.to_string(),
|
||||
Ok(_) => "".to_owned(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect_tcp<'t, T: IntoTargetAddr<'t>>(
|
||||
target: T,
|
||||
local: SocketAddr,
|
||||
ms_timeout: u64,
|
||||
) -> ResultType<FramedStream> {
|
||||
let target_addr = target.into_target_addr()?;
|
||||
|
||||
if let Some(conf) = Config::get_socks() {
|
||||
FramedStream::connect(
|
||||
conf.proxy.as_str(),
|
||||
target_addr,
|
||||
local,
|
||||
conf.username.as_str(),
|
||||
conf.password.as_str(),
|
||||
ms_timeout,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
let addr = std::net::ToSocketAddrs::to_socket_addrs(&target_addr)?
|
||||
.next()
|
||||
.context("Invalid target addr")?;
|
||||
Ok(FramedStream::new(addr, local, ms_timeout).await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_udp<T: ToSocketAddrs>(local: T, ms_timeout: u64) -> ResultType<FramedSocket> {
|
||||
match Config::get_socks() {
|
||||
None => Ok(FramedSocket::new(local).await?),
|
||||
Some(conf) => {
|
||||
let socket = FramedSocket::new_proxy(
|
||||
conf.proxy.as_str(),
|
||||
local,
|
||||
conf.username.as_str(),
|
||||
conf.password.as_str(),
|
||||
ms_timeout,
|
||||
)
|
||||
.await?;
|
||||
Ok(socket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn rebind_udp<T: ToSocketAddrs>(local: T) -> ResultType<Option<FramedSocket>> {
|
||||
match Config::get_network_type() {
|
||||
NetworkType::Direct => Ok(Some(FramedSocket::new(local).await?)),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
285
rust-rdp/rust-desk/libs/hbb_common/src/tcp.rs
Normal file
285
rust-rdp/rust-desk/libs/hbb_common/src/tcp.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
use crate::{bail, bytes_codec::BytesCodec, ResultType};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use protobuf::Message;
|
||||
use sodiumoxide::crypto::secretbox::{self, Key, Nonce};
|
||||
use std::{
|
||||
io::{self, Error, ErrorKind},
|
||||
net::SocketAddr,
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite, ReadBuf},
|
||||
net::{lookup_host, TcpListener, TcpSocket, ToSocketAddrs},
|
||||
};
|
||||
use tokio_socks::{tcp::Socks5Stream, IntoTargetAddr, ToProxyAddrs};
|
||||
use tokio_util::codec::Framed;
|
||||
|
||||
pub trait TcpStreamTrait: AsyncRead + AsyncWrite + Unpin {}
|
||||
pub struct DynTcpStream(Box<dyn TcpStreamTrait + Send + Sync>);
|
||||
|
||||
pub struct FramedStream(
|
||||
Framed<DynTcpStream, BytesCodec>,
|
||||
SocketAddr,
|
||||
Option<(Key, u64, u64)>,
|
||||
u64,
|
||||
);
|
||||
|
||||
impl Deref for FramedStream {
|
||||
type Target = Framed<DynTcpStream, BytesCodec>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for FramedStream {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DynTcpStream {
|
||||
type Target = Box<dyn TcpStreamTrait + Send + Sync>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for DynTcpStream {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result<TcpSocket, std::io::Error> {
|
||||
let socket = match addr {
|
||||
std::net::SocketAddr::V4(..) => TcpSocket::new_v4()?,
|
||||
std::net::SocketAddr::V6(..) => TcpSocket::new_v6()?,
|
||||
};
|
||||
if reuse {
|
||||
// windows has no reuse_port, but it's reuse_address
|
||||
// almost equals to unix's reuse_port + reuse_address,
|
||||
// though may introduce nondeterministic behavior
|
||||
#[cfg(unix)]
|
||||
socket.set_reuseport(true)?;
|
||||
socket.set_reuseaddr(true)?;
|
||||
}
|
||||
socket.bind(addr)?;
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
impl FramedStream {
|
||||
pub async fn new<T1: ToSocketAddrs, T2: ToSocketAddrs>(
|
||||
remote_addr: T1,
|
||||
local_addr: T2,
|
||||
ms_timeout: u64,
|
||||
) -> ResultType<Self> {
|
||||
for local_addr in lookup_host(&local_addr).await? {
|
||||
for remote_addr in lookup_host(&remote_addr).await? {
|
||||
let stream = super::timeout(
|
||||
ms_timeout,
|
||||
new_socket(local_addr, true)?.connect(remote_addr),
|
||||
)
|
||||
.await??;
|
||||
stream.set_nodelay(true).ok();
|
||||
let addr = stream.local_addr()?;
|
||||
return Ok(Self(
|
||||
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
||||
addr,
|
||||
None,
|
||||
0,
|
||||
));
|
||||
}
|
||||
}
|
||||
bail!("could not resolve to any address");
|
||||
}
|
||||
|
||||
pub async fn connect<'a, 't, P, T1, T2>(
|
||||
proxy: P,
|
||||
target: T1,
|
||||
local: T2,
|
||||
username: &'a str,
|
||||
password: &'a str,
|
||||
ms_timeout: u64,
|
||||
) -> ResultType<Self>
|
||||
where
|
||||
P: ToProxyAddrs,
|
||||
T1: IntoTargetAddr<'t>,
|
||||
T2: ToSocketAddrs,
|
||||
{
|
||||
if let Some(local) = lookup_host(&local).await?.next() {
|
||||
if let Some(proxy) = proxy.to_proxy_addrs().next().await {
|
||||
let stream =
|
||||
super::timeout(ms_timeout, new_socket(local, true)?.connect(proxy?)).await??;
|
||||
stream.set_nodelay(true).ok();
|
||||
let stream = if username.trim().is_empty() {
|
||||
super::timeout(
|
||||
ms_timeout,
|
||||
Socks5Stream::connect_with_socket(stream, target),
|
||||
)
|
||||
.await??
|
||||
} else {
|
||||
super::timeout(
|
||||
ms_timeout,
|
||||
Socks5Stream::connect_with_password_and_socket(
|
||||
stream, target, username, password,
|
||||
),
|
||||
)
|
||||
.await??
|
||||
};
|
||||
let addr = stream.local_addr()?;
|
||||
return Ok(Self(
|
||||
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
||||
addr,
|
||||
None,
|
||||
0,
|
||||
));
|
||||
};
|
||||
};
|
||||
bail!("could not resolve to any address");
|
||||
}
|
||||
|
||||
pub fn local_addr(&self) -> SocketAddr {
|
||||
self.1
|
||||
}
|
||||
|
||||
pub fn set_send_timeout(&mut self, ms: u64) {
|
||||
self.3 = ms;
|
||||
}
|
||||
|
||||
pub fn from(stream: impl TcpStreamTrait + Send + Sync + 'static, addr: SocketAddr) -> Self {
|
||||
Self(
|
||||
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
||||
addr,
|
||||
None,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_raw(&mut self) {
|
||||
self.0.codec_mut().set_raw();
|
||||
self.2 = None;
|
||||
}
|
||||
|
||||
pub fn is_secured(&self) -> bool {
|
||||
self.2.is_some()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn send(&mut self, msg: &impl Message) -> ResultType<()> {
|
||||
self.send_raw(msg.write_to_bytes()?).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn send_raw(&mut self, msg: Vec<u8>) -> ResultType<()> {
|
||||
let mut msg = msg;
|
||||
if let Some(key) = self.2.as_mut() {
|
||||
key.1 += 1;
|
||||
let nonce = Self::get_nonce(key.1);
|
||||
msg = secretbox::seal(&msg, &nonce, &key.0);
|
||||
}
|
||||
self.send_bytes(bytes::Bytes::from(msg)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn send_bytes(&mut self, bytes: Bytes) -> ResultType<()> {
|
||||
if self.3 > 0 {
|
||||
super::timeout(self.3, self.0.send(bytes)).await??;
|
||||
} else {
|
||||
self.0.send(bytes).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn next(&mut self) -> Option<Result<BytesMut, Error>> {
|
||||
let mut res = self.0.next().await;
|
||||
if let Some(key) = self.2.as_mut() {
|
||||
if let Some(Ok(bytes)) = res.as_mut() {
|
||||
key.2 += 1;
|
||||
let nonce = Self::get_nonce(key.2);
|
||||
match secretbox::open(&bytes, &nonce, &key.0) {
|
||||
Ok(res) => {
|
||||
bytes.clear();
|
||||
bytes.put_slice(&res);
|
||||
}
|
||||
Err(()) => {
|
||||
return Some(Err(Error::new(ErrorKind::Other, "decryption error")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn next_timeout(&mut self, ms: u64) -> Option<Result<BytesMut, Error>> {
|
||||
if let Ok(res) = super::timeout(ms, self.next()).await {
|
||||
res
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_key(&mut self, key: Key) {
|
||||
self.2 = Some((key, 0, 0));
|
||||
}
|
||||
|
||||
fn get_nonce(seqnum: u64) -> Nonce {
|
||||
let mut nonce = Nonce([0u8; secretbox::NONCEBYTES]);
|
||||
nonce.0[..std::mem::size_of_val(&seqnum)].copy_from_slice(&seqnum.to_le_bytes());
|
||||
nonce
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_BACKLOG: u32 = 128;
|
||||
|
||||
#[allow(clippy::never_loop)]
|
||||
pub async fn new_listener<T: ToSocketAddrs>(addr: T, reuse: bool) -> ResultType<TcpListener> {
|
||||
if !reuse {
|
||||
Ok(TcpListener::bind(addr).await?)
|
||||
} else {
|
||||
for addr in lookup_host(&addr).await? {
|
||||
let socket = new_socket(addr, true)?;
|
||||
return Ok(socket.listen(DEFAULT_BACKLOG)?);
|
||||
}
|
||||
bail!("could not resolve to any address");
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpin for DynTcpStream {}
|
||||
|
||||
impl AsyncRead for DynTcpStream {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
AsyncRead::poll_read(Pin::new(&mut self.0), cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for DynTcpStream {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
AsyncWrite::poll_flush(Pin::new(&mut self.0), cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncRead + AsyncWrite + Unpin> TcpStreamTrait for R {}
|
||||
144
rust-rdp/rust-desk/libs/hbb_common/src/udp.rs
Normal file
144
rust-rdp/rust-desk/libs/hbb_common/src/udp.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use crate::{bail, ResultType};
|
||||
use anyhow::anyhow;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use protobuf::Message;
|
||||
use socket2::{Domain, Socket, Type};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::{ToSocketAddrs, UdpSocket};
|
||||
use tokio_socks::{udp::Socks5UdpFramed, IntoTargetAddr, TargetAddr, ToProxyAddrs};
|
||||
use tokio_util::{codec::BytesCodec, udp::UdpFramed};
|
||||
|
||||
pub enum FramedSocket {
|
||||
Direct(UdpFramed<BytesCodec>),
|
||||
ProxySocks(Socks5UdpFramed),
|
||||
}
|
||||
|
||||
fn new_socket(addr: SocketAddr, reuse: bool) -> Result<Socket, std::io::Error> {
|
||||
let socket = match addr {
|
||||
SocketAddr::V4(..) => Socket::new(Domain::ipv4(), Type::dgram(), None),
|
||||
SocketAddr::V6(..) => Socket::new(Domain::ipv6(), Type::dgram(), None),
|
||||
}?;
|
||||
if reuse {
|
||||
// windows has no reuse_port, but it's reuse_address
|
||||
// almost equals to unix's reuse_port + reuse_address,
|
||||
// though may introduce nondeterministic behavior
|
||||
#[cfg(unix)]
|
||||
socket.set_reuse_port(true)?;
|
||||
socket.set_reuse_address(true)?;
|
||||
}
|
||||
socket.bind(&addr.into())?;
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
impl FramedSocket {
|
||||
pub async fn new<T: ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
||||
let socket = UdpSocket::bind(addr).await?;
|
||||
Ok(Self::Direct(UdpFramed::new(socket, BytesCodec::new())))
|
||||
}
|
||||
|
||||
#[allow(clippy::never_loop)]
|
||||
pub async fn new_reuse<T: std::net::ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
||||
for addr in addr.to_socket_addrs()? {
|
||||
let socket = new_socket(addr, true)?.into_udp_socket();
|
||||
return Ok(Self::Direct(UdpFramed::new(
|
||||
UdpSocket::from_std(socket)?,
|
||||
BytesCodec::new(),
|
||||
)));
|
||||
}
|
||||
bail!("could not resolve to any address");
|
||||
}
|
||||
|
||||
pub async fn new_proxy<'a, 't, P: ToProxyAddrs, T: ToSocketAddrs>(
|
||||
proxy: P,
|
||||
local: T,
|
||||
username: &'a str,
|
||||
password: &'a str,
|
||||
ms_timeout: u64,
|
||||
) -> ResultType<Self> {
|
||||
let framed = if username.trim().is_empty() {
|
||||
super::timeout(ms_timeout, Socks5UdpFramed::connect(proxy, Some(local))).await??
|
||||
} else {
|
||||
super::timeout(
|
||||
ms_timeout,
|
||||
Socks5UdpFramed::connect_with_password(proxy, Some(local), username, password),
|
||||
)
|
||||
.await??
|
||||
};
|
||||
log::trace!(
|
||||
"Socks5 udp connected, local addr: {:?}, target addr: {}",
|
||||
framed.local_addr(),
|
||||
framed.socks_addr()
|
||||
);
|
||||
Ok(Self::ProxySocks(framed))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn send(
|
||||
&mut self,
|
||||
msg: &impl Message,
|
||||
addr: impl IntoTargetAddr<'_>,
|
||||
) -> ResultType<()> {
|
||||
let addr = addr.into_target_addr()?.to_owned();
|
||||
let send_data = Bytes::from(msg.write_to_bytes()?);
|
||||
let _ = match self {
|
||||
Self::Direct(f) => match addr {
|
||||
TargetAddr::Ip(addr) => f.send((send_data, addr)).await?,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Self::ProxySocks(f) => f.send((send_data, addr)).await?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/68733302/1926020
|
||||
#[inline]
|
||||
pub async fn send_raw(
|
||||
&mut self,
|
||||
msg: &'static [u8],
|
||||
addr: impl IntoTargetAddr<'static>,
|
||||
) -> ResultType<()> {
|
||||
let addr = addr.into_target_addr()?.to_owned();
|
||||
|
||||
let _ = match self {
|
||||
Self::Direct(f) => match addr {
|
||||
TargetAddr::Ip(addr) => f.send((Bytes::from(msg), addr)).await?,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Self::ProxySocks(f) => f.send((Bytes::from(msg), addr)).await?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn next(&mut self) -> Option<ResultType<(BytesMut, TargetAddr<'static>)>> {
|
||||
match self {
|
||||
Self::Direct(f) => match f.next().await {
|
||||
Some(Ok((data, addr))) => {
|
||||
Some(Ok((data, addr.into_target_addr().ok()?.to_owned())))
|
||||
}
|
||||
Some(Err(e)) => Some(Err(anyhow!(e))),
|
||||
None => None,
|
||||
},
|
||||
Self::ProxySocks(f) => match f.next().await {
|
||||
Some(Ok((data, _))) => Some(Ok((data.data, data.dst_addr))),
|
||||
Some(Err(e)) => Some(Err(anyhow!(e))),
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn next_timeout(
|
||||
&mut self,
|
||||
ms: u64,
|
||||
) -> Option<ResultType<(BytesMut, TargetAddr<'static>)>> {
|
||||
if let Ok(res) =
|
||||
tokio::time::timeout(std::time::Duration::from_millis(ms), self.next()).await
|
||||
{
|
||||
res
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
4
rust-rdp/rust-desk/libs/scrap/.gitignore
vendored
Normal file
4
rust-rdp/rust-desk/libs/scrap/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
generated/
|
||||
42
rust-rdp/rust-desk/libs/scrap/Cargo.toml
Normal file
42
rust-rdp/rust-desk/libs/scrap/Cargo.toml
Normal file
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "scrap"
|
||||
description = "Screen capture made easy."
|
||||
version = "0.5.0"
|
||||
repository = "https://github.com/quadrupleslap/scrap"
|
||||
documentation = "https://docs.rs/scrap"
|
||||
keywords = ["screen", "capture", "record"]
|
||||
license = "MIT"
|
||||
authors = ["Ram <quadrupleslap@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
wayland = ["gstreamer", "gstreamer-app", "gstreamer-video", "dbus", "tracing"]
|
||||
|
||||
[dependencies]
|
||||
block = "0.1"
|
||||
cfg-if = "1.0"
|
||||
libc = "0.2"
|
||||
num_cpus = "1.13"
|
||||
|
||||
[dependencies.winapi]
|
||||
version = "0.3"
|
||||
default-features = true
|
||||
features = ["dxgi", "dxgi1_2", "dxgi1_5", "d3d11", "winuser"]
|
||||
|
||||
[dev-dependencies]
|
||||
repng = "0.2"
|
||||
docopt = "1.1"
|
||||
webm = "1.0"
|
||||
serde = {version="1.0", features=["derive"]}
|
||||
quest = "0.3"
|
||||
|
||||
[build-dependencies]
|
||||
target_build_utils = "0.3"
|
||||
bindgen = "0.59"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
dbus = { version = "0.9", optional = true }
|
||||
tracing = { version = "0.1", optional = true }
|
||||
gstreamer = { version = "0.16", optional = true }
|
||||
gstreamer-app = { version = "0.16", features = ["v1_10"], optional = true }
|
||||
gstreamer-video = { version = "0.16", optional = true }
|
||||
62
rust-rdp/rust-desk/libs/scrap/README.md
Normal file
62
rust-rdp/rust-desk/libs/scrap/README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
Derived from https://github.com/quadrupleslap/scrap
|
||||
|
||||
# scrap
|
||||
|
||||
Scrap records your screen! At least it does if you're on Windows, macOS, or Linux.
|
||||
|
||||
## Usage
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
scrap = "0.5"
|
||||
```
|
||||
|
||||
Its API is as simple as it gets!
|
||||
|
||||
```rust
|
||||
struct Display; /// A screen.
|
||||
struct Frame; /// An array of the pixels that were on-screen.
|
||||
struct Capturer; /// A recording instance.
|
||||
|
||||
impl Capturer {
|
||||
/// Begin recording.
|
||||
pub fn new(display: Display) -> io::Result<Capturer>;
|
||||
|
||||
/// Try to get a frame.
|
||||
/// Returns WouldBlock if it's not ready yet.
|
||||
pub fn frame<'a>(&'a mut self) -> io::Result<Frame<'a>>;
|
||||
|
||||
pub fn width(&self) -> usize;
|
||||
pub fn height(&self) -> usize;
|
||||
}
|
||||
|
||||
impl Display {
|
||||
/// The primary screen.
|
||||
pub fn primary() -> io::Result<Display>;
|
||||
|
||||
/// All the screens.
|
||||
pub fn all() -> io::Result<Vec<Display>>;
|
||||
|
||||
pub fn width(&self) -> usize;
|
||||
pub fn height(&self) -> usize;
|
||||
}
|
||||
|
||||
impl<'a> ops::Deref for Frame<'a> {
|
||||
/// A frame is just an array of bytes.
|
||||
type Target = [u8];
|
||||
}
|
||||
```
|
||||
|
||||
## The Frame Format
|
||||
|
||||
- The frame format is guaranteed to be **packed BGRA**.
|
||||
- The width and height are guaranteed to remain constant.
|
||||
- The stride might be greater than the width, and it may also vary between frames.
|
||||
|
||||
## System Requirements
|
||||
|
||||
OS | Minimum Requirements
|
||||
--------|---------------------
|
||||
macOS | macOS 10.8
|
||||
Linux | XCB + SHM + RandR
|
||||
Windows | DirectX 11.1
|
||||
114
rust-rdp/rust-desk/libs/scrap/build.rs
Normal file
114
rust-rdp/rust-desk/libs/scrap/build.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
fn find_package(name: &str) -> Vec<PathBuf> {
|
||||
let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap();
|
||||
let mut path: PathBuf = vcpkg_root.into();
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let mut target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
if target_arch == "x86_64" {
|
||||
target_arch = "x64".to_owned();
|
||||
} else if target_arch == "aarch64" {
|
||||
target_arch = "arm64".to_owned();
|
||||
}
|
||||
let mut target = if target_os == "macos" {
|
||||
"x64-osx".to_owned()
|
||||
} else if target_os == "windows" {
|
||||
"x64-windows-static".to_owned()
|
||||
} else {
|
||||
format!("{}-{}", target_arch, target_os)
|
||||
};
|
||||
if target_arch == "x86" {
|
||||
target = target.replace("x64", "x86");
|
||||
}
|
||||
println!("cargo:info={}", target);
|
||||
path.push("installed");
|
||||
path.push(target);
|
||||
let lib = name.trim_start_matches("lib").to_string();
|
||||
println!("{}", format!("cargo:rustc-link-lib=static={}", lib));
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"cargo:rustc-link-search={}",
|
||||
path.join("lib").to_str().unwrap()
|
||||
)
|
||||
);
|
||||
let include = path.join("include");
|
||||
println!("{}", format!("cargo:include={}", include.to_str().unwrap()));
|
||||
vec![include]
|
||||
}
|
||||
|
||||
fn generate_bindings(
|
||||
ffi_header: &Path,
|
||||
include_paths: &[PathBuf],
|
||||
ffi_rs: &Path,
|
||||
exact_file: &Path,
|
||||
) {
|
||||
let mut b = bindgen::builder()
|
||||
.header(ffi_header.to_str().unwrap())
|
||||
.allowlist_type("^[vV].*")
|
||||
.allowlist_var("^[vV].*")
|
||||
.allowlist_function("^[vV].*")
|
||||
.rustified_enum("^v.*")
|
||||
.trust_clang_mangling(false)
|
||||
.layout_tests(false) // breaks 32/64-bit compat
|
||||
.generate_comments(false); // vpx comments have prefix /*!\
|
||||
|
||||
for dir in include_paths {
|
||||
b = b.clang_arg(format!("-I{}", dir.display()));
|
||||
}
|
||||
|
||||
b.generate().unwrap().write_to_file(ffi_rs).unwrap();
|
||||
fs::copy(ffi_rs, exact_file).ok(); // ignore failure
|
||||
}
|
||||
|
||||
fn gen_vpx() {
|
||||
let includes = find_package("libvpx");
|
||||
let src_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
||||
let src_dir = Path::new(&src_dir);
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let out_dir = Path::new(&out_dir);
|
||||
|
||||
let ffi_header = src_dir.join("vpx_ffi.h");
|
||||
println!("rerun-if-changed={}", ffi_header.display());
|
||||
for dir in &includes {
|
||||
println!("rerun-if-changed={}", dir.display());
|
||||
}
|
||||
|
||||
let ffi_rs = out_dir.join("vpx_ffi.rs");
|
||||
let exact_file = src_dir.join("generated").join("vpx_ffi.rs");
|
||||
generate_bindings(&ffi_header, &includes, &ffi_rs, &exact_file);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// note: all link symbol names in x86 (32-bit) are prefixed wth "_".
|
||||
// run "rustup show" to show current default toolchain, if it is stable-x86-pc-windows-msvc,
|
||||
// please install x64 toolchain by "rustup toolchain install stable-x86_64-pc-windows-msvc",
|
||||
// then set x64 to default by "rustup default stable-x86_64-pc-windows-msvc"
|
||||
let target = target_build_utils::TargetInfo::new();
|
||||
if target.unwrap().target_pointer_width() != "64" {
|
||||
// panic!("Only support 64bit system");
|
||||
}
|
||||
env::remove_var("CARGO_CFG_TARGET_FEATURE");
|
||||
env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
|
||||
|
||||
find_package("libyuv");
|
||||
gen_vpx();
|
||||
|
||||
// there is problem with cfg(target_os) in build.rs, so use our workaround
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
if target_os == "android" || target_os == "ios" {
|
||||
// nothing
|
||||
} else if cfg!(windows) {
|
||||
// The first choice is Windows because DXGI is amazing.
|
||||
println!("cargo:rustc-cfg=dxgi");
|
||||
} else if cfg!(target_os = "macos") {
|
||||
// Quartz is second because macOS is the (annoying) exception.
|
||||
println!("cargo:rustc-cfg=quartz");
|
||||
} else if cfg!(unix) {
|
||||
// On UNIX we pray that X11 (with XCB) is available.
|
||||
println!("cargo:rustc-cfg=x11");
|
||||
}
|
||||
}
|
||||
51
rust-rdp/rust-desk/libs/scrap/examples/ffplay.rs
Normal file
51
rust-rdp/rust-desk/libs/scrap/examples/ffplay.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
extern crate scrap;
|
||||
|
||||
fn main() {
|
||||
use scrap::{Capturer, Display};
|
||||
use std::io::ErrorKind::WouldBlock;
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
let d = Display::primary().unwrap();
|
||||
let (w, h) = (d.width(), d.height());
|
||||
|
||||
let child = Command::new("ffplay")
|
||||
.args(&[
|
||||
"-f",
|
||||
"rawvideo",
|
||||
"-pixel_format",
|
||||
"bgr0",
|
||||
"-video_size",
|
||||
&format!("{}x{}", w, h),
|
||||
"-framerate",
|
||||
"60",
|
||||
"-",
|
||||
])
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("This example requires ffplay.");
|
||||
|
||||
let mut capturer = Capturer::new(d, false).unwrap();
|
||||
let mut out = child.stdin.unwrap();
|
||||
|
||||
loop {
|
||||
match capturer.frame(0) {
|
||||
Ok(frame) => {
|
||||
// Write the frame, removing end-of-row padding.
|
||||
let stride = frame.len() / h;
|
||||
let rowlen = 4 * w;
|
||||
for row in frame.chunks(stride) {
|
||||
let row = &row[..rowlen];
|
||||
out.write_all(row).unwrap();
|
||||
}
|
||||
}
|
||||
Err(ref e) if e.kind() == WouldBlock => {
|
||||
// Wait for the frame.
|
||||
}
|
||||
Err(_) => {
|
||||
// We're done here.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
rust-rdp/rust-desk/libs/scrap/examples/list.rs
Normal file
16
rust-rdp/rust-desk/libs/scrap/examples/list.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
extern crate scrap;
|
||||
|
||||
use scrap::Display;
|
||||
|
||||
fn main() {
|
||||
let displays = Display::all().unwrap();
|
||||
|
||||
for (i, display) in displays.iter().enumerate() {
|
||||
println!(
|
||||
"Display {} [{}x{}]",
|
||||
i + 1,
|
||||
display.width(),
|
||||
display.height()
|
||||
);
|
||||
}
|
||||
}
|
||||
160
rust-rdp/rust-desk/libs/scrap/examples/record-screen.rs
Normal file
160
rust-rdp/rust-desk/libs/scrap/examples/record-screen.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
extern crate docopt;
|
||||
extern crate quest;
|
||||
extern crate repng;
|
||||
extern crate scrap;
|
||||
extern crate serde;
|
||||
extern crate webm;
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{io, thread};
|
||||
|
||||
use docopt::Docopt;
|
||||
use webm::mux;
|
||||
use webm::mux::Track;
|
||||
|
||||
use scrap::codec as vpx_encode;
|
||||
use scrap::{Capturer, Display, STRIDE_ALIGN};
|
||||
|
||||
const USAGE: &'static str = "
|
||||
Simple WebM screen capture.
|
||||
|
||||
Usage:
|
||||
record-screen <path> [--time=<s>] [--fps=<fps>] [--bv=<kbps>] [--ba=<kbps>] [--codec CODEC]
|
||||
record-screen (-h | --help)
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
--time=<s> Recording duration in seconds.
|
||||
--fps=<fps> Frames per second [default: 30].
|
||||
--bv=<kbps> Video bitrate in kilobits per second [default: 5000].
|
||||
--ba=<kbps> Audio bitrate in kilobits per second [default: 96].
|
||||
--codec CODEC Configure the codec used. [default: vp9]
|
||||
Valid values: vp8, vp9.
|
||||
";
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Args {
|
||||
arg_path: PathBuf,
|
||||
flag_codec: Codec,
|
||||
flag_time: Option<u64>,
|
||||
flag_fps: u64,
|
||||
flag_bv: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
enum Codec {
|
||||
Vp8,
|
||||
Vp9,
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
.and_then(|d| d.deserialize())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
|
||||
let duration = args.flag_time.map(Duration::from_secs);
|
||||
|
||||
let d = Display::primary().unwrap();
|
||||
let (width, height) = (d.width() as u32, d.height() as u32);
|
||||
|
||||
// Setup the multiplexer.
|
||||
|
||||
let out = match {
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&args.arg_path)
|
||||
} {
|
||||
Ok(file) => file,
|
||||
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {
|
||||
if loop {
|
||||
quest::ask("Overwrite the existing file? [y/N] ");
|
||||
if let Some(b) = quest::yesno(false)? {
|
||||
break b;
|
||||
}
|
||||
} {
|
||||
File::create(&args.arg_path)?
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
let mut webm =
|
||||
mux::Segment::new(mux::Writer::new(out)).expect("Could not initialize the multiplexer.");
|
||||
|
||||
let (vpx_codec, mux_codec) = match args.flag_codec {
|
||||
Codec::Vp8 => (vpx_encode::VideoCodecId::VP8, mux::VideoCodecId::VP8),
|
||||
Codec::Vp9 => (vpx_encode::VideoCodecId::VP9, mux::VideoCodecId::VP9),
|
||||
};
|
||||
|
||||
let mut vt = webm.add_video_track(width, height, None, mux_codec);
|
||||
|
||||
// Setup the encoder.
|
||||
|
||||
let mut vpx = vpx_encode::Encoder::new(
|
||||
&vpx_encode::Config {
|
||||
width,
|
||||
height,
|
||||
timebase: [1, 1000],
|
||||
bitrate: args.flag_bv,
|
||||
codec: vpx_codec,
|
||||
rc_min_quantizer: 0,
|
||||
rc_max_quantizer: 0,
|
||||
speed: 6,
|
||||
},
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Start recording.
|
||||
|
||||
let start = Instant::now();
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
|
||||
thread::spawn({
|
||||
let stop = stop.clone();
|
||||
move || {
|
||||
let _ = quest::ask("Recording! Press ⏎ to stop.");
|
||||
let _ = quest::text();
|
||||
stop.store(true, Ordering::Release);
|
||||
}
|
||||
});
|
||||
|
||||
let spf = Duration::from_nanos(1_000_000_000 / args.flag_fps);
|
||||
|
||||
// Capturer object is expensive, avoiding to create it frequently.
|
||||
let mut c = Capturer::new(d, true).unwrap();
|
||||
while !stop.load(Ordering::Acquire) {
|
||||
let now = Instant::now();
|
||||
let time = now - start;
|
||||
|
||||
if Some(true) == duration.map(|d| time > d) {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Ok(frame) = c.frame(0) {
|
||||
let ms = time.as_secs() * 1000 + time.subsec_millis() as u64;
|
||||
|
||||
for frame in vpx.encode(ms as i64, &frame, STRIDE_ALIGN).unwrap() {
|
||||
vt.add_frame(frame.data, frame.pts as u64 * 1_000_000, frame.key);
|
||||
}
|
||||
}
|
||||
|
||||
let dt = now.elapsed();
|
||||
if dt < spf {
|
||||
thread::sleep(spf - dt);
|
||||
}
|
||||
}
|
||||
|
||||
// End things.
|
||||
|
||||
let _ = webm.finalize(None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
126
rust-rdp/rust-desk/libs/scrap/examples/screenshot.rs
Normal file
126
rust-rdp/rust-desk/libs/scrap/examples/screenshot.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
extern crate repng;
|
||||
extern crate scrap;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind::WouldBlock;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use scrap::{i420_to_rgb, Capturer, Display};
|
||||
|
||||
fn main() {
|
||||
let n = Display::all().unwrap().len();
|
||||
for i in 0..n {
|
||||
record(i);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_display(i: usize) -> Display {
|
||||
Display::all().unwrap().remove(i)
|
||||
}
|
||||
|
||||
fn record(i: usize) {
|
||||
let one_second = Duration::new(1, 0);
|
||||
let one_frame = one_second / 60;
|
||||
|
||||
for d in Display::all().unwrap() {
|
||||
println!("{:?} {} {}", d.origin(), d.width(), d.height());
|
||||
}
|
||||
|
||||
let display = get_display(i);
|
||||
let mut capturer = Capturer::new(display, false).expect("Couldn't begin capture.");
|
||||
let (w, h) = (capturer.width(), capturer.height());
|
||||
|
||||
loop {
|
||||
// Wait until there's a frame.
|
||||
|
||||
let buffer = match capturer.frame(0) {
|
||||
Ok(buffer) => buffer,
|
||||
Err(error) => {
|
||||
if error.kind() == WouldBlock {
|
||||
// Keep spinning.
|
||||
thread::sleep(one_frame);
|
||||
continue;
|
||||
} else {
|
||||
panic!("Error: {}", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
println!("Captured! Saving...");
|
||||
|
||||
// Flip the BGRA image into a RGBA image.
|
||||
|
||||
let mut bitflipped = Vec::with_capacity(w * h * 4);
|
||||
let stride = buffer.len() / h;
|
||||
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let i = stride * y + 4 * x;
|
||||
bitflipped.extend_from_slice(&[buffer[i + 2], buffer[i + 1], buffer[i], 255]);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the image.
|
||||
|
||||
let name = format!("screenshot{}_1.png", i);
|
||||
repng::encode(
|
||||
File::create(name.clone()).unwrap(),
|
||||
w as u32,
|
||||
h as u32,
|
||||
&bitflipped,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
println!("Image saved to `{}`.", name);
|
||||
break;
|
||||
}
|
||||
|
||||
drop(capturer);
|
||||
let display = get_display(i);
|
||||
let mut capturer = Capturer::new(display, true).expect("Couldn't begin capture.");
|
||||
let (w, h) = (capturer.width(), capturer.height());
|
||||
|
||||
loop {
|
||||
// Wait until there's a frame.
|
||||
|
||||
let buffer = match capturer.frame(0) {
|
||||
Ok(buffer) => buffer,
|
||||
Err(error) => {
|
||||
if error.kind() == WouldBlock {
|
||||
// Keep spinning.
|
||||
thread::sleep(one_frame);
|
||||
continue;
|
||||
} else {
|
||||
panic!("Error: {}", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
println!("Captured! Saving...");
|
||||
|
||||
let mut frame = Default::default();
|
||||
i420_to_rgb(w, h, &buffer, &mut frame);
|
||||
|
||||
let mut bitflipped = Vec::with_capacity(w * h * 4);
|
||||
let stride = frame.len() / h;
|
||||
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let i = stride * y + 3 * x;
|
||||
bitflipped.extend_from_slice(&[frame[i], frame[i + 1], frame[i + 2], 255]);
|
||||
}
|
||||
}
|
||||
let name = format!("screenshot{}_2.png", i);
|
||||
repng::encode(
|
||||
File::create(name.clone()).unwrap(),
|
||||
w as u32,
|
||||
h as u32,
|
||||
&bitflipped,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
println!("Image saved to `{}`.", name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
536
rust-rdp/rust-desk/libs/scrap/src/common/codec.rs
Normal file
536
rust-rdp/rust-desk/libs/scrap/src/common/codec.rs
Normal file
@@ -0,0 +1,536 @@
|
||||
// https://github.com/astraw/vpx-encode
|
||||
// https://github.com/astraw/env-libvpx-sys
|
||||
// https://github.com/rust-av/vpx-rs/blob/master/src/decoder.rs
|
||||
|
||||
use super::vpx::{vp8e_enc_control_id::*, vpx_codec_err_t::*, *};
|
||||
use std::os::raw::{c_int, c_uint};
|
||||
use std::{ptr, slice};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum VideoCodecId {
|
||||
VP8,
|
||||
VP9,
|
||||
}
|
||||
|
||||
impl Default for VideoCodecId {
|
||||
fn default() -> VideoCodecId {
|
||||
VideoCodecId::VP9
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Encoder {
|
||||
ctx: vpx_codec_ctx_t,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
pub struct Decoder {
|
||||
ctx: vpx_codec_ctx_t,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
FailedCall(String),
|
||||
BadPtr(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
macro_rules! call_vpx {
|
||||
($x:expr) => {{
|
||||
let result = unsafe { $x }; // original expression
|
||||
let result_int = unsafe { std::mem::transmute::<_, i32>(result) };
|
||||
if result_int != 0 {
|
||||
return Err(Error::FailedCall(format!(
|
||||
"errcode={} {}:{}:{}:{}",
|
||||
result_int,
|
||||
module_path!(),
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
))
|
||||
.into());
|
||||
}
|
||||
result
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! call_vpx_ptr {
|
||||
($x:expr) => {{
|
||||
let result = unsafe { $x }; // original expression
|
||||
let result_int = unsafe { std::mem::transmute::<_, isize>(result) };
|
||||
if result_int == 0 {
|
||||
return Err(Error::BadPtr(format!(
|
||||
"errcode={} {}:{}:{}:{}",
|
||||
result_int,
|
||||
module_path!(),
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
))
|
||||
.into());
|
||||
}
|
||||
result
|
||||
}};
|
||||
}
|
||||
|
||||
impl Encoder {
|
||||
pub fn new(config: &Config, num_threads: u32) -> Result<Self> {
|
||||
let i;
|
||||
if cfg!(feature = "VP8") {
|
||||
i = match config.codec {
|
||||
VideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
|
||||
VideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
|
||||
};
|
||||
} else {
|
||||
i = call_vpx_ptr!(vpx_codec_vp9_cx());
|
||||
}
|
||||
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
||||
|
||||
// https://www.webmproject.org/docs/encoder-parameters/
|
||||
// default: c.rc_min_quantizer = 0, c.rc_max_quantizer = 63
|
||||
// try rc_resize_allowed later
|
||||
|
||||
c.g_w = config.width;
|
||||
c.g_h = config.height;
|
||||
c.g_timebase.num = config.timebase[0];
|
||||
c.g_timebase.den = config.timebase[1];
|
||||
c.rc_target_bitrate = config.bitrate;
|
||||
c.rc_undershoot_pct = 95;
|
||||
c.rc_dropframe_thresh = 25;
|
||||
if config.rc_min_quantizer > 0 {
|
||||
c.rc_min_quantizer = config.rc_min_quantizer;
|
||||
}
|
||||
if config.rc_max_quantizer > 0 {
|
||||
c.rc_max_quantizer = config.rc_max_quantizer;
|
||||
}
|
||||
let mut speed = config.speed;
|
||||
if speed <= 0 {
|
||||
speed = 6;
|
||||
}
|
||||
|
||||
c.g_threads = if num_threads == 0 {
|
||||
num_cpus::get() as _
|
||||
} else {
|
||||
num_threads
|
||||
};
|
||||
c.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
|
||||
// https://developers.google.com/media/vp9/bitrate-modes/
|
||||
// Constant Bitrate mode (CBR) is recommended for live streaming with VP9.
|
||||
c.rc_end_usage = vpx_rc_mode::VPX_CBR;
|
||||
// c.kf_min_dist = 0;
|
||||
// c.kf_max_dist = 999999;
|
||||
c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED; // reduce bandwidth a lot
|
||||
|
||||
/*
|
||||
VPX encoder支持two-pass encode,这是为了rate control的。
|
||||
对于两遍编码,就是需要整个编码过程做两次,第一次会得到一些新的控制参数来进行第二遍的编码,
|
||||
这样可以在相同的bitrate下得到最好的PSNR
|
||||
*/
|
||||
|
||||
let mut ctx = Default::default();
|
||||
call_vpx!(vpx_codec_enc_init_ver(
|
||||
&mut ctx,
|
||||
i,
|
||||
&c,
|
||||
0,
|
||||
VPX_ENCODER_ABI_VERSION as _
|
||||
));
|
||||
|
||||
if config.codec == VideoCodecId::VP9 {
|
||||
// set encoder internal speed settings
|
||||
// in ffmpeg, it is --speed option
|
||||
/*
|
||||
set to 0 or a positive value 1-16, the codec will try to adapt its
|
||||
complexity depending on the time it spends encoding. Increasing this
|
||||
number will make the speed go up and the quality go down.
|
||||
Negative values mean strict enforcement of this
|
||||
while positive values are adaptive
|
||||
*/
|
||||
/* https://developers.google.com/media/vp9/live-encoding
|
||||
Speed 5 to 8 should be used for live / real-time encoding.
|
||||
Lower numbers (5 or 6) are higher quality but require more CPU power.
|
||||
Higher numbers (7 or 8) will be lower quality but more manageable for lower latency
|
||||
use cases and also for lower CPU power devices such as mobile.
|
||||
*/
|
||||
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, speed,));
|
||||
// set row level multi-threading
|
||||
/*
|
||||
as some people in comments and below have already commented,
|
||||
more recent versions of libvpx support -row-mt 1 to enable tile row
|
||||
multi-threading. This can increase the number of tiles by up to 4x in VP9
|
||||
(since the max number of tile rows is 4, regardless of video height).
|
||||
To enable this, use -tile-rows N where N is the number of tile rows in
|
||||
log2 units (so -tile-rows 1 means 2 tile rows and -tile-rows 2 means 4 tile
|
||||
rows). The total number of active threads will then be equal to
|
||||
$tile_rows * $tile_columns
|
||||
*/
|
||||
call_vpx!(vpx_codec_control_(
|
||||
&mut ctx,
|
||||
VP9E_SET_ROW_MT as _,
|
||||
1 as c_int
|
||||
));
|
||||
|
||||
call_vpx!(vpx_codec_control_(
|
||||
&mut ctx,
|
||||
VP9E_SET_TILE_COLUMNS as _,
|
||||
4 as c_int
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
ctx,
|
||||
width: config.width as _,
|
||||
height: config.height as _,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode(&mut self, pts: i64, data: &[u8], stride_align: usize) -> Result<EncodeFrames> {
|
||||
assert!(2 * data.len() >= 3 * self.width * self.height);
|
||||
|
||||
let mut image = Default::default();
|
||||
call_vpx_ptr!(vpx_img_wrap(
|
||||
&mut image,
|
||||
vpx_img_fmt::VPX_IMG_FMT_I420,
|
||||
self.width as _,
|
||||
self.height as _,
|
||||
stride_align as _,
|
||||
data.as_ptr() as _,
|
||||
));
|
||||
|
||||
call_vpx!(vpx_codec_encode(
|
||||
&mut self.ctx,
|
||||
&image,
|
||||
pts as _,
|
||||
1, // Duration
|
||||
0, // Flags
|
||||
VPX_DL_REALTIME as _,
|
||||
));
|
||||
|
||||
Ok(EncodeFrames {
|
||||
ctx: &mut self.ctx,
|
||||
iter: ptr::null(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Notify the encoder to return any pending packets
|
||||
pub fn flush(&mut self) -> Result<EncodeFrames> {
|
||||
call_vpx!(vpx_codec_encode(
|
||||
&mut self.ctx,
|
||||
ptr::null(),
|
||||
-1, // PTS
|
||||
1, // Duration
|
||||
0, // Flags
|
||||
VPX_DL_REALTIME as _,
|
||||
));
|
||||
|
||||
Ok(EncodeFrames {
|
||||
ctx: &mut self.ctx,
|
||||
iter: ptr::null(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Encoder {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let result = vpx_codec_destroy(&mut self.ctx);
|
||||
if result != VPX_CODEC_OK {
|
||||
panic!("failed to destroy vpx codec");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct EncodeFrame<'a> {
|
||||
/// Compressed data.
|
||||
pub data: &'a [u8],
|
||||
/// Whether the frame is a keyframe.
|
||||
pub key: bool,
|
||||
/// Presentation timestamp (in timebase units).
|
||||
pub pts: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Config {
|
||||
/// The width (in pixels).
|
||||
pub width: c_uint,
|
||||
/// The height (in pixels).
|
||||
pub height: c_uint,
|
||||
/// The timebase numerator and denominator (in seconds).
|
||||
pub timebase: [c_int; 2],
|
||||
/// The target bitrate (in kilobits per second).
|
||||
pub bitrate: c_uint,
|
||||
/// The codec
|
||||
pub codec: VideoCodecId,
|
||||
pub rc_min_quantizer: u32,
|
||||
pub rc_max_quantizer: u32,
|
||||
pub speed: i32,
|
||||
}
|
||||
|
||||
pub struct EncodeFrames<'a> {
|
||||
ctx: &'a mut vpx_codec_ctx_t,
|
||||
iter: vpx_codec_iter_t,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for EncodeFrames<'a> {
|
||||
type Item = EncodeFrame<'a>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
unsafe {
|
||||
let pkt = vpx_codec_get_cx_data(self.ctx, &mut self.iter);
|
||||
if pkt.is_null() {
|
||||
return None;
|
||||
} else if (*pkt).kind == vpx_codec_cx_pkt_kind::VPX_CODEC_CX_FRAME_PKT {
|
||||
let f = &(*pkt).data.frame;
|
||||
return Some(Self::Item {
|
||||
data: slice::from_raw_parts(f.buf as _, f.sz as _),
|
||||
key: (f.flags & VPX_FRAME_IS_KEY) != 0,
|
||||
pts: f.pts,
|
||||
});
|
||||
} else {
|
||||
// Ignore the packet.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder {
|
||||
/// Create a new decoder
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The function may fail if the underlying libvpx does not provide
|
||||
/// the VP9 decoder.
|
||||
pub fn new(codec: VideoCodecId, num_threads: u32) -> Result<Self> {
|
||||
// This is sound because `vpx_codec_ctx` is a repr(C) struct without any field that can
|
||||
// cause UB if uninitialized.
|
||||
let i;
|
||||
if cfg!(feature = "VP8") {
|
||||
i = match codec {
|
||||
VideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
|
||||
VideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
|
||||
};
|
||||
} else {
|
||||
i = call_vpx_ptr!(vpx_codec_vp9_dx());
|
||||
}
|
||||
let mut ctx = Default::default();
|
||||
let cfg = vpx_codec_dec_cfg_t {
|
||||
threads: if num_threads == 0 {
|
||||
num_cpus::get() as _
|
||||
} else {
|
||||
num_threads
|
||||
},
|
||||
w: 0,
|
||||
h: 0,
|
||||
};
|
||||
/*
|
||||
unsafe {
|
||||
println!("{}", vpx_codec_get_caps(i));
|
||||
}
|
||||
*/
|
||||
call_vpx!(vpx_codec_dec_init_ver(
|
||||
&mut ctx,
|
||||
i,
|
||||
&cfg,
|
||||
0,
|
||||
VPX_DECODER_ABI_VERSION as _,
|
||||
));
|
||||
Ok(Self { ctx })
|
||||
}
|
||||
|
||||
pub fn decode2rgb(&mut self, data: &[u8], rgba: bool) -> Result<Vec<u8>> {
|
||||
let mut img = Image::new();
|
||||
for frame in self.decode(data)? {
|
||||
drop(img);
|
||||
img = frame;
|
||||
}
|
||||
for frame in self.flush()? {
|
||||
drop(img);
|
||||
img = frame;
|
||||
}
|
||||
if img.is_null() {
|
||||
Ok(Vec::new())
|
||||
} else {
|
||||
let mut out = Default::default();
|
||||
img.rgb(1, rgba, &mut out);
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Feed some compressed data to the encoder
|
||||
///
|
||||
/// The `data` slice is sent to the decoder
|
||||
///
|
||||
/// It matches a call to `vpx_codec_decode`.
|
||||
pub fn decode(&mut self, data: &[u8]) -> Result<DecodeFrames> {
|
||||
call_vpx!(vpx_codec_decode(
|
||||
&mut self.ctx,
|
||||
data.as_ptr(),
|
||||
data.len() as _,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
));
|
||||
|
||||
Ok(DecodeFrames {
|
||||
ctx: &mut self.ctx,
|
||||
iter: ptr::null(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Notify the decoder to return any pending frame
|
||||
pub fn flush(&mut self) -> Result<DecodeFrames> {
|
||||
call_vpx!(vpx_codec_decode(
|
||||
&mut self.ctx,
|
||||
ptr::null(),
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
0
|
||||
));
|
||||
Ok(DecodeFrames {
|
||||
ctx: &mut self.ctx,
|
||||
iter: ptr::null(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Decoder {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let result = vpx_codec_destroy(&mut self.ctx);
|
||||
if result != VPX_CODEC_OK {
|
||||
panic!("failed to destroy vpx codec");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DecodeFrames<'a> {
|
||||
ctx: &'a mut vpx_codec_ctx_t,
|
||||
iter: vpx_codec_iter_t,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DecodeFrames<'a> {
|
||||
type Item = Image;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let img = unsafe { vpx_codec_get_frame(self.ctx, &mut self.iter) };
|
||||
if img.is_null() {
|
||||
return None;
|
||||
} else {
|
||||
return Some(Image(img));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://chromium.googlesource.com/webm/libvpx/+/bali/vpx/src/vpx_image.c
|
||||
pub struct Image(*mut vpx_image_t);
|
||||
impl Image {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self(std::ptr::null_mut())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.0.is_null()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> usize {
|
||||
self.inner().d_w as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize {
|
||||
self.inner().d_h as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn format(&self) -> vpx_img_fmt_t {
|
||||
// VPX_IMG_FMT_I420
|
||||
self.inner().fmt
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &vpx_image_t {
|
||||
unsafe { &*self.0 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stride(&self, iplane: usize) -> i32 {
|
||||
self.inner().stride[iplane]
|
||||
}
|
||||
|
||||
pub fn rgb(&self, stride_align: usize, rgba: bool, dst: &mut Vec<u8>) {
|
||||
let h = self.height();
|
||||
let mut w = self.width();
|
||||
let bps = if rgba { 4 } else { 3 };
|
||||
w = (w + stride_align - 1) & !(stride_align - 1);
|
||||
dst.resize(h * w * bps, 0);
|
||||
let img = self.inner();
|
||||
unsafe {
|
||||
if rgba {
|
||||
super::I420ToARGB(
|
||||
img.planes[0],
|
||||
img.stride[0],
|
||||
img.planes[1],
|
||||
img.stride[1],
|
||||
img.planes[2],
|
||||
img.stride[2],
|
||||
dst.as_mut_ptr(),
|
||||
(w * bps) as _,
|
||||
self.width() as _,
|
||||
self.height() as _,
|
||||
);
|
||||
} else {
|
||||
super::I420ToRAW(
|
||||
img.planes[0],
|
||||
img.stride[0],
|
||||
img.planes[1],
|
||||
img.stride[1],
|
||||
img.planes[2],
|
||||
img.stride[2],
|
||||
dst.as_mut_ptr(),
|
||||
(w * bps) as _,
|
||||
self.width() as _,
|
||||
self.height() as _,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn data(&self) -> (&[u8], &[u8], &[u8]) {
|
||||
unsafe {
|
||||
let img = self.inner();
|
||||
let h = (img.d_h as usize + 1) & !1;
|
||||
let n = img.stride[0] as usize * h;
|
||||
let y = slice::from_raw_parts(img.planes[0], n);
|
||||
let n = img.stride[1] as usize * (h >> 1);
|
||||
let u = slice::from_raw_parts(img.planes[1], n);
|
||||
let v = slice::from_raw_parts(img.planes[2], n);
|
||||
(y, u, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Image {
|
||||
fn drop(&mut self) {
|
||||
if !self.0.is_null() {
|
||||
unsafe { vpx_img_free(self.0) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for vpx_codec_ctx_t {}
|
||||
188
rust-rdp/rust-desk/libs/scrap/src/common/convert.rs
Normal file
188
rust-rdp/rust-desk/libs/scrap/src/common/convert.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
use super::vpx::*;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
extern "C" {
|
||||
// seems libyuv uses reverse byte order compared with our view
|
||||
|
||||
pub fn ARGBRotate(
|
||||
src_argb: *const u8,
|
||||
src_stride_argb: c_int,
|
||||
dst_argb: *mut u8,
|
||||
dst_stride_argb: c_int,
|
||||
src_width: c_int,
|
||||
src_height: c_int,
|
||||
mode: c_int,
|
||||
) -> c_int;
|
||||
|
||||
pub fn ARGBMirror(
|
||||
src_argb: *const u8,
|
||||
src_stride_argb: c_int,
|
||||
dst_argb: *mut u8,
|
||||
dst_stride_argb: c_int,
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
) -> c_int;
|
||||
|
||||
pub fn ARGBToI420(
|
||||
src_bgra: *const u8,
|
||||
src_stride_bgra: c_int,
|
||||
dst_y: *mut u8,
|
||||
dst_stride_y: c_int,
|
||||
dst_u: *mut u8,
|
||||
dst_stride_u: c_int,
|
||||
dst_v: *mut u8,
|
||||
dst_stride_v: c_int,
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
) -> c_int;
|
||||
|
||||
pub fn NV12ToI420(
|
||||
src_y: *const u8,
|
||||
src_stride_y: c_int,
|
||||
src_uv: *const u8,
|
||||
src_stride_uv: c_int,
|
||||
dst_y: *mut u8,
|
||||
dst_stride_y: c_int,
|
||||
dst_u: *mut u8,
|
||||
dst_stride_u: c_int,
|
||||
dst_v: *mut u8,
|
||||
dst_stride_v: c_int,
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
) -> c_int;
|
||||
|
||||
// I420ToRGB24: RGB little endian (bgr in memory)
|
||||
// I420ToRaw: RGB big endian (rgb in memory) to RGBA.
|
||||
pub fn I420ToRAW(
|
||||
src_y: *const u8,
|
||||
src_stride_y: c_int,
|
||||
src_u: *const u8,
|
||||
src_stride_u: c_int,
|
||||
src_v: *const u8,
|
||||
src_stride_v: c_int,
|
||||
dst_rgba: *mut u8,
|
||||
dst_stride_raw: c_int,
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
) -> c_int;
|
||||
|
||||
pub fn I420ToARGB(
|
||||
src_y: *const u8,
|
||||
src_stride_y: c_int,
|
||||
src_u: *const u8,
|
||||
src_stride_u: c_int,
|
||||
src_v: *const u8,
|
||||
src_stride_v: c_int,
|
||||
dst_rgba: *mut u8,
|
||||
dst_stride_rgba: c_int,
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
// https://github.com/webmproject/libvpx/blob/master/vpx/src/vpx_image.c
|
||||
#[inline]
|
||||
fn get_vpx_i420_stride(
|
||||
width: usize,
|
||||
height: usize,
|
||||
stride_align: usize,
|
||||
) -> (usize, usize, usize, usize, usize, usize) {
|
||||
let mut img = Default::default();
|
||||
unsafe {
|
||||
vpx_img_wrap(
|
||||
&mut img,
|
||||
vpx_img_fmt::VPX_IMG_FMT_I420,
|
||||
width as _,
|
||||
height as _,
|
||||
stride_align as _,
|
||||
0x1 as _,
|
||||
);
|
||||
}
|
||||
(
|
||||
img.w as _,
|
||||
img.h as _,
|
||||
img.stride[0] as _,
|
||||
img.stride[1] as _,
|
||||
img.planes[1] as usize - img.planes[0] as usize,
|
||||
img.planes[2] as usize - img.planes[0] as usize,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn i420_to_rgb(width: usize, height: usize, src: &[u8], dst: &mut Vec<u8>) {
|
||||
let (_, _, src_stride_y, src_stride_uv, u, v) =
|
||||
get_vpx_i420_stride(width, height, super::STRIDE_ALIGN);
|
||||
let src_y = src.as_ptr();
|
||||
let src_u = src[u..].as_ptr();
|
||||
let src_v = src[v..].as_ptr();
|
||||
dst.resize(width * height * 3, 0);
|
||||
unsafe {
|
||||
super::I420ToRAW(
|
||||
src_y,
|
||||
src_stride_y as _,
|
||||
src_u,
|
||||
src_stride_uv as _,
|
||||
src_v,
|
||||
src_stride_uv as _,
|
||||
dst.as_mut_ptr(),
|
||||
(width * 3) as _,
|
||||
width as _,
|
||||
height as _,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn bgra_to_i420(width: usize, height: usize, src: &[u8], dst: &mut Vec<u8>) {
|
||||
let (_, h, dst_stride_y, dst_stride_uv, u, v) =
|
||||
get_vpx_i420_stride(width, height, super::STRIDE_ALIGN);
|
||||
let bps = 12;
|
||||
dst.resize(h * dst_stride_y * bps / 8, 0);
|
||||
let dst_y = dst.as_mut_ptr();
|
||||
let dst_u = dst[u..].as_mut_ptr();
|
||||
let dst_v = dst[v..].as_mut_ptr();
|
||||
unsafe {
|
||||
ARGBToI420(
|
||||
src.as_ptr(),
|
||||
(src.len() / height) as _,
|
||||
dst_y,
|
||||
dst_stride_y as _,
|
||||
dst_u,
|
||||
dst_stride_uv as _,
|
||||
dst_v,
|
||||
dst_stride_uv as _,
|
||||
width as _,
|
||||
height as _,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn nv12_to_i420(
|
||||
src_y: *const u8,
|
||||
src_stride_y: c_int,
|
||||
src_uv: *const u8,
|
||||
src_stride_uv: c_int,
|
||||
width: usize,
|
||||
height: usize,
|
||||
dst: &mut Vec<u8>,
|
||||
) {
|
||||
let (w, h, dst_stride_y, dst_stride_uv, u, v) =
|
||||
get_vpx_i420_stride(width, height, super::STRIDE_ALIGN);
|
||||
let bps = 12;
|
||||
dst.resize(h * w * bps / 8, 0);
|
||||
let dst_y = dst.as_mut_ptr();
|
||||
let dst_u = dst[u..].as_mut_ptr();
|
||||
let dst_v = dst[v..].as_mut_ptr();
|
||||
NV12ToI420(
|
||||
src_y,
|
||||
src_stride_y,
|
||||
src_uv,
|
||||
src_stride_uv,
|
||||
dst_y,
|
||||
dst_stride_y as _,
|
||||
dst_u,
|
||||
dst_stride_uv as _,
|
||||
dst_v,
|
||||
dst_stride_uv as _,
|
||||
width as _,
|
||||
height as _,
|
||||
);
|
||||
}
|
||||
113
rust-rdp/rust-desk/libs/scrap/src/common/dxgi.rs
Normal file
113
rust-rdp/rust-desk/libs/scrap/src/common/dxgi.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use crate::dxgi;
|
||||
use std::io::ErrorKind::{NotFound, TimedOut, WouldBlock};
|
||||
use std::{io, ops};
|
||||
|
||||
pub struct Capturer {
|
||||
inner: dxgi::Capturer,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
impl Capturer {
|
||||
pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> {
|
||||
let width = display.width();
|
||||
let height = display.height();
|
||||
let inner = dxgi::Capturer::new(display.0, yuv)?;
|
||||
Ok(Capturer {
|
||||
inner,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_gdi(&self) -> bool {
|
||||
self.inner.is_gdi()
|
||||
}
|
||||
|
||||
pub fn set_gdi(&mut self) -> bool {
|
||||
self.inner.set_gdi()
|
||||
}
|
||||
|
||||
pub fn cancel_gdi(&mut self) {
|
||||
self.inner.cancel_gdi()
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
match self.inner.frame(timeout_ms) {
|
||||
Ok(frame) => Ok(Frame(frame)),
|
||||
Err(ref error) if error.kind() == TimedOut => Err(WouldBlock.into()),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Frame<'a>(&'a [u8]);
|
||||
|
||||
impl<'a> ops::Deref for Frame<'a> {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Display(dxgi::Display);
|
||||
|
||||
impl Display {
|
||||
pub fn primary() -> io::Result<Display> {
|
||||
// not implemented yet
|
||||
Err(NotFound.into())
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Display>> {
|
||||
let tmp = Self::all_().unwrap_or(Default::default());
|
||||
if tmp.is_empty() {
|
||||
println!("Display got from gdi");
|
||||
return Ok(dxgi::Displays::get_from_gdi()
|
||||
.drain(..)
|
||||
.map(Display)
|
||||
.collect::<Vec<_>>());
|
||||
}
|
||||
Ok(tmp)
|
||||
}
|
||||
|
||||
fn all_() -> io::Result<Vec<Display>> {
|
||||
Ok(dxgi::Displays::new()?.map(Display).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.width() as usize
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.0.height() as usize
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::prelude::*;
|
||||
OsString::from_wide(self.0.name())
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
self.0.is_online()
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> (i32, i32) {
|
||||
self.0.origin()
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-devmodea
|
||||
self.origin() == (0, 0)
|
||||
}
|
||||
}
|
||||
117
rust-rdp/rust-desk/libs/scrap/src/common/linux.rs
Normal file
117
rust-rdp/rust-desk/libs/scrap/src/common/linux.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use crate::common::{
|
||||
wayland,
|
||||
x11::{self, Frame},
|
||||
};
|
||||
use std::io;
|
||||
|
||||
pub enum Capturer {
|
||||
X11(x11::Capturer),
|
||||
WAYLAND(wayland::Capturer),
|
||||
}
|
||||
|
||||
impl Capturer {
|
||||
pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> {
|
||||
Ok(match display {
|
||||
Display::X11(d) => Capturer::X11(x11::Capturer::new(d, yuv)?),
|
||||
Display::WAYLAND(d) => Capturer::WAYLAND(wayland::Capturer::new(d, yuv)?),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
match self {
|
||||
Capturer::X11(d) => d.width(),
|
||||
Capturer::WAYLAND(d) => d.width(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
match self {
|
||||
Capturer::X11(d) => d.height(),
|
||||
Capturer::WAYLAND(d) => d.height(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
match self {
|
||||
Capturer::X11(d) => d.frame(timeout_ms),
|
||||
Capturer::WAYLAND(d) => d.frame(timeout_ms),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Display {
|
||||
X11(x11::Display),
|
||||
WAYLAND(wayland::Display),
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_wayland() -> bool {
|
||||
std::env::var("IS_WAYLAND").is_ok()
|
||||
|| std::env::var("XDG_SESSION_TYPE") == Ok("wayland".to_owned())
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub fn primary() -> io::Result<Display> {
|
||||
Ok(if is_wayland() {
|
||||
Display::WAYLAND(wayland::Display::primary()?)
|
||||
} else {
|
||||
Display::X11(x11::Display::primary()?)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Display>> {
|
||||
Ok(if is_wayland() {
|
||||
wayland::Display::all()?
|
||||
.drain(..)
|
||||
.map(|x| Display::WAYLAND(x))
|
||||
.collect()
|
||||
} else {
|
||||
x11::Display::all()?
|
||||
.drain(..)
|
||||
.map(|x| Display::X11(x))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
match self {
|
||||
Display::X11(d) => d.width(),
|
||||
Display::WAYLAND(d) => d.width(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
match self {
|
||||
Display::X11(d) => d.height(),
|
||||
Display::WAYLAND(d) => d.height(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> (i32, i32) {
|
||||
match self {
|
||||
Display::X11(d) => d.origin(),
|
||||
Display::WAYLAND(d) => d.origin(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
match self {
|
||||
Display::X11(d) => d.is_online(),
|
||||
Display::WAYLAND(d) => d.is_online(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
match self {
|
||||
Display::X11(d) => d.is_primary(),
|
||||
Display::WAYLAND(d) => d.is_primary(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
Display::X11(d) => d.name(),
|
||||
Display::WAYLAND(d) => d.name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
32
rust-rdp/rust-desk/libs/scrap/src/common/mod.rs
Normal file
32
rust-rdp/rust-desk/libs/scrap/src/common/mod.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
pub use self::codec::*;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(quartz)] {
|
||||
mod quartz;
|
||||
pub use self::quartz::*;
|
||||
} else if #[cfg(x11)] {
|
||||
cfg_if! {
|
||||
if #[cfg(feature="wayland")] {
|
||||
mod linux;
|
||||
mod wayland;
|
||||
mod x11;
|
||||
pub use self::linux::*;
|
||||
} else {
|
||||
mod x11;
|
||||
pub use self::x11::*;
|
||||
}
|
||||
}
|
||||
} else if #[cfg(dxgi)] {
|
||||
mod dxgi;
|
||||
pub use self::dxgi::*;
|
||||
} else {
|
||||
//TODO: Fallback implementation.
|
||||
}
|
||||
}
|
||||
|
||||
pub mod codec;
|
||||
mod convert;
|
||||
pub use self::convert::*;
|
||||
pub const STRIDE_ALIGN: usize = 16; // commonly used in libvpx vpx_img_alloc caller
|
||||
|
||||
mod vpx;
|
||||
125
rust-rdp/rust-desk/libs/scrap/src/common/quartz.rs
Normal file
125
rust-rdp/rust-desk/libs/scrap/src/common/quartz.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use crate::quartz;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Arc, Mutex, TryLockError};
|
||||
use std::{io, mem, ops};
|
||||
|
||||
pub struct Capturer {
|
||||
inner: quartz::Capturer,
|
||||
frame: Arc<Mutex<Option<quartz::Frame>>>,
|
||||
use_yuv: bool,
|
||||
i420: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Capturer {
|
||||
pub fn new(display: Display, use_yuv: bool) -> io::Result<Capturer> {
|
||||
let frame = Arc::new(Mutex::new(None));
|
||||
|
||||
let f = frame.clone();
|
||||
let inner = quartz::Capturer::new(
|
||||
display.0,
|
||||
display.width(),
|
||||
display.height(),
|
||||
if use_yuv {
|
||||
quartz::PixelFormat::YCbCr420Full
|
||||
} else {
|
||||
quartz::PixelFormat::Argb8888
|
||||
},
|
||||
Default::default(),
|
||||
move |inner| {
|
||||
if let Ok(mut f) = f.lock() {
|
||||
*f = Some(inner);
|
||||
}
|
||||
},
|
||||
)
|
||||
.map_err(|_| io::Error::from(io::ErrorKind::Other))?;
|
||||
|
||||
Ok(Capturer {
|
||||
inner,
|
||||
frame,
|
||||
use_yuv,
|
||||
i420: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.inner.width()
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.inner.height()
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
match self.frame.try_lock() {
|
||||
Ok(mut handle) => {
|
||||
let mut frame = None;
|
||||
mem::swap(&mut frame, &mut handle);
|
||||
|
||||
match frame {
|
||||
Some(mut frame) => {
|
||||
if self.use_yuv {
|
||||
frame.nv12_to_i420(self.width(), self.height(), &mut self.i420);
|
||||
}
|
||||
Ok(Frame(frame, PhantomData))
|
||||
}
|
||||
|
||||
None => Err(io::ErrorKind::WouldBlock.into()),
|
||||
}
|
||||
}
|
||||
|
||||
Err(TryLockError::WouldBlock) => Err(io::ErrorKind::WouldBlock.into()),
|
||||
|
||||
Err(TryLockError::Poisoned(..)) => Err(io::ErrorKind::Other.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Frame<'a>(quartz::Frame, PhantomData<&'a [u8]>);
|
||||
|
||||
impl<'a> ops::Deref for Frame<'a> {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Display(quartz::Display);
|
||||
|
||||
impl Display {
|
||||
pub fn primary() -> io::Result<Display> {
|
||||
Ok(Display(quartz::Display::primary()))
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Display>> {
|
||||
Ok(quartz::Display::online()
|
||||
.map_err(|_| io::Error::from(io::ErrorKind::Other))?
|
||||
.into_iter()
|
||||
.map(Display)
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.width()
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.0.height()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.0.id().to_string()
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
self.0.is_online()
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> (i32, i32) {
|
||||
let o = self.0.bounds().origin;
|
||||
(o.x as _, o.y as _)
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
self.0.is_primary()
|
||||
}
|
||||
}
|
||||
25
rust-rdp/rust-desk/libs/scrap/src/common/vpx.rs
Normal file
25
rust-rdp/rust-desk/libs/scrap/src/common/vpx.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(improper_ctypes)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
impl Default for vpx_codec_enc_cfg {
|
||||
fn default() -> Self {
|
||||
unsafe { std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for vpx_codec_ctx {
|
||||
fn default() -> Self {
|
||||
unsafe { std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for vpx_image_t {
|
||||
fn default() -> Self {
|
||||
unsafe { std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/vpx_ffi.rs"));
|
||||
81
rust-rdp/rust-desk/libs/scrap/src/common/wayland.rs
Normal file
81
rust-rdp/rust-desk/libs/scrap/src/common/wayland.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use crate::common::x11::Frame;
|
||||
use crate::wayland::{capturable::*, *};
|
||||
use std::io;
|
||||
|
||||
pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>);
|
||||
|
||||
fn map_err<E: ToString>(err: E) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, err.to_string())
|
||||
}
|
||||
|
||||
impl Capturer {
|
||||
pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> {
|
||||
let r = display.0.recorder(false).map_err(map_err)?;
|
||||
Ok(Capturer(display, r, yuv, Default::default()))
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.width()
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.0.height()
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
match self.1.capture(timeout_ms as _).map_err(map_err)? {
|
||||
PixelProvider::BGR0(w, h, x) => Ok(Frame(if self.2 {
|
||||
crate::common::bgra_to_i420(w as _, h as _, &x, &mut self.3);
|
||||
&self.3[..]
|
||||
} else {
|
||||
x
|
||||
})),
|
||||
PixelProvider::NONE => Err(std::io::ErrorKind::WouldBlock.into()),
|
||||
_ => Err(map_err("Invalid data")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Display(pipewire::PipeWireCapturable);
|
||||
|
||||
impl Display {
|
||||
pub fn primary() -> io::Result<Display> {
|
||||
let mut all = Display::all()?;
|
||||
if all.is_empty() {
|
||||
return Err(io::ErrorKind::NotFound.into());
|
||||
}
|
||||
Ok(all.remove(0))
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Display>> {
|
||||
Ok(pipewire::get_capturables(false)
|
||||
.map_err(map_err)?
|
||||
.drain(..)
|
||||
.map(|x| Display(x))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.size.0
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.0.size.1
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> (i32, i32) {
|
||||
self.0.position
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
87
rust-rdp/rust-desk/libs/scrap/src/common/x11.rs
Normal file
87
rust-rdp/rust-desk/libs/scrap/src/common/x11.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use crate::x11;
|
||||
use std::{io, ops};
|
||||
|
||||
pub struct Capturer(x11::Capturer);
|
||||
|
||||
impl Capturer {
|
||||
pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> {
|
||||
x11::Capturer::new(display.0, yuv).map(Capturer)
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.display().rect().w as usize
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.0.display().rect().h as usize
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
Ok(Frame(self.0.frame()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Frame<'a>(pub(crate) &'a [u8]);
|
||||
|
||||
impl<'a> ops::Deref for Frame<'a> {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Display(x11::Display);
|
||||
|
||||
impl Display {
|
||||
pub fn primary() -> io::Result<Display> {
|
||||
let server = match x11::Server::default() {
|
||||
Ok(server) => server,
|
||||
Err(_) => return Err(io::ErrorKind::ConnectionRefused.into()),
|
||||
};
|
||||
|
||||
let mut displays = x11::Server::displays(server);
|
||||
let mut best = displays.next();
|
||||
if best.as_ref().map(|x| x.is_default()) == Some(false) {
|
||||
best = displays.find(|x| x.is_default()).or(best);
|
||||
}
|
||||
|
||||
match best {
|
||||
Some(best) => Ok(Display(best)),
|
||||
None => Err(io::ErrorKind::NotFound.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Display>> {
|
||||
let server = match x11::Server::default() {
|
||||
Ok(server) => server,
|
||||
Err(_) => return Err(io::ErrorKind::ConnectionRefused.into()),
|
||||
};
|
||||
|
||||
Ok(x11::Server::displays(server).map(Display).collect())
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.0.rect().w as usize
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.0.rect().h as usize
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> (i32, i32) {
|
||||
let r = self.0.rect();
|
||||
(r.x as _, r.y as _)
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
self.0.is_default()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
213
rust-rdp/rust-desk/libs/scrap/src/dxgi/gdi.rs
Normal file
213
rust-rdp/rust-desk/libs/scrap/src/dxgi/gdi.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
use std::mem::size_of;
|
||||
use winapi::{
|
||||
shared::windef::{HBITMAP, HDC},
|
||||
um::wingdi::{
|
||||
BitBlt,
|
||||
CreateCompatibleBitmap,
|
||||
CreateCompatibleDC,
|
||||
CreateDCW,
|
||||
DeleteDC,
|
||||
DeleteObject,
|
||||
GetDIBits,
|
||||
SelectObject,
|
||||
BITMAPINFO,
|
||||
BITMAPINFOHEADER,
|
||||
BI_RGB,
|
||||
CAPTUREBLT,
|
||||
DIB_RGB_COLORS, //CAPTUREBLT,
|
||||
HGDI_ERROR,
|
||||
RGBQUAD,
|
||||
SRCCOPY,
|
||||
},
|
||||
};
|
||||
|
||||
const PIXEL_WIDTH: i32 = 4;
|
||||
|
||||
pub struct CapturerGDI {
|
||||
screen_dc: HDC,
|
||||
dc: HDC,
|
||||
bmp: HBITMAP,
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
impl CapturerGDI {
|
||||
pub fn new(name: &[u16], width: i32, height: i32) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
/* or Enumerate monitors with EnumDisplayMonitors,
|
||||
https://stackoverflow.com/questions/34987695/how-can-i-get-an-hmonitor-handle-from-a-display-device-name
|
||||
#[no_mangle]
|
||||
pub extern "C" fn callback(m: HMONITOR, dc: HDC, rect: LPRECT, lp: LPARAM) -> BOOL {}
|
||||
*/
|
||||
/*
|
||||
shared::windef::HMONITOR,
|
||||
winuser::{GetMonitorInfoW, GetSystemMetrics, MONITORINFOEXW},
|
||||
let mut mi: MONITORINFOEXW = std::mem::MaybeUninit::uninit().assume_init();
|
||||
mi.cbSize = size_of::<MONITORINFOEXW>() as _;
|
||||
if GetMonitorInfoW(m, &mut mi as *mut MONITORINFOEXW as _) == 0 {
|
||||
return Err(format!("Failed to get monitor information of: {:?}", m).into());
|
||||
}
|
||||
*/
|
||||
unsafe {
|
||||
if name.is_empty() {
|
||||
return Err("Empty display name".into());
|
||||
}
|
||||
let screen_dc = CreateDCW(&name[0], 0 as _, 0 as _, 0 as _);
|
||||
if screen_dc.is_null() {
|
||||
return Err("Failed to create dc from monitor name".into());
|
||||
}
|
||||
|
||||
// Create a Windows Bitmap, and copy the bits into it
|
||||
let dc = CreateCompatibleDC(screen_dc);
|
||||
if dc.is_null() {
|
||||
DeleteDC(screen_dc);
|
||||
return Err("Can't get a Windows display".into());
|
||||
}
|
||||
|
||||
let bmp = CreateCompatibleBitmap(screen_dc, width, height);
|
||||
if bmp.is_null() {
|
||||
DeleteDC(screen_dc);
|
||||
DeleteDC(dc);
|
||||
return Err("Can't create a Windows buffer".into());
|
||||
}
|
||||
|
||||
let res = SelectObject(dc, bmp as _);
|
||||
if res.is_null() || res == HGDI_ERROR {
|
||||
DeleteDC(screen_dc);
|
||||
DeleteDC(dc);
|
||||
DeleteObject(bmp as _);
|
||||
return Err("Can't select Windows buffer".into());
|
||||
}
|
||||
Ok(Self {
|
||||
screen_dc,
|
||||
dc,
|
||||
bmp,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame(&self, data: &mut Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
unsafe {
|
||||
let res = BitBlt(
|
||||
self.dc,
|
||||
0,
|
||||
0,
|
||||
self.width,
|
||||
self.height,
|
||||
self.screen_dc,
|
||||
0,
|
||||
0,
|
||||
SRCCOPY | CAPTUREBLT, // CAPTUREBLT enable layered window but also make cursor blinking
|
||||
);
|
||||
if res == 0 {
|
||||
return Err("Failed to copy screen to Windows buffer".into());
|
||||
}
|
||||
|
||||
let stride = self.width * PIXEL_WIDTH;
|
||||
let size: usize = (stride * self.height) as usize;
|
||||
let mut data1: Vec<u8> = Vec::with_capacity(size);
|
||||
data1.set_len(size);
|
||||
data.resize(size, 0);
|
||||
|
||||
let mut bmi = BITMAPINFO {
|
||||
bmiHeader: BITMAPINFOHEADER {
|
||||
biSize: size_of::<BITMAPINFOHEADER>() as _,
|
||||
biWidth: self.width as _,
|
||||
biHeight: self.height as _,
|
||||
biPlanes: 1,
|
||||
biBitCount: (8 * PIXEL_WIDTH) as _,
|
||||
biCompression: BI_RGB,
|
||||
biSizeImage: (self.width * self.height * PIXEL_WIDTH) as _,
|
||||
biXPelsPerMeter: 0,
|
||||
biYPelsPerMeter: 0,
|
||||
biClrUsed: 0,
|
||||
biClrImportant: 0,
|
||||
},
|
||||
bmiColors: [RGBQUAD {
|
||||
rgbBlue: 0,
|
||||
rgbGreen: 0,
|
||||
rgbRed: 0,
|
||||
rgbReserved: 0,
|
||||
}],
|
||||
};
|
||||
|
||||
// copy bits into Vec
|
||||
let res = GetDIBits(
|
||||
self.dc,
|
||||
self.bmp,
|
||||
0,
|
||||
self.height as _,
|
||||
&mut data[0] as *mut u8 as _,
|
||||
&mut bmi as _,
|
||||
DIB_RGB_COLORS,
|
||||
);
|
||||
if res == 0 {
|
||||
return Err("GetDIBits failed".into());
|
||||
}
|
||||
crate::common::ARGBMirror(
|
||||
data.as_ptr(),
|
||||
stride,
|
||||
data1.as_mut_ptr(),
|
||||
stride,
|
||||
self.width,
|
||||
self.height,
|
||||
);
|
||||
crate::common::ARGBRotate(
|
||||
data1.as_ptr(),
|
||||
stride,
|
||||
data.as_mut_ptr(),
|
||||
stride,
|
||||
self.width,
|
||||
self.height,
|
||||
180,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CapturerGDI {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
DeleteDC(self.screen_dc);
|
||||
DeleteDC(self.dc);
|
||||
DeleteObject(self.bmp as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::*;
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test() {
|
||||
match Displays::new().unwrap().next() {
|
||||
Some(d) => {
|
||||
let w = d.width();
|
||||
let h = d.height();
|
||||
let c = CapturerGDI::new(d.name(), w, h).unwrap();
|
||||
let mut data = Vec::new();
|
||||
c.frame(&mut data).unwrap();
|
||||
let mut bitflipped = Vec::with_capacity((w * h * 4) as usize);
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let i = (w * 4 * y + 4 * x) as usize;
|
||||
bitflipped.extend_from_slice(&[data[i + 2], data[i + 1], data[i], 255]);
|
||||
}
|
||||
}
|
||||
repng::encode(
|
||||
std::fs::File::create("gdi_screen.png").unwrap(),
|
||||
d.width() as u32,
|
||||
d.height() as u32,
|
||||
&bitflipped,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
_ => {
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
568
rust-rdp/rust-desk/libs/scrap/src/dxgi/mod.rs
Normal file
568
rust-rdp/rust-desk/libs/scrap/src/dxgi/mod.rs
Normal file
@@ -0,0 +1,568 @@
|
||||
use std::{io, mem, ptr, slice};
|
||||
pub mod gdi;
|
||||
pub use gdi::CapturerGDI;
|
||||
|
||||
use winapi::{
|
||||
shared::{
|
||||
dxgi::*,
|
||||
dxgi1_2::*,
|
||||
dxgitype::*,
|
||||
minwindef::{DWORD, FALSE, TRUE, UINT},
|
||||
ntdef::LONG,
|
||||
windef::HMONITOR,
|
||||
winerror::*,
|
||||
// dxgiformat::{DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_420_OPAQUE},
|
||||
},
|
||||
um::{
|
||||
d3d11::*, d3dcommon::D3D_DRIVER_TYPE_UNKNOWN, unknwnbase::IUnknown, wingdi::*,
|
||||
winnt::HRESULT, winuser::*,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ComPtr<T>(*mut T);
|
||||
impl<T> ComPtr<T> {
|
||||
fn is_null(&self) -> bool {
|
||||
self.0.is_null()
|
||||
}
|
||||
}
|
||||
impl<T> Drop for ComPtr<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.is_null() {
|
||||
(*(self.0 as *mut IUnknown)).Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Capturer {
|
||||
device: ComPtr<ID3D11Device>,
|
||||
display: Display,
|
||||
context: ComPtr<ID3D11DeviceContext>,
|
||||
duplication: ComPtr<IDXGIOutputDuplication>,
|
||||
fastlane: bool,
|
||||
surface: ComPtr<IDXGISurface>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
use_yuv: bool,
|
||||
yuv: Vec<u8>,
|
||||
rotated: Vec<u8>,
|
||||
gdi_capturer: Option<CapturerGDI>,
|
||||
gdi_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Capturer {
|
||||
pub fn new(display: Display, use_yuv: bool) -> io::Result<Capturer> {
|
||||
let mut device = ptr::null_mut();
|
||||
let mut context = ptr::null_mut();
|
||||
let mut duplication = ptr::null_mut();
|
||||
let mut desc = unsafe { mem::MaybeUninit::uninit().assume_init() };
|
||||
let mut gdi_capturer = None;
|
||||
|
||||
let mut res = if display.gdi {
|
||||
wrap_hresult(1)
|
||||
} else {
|
||||
wrap_hresult(unsafe {
|
||||
D3D11CreateDevice(
|
||||
display.adapter.0 as *mut _,
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
ptr::null_mut(), // No software rasterizer.
|
||||
0, // No device flags.
|
||||
ptr::null_mut(), // Feature levels.
|
||||
0, // Feature levels' length.
|
||||
D3D11_SDK_VERSION,
|
||||
&mut device,
|
||||
ptr::null_mut(),
|
||||
&mut context,
|
||||
)
|
||||
})
|
||||
};
|
||||
let device = ComPtr(device);
|
||||
let context = ComPtr(context);
|
||||
|
||||
if res.is_err() {
|
||||
gdi_capturer = display.create_gdi();
|
||||
println!("Fallback to GDI");
|
||||
if gdi_capturer.is_some() {
|
||||
res = Ok(());
|
||||
}
|
||||
} else {
|
||||
res = wrap_hresult(unsafe {
|
||||
let hres = (*display.inner.0).DuplicateOutput(device.0 as *mut _, &mut duplication);
|
||||
if hres != S_OK {
|
||||
gdi_capturer = display.create_gdi();
|
||||
println!("Fallback to GDI");
|
||||
if gdi_capturer.is_some() {
|
||||
S_OK
|
||||
} else {
|
||||
hres
|
||||
}
|
||||
} else {
|
||||
hres
|
||||
}
|
||||
// NVFBC(NVIDIA Capture SDK) which xpra used already deprecated, https://developer.nvidia.com/capture-sdk
|
||||
|
||||
// also try high version DXGI for better performance, e.g.
|
||||
// https://docs.microsoft.com/zh-cn/windows/win32/direct3ddxgi/dxgi-1-2-improvements
|
||||
// dxgi-1-6 may too high, only support win10 (2018)
|
||||
// https://docs.microsoft.com/zh-cn/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
|
||||
// DXGI_FORMAT_420_OPAQUE
|
||||
// IDXGIOutputDuplication::GetFrameDirtyRects and IDXGIOutputDuplication::GetFrameMoveRects
|
||||
// can help us update screen incrementally
|
||||
|
||||
/* // not supported on my PC, try in the future
|
||||
let format : Vec<DXGI_FORMAT> = vec![DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_420_OPAQUE];
|
||||
(*display.inner).DuplicateOutput1(
|
||||
device as *mut _,
|
||||
0 as UINT,
|
||||
2 as UINT,
|
||||
format.as_ptr(),
|
||||
&mut duplication
|
||||
)
|
||||
*/
|
||||
|
||||
// if above not work, I think below should not work either, try later
|
||||
// https://developer.nvidia.com/capture-sdk deprecated
|
||||
// examples using directx + nvideo sdk for GPU-accelerated video encoding/decoding
|
||||
// https://github.com/NVIDIA/video-sdk-samples
|
||||
});
|
||||
}
|
||||
|
||||
res?;
|
||||
|
||||
if !duplication.is_null() {
|
||||
unsafe {
|
||||
(*duplication).GetDesc(&mut desc);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Capturer {
|
||||
device,
|
||||
context,
|
||||
duplication: ComPtr(duplication),
|
||||
fastlane: desc.DesktopImageInSystemMemory == TRUE,
|
||||
surface: ComPtr(ptr::null_mut()),
|
||||
width: display.width() as usize,
|
||||
height: display.height() as usize,
|
||||
display,
|
||||
use_yuv,
|
||||
yuv: Vec::new(),
|
||||
rotated: Vec::new(),
|
||||
gdi_capturer,
|
||||
gdi_buffer: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_gdi(&self) -> bool {
|
||||
self.gdi_capturer.is_some()
|
||||
}
|
||||
|
||||
pub fn set_gdi(&mut self) -> bool {
|
||||
self.gdi_capturer = self.display.create_gdi();
|
||||
self.is_gdi()
|
||||
}
|
||||
|
||||
pub fn cancel_gdi(&mut self) {
|
||||
self.gdi_buffer = Vec::new();
|
||||
self.gdi_capturer.take();
|
||||
}
|
||||
|
||||
unsafe fn load_frame(&mut self, timeout: UINT) -> io::Result<(*const u8, i32)> {
|
||||
let mut frame = ptr::null_mut();
|
||||
let mut info = mem::MaybeUninit::uninit().assume_init();
|
||||
|
||||
wrap_hresult((*self.duplication.0).AcquireNextFrame(timeout, &mut info, &mut frame))?;
|
||||
let frame = ComPtr(frame);
|
||||
|
||||
if *info.LastPresentTime.QuadPart() == 0 {
|
||||
return Err(std::io::ErrorKind::WouldBlock.into());
|
||||
}
|
||||
|
||||
let mut rect = mem::MaybeUninit::uninit().assume_init();
|
||||
if self.fastlane {
|
||||
wrap_hresult((*self.duplication.0).MapDesktopSurface(&mut rect))?;
|
||||
} else {
|
||||
self.surface = ComPtr(self.ohgodwhat(frame.0)?);
|
||||
wrap_hresult((*self.surface.0).Map(&mut rect, DXGI_MAP_READ))?;
|
||||
}
|
||||
Ok((rect.pBits, rect.Pitch))
|
||||
}
|
||||
|
||||
// copy from GPU memory to system memory
|
||||
unsafe fn ohgodwhat(&mut self, frame: *mut IDXGIResource) -> io::Result<*mut IDXGISurface> {
|
||||
let mut texture: *mut ID3D11Texture2D = ptr::null_mut();
|
||||
(*frame).QueryInterface(
|
||||
&IID_ID3D11Texture2D,
|
||||
&mut texture as *mut *mut _ as *mut *mut _,
|
||||
);
|
||||
let texture = ComPtr(texture);
|
||||
|
||||
let mut texture_desc = mem::MaybeUninit::uninit().assume_init();
|
||||
(*texture.0).GetDesc(&mut texture_desc);
|
||||
|
||||
texture_desc.Usage = D3D11_USAGE_STAGING;
|
||||
texture_desc.BindFlags = 0;
|
||||
texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
texture_desc.MiscFlags = 0;
|
||||
|
||||
let mut readable = ptr::null_mut();
|
||||
wrap_hresult((*self.device.0).CreateTexture2D(
|
||||
&mut texture_desc,
|
||||
ptr::null(),
|
||||
&mut readable,
|
||||
))?;
|
||||
(*readable).SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
|
||||
let readable = ComPtr(readable);
|
||||
|
||||
let mut surface = ptr::null_mut();
|
||||
(*readable.0).QueryInterface(
|
||||
&IID_IDXGISurface,
|
||||
&mut surface as *mut *mut _ as *mut *mut _,
|
||||
);
|
||||
|
||||
(*self.context.0).CopyResource(readable.0 as *mut _, texture.0 as *mut _);
|
||||
|
||||
Ok(surface)
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, timeout: UINT) -> io::Result<&'a [u8]> {
|
||||
unsafe {
|
||||
// Release last frame.
|
||||
// No error checking needed because we don't care.
|
||||
// None of the errors crash anyway.
|
||||
let result = {
|
||||
if let Some(gdi_capturer) = &self.gdi_capturer {
|
||||
match gdi_capturer.frame(&mut self.gdi_buffer) {
|
||||
Ok(_) => &self.gdi_buffer,
|
||||
Err(err) => {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, err.to_string()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.unmap();
|
||||
let r = self.load_frame(timeout)?;
|
||||
let rotate = match self.display.rotation() {
|
||||
DXGI_MODE_ROTATION_IDENTITY | DXGI_MODE_ROTATION_UNSPECIFIED => 0,
|
||||
DXGI_MODE_ROTATION_ROTATE90 => 90,
|
||||
DXGI_MODE_ROTATION_ROTATE180 => 180,
|
||||
DXGI_MODE_ROTATION_ROTATE270 => 270,
|
||||
_ => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Unknown roration".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
if rotate == 0 {
|
||||
slice::from_raw_parts(r.0, r.1 as usize * self.height)
|
||||
} else {
|
||||
self.rotated.resize(self.width * self.height * 4, 0);
|
||||
crate::common::ARGBRotate(
|
||||
r.0,
|
||||
r.1,
|
||||
self.rotated.as_mut_ptr(),
|
||||
4 * self.width as i32,
|
||||
if rotate == 180 {
|
||||
self.width
|
||||
} else {
|
||||
self.height
|
||||
} as _,
|
||||
if rotate != 180 {
|
||||
self.width
|
||||
} else {
|
||||
self.height
|
||||
} as _,
|
||||
rotate,
|
||||
);
|
||||
&self.rotated[..]
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok({
|
||||
if self.use_yuv {
|
||||
crate::common::bgra_to_i420(
|
||||
self.width as usize,
|
||||
self.height as usize,
|
||||
&result,
|
||||
&mut self.yuv,
|
||||
);
|
||||
&self.yuv[..]
|
||||
} else {
|
||||
result
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn unmap(&self) {
|
||||
unsafe {
|
||||
(*self.duplication.0).ReleaseFrame();
|
||||
if self.fastlane {
|
||||
(*self.duplication.0).UnMapDesktopSurface();
|
||||
} else {
|
||||
if !self.surface.is_null() {
|
||||
(*self.surface.0).Unmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Capturer {
|
||||
fn drop(&mut self) {
|
||||
if !self.duplication.is_null() {
|
||||
self.unmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Displays {
|
||||
factory: ComPtr<IDXGIFactory1>,
|
||||
adapter: ComPtr<IDXGIAdapter1>,
|
||||
/// Index of the CURRENT adapter.
|
||||
nadapter: UINT,
|
||||
/// Index of the NEXT display to fetch.
|
||||
ndisplay: UINT,
|
||||
}
|
||||
|
||||
impl Displays {
|
||||
pub fn new() -> io::Result<Displays> {
|
||||
let mut factory = ptr::null_mut();
|
||||
wrap_hresult(unsafe { CreateDXGIFactory1(&IID_IDXGIFactory1, &mut factory) })?;
|
||||
|
||||
let factory = factory as *mut IDXGIFactory1;
|
||||
let mut adapter = ptr::null_mut();
|
||||
unsafe {
|
||||
// On error, our adapter is null, so it's fine.
|
||||
(*factory).EnumAdapters1(0, &mut adapter);
|
||||
};
|
||||
|
||||
Ok(Displays {
|
||||
factory: ComPtr(factory),
|
||||
adapter: ComPtr(adapter),
|
||||
nadapter: 0,
|
||||
ndisplay: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_from_gdi() -> Vec<Display> {
|
||||
let mut all = Vec::new();
|
||||
let mut i: DWORD = 0;
|
||||
loop {
|
||||
let mut d: DISPLAY_DEVICEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
|
||||
d.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as _;
|
||||
let ok = unsafe { EnumDisplayDevicesW(std::ptr::null(), i, &mut d as _, 0) };
|
||||
if ok == FALSE {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
if 0 == (d.StateFlags & DISPLAY_DEVICE_ACTIVE)
|
||||
|| (d.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) > 0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// let is_primary = (d.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) > 0;
|
||||
let mut disp = Display {
|
||||
inner: ComPtr(std::ptr::null_mut()),
|
||||
adapter: ComPtr(std::ptr::null_mut()),
|
||||
desc: unsafe { std::mem::zeroed() },
|
||||
gdi: true,
|
||||
};
|
||||
disp.desc.DeviceName = d.DeviceName;
|
||||
let mut m: DEVMODEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
|
||||
m.dmSize = std::mem::size_of::<DEVMODEW>() as _;
|
||||
m.dmDriverExtra = 0;
|
||||
let ok = unsafe {
|
||||
EnumDisplaySettingsExW(
|
||||
disp.desc.DeviceName.as_ptr(),
|
||||
ENUM_CURRENT_SETTINGS,
|
||||
&mut m as _,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if ok == FALSE {
|
||||
continue;
|
||||
}
|
||||
disp.desc.DesktopCoordinates.left = unsafe { m.u1.s2().dmPosition.x };
|
||||
disp.desc.DesktopCoordinates.top = unsafe { m.u1.s2().dmPosition.y };
|
||||
disp.desc.DesktopCoordinates.right =
|
||||
disp.desc.DesktopCoordinates.left + m.dmPelsWidth as i32;
|
||||
disp.desc.DesktopCoordinates.bottom =
|
||||
disp.desc.DesktopCoordinates.top + m.dmPelsHeight as i32;
|
||||
disp.desc.AttachedToDesktop = 1;
|
||||
all.push(disp);
|
||||
}
|
||||
all
|
||||
}
|
||||
|
||||
// No Adapter => Some(None)
|
||||
// Non-Empty Adapter => Some(Some(OUTPUT))
|
||||
// End of Adapter => None
|
||||
fn read_and_invalidate(&mut self) -> Option<Option<Display>> {
|
||||
// If there is no adapter, there is nothing left for us to do.
|
||||
|
||||
if self.adapter.is_null() {
|
||||
return Some(None);
|
||||
}
|
||||
|
||||
// Otherwise, we get the next output of the current adapter.
|
||||
|
||||
let output = unsafe {
|
||||
let mut output = ptr::null_mut();
|
||||
(*self.adapter.0).EnumOutputs(self.ndisplay, &mut output);
|
||||
ComPtr(output)
|
||||
};
|
||||
|
||||
// If the current adapter is done, we free it.
|
||||
// We return None so the caller gets the next adapter and tries again.
|
||||
|
||||
if output.is_null() {
|
||||
self.adapter = ComPtr(ptr::null_mut());
|
||||
return None;
|
||||
}
|
||||
|
||||
// Advance to the next display.
|
||||
|
||||
self.ndisplay += 1;
|
||||
|
||||
// We get the display's details.
|
||||
|
||||
let desc = unsafe {
|
||||
let mut desc = mem::MaybeUninit::uninit().assume_init();
|
||||
(*output.0).GetDesc(&mut desc);
|
||||
desc
|
||||
};
|
||||
|
||||
// We cast it up to the version needed for desktop duplication.
|
||||
|
||||
let mut inner: *mut IDXGIOutput1 = ptr::null_mut();
|
||||
unsafe {
|
||||
(*output.0).QueryInterface(&IID_IDXGIOutput1, &mut inner as *mut *mut _ as *mut *mut _);
|
||||
}
|
||||
|
||||
// If it's null, we have an error.
|
||||
// So we act like the adapter is done.
|
||||
|
||||
if inner.is_null() {
|
||||
self.adapter = ComPtr(ptr::null_mut());
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(*self.adapter.0).AddRef();
|
||||
}
|
||||
|
||||
Some(Some(Display {
|
||||
inner: ComPtr(inner),
|
||||
adapter: ComPtr(self.adapter.0),
|
||||
desc,
|
||||
gdi: false,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Displays {
|
||||
type Item = Display;
|
||||
fn next(&mut self) -> Option<Display> {
|
||||
if let Some(res) = self.read_and_invalidate() {
|
||||
res
|
||||
} else {
|
||||
// We need to replace the adapter.
|
||||
|
||||
self.ndisplay = 0;
|
||||
self.nadapter += 1;
|
||||
|
||||
self.adapter = unsafe {
|
||||
let mut adapter = ptr::null_mut();
|
||||
(*self.factory.0).EnumAdapters1(self.nadapter, &mut adapter);
|
||||
ComPtr(adapter)
|
||||
};
|
||||
|
||||
if let Some(res) = self.read_and_invalidate() {
|
||||
res
|
||||
} else {
|
||||
// All subsequent adapters will also be empty.
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Display {
|
||||
inner: ComPtr<IDXGIOutput1>,
|
||||
adapter: ComPtr<IDXGIAdapter1>,
|
||||
desc: DXGI_OUTPUT_DESC,
|
||||
gdi: bool,
|
||||
}
|
||||
|
||||
// optimized for updated region
|
||||
// https://github.com/dchapyshev/aspia/blob/master/source/base/desktop/win/dxgi_output_duplicator.cc
|
||||
// rotation
|
||||
// https://github.com/bryal/dxgcap-rs/blob/master/src/lib.rs
|
||||
|
||||
impl Display {
|
||||
pub fn width(&self) -> LONG {
|
||||
self.desc.DesktopCoordinates.right - self.desc.DesktopCoordinates.left
|
||||
}
|
||||
|
||||
pub fn height(&self) -> LONG {
|
||||
self.desc.DesktopCoordinates.bottom - self.desc.DesktopCoordinates.top
|
||||
}
|
||||
|
||||
pub fn attached_to_desktop(&self) -> bool {
|
||||
self.desc.AttachedToDesktop != 0
|
||||
}
|
||||
|
||||
pub fn rotation(&self) -> DXGI_MODE_ROTATION {
|
||||
self.desc.Rotation
|
||||
}
|
||||
|
||||
fn create_gdi(&self) -> Option<CapturerGDI> {
|
||||
if let Ok(res) = CapturerGDI::new(self.name(), self.width(), self.height()) {
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hmonitor(&self) -> HMONITOR {
|
||||
self.desc.Monitor
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &[u16] {
|
||||
let s = &self.desc.DeviceName;
|
||||
let i = s.iter().position(|&x| x == 0).unwrap_or(s.len());
|
||||
&s[..i]
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
self.desc.AttachedToDesktop != 0
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> (LONG, LONG) {
|
||||
(
|
||||
self.desc.DesktopCoordinates.left,
|
||||
self.desc.DesktopCoordinates.top,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_hresult(x: HRESULT) -> io::Result<()> {
|
||||
use std::io::ErrorKind::*;
|
||||
Err((match x {
|
||||
S_OK => return Ok(()),
|
||||
DXGI_ERROR_ACCESS_LOST => ConnectionReset,
|
||||
DXGI_ERROR_WAIT_TIMEOUT => TimedOut,
|
||||
DXGI_ERROR_INVALID_CALL => InvalidData,
|
||||
E_ACCESSDENIED => PermissionDenied,
|
||||
DXGI_ERROR_UNSUPPORTED => ConnectionRefused,
|
||||
DXGI_ERROR_NOT_CURRENTLY_AVAILABLE => Interrupted,
|
||||
DXGI_ERROR_SESSION_DISCONNECTED => ConnectionAborted,
|
||||
E_INVALIDARG => InvalidInput,
|
||||
_ => {
|
||||
// 0x8000ffff https://www.auslogics.com/en/articles/windows-10-update-error-0x8000ffff-fixed/
|
||||
return Err(io::Error::new(Other, format!("Error code: {:#X}", x)));
|
||||
}
|
||||
})
|
||||
.into())
|
||||
}
|
||||
23
rust-rdp/rust-desk/libs/scrap/src/lib.rs
Normal file
23
rust-rdp/rust-desk/libs/scrap/src/lib.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
#[cfg(quartz)]
|
||||
extern crate block;
|
||||
#[macro_use]
|
||||
extern crate cfg_if;
|
||||
pub extern crate libc;
|
||||
#[cfg(dxgi)]
|
||||
extern crate winapi;
|
||||
|
||||
pub use common::*;
|
||||
|
||||
#[cfg(quartz)]
|
||||
pub mod quartz;
|
||||
|
||||
#[cfg(x11)]
|
||||
pub mod x11;
|
||||
|
||||
#[cfg(all(x11, feature="wayland"))]
|
||||
pub mod wayland;
|
||||
|
||||
#[cfg(dxgi)]
|
||||
pub mod dxgi;
|
||||
|
||||
mod common;
|
||||
111
rust-rdp/rust-desk/libs/scrap/src/quartz/capturer.rs
Normal file
111
rust-rdp/rust-desk/libs/scrap/src/quartz/capturer.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::ptr;
|
||||
|
||||
use block::{Block, ConcreteBlock};
|
||||
use libc::c_void;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::config::Config;
|
||||
use super::display::Display;
|
||||
use super::ffi::*;
|
||||
use super::frame::Frame;
|
||||
|
||||
pub struct Capturer {
|
||||
stream: CGDisplayStreamRef,
|
||||
queue: DispatchQueue,
|
||||
|
||||
width: usize,
|
||||
height: usize,
|
||||
format: PixelFormat,
|
||||
display: Display,
|
||||
stopped: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl Capturer {
|
||||
pub fn new<F: Fn(Frame) + 'static>(
|
||||
display: Display,
|
||||
width: usize,
|
||||
height: usize,
|
||||
format: PixelFormat,
|
||||
config: Config,
|
||||
handler: F,
|
||||
) -> Result<Capturer, CGError> {
|
||||
let stopped = Arc::new(Mutex::new(false));
|
||||
let cloned_stopped = stopped.clone();
|
||||
let handler: FrameAvailableHandler = ConcreteBlock::new(move |status, _, surface, _| {
|
||||
use self::CGDisplayStreamFrameStatus::*;
|
||||
if status == Stopped {
|
||||
let mut lock = cloned_stopped.lock().unwrap();
|
||||
*lock = true;
|
||||
return;
|
||||
}
|
||||
if status == FrameComplete {
|
||||
handler(unsafe { Frame::new(surface) });
|
||||
}
|
||||
})
|
||||
.copy();
|
||||
|
||||
let queue = unsafe {
|
||||
dispatch_queue_create(
|
||||
b"quadrupleslap.scrap\0".as_ptr() as *const i8,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
let stream = unsafe {
|
||||
let config = config.build();
|
||||
let stream = CGDisplayStreamCreateWithDispatchQueue(
|
||||
display.id(),
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
config,
|
||||
queue,
|
||||
&*handler as *const Block<_, _> as *const c_void,
|
||||
);
|
||||
CFRelease(config);
|
||||
stream
|
||||
};
|
||||
|
||||
match unsafe { CGDisplayStreamStart(stream) } {
|
||||
CGError::Success => Ok(Capturer {
|
||||
stream,
|
||||
queue,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
display,
|
||||
stopped,
|
||||
}),
|
||||
x => Err(x),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
pub fn format(&self) -> PixelFormat {
|
||||
self.format
|
||||
}
|
||||
pub fn display(&self) -> Display {
|
||||
self.display
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Capturer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = CGDisplayStreamStop(self.stream);
|
||||
loop {
|
||||
if *self.stopped.lock().unwrap() {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
}
|
||||
CFRelease(self.stream);
|
||||
dispatch_release(self.queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
rust-rdp/rust-desk/libs/scrap/src/quartz/config.rs
Normal file
75
rust-rdp/rust-desk/libs/scrap/src/quartz/config.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::ptr;
|
||||
|
||||
use libc::c_void;
|
||||
|
||||
use super::ffi::*;
|
||||
|
||||
//TODO: Color space, YCbCr matrix.
|
||||
pub struct Config {
|
||||
/// Whether the cursor is visible.
|
||||
pub cursor: bool,
|
||||
/// Whether it should letterbox or stretch.
|
||||
pub letterbox: bool,
|
||||
/// Minimum seconds per frame.
|
||||
pub throttle: f64,
|
||||
/// How many frames are allocated.
|
||||
/// 3 is the recommended value.
|
||||
/// 8 is the maximum value.
|
||||
pub queue_length: i8,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Don't forget to CFRelease this!
|
||||
pub fn build(self) -> CFDictionaryRef {
|
||||
unsafe {
|
||||
let throttle = CFNumberCreate(
|
||||
ptr::null_mut(),
|
||||
CFNumberType::Float64,
|
||||
&self.throttle as *const _ as *const c_void,
|
||||
);
|
||||
let queue_length = CFNumberCreate(
|
||||
ptr::null_mut(),
|
||||
CFNumberType::SInt8,
|
||||
&self.queue_length as *const _ as *const c_void,
|
||||
);
|
||||
|
||||
let keys: [CFStringRef; 4] = [
|
||||
kCGDisplayStreamShowCursor,
|
||||
kCGDisplayStreamPreserveAspectRatio,
|
||||
kCGDisplayStreamMinimumFrameTime,
|
||||
kCGDisplayStreamQueueDepth,
|
||||
];
|
||||
let values: [*mut c_void; 4] = [
|
||||
cfbool(self.cursor),
|
||||
cfbool(self.letterbox),
|
||||
throttle,
|
||||
queue_length,
|
||||
];
|
||||
|
||||
let res = CFDictionaryCreate(
|
||||
ptr::null_mut(),
|
||||
keys.as_ptr(),
|
||||
values.as_ptr(),
|
||||
4,
|
||||
&kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks,
|
||||
);
|
||||
|
||||
CFRelease(throttle);
|
||||
CFRelease(queue_length);
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
cursor: false,
|
||||
letterbox: true,
|
||||
throttle: 0.0,
|
||||
queue_length: 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
63
rust-rdp/rust-desk/libs/scrap/src/quartz/display.rs
Normal file
63
rust-rdp/rust-desk/libs/scrap/src/quartz/display.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use std::mem;
|
||||
|
||||
use super::ffi::*;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Display(u32);
|
||||
|
||||
impl Display {
|
||||
pub fn primary() -> Display {
|
||||
Display(unsafe { CGMainDisplayID() })
|
||||
}
|
||||
|
||||
pub fn online() -> Result<Vec<Display>, CGError> {
|
||||
unsafe {
|
||||
let mut arr: [u32; 16] = mem::MaybeUninit::uninit().assume_init();
|
||||
let mut len: u32 = 0;
|
||||
|
||||
match CGGetOnlineDisplayList(16, arr.as_mut_ptr(), &mut len) {
|
||||
CGError::Success => (),
|
||||
x => return Err(x),
|
||||
}
|
||||
|
||||
let mut res = Vec::with_capacity(16);
|
||||
for i in 0..len as usize {
|
||||
res.push(Display(*arr.get_unchecked(i)));
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn width(self) -> usize {
|
||||
unsafe { CGDisplayPixelsWide(self.0) }
|
||||
}
|
||||
|
||||
pub fn height(self) -> usize {
|
||||
unsafe { CGDisplayPixelsHigh(self.0) }
|
||||
}
|
||||
|
||||
pub fn is_builtin(self) -> bool {
|
||||
unsafe { CGDisplayIsBuiltin(self.0) != 0 }
|
||||
}
|
||||
|
||||
pub fn is_primary(self) -> bool {
|
||||
unsafe { CGDisplayIsMain(self.0) != 0 }
|
||||
}
|
||||
|
||||
pub fn is_active(self) -> bool {
|
||||
unsafe { CGDisplayIsActive(self.0) != 0 }
|
||||
}
|
||||
|
||||
pub fn is_online(self) -> bool {
|
||||
unsafe { CGDisplayIsOnline(self.0) != 0 }
|
||||
}
|
||||
|
||||
pub fn bounds(self) -> CGRect {
|
||||
unsafe { CGDisplayBounds(self.0) }
|
||||
}
|
||||
}
|
||||
240
rust-rdp/rust-desk/libs/scrap/src/quartz/ffi.rs
Normal file
240
rust-rdp/rust-desk/libs/scrap/src/quartz/ffi.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use block::RcBlock;
|
||||
use libc::c_void;
|
||||
|
||||
pub type CGDisplayStreamRef = *mut c_void;
|
||||
pub type CFDictionaryRef = *mut c_void;
|
||||
pub type CFBooleanRef = *mut c_void;
|
||||
pub type CFNumberRef = *mut c_void;
|
||||
pub type CFStringRef = *mut c_void;
|
||||
pub type CGDisplayStreamUpdateRef = *mut c_void;
|
||||
pub type IOSurfaceRef = *mut c_void;
|
||||
pub type DispatchQueue = *mut c_void;
|
||||
pub type DispatchQueueAttr = *mut c_void;
|
||||
pub type CFAllocatorRef = *mut c_void;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CFDictionaryKeyCallBacks {
|
||||
callbacks: [usize; 5],
|
||||
version: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CFDictionaryValueCallBacks {
|
||||
callbacks: [usize; 4],
|
||||
version: i32,
|
||||
}
|
||||
|
||||
macro_rules! pixel_format {
|
||||
($a:expr, $b:expr, $c:expr, $d:expr) => {
|
||||
($a as i32) << 24 | ($b as i32) << 16 | ($c as i32) << 8 | ($d as i32)
|
||||
};
|
||||
}
|
||||
|
||||
pub const SURFACE_LOCK_READ_ONLY: u32 = 0x0000_0001;
|
||||
pub const SURFACE_LOCK_AVOID_SYNC: u32 = 0x0000_0002;
|
||||
|
||||
pub fn cfbool(x: bool) -> CFBooleanRef {
|
||||
unsafe {
|
||||
if x {
|
||||
kCFBooleanTrue
|
||||
} else {
|
||||
kCFBooleanFalse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum CGDisplayStreamFrameStatus {
|
||||
/// A new frame was generated.
|
||||
FrameComplete = 0,
|
||||
/// A new frame was not generated because the display did not change.
|
||||
FrameIdle = 1,
|
||||
/// A new frame was not generated because the display has gone blank.
|
||||
FrameBlank = 2,
|
||||
/// The display stream was stopped.
|
||||
Stopped = 3,
|
||||
#[doc(hidden)]
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum CFNumberType {
|
||||
/* Fixed-width types */
|
||||
SInt8 = 1,
|
||||
SInt16 = 2,
|
||||
SInt32 = 3,
|
||||
SInt64 = 4,
|
||||
Float32 = 5,
|
||||
Float64 = 6,
|
||||
/* 64-bit IEEE 754 */
|
||||
/* Basic C types */
|
||||
Char = 7,
|
||||
Short = 8,
|
||||
Int = 9,
|
||||
Long = 10,
|
||||
LongLong = 11,
|
||||
Float = 12,
|
||||
Double = 13,
|
||||
/* Other */
|
||||
CFIndex = 14,
|
||||
NSInteger = 15,
|
||||
CGFloat = 16,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
#[must_use]
|
||||
pub enum CGError {
|
||||
Success = 0,
|
||||
Failure = 1000,
|
||||
IllegalArgument = 1001,
|
||||
InvalidConnection = 1002,
|
||||
InvalidContext = 1003,
|
||||
CannotComplete = 1004,
|
||||
NotImplemented = 1006,
|
||||
RangeCheck = 1007,
|
||||
TypeCheck = 1008,
|
||||
InvalidOperation = 1010,
|
||||
NoneAvailable = 1011,
|
||||
#[doc(hidden)]
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum PixelFormat {
|
||||
/// Packed Little Endian ARGB8888
|
||||
Argb8888 = pixel_format!('B', 'G', 'R', 'A'),
|
||||
/// Packed Little Endian ARGB2101010
|
||||
Argb2101010 = pixel_format!('l', '1', '0', 'r'),
|
||||
/// 2-plane "video" range YCbCr 4:2:0
|
||||
YCbCr420Video = pixel_format!('4', '2', '0', 'v'),
|
||||
/// 2-plane "full" range YCbCr 4:2:0
|
||||
YCbCr420Full = pixel_format!('4', '2', '0', 'f'),
|
||||
#[doc(hidden)]
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
pub type CGDisplayStreamFrameAvailableHandler = *const c_void;
|
||||
|
||||
pub type FrameAvailableHandler = RcBlock<
|
||||
(
|
||||
CGDisplayStreamFrameStatus, // status
|
||||
u64, // displayTime
|
||||
IOSurfaceRef, // frameSurface
|
||||
CGDisplayStreamUpdateRef, // updateRef
|
||||
),
|
||||
(),
|
||||
>;
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub type CGFloat = f64;
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
pub type CGFloat = f32;
|
||||
#[repr(C)]
|
||||
pub struct CGPoint {
|
||||
pub x: CGFloat,
|
||||
pub y: CGFloat,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct CGSize {
|
||||
pub width: CGFloat,
|
||||
pub height: CGFloat,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct CGRect {
|
||||
pub origin: CGPoint,
|
||||
pub size: CGSize,
|
||||
}
|
||||
|
||||
#[link(name = "System", kind = "dylib")]
|
||||
#[link(name = "CoreGraphics", kind = "framework")]
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
#[link(name = "IOSurface", kind = "framework")]
|
||||
extern "C" {
|
||||
// CoreGraphics
|
||||
|
||||
pub static kCGDisplayStreamShowCursor: CFStringRef;
|
||||
pub static kCGDisplayStreamPreserveAspectRatio: CFStringRef;
|
||||
pub static kCGDisplayStreamMinimumFrameTime: CFStringRef;
|
||||
pub static kCGDisplayStreamQueueDepth: CFStringRef;
|
||||
|
||||
pub fn CGDisplayStreamCreateWithDispatchQueue(
|
||||
display: u32,
|
||||
output_width: usize,
|
||||
output_height: usize,
|
||||
pixel_format: PixelFormat,
|
||||
properties: CFDictionaryRef,
|
||||
queue: DispatchQueue,
|
||||
handler: CGDisplayStreamFrameAvailableHandler,
|
||||
) -> CGDisplayStreamRef;
|
||||
|
||||
pub fn CGDisplayStreamStart(displayStream: CGDisplayStreamRef) -> CGError;
|
||||
|
||||
pub fn CGDisplayStreamStop(displayStream: CGDisplayStreamRef) -> CGError;
|
||||
|
||||
pub fn CGMainDisplayID() -> u32;
|
||||
pub fn CGDisplayPixelsWide(display: u32) -> usize;
|
||||
pub fn CGDisplayPixelsHigh(display: u32) -> usize;
|
||||
|
||||
pub fn CGGetOnlineDisplayList(
|
||||
max_displays: u32,
|
||||
online_displays: *mut u32,
|
||||
display_count: *mut u32,
|
||||
) -> CGError;
|
||||
|
||||
pub fn CGDisplayIsBuiltin(display: u32) -> i32;
|
||||
pub fn CGDisplayIsMain(display: u32) -> i32;
|
||||
pub fn CGDisplayIsActive(display: u32) -> i32;
|
||||
pub fn CGDisplayIsOnline(display: u32) -> i32;
|
||||
|
||||
pub fn CGDisplayBounds(display: u32) -> CGRect;
|
||||
|
||||
// IOSurface
|
||||
|
||||
pub fn IOSurfaceGetAllocSize(buffer: IOSurfaceRef) -> usize;
|
||||
pub fn IOSurfaceGetBaseAddress(buffer: IOSurfaceRef) -> *mut c_void;
|
||||
pub fn IOSurfaceIncrementUseCount(buffer: IOSurfaceRef);
|
||||
pub fn IOSurfaceDecrementUseCount(buffer: IOSurfaceRef);
|
||||
pub fn IOSurfaceLock(buffer: IOSurfaceRef, options: u32, seed: *mut u32) -> i32;
|
||||
pub fn IOSurfaceUnlock(buffer: IOSurfaceRef, options: u32, seed: *mut u32) -> i32;
|
||||
pub fn IOSurfaceGetBaseAddressOfPlane(buffer: IOSurfaceRef, index: usize) -> *mut c_void;
|
||||
pub fn IOSurfaceGetBytesPerRowOfPlane(buffer: IOSurfaceRef, index: usize) -> usize;
|
||||
|
||||
// Dispatch
|
||||
|
||||
pub fn dispatch_queue_create(label: *const i8, attr: DispatchQueueAttr) -> DispatchQueue;
|
||||
|
||||
pub fn dispatch_release(object: DispatchQueue);
|
||||
|
||||
// Core Foundation
|
||||
|
||||
pub static kCFTypeDictionaryKeyCallBacks: CFDictionaryKeyCallBacks;
|
||||
pub static kCFTypeDictionaryValueCallBacks: CFDictionaryValueCallBacks;
|
||||
|
||||
// EVEN THE BOOLEANS ARE REFERENCES.
|
||||
pub static kCFBooleanTrue: CFBooleanRef;
|
||||
pub static kCFBooleanFalse: CFBooleanRef;
|
||||
|
||||
pub fn CFNumberCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
theType: CFNumberType,
|
||||
valuePtr: *const c_void,
|
||||
) -> CFNumberRef;
|
||||
|
||||
pub fn CFDictionaryCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
keys: *const *mut c_void,
|
||||
values: *const *mut c_void,
|
||||
numValues: i64,
|
||||
keyCallBacks: *const CFDictionaryKeyCallBacks,
|
||||
valueCallBacks: *const CFDictionaryValueCallBacks,
|
||||
) -> CFDictionaryRef;
|
||||
|
||||
pub fn CFRetain(cf: *const c_void);
|
||||
pub fn CFRelease(cf: *const c_void);
|
||||
}
|
||||
79
rust-rdp/rust-desk/libs/scrap/src/quartz/frame.rs
Normal file
79
rust-rdp/rust-desk/libs/scrap/src/quartz/frame.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use std::{ops, ptr, slice};
|
||||
|
||||
use super::ffi::*;
|
||||
|
||||
pub struct Frame {
|
||||
surface: IOSurfaceRef,
|
||||
inner: &'static [u8],
|
||||
i420: *mut u8,
|
||||
i420_len: usize,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
pub unsafe fn new(surface: IOSurfaceRef) -> Frame {
|
||||
CFRetain(surface);
|
||||
IOSurfaceIncrementUseCount(surface);
|
||||
|
||||
IOSurfaceLock(surface, SURFACE_LOCK_READ_ONLY, ptr::null_mut());
|
||||
|
||||
let inner = slice::from_raw_parts(
|
||||
IOSurfaceGetBaseAddress(surface) as *const u8,
|
||||
IOSurfaceGetAllocSize(surface),
|
||||
);
|
||||
|
||||
Frame {
|
||||
surface,
|
||||
inner,
|
||||
i420: ptr::null_mut(),
|
||||
i420_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nv12_to_i420<'a>(&'a mut self, w: usize, h: usize, i420: &'a mut Vec<u8>) {
|
||||
if self.inner.is_empty() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let plane0 = IOSurfaceGetBaseAddressOfPlane(self.surface, 0);
|
||||
let stride0 = IOSurfaceGetBytesPerRowOfPlane(self.surface, 0);
|
||||
let plane1 = IOSurfaceGetBaseAddressOfPlane(self.surface, 1);
|
||||
let stride1 = IOSurfaceGetBytesPerRowOfPlane(self.surface, 1);
|
||||
crate::common::nv12_to_i420(
|
||||
plane0 as _,
|
||||
stride0 as _,
|
||||
plane1 as _,
|
||||
stride1 as _,
|
||||
w,
|
||||
h,
|
||||
i420,
|
||||
);
|
||||
self.i420 = i420.as_mut_ptr() as _;
|
||||
self.i420_len = i420.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Frame {
|
||||
type Target = [u8];
|
||||
fn deref<'a>(&'a self) -> &'a [u8] {
|
||||
if self.i420.is_null() {
|
||||
self.inner
|
||||
} else {
|
||||
unsafe {
|
||||
let inner = slice::from_raw_parts(self.i420 as *const u8, self.i420_len);
|
||||
inner
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Frame {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
IOSurfaceUnlock(self.surface, SURFACE_LOCK_READ_ONLY, ptr::null_mut());
|
||||
|
||||
IOSurfaceDecrementUseCount(self.surface);
|
||||
CFRelease(self.surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
rust-rdp/rust-desk/libs/scrap/src/quartz/mod.rs
Normal file
11
rust-rdp/rust-desk/libs/scrap/src/quartz/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
pub use self::capturer::Capturer;
|
||||
pub use self::config::Config;
|
||||
pub use self::display::Display;
|
||||
pub use self::ffi::{CGError, PixelFormat};
|
||||
pub use self::frame::Frame;
|
||||
|
||||
mod capturer;
|
||||
mod config;
|
||||
mod display;
|
||||
pub mod ffi;
|
||||
mod frame;
|
||||
3
rust-rdp/rust-desk/libs/scrap/src/wayland.rs
Normal file
3
rust-rdp/rust-desk/libs/scrap/src/wayland.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod pipewire;
|
||||
mod pipewire_dbus;
|
||||
pub mod capturable;
|
||||
11
rust-rdp/rust-desk/libs/scrap/src/wayland/README.md
Normal file
11
rust-rdp/rust-desk/libs/scrap/src/wayland/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# About
|
||||
|
||||
Derived from https://github.com/H-M-H/Weylus/tree/master/src/capturable with the author's consent, https://github.com/rustdesk/rustdesk/issues/56#issuecomment-882727967
|
||||
|
||||
# Dep
|
||||
|
||||
Works fine on Ubuntu 21.04 with pipewire 3 and xdg-desktop-portal 1.8
|
||||
|
||||
`
|
||||
apt install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
||||
`
|
||||
58
rust-rdp/rust-desk/libs/scrap/src/wayland/capturable.rs
Normal file
58
rust-rdp/rust-desk/libs/scrap/src/wayland/capturable.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::boxed::Box;
|
||||
use std::error::Error;
|
||||
|
||||
pub enum PixelProvider<'a> {
|
||||
// 8 bits per color
|
||||
RGB(usize, usize, &'a [u8]),
|
||||
BGR0(usize, usize, &'a [u8]),
|
||||
// width, height, stride
|
||||
BGR0S(usize, usize, usize, &'a [u8]),
|
||||
NONE,
|
||||
}
|
||||
|
||||
impl<'a> PixelProvider<'a> {
|
||||
pub fn size(&self) -> (usize, usize) {
|
||||
match self {
|
||||
PixelProvider::RGB(w, h, _) => (*w, *h),
|
||||
PixelProvider::BGR0(w, h, _) => (*w, *h),
|
||||
PixelProvider::BGR0S(w, h, _, _) => (*w, *h),
|
||||
PixelProvider::NONE => (0, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Recorder {
|
||||
fn capture(&mut self, timeout_ms: u64) -> Result<PixelProvider, Box<dyn Error>>;
|
||||
}
|
||||
|
||||
pub trait BoxCloneCapturable {
|
||||
fn box_clone(&self) -> Box<dyn Capturable>;
|
||||
}
|
||||
|
||||
impl<T> BoxCloneCapturable for T
|
||||
where
|
||||
T: Clone + Capturable + 'static,
|
||||
{
|
||||
fn box_clone(&self) -> Box<dyn Capturable> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Capturable: Send + BoxCloneCapturable {
|
||||
/// Name of the Capturable, for example the window title, if it is a window.
|
||||
fn name(&self) -> String;
|
||||
/// Return x, y, width, height of the Capturable as floats relative to the absolute size of the
|
||||
/// screen. For example x=0.5, y=0.0, width=0.5, height=1.0 means the right half of the screen.
|
||||
fn geometry_relative(&self) -> Result<(f64, f64, f64, f64), Box<dyn Error>>;
|
||||
/// Callback that is called right before input is simulated.
|
||||
/// Useful to focus the window on input.
|
||||
fn before_input(&mut self) -> Result<(), Box<dyn Error>>;
|
||||
/// Return a Recorder that can record the current capturable.
|
||||
fn recorder(&self, capture_cursor: bool) -> Result<Box<dyn Recorder>, Box<dyn Error>>;
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn Capturable> {
|
||||
fn clone(&self) -> Self {
|
||||
self.box_clone()
|
||||
}
|
||||
}
|
||||
530
rust-rdp/rust-desk/libs/scrap/src/wayland/pipewire.rs
Normal file
530
rust-rdp/rust-desk/libs/scrap/src/wayland/pipewire.rs
Normal file
@@ -0,0 +1,530 @@
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::sync::{atomic::AtomicBool, Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use dbus::{
|
||||
arg::{OwnedFd, PropMap, RefArg, Variant},
|
||||
blocking::{Proxy, SyncConnection},
|
||||
message::{MatchRule, MessageType},
|
||||
Message,
|
||||
};
|
||||
|
||||
use gstreamer as gst;
|
||||
use gstreamer::prelude::*;
|
||||
use gstreamer_app::AppSink;
|
||||
|
||||
use super::capturable::PixelProvider;
|
||||
use super::capturable::{Capturable, Recorder};
|
||||
|
||||
use super::pipewire_dbus::{OrgFreedesktopPortalRequestResponse, OrgFreedesktopPortalScreenCast};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct PwStreamInfo {
|
||||
path: u64,
|
||||
source_type: u64,
|
||||
position: (i32, i32),
|
||||
size: (usize, usize),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DBusError(String);
|
||||
|
||||
impl std::fmt::Display for DBusError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self(s) = self;
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for DBusError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GStreamerError(String);
|
||||
|
||||
impl std::fmt::Display for GStreamerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self(s) = self;
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for GStreamerError {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PipeWireCapturable {
|
||||
// connection needs to be kept alive for recording
|
||||
dbus_conn: Arc<SyncConnection>,
|
||||
fd: OwnedFd,
|
||||
path: u64,
|
||||
source_type: u64,
|
||||
pub position: (i32, i32),
|
||||
pub size: (usize, usize),
|
||||
}
|
||||
|
||||
impl PipeWireCapturable {
|
||||
fn new(conn: Arc<SyncConnection>, fd: OwnedFd, stream: PwStreamInfo) -> Self {
|
||||
Self {
|
||||
dbus_conn: conn,
|
||||
fd,
|
||||
path: stream.path,
|
||||
source_type: stream.source_type,
|
||||
position: stream.position,
|
||||
size: stream.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PipeWireCapturable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"PipeWireCapturable {{dbus: {}, fd: {}, path: {}, source_type: {}}}",
|
||||
self.dbus_conn.unique_name(),
|
||||
self.fd.as_raw_fd(),
|
||||
self.path,
|
||||
self.source_type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Capturable for PipeWireCapturable {
|
||||
fn name(&self) -> String {
|
||||
let type_str = match self.source_type {
|
||||
1 => "Desktop",
|
||||
2 => "Window",
|
||||
_ => "Unknow",
|
||||
};
|
||||
format!("Pipewire {}, path: {}", type_str, self.path)
|
||||
}
|
||||
|
||||
fn geometry_relative(&self) -> Result<(f64, f64, f64, f64), Box<dyn Error>> {
|
||||
Ok((0.0, 0.0, 1.0, 1.0))
|
||||
}
|
||||
|
||||
fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recorder(&self, _capture_cursor: bool) -> Result<Box<dyn Recorder>, Box<dyn Error>> {
|
||||
Ok(Box::new(PipeWireRecorder::new(self.clone())?))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PipeWireRecorder {
|
||||
buffer: Option<gst::MappedBuffer<gst::buffer::Readable>>,
|
||||
buffer_cropped: Vec<u8>,
|
||||
is_cropped: bool,
|
||||
pipeline: gst::Pipeline,
|
||||
appsink: AppSink,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
impl PipeWireRecorder {
|
||||
pub fn new(capturable: PipeWireCapturable) -> Result<Self, Box<dyn Error>> {
|
||||
let pipeline = gst::Pipeline::new(None);
|
||||
|
||||
let src = gst::ElementFactory::make("pipewiresrc", None)?;
|
||||
src.set_property("fd", &capturable.fd.as_raw_fd())?;
|
||||
src.set_property("path", &format!("{}", capturable.path))?;
|
||||
|
||||
// For some reason pipewire blocks on destruction of AppSink if this is not set to true,
|
||||
// see: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/982
|
||||
src.set_property("always-copy", &true)?;
|
||||
|
||||
let sink = gst::ElementFactory::make("appsink", None)?;
|
||||
sink.set_property("drop", &true)?;
|
||||
sink.set_property("max-buffers", &1u32)?;
|
||||
|
||||
pipeline.add_many(&[&src, &sink])?;
|
||||
src.link(&sink)?;
|
||||
let appsink = sink
|
||||
.dynamic_cast::<AppSink>()
|
||||
.map_err(|_| GStreamerError("Sink element is expected to be an appsink!".into()))?;
|
||||
appsink.set_caps(Some(&gst::Caps::new_simple(
|
||||
"video/x-raw",
|
||||
&[("format", &"BGRx")],
|
||||
)));
|
||||
|
||||
pipeline.set_state(gst::State::Playing)?;
|
||||
Ok(Self {
|
||||
pipeline,
|
||||
appsink,
|
||||
buffer: None,
|
||||
width: 0,
|
||||
height: 0,
|
||||
buffer_cropped: vec![],
|
||||
is_cropped: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Recorder for PipeWireRecorder {
|
||||
fn capture(&mut self, timeout_ms: u64) -> Result<PixelProvider, Box<dyn Error>> {
|
||||
if let Some(sample) = self
|
||||
.appsink
|
||||
.try_pull_sample(gst::ClockTime::from_mseconds(timeout_ms))
|
||||
{
|
||||
let cap = sample
|
||||
.get_caps()
|
||||
.ok_or("Failed get caps")?
|
||||
.get_structure(0)
|
||||
.ok_or("Failed to get structure")?;
|
||||
let w: i32 = cap.get_value("width")?.get_some()?;
|
||||
let h: i32 = cap.get_value("height")?.get_some()?;
|
||||
let w = w as usize;
|
||||
let h = h as usize;
|
||||
let buf = sample
|
||||
.get_buffer_owned()
|
||||
.ok_or_else(|| GStreamerError("Failed to get owned buffer.".into()))?;
|
||||
let mut crop = buf
|
||||
.get_meta::<gstreamer_video::VideoCropMeta>()
|
||||
.map(|m| m.get_rect());
|
||||
// only crop if necessary
|
||||
if Some((0, 0, w as u32, h as u32)) == crop {
|
||||
crop = None;
|
||||
}
|
||||
let buf = buf
|
||||
.into_mapped_buffer_readable()
|
||||
.map_err(|_| GStreamerError("Failed to map buffer.".into()))?;
|
||||
let buf_size = buf.get_size();
|
||||
// BGRx is 4 bytes per pixel
|
||||
if buf_size != (w * h * 4) {
|
||||
// for some reason the width and height of the caps do not guarantee correct buffer
|
||||
// size, so ignore those buffers, see:
|
||||
// https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/985
|
||||
trace!(
|
||||
"Size of mapped buffer: {} does NOT match size of capturable {}x{}@BGRx, \
|
||||
dropping it!",
|
||||
buf_size,
|
||||
w,
|
||||
h
|
||||
);
|
||||
} else {
|
||||
// Copy region specified by crop into self.buffer_cropped
|
||||
// TODO: Figure out if ffmpeg provides a zero copy alternative
|
||||
if let Some((x_off, y_off, w_crop, h_crop)) = crop {
|
||||
let x_off = x_off as usize;
|
||||
let y_off = y_off as usize;
|
||||
let w_crop = w_crop as usize;
|
||||
let h_crop = h_crop as usize;
|
||||
self.buffer_cropped.clear();
|
||||
let data = buf.as_slice();
|
||||
// BGRx is 4 bytes per pixel
|
||||
self.buffer_cropped.reserve(w_crop * h_crop * 4);
|
||||
for y in y_off..(y_off + h_crop) {
|
||||
let i = 4 * (w * y + x_off);
|
||||
self.buffer_cropped.extend(&data[i..i + 4 * w_crop]);
|
||||
}
|
||||
self.width = w_crop;
|
||||
self.height = h_crop;
|
||||
} else {
|
||||
self.width = w;
|
||||
self.height = h;
|
||||
}
|
||||
self.is_cropped = crop.is_some();
|
||||
self.buffer = Some(buf);
|
||||
}
|
||||
} else {
|
||||
return Ok(PixelProvider::NONE);
|
||||
}
|
||||
if self.buffer.is_none() {
|
||||
return Err(Box::new(GStreamerError("No buffer available!".into())));
|
||||
}
|
||||
Ok(PixelProvider::BGR0(
|
||||
self.width,
|
||||
self.height,
|
||||
if self.is_cropped {
|
||||
self.buffer_cropped.as_slice()
|
||||
} else {
|
||||
self.buffer.as_ref().unwrap().as_slice()
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PipeWireRecorder {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = self.pipeline.set_state(gst::State::Null) {
|
||||
warn!("Failed to stop GStreamer pipeline: {}.", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_response<F>(
|
||||
conn: &SyncConnection,
|
||||
path: dbus::Path<'static>,
|
||||
mut f: F,
|
||||
failure_out: Arc<AtomicBool>,
|
||||
) -> Result<dbus::channel::Token, dbus::Error>
|
||||
where
|
||||
F: FnMut(
|
||||
OrgFreedesktopPortalRequestResponse,
|
||||
&SyncConnection,
|
||||
&Message,
|
||||
) -> Result<(), Box<dyn Error>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
{
|
||||
let mut m = MatchRule::new();
|
||||
m.path = Some(path);
|
||||
m.msg_type = Some(MessageType::Signal);
|
||||
m.sender = Some("org.freedesktop.portal.Desktop".into());
|
||||
m.interface = Some("org.freedesktop.portal.Request".into());
|
||||
conn.add_match(m, move |r: OrgFreedesktopPortalRequestResponse, c, m| {
|
||||
debug!("Response from DBus: response: {:?}, message: {:?}", r, m);
|
||||
match r.response {
|
||||
0 => {}
|
||||
1 => {
|
||||
warn!("DBus response: User cancelled interaction.");
|
||||
failure_out.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
return true;
|
||||
}
|
||||
c => {
|
||||
warn!("DBus response: Unknown error, code: {}.", c);
|
||||
failure_out.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if let Err(err) = f(r, c, m) {
|
||||
warn!("Error requesting screen capture via dbus: {}", err);
|
||||
failure_out.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
fn get_portal(conn: &SyncConnection) -> Proxy<&SyncConnection> {
|
||||
conn.with_proxy(
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
Duration::from_millis(1000),
|
||||
)
|
||||
}
|
||||
|
||||
fn streams_from_response(response: OrgFreedesktopPortalRequestResponse) -> Vec<PwStreamInfo> {
|
||||
(move || {
|
||||
Some(
|
||||
response
|
||||
.results
|
||||
.get("streams")?
|
||||
.as_iter()?
|
||||
.next()?
|
||||
.as_iter()?
|
||||
.filter_map(|stream| {
|
||||
let mut itr = stream.as_iter()?;
|
||||
let path = itr.next()?.as_u64()?;
|
||||
let (keys, values): (Vec<(usize, &dyn RefArg)>, Vec<(usize, &dyn RefArg)>) =
|
||||
itr.next()?
|
||||
.as_iter()?
|
||||
.enumerate()
|
||||
.partition(|(i, _)| i % 2 == 0);
|
||||
let attributes = keys
|
||||
.iter()
|
||||
.filter_map(|(_, key)| Some(key.as_str()?.to_owned()))
|
||||
.zip(
|
||||
values
|
||||
.iter()
|
||||
.map(|(_, arg)| *arg)
|
||||
.collect::<Vec<&dyn RefArg>>(),
|
||||
)
|
||||
.collect::<HashMap<String, &dyn RefArg>>();
|
||||
let mut info = PwStreamInfo {
|
||||
path,
|
||||
source_type: attributes
|
||||
.get("source_type")
|
||||
.map_or(Some(0), |v| v.as_u64())?,
|
||||
position: (0, 0),
|
||||
size: (0, 0),
|
||||
};
|
||||
let v = attributes
|
||||
.get("size")?
|
||||
.as_iter()?
|
||||
.filter_map(|v| {
|
||||
Some(
|
||||
v.as_iter()?
|
||||
.map(|x| x.as_i64().unwrap_or(0))
|
||||
.collect::<Vec<i64>>(),
|
||||
)
|
||||
})
|
||||
.next();
|
||||
if let Some(v) = v {
|
||||
if v.len() == 2 {
|
||||
info.size.0 = v[0] as _;
|
||||
info.size.1 = v[1] as _;
|
||||
}
|
||||
}
|
||||
let v = attributes
|
||||
.get("position")?
|
||||
.as_iter()?
|
||||
.filter_map(|v| {
|
||||
Some(
|
||||
v.as_iter()?
|
||||
.map(|x| x.as_i64().unwrap_or(0))
|
||||
.collect::<Vec<i64>>(),
|
||||
)
|
||||
})
|
||||
.next();
|
||||
if let Some(v) = v {
|
||||
if v.len() == 2 {
|
||||
info.position.0 = v[0] as _;
|
||||
info.position.1 = v[1] as _;
|
||||
}
|
||||
}
|
||||
Some(info)
|
||||
})
|
||||
.collect::<Vec<PwStreamInfo>>(),
|
||||
)
|
||||
})()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
static mut INIT: bool = false;
|
||||
|
||||
// mostly inspired by https://gitlab.gnome.org/snippets/19
|
||||
fn request_screen_cast(
|
||||
capture_cursor: bool,
|
||||
) -> Result<(SyncConnection, OwnedFd, Vec<PwStreamInfo>), Box<dyn Error>> {
|
||||
unsafe {
|
||||
if !INIT {
|
||||
gstreamer::init()?;
|
||||
INIT = true;
|
||||
}
|
||||
}
|
||||
let conn = SyncConnection::new_session()?;
|
||||
let portal = get_portal(&conn);
|
||||
let mut args: PropMap = HashMap::new();
|
||||
let fd: Arc<Mutex<Option<OwnedFd>>> = Arc::new(Mutex::new(None));
|
||||
let fd_res = fd.clone();
|
||||
let streams: Arc<Mutex<Vec<PwStreamInfo>>> = Arc::new(Mutex::new(Vec::new()));
|
||||
let streams_res = streams.clone();
|
||||
let failure = Arc::new(AtomicBool::new(false));
|
||||
let failure_res = failure.clone();
|
||||
args.insert(
|
||||
"session_handle_token".to_string(),
|
||||
Variant(Box::new("u1".to_string())),
|
||||
);
|
||||
args.insert(
|
||||
"handle_token".to_string(),
|
||||
Variant(Box::new("u1".to_string())),
|
||||
);
|
||||
let path = portal.create_session(args)?;
|
||||
handle_response(
|
||||
&conn,
|
||||
path,
|
||||
move |r: OrgFreedesktopPortalRequestResponse, c, _| {
|
||||
let portal = get_portal(c);
|
||||
let mut args: PropMap = HashMap::new();
|
||||
args.insert(
|
||||
"handle_token".to_string(),
|
||||
Variant(Box::new("u2".to_string())),
|
||||
);
|
||||
// https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-method-org-freedesktop-portal-ScreenCast.SelectSources
|
||||
args.insert("multiple".into(), Variant(Box::new(true)));
|
||||
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32)));
|
||||
|
||||
let cursor_mode = if capture_cursor { 2u32 } else { 1u32 };
|
||||
let plasma = std::env::var("DESKTOP_SESSION").map_or(false, |s| s.contains("plasma"));
|
||||
if plasma && capture_cursor {
|
||||
// Warn the user if capturing the cursor is tried on kde as this can crash
|
||||
// kwin_wayland and tear down the plasma desktop, see:
|
||||
// https://bugs.kde.org/show_bug.cgi?id=435042
|
||||
warn!("You are attempting to capture the cursor under KDE Plasma, this may crash your \
|
||||
desktop, see https://bugs.kde.org/show_bug.cgi?id=435042 for details! \
|
||||
You have been warned.");
|
||||
}
|
||||
args.insert("cursor_mode".into(), Variant(Box::new(cursor_mode)));
|
||||
let session: dbus::Path = r
|
||||
.results
|
||||
.get("session_handle")
|
||||
.ok_or_else(|| {
|
||||
DBusError(format!(
|
||||
"Failed to obtain session_handle from response: {:?}",
|
||||
r
|
||||
))
|
||||
})?
|
||||
.as_str()
|
||||
.ok_or_else(|| DBusError("Failed to convert session_handle to string.".into()))?
|
||||
.to_string()
|
||||
.into();
|
||||
let path = portal.select_sources(session.clone(), args)?;
|
||||
let fd = fd.clone();
|
||||
let streams = streams.clone();
|
||||
let failure = failure.clone();
|
||||
let failure_out = failure.clone();
|
||||
handle_response(
|
||||
c,
|
||||
path,
|
||||
move |_: OrgFreedesktopPortalRequestResponse, c, _| {
|
||||
let portal = get_portal(c);
|
||||
let mut args: PropMap = HashMap::new();
|
||||
args.insert(
|
||||
"handle_token".to_string(),
|
||||
Variant(Box::new("u3".to_string())),
|
||||
);
|
||||
let path = portal.start(session.clone(), "", args)?;
|
||||
let session = session.clone();
|
||||
let fd = fd.clone();
|
||||
let streams = streams.clone();
|
||||
let failure = failure.clone();
|
||||
let failure_out = failure.clone();
|
||||
handle_response(
|
||||
c,
|
||||
path,
|
||||
move |r: OrgFreedesktopPortalRequestResponse, c, _| {
|
||||
streams
|
||||
.clone()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.append(&mut streams_from_response(r));
|
||||
let portal = get_portal(c);
|
||||
fd.clone().lock().unwrap().replace(
|
||||
portal.open_pipe_wire_remote(session.clone(), HashMap::new())?,
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
failure_out,
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
failure_out,
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
failure_res.clone(),
|
||||
)?;
|
||||
// wait 3 minutes for user interaction
|
||||
for _ in 0..1800 {
|
||||
conn.process(Duration::from_millis(100))?;
|
||||
// Once we got a file descriptor we are done!
|
||||
if fd_res.lock().unwrap().is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
if failure_res.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let fd_res = fd_res.lock().unwrap();
|
||||
let streams_res = streams_res.lock().unwrap();
|
||||
if fd_res.is_some() && !streams_res.is_empty() {
|
||||
Ok((conn, fd_res.clone().unwrap(), streams_res.clone()))
|
||||
} else {
|
||||
Err(Box::new(DBusError(
|
||||
"Failed to obtain screen capture.".into(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_capturables(capture_cursor: bool) -> Result<Vec<PipeWireCapturable>, Box<dyn Error>> {
|
||||
let (conn, fd, streams) = request_screen_cast(capture_cursor)?;
|
||||
let conn = Arc::new(conn);
|
||||
Ok(streams
|
||||
.into_iter()
|
||||
.map(|s| PipeWireCapturable::new(conn.clone(), fd.clone(), s))
|
||||
.collect())
|
||||
}
|
||||
144
rust-rdp/rust-desk/libs/scrap/src/wayland/pipewire_dbus.rs
Normal file
144
rust-rdp/rust-desk/libs/scrap/src/wayland/pipewire_dbus.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
// This code was autogenerated with `dbus-codegen-rust -c blocking -m None`, see https://github.com/diwic/dbus-rs
|
||||
use dbus;
|
||||
#[allow(unused_imports)]
|
||||
use dbus::arg;
|
||||
use dbus::blocking;
|
||||
|
||||
pub trait OrgFreedesktopPortalScreenCast {
|
||||
fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'static>, dbus::Error>;
|
||||
fn select_sources(
|
||||
&self,
|
||||
session_handle: dbus::Path,
|
||||
options: arg::PropMap,
|
||||
) -> Result<dbus::Path<'static>, dbus::Error>;
|
||||
fn start(
|
||||
&self,
|
||||
session_handle: dbus::Path,
|
||||
parent_window: &str,
|
||||
options: arg::PropMap,
|
||||
) -> Result<dbus::Path<'static>, dbus::Error>;
|
||||
fn open_pipe_wire_remote(
|
||||
&self,
|
||||
session_handle: dbus::Path,
|
||||
options: arg::PropMap,
|
||||
) -> Result<arg::OwnedFd, dbus::Error>;
|
||||
fn available_source_types(&self) -> Result<u32, dbus::Error>;
|
||||
fn available_cursor_modes(&self) -> Result<u32, dbus::Error>;
|
||||
fn version(&self) -> Result<u32, dbus::Error>;
|
||||
}
|
||||
|
||||
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>>
|
||||
OrgFreedesktopPortalScreenCast for blocking::Proxy<'a, C>
|
||||
{
|
||||
fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'static>, dbus::Error> {
|
||||
self.method_call(
|
||||
"org.freedesktop.portal.ScreenCast",
|
||||
"CreateSession",
|
||||
(options,),
|
||||
)
|
||||
.map(|r: (dbus::Path<'static>,)| r.0)
|
||||
}
|
||||
|
||||
fn select_sources(
|
||||
&self,
|
||||
session_handle: dbus::Path,
|
||||
options: arg::PropMap,
|
||||
) -> Result<dbus::Path<'static>, dbus::Error> {
|
||||
self.method_call(
|
||||
"org.freedesktop.portal.ScreenCast",
|
||||
"SelectSources",
|
||||
(session_handle, options),
|
||||
)
|
||||
.map(|r: (dbus::Path<'static>,)| r.0)
|
||||
}
|
||||
|
||||
fn start(
|
||||
&self,
|
||||
session_handle: dbus::Path,
|
||||
parent_window: &str,
|
||||
options: arg::PropMap,
|
||||
) -> Result<dbus::Path<'static>, dbus::Error> {
|
||||
self.method_call(
|
||||
"org.freedesktop.portal.ScreenCast",
|
||||
"Start",
|
||||
(session_handle, parent_window, options),
|
||||
)
|
||||
.map(|r: (dbus::Path<'static>,)| r.0)
|
||||
}
|
||||
|
||||
fn open_pipe_wire_remote(
|
||||
&self,
|
||||
session_handle: dbus::Path,
|
||||
options: arg::PropMap,
|
||||
) -> Result<arg::OwnedFd, dbus::Error> {
|
||||
self.method_call(
|
||||
"org.freedesktop.portal.ScreenCast",
|
||||
"OpenPipeWireRemote",
|
||||
(session_handle, options),
|
||||
)
|
||||
.map(|r: (arg::OwnedFd,)| r.0)
|
||||
}
|
||||
|
||||
fn available_source_types(&self) -> Result<u32, dbus::Error> {
|
||||
<Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
|
||||
&self,
|
||||
"org.freedesktop.portal.ScreenCast",
|
||||
"AvailableSourceTypes",
|
||||
)
|
||||
}
|
||||
|
||||
fn available_cursor_modes(&self) -> Result<u32, dbus::Error> {
|
||||
<Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
|
||||
&self,
|
||||
"org.freedesktop.portal.ScreenCast",
|
||||
"AvailableCursorModes",
|
||||
)
|
||||
}
|
||||
|
||||
fn version(&self) -> Result<u32, dbus::Error> {
|
||||
<Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
|
||||
&self,
|
||||
"org.freedesktop.portal.ScreenCast",
|
||||
"version",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OrgFreedesktopPortalRequest {
|
||||
fn close(&self) -> Result<(), dbus::Error>;
|
||||
}
|
||||
|
||||
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgFreedesktopPortalRequest
|
||||
for blocking::Proxy<'a, C>
|
||||
{
|
||||
fn close(&self) -> Result<(), dbus::Error> {
|
||||
self.method_call("org.freedesktop.portal.Request", "Close", ())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OrgFreedesktopPortalRequestResponse {
|
||||
pub response: u32,
|
||||
pub results: arg::PropMap,
|
||||
}
|
||||
|
||||
impl arg::AppendAll for OrgFreedesktopPortalRequestResponse {
|
||||
fn append(&self, i: &mut arg::IterAppend) {
|
||||
arg::RefArg::append(&self.response, i);
|
||||
arg::RefArg::append(&self.results, i);
|
||||
}
|
||||
}
|
||||
|
||||
impl arg::ReadAll for OrgFreedesktopPortalRequestResponse {
|
||||
fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
|
||||
Ok(OrgFreedesktopPortalRequestResponse {
|
||||
response: i.read()?,
|
||||
results: i.read()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl dbus::message::SignalArgs for OrgFreedesktopPortalRequestResponse {
|
||||
const NAME: &'static str = "Response";
|
||||
const INTERFACE: &'static str = "org.freedesktop.portal.Request";
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user