Merge remote-tracking branch 'upstream/master'
# Conflicts: # src/server/connection.rs
This commit is contained in:
commit
7b24835c9e
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -4121,9 +4121,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.1"
|
version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
@ -6736,18 +6736,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webm"
|
name = "webm"
|
||||||
version = "1.0.2"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/21pages/rust-webm#d2c4d3ac133c7b0e4c0f656da710b48391981e64"
|
||||||
checksum = "ecb047148a12ef1fd8ab26302bca7e82036f005c3073b48e17cc1b44ec577136"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"webm-sys",
|
"webm-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webm-sys"
|
name = "webm-sys"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/21pages/rust-webm#d2c4d3ac133c7b0e4c0f656da710b48391981e64"
|
||||||
checksum = "0ded6ec82ccf51fe265b0b2b1579cac839574ed910c17baac58e807f8a9de7f3"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
55
Dockerfile
55
Dockerfile
@ -1,21 +1,54 @@
|
|||||||
FROM debian
|
FROM debian:bullseye-slim
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
RUN apt update -y && apt install -y g++ gcc git curl 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 libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev cmake ninja-build && rm -rf /var/lib/apt/lists/*
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt update -y && \
|
||||||
|
apt install --yes --no-install-recommends \
|
||||||
|
g++ \
|
||||||
|
gcc \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
nasm \
|
||||||
|
yasm \
|
||||||
|
libgtk-3-dev \
|
||||||
|
clang \
|
||||||
|
libxcb-randr0-dev \
|
||||||
|
libxdo-dev \
|
||||||
|
libxfixes-dev \
|
||||||
|
libxcb-shape0-dev \
|
||||||
|
libxcb-xfixes0-dev \
|
||||||
|
libasound2-dev \
|
||||||
|
libpulse-dev \
|
||||||
|
make \
|
||||||
|
cmake \
|
||||||
|
unzip \
|
||||||
|
zip \
|
||||||
|
sudo \
|
||||||
|
libgstreamer1.0-dev \
|
||||||
|
libgstreamer-plugins-base1.0-dev \
|
||||||
|
ca-certificates \
|
||||||
|
ninja-build && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN git clone --branch 2023.04.15 --depth=1 https://github.com/microsoft/vcpkg
|
RUN git clone --branch 2023.04.15 --depth=1 https://github.com/microsoft/vcpkg && \
|
||||||
RUN /vcpkg/bootstrap-vcpkg.sh -disableMetrics
|
/vcpkg/bootstrap-vcpkg.sh -disableMetrics && \
|
||||||
RUN /vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom
|
/vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom
|
||||||
|
|
||||||
|
RUN groupadd -r user && \
|
||||||
|
useradd -r -g user user --home /home/user && \
|
||||||
|
mkdir -p /home/user/rustdesk && \
|
||||||
|
chown -R user: /home/user && \
|
||||||
|
echo "user ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/user
|
||||||
|
|
||||||
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
|
WORKDIR /home/user
|
||||||
RUN curl -LO https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
RUN curl -LO https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||||
|
|
||||||
USER user
|
USER user
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \
|
||||||
RUN chmod +x rustup.sh
|
chmod +x rustup.sh && \
|
||||||
RUN ./rustup.sh -y
|
./rustup.sh -y
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
ENV HOME=/home/user
|
ENV HOME=/home/user
|
||||||
COPY ./entrypoint /
|
COPY ./entrypoint.sh /
|
||||||
ENTRYPOINT ["/entrypoint"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
34
README.md
34
README.md
@ -49,7 +49,7 @@ Go through [DEVCONTAINER.md](docs/DEVCONTAINER.md) for more info.
|
|||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
Desktop versions use [Sciter](https://sciter.com/) or Flutter for GUI, this tutorial is for Sciter only.
|
Desktop versions use Flutter or Sciter (deprecated) for GUI, this tutorial is for Sciter only, since it is easier and more friendly to starter. Check out our [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) for building Flutter version.
|
||||||
|
|
||||||
Please download Sciter dynamic library yourself.
|
Please download Sciter dynamic library yourself.
|
||||||
|
|
||||||
@ -135,34 +135,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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.
|
|
||||||
|
|
||||||
## Wayland support
|
|
||||||
|
|
||||||
Wayland does not seem to provide any API for sending keypresses to other windows. Therefore, the RustDesk uses an API from a lower level, namely the `/dev/uinput` device (Linux kernel level).
|
|
||||||
|
|
||||||
When Wayland is the controlled side, you have to start in the following way:
|
|
||||||
```bash
|
|
||||||
# Start uinput service
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
**Notice**: Wayland screen recording uses different interfaces. RustDesk currently only supports org.freedesktop.portal.ScreenCast.
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# Not support
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Support
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## How to build with Docker
|
## How to build with Docker
|
||||||
|
|
||||||
Begin by cloning the repository and building the Docker container:
|
Begin by cloning the repository and building the Docker container:
|
||||||
@ -198,12 +170,12 @@ Please ensure that you are running these commands from the root of the RustDesk
|
|||||||
- **[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/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/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
|
- **[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/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: obsolete Sciter UI (deprecated)
|
||||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/clipboard/input/video services, and network connections
|
- **[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/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/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
|
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform specific code
|
||||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for mobile
|
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for desktop and mobile
|
||||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter web client
|
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter web client
|
||||||
|
|
||||||
## Snapshots
|
## Snapshots
|
||||||
|
@ -118,10 +118,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### X11 (Xorg) إلى Wayland تغيير
|
|
||||||
|
|
||||||
افتراضية GNOME session ك Xorg إتبع [هذه](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) الخطوات لإعداد Wayland لا تدعم RustDesk
|
|
||||||
|
|
||||||
## Docker طريقة البناء باستخدام
|
## Docker طريقة البناء باستخدام
|
||||||
|
|
||||||
ابدأ باستنساخ المستودع وبناء الكونتاينر:
|
ابدأ باستنساخ المستودع وبناء الكونتاينر:
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
<a href="#file-structure">Struktura</a> •
|
<a href="#file-structure">Struktura</a> •
|
||||||
<a href="#snapshot">Ukázky</a><br>
|
<a href="#snapshot">Ukázky</a><br>
|
||||||
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
|
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
|
||||||
<b>Potřebujeme Vaši pomoc s překláním textů tohoto ČTIMNE, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">uživatelského rozhraní aplikace RustDesk</a> a <a href="https://github.com/rustdesk/doc.rustdesk.com">dokumentace k ní</a> do vašeho jazyka</b>
|
<b>Potřebujeme Vaši pomoc s překladem tohoto README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">uživatelského rozhraní aplikace RustDesk</a> a <a href="https://github.com/rustdesk/doc.rustdesk.com">dokumentace k ní</a> do vašeho jazyka</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Dopisujte si s námi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
Popovídejte si s námi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||||
|
|
||||||
|
|
||||||
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
|
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
|
||||||
@ -44,7 +44,7 @@ Varianta pro mobilní platformy používá aplikační rámec (framework) Flutte
|
|||||||
|
|
||||||
- Připravte si vývojové prostředí pro jazyky Rust a C++
|
- Připravte si vývojové prostředí pro jazyky Rust a C++
|
||||||
|
|
||||||
- Nainstalujte [vcpkg](https://github.com/microsoft/vcpkg), a nastavte správně proměnnou prostsředí `VCPKG_ROOT`
|
- Nainstalujte [vcpkg](https://github.com/microsoft/vcpkg), a správně nastavte proměnnou prostředí `VCPKG_ROOT`
|
||||||
|
|
||||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||||
- Linux/MacOS: vcpkg install libvpx libyuv opus aom
|
- Linux/MacOS: vcpkg install libvpx libyuv opus aom
|
||||||
@ -111,10 +111,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Změna z Wayland na X11 (Xorg)
|
|
||||||
|
|
||||||
RustDesk (zatím) nepodporuje zobrazovací server Wayland. Jak nastavit Xorg jako výchozí pro relace v prostředí GNOME naleznete [zde](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/).
|
|
||||||
|
|
||||||
## Jak sestavit prostřednictvím Docker kontejnerizace
|
## Jak sestavit prostřednictvím Docker kontejnerizace
|
||||||
|
|
||||||
Začněte tím, že si naklonujete tento repozitář a sestavíte docker kontejner:
|
Začněte tím, že si naklonujete tento repozitář a sestavíte docker kontejner:
|
||||||
@ -131,7 +127,7 @@ Poté pokaždé, když bude třeba aplikaci sestavit, spusťte následující p
|
|||||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||||
```
|
```
|
||||||
|
|
||||||
Všimněte si, že prvotní sestavení může trvat déle (než se do mezipaměti uloží veškeré softwarové součásti, které jsou potřeba) – následná opakování už budou rychlejší. Dále, pokud potřebujete příkazu pro sestavení zadat nějaké argumenty, je možné je zapsat na konec příkazu na pozici `<OPTIONAL-ARGS>`. Například, pokud byste chtěli sestavit optimalizovaně pro vydání, spustili byste výše uvedený příkaz následovaný `--release`. Výsledný spustitelný soubor se objeví v cílové složce na vašem systému a bude ho možné spustit pomocí:
|
Všimněte si, že prvotní sestavení může trvat déle (než se do mezipaměti uloží veškeré softwarové součásti, které jsou potřeba) – následná opakování už budou rychlejší. Pokud navíc potřebujete zadat různé argumenty příkazu pro sestavení, můžete tak učinit na konci příkazu v pozici `<OPTIONAL-ARGS>`. Například, pokud byste chtěli sestavit optimalizovanou verzi pro vydání, spustili byste výše uvedený příkaz následovaný `--release`. Výsledný spustitelný soubor se objeví v cílové složce na vašem systému a bude ho možné spustit pomocí:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
target/debug/rustdesk
|
target/debug/rustdesk
|
||||||
@ -143,7 +139,7 @@ Nebo, pokud spouštíte variantu pro vydání:
|
|||||||
target/release/rustdesk
|
target/release/rustdesk
|
||||||
```
|
```
|
||||||
|
|
||||||
Zajistětě, abyste tyto příkazy spouštěli z kořene repozitáře s RustDesk, jinak aplikace nemusí být schopná nalézt potřebné prostředky (resources). Také si všimněte, že ostatní dílčí príkazy nástroje cargo, jako třeba `install` nebo `run` zatím nejsou prostřednictvím této metody podporovány, protože by vedly k instalaci či spuštění program uvnitř kontejneru namísto přímo v systému.
|
Ujistěte se, že tyto příkazy spouštíte z kořenového adresáře RustDesk, jinak aplikace nemusí být schopná nalézt potřebné prostředky (resources). Také si všimněte, že ostatní dílčí príkazy nástroje cargo, jako třeba `install` nebo `run` zatím nejsou prostřednictvím této metody podporovány, protože by vedly k instalaci či spuštění program uvnitř kontejneru namísto přímo v systému.
|
||||||
|
|
||||||
## Struktura souborů
|
## Struktura souborů
|
||||||
|
|
||||||
|
@ -108,33 +108,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Skift Wayland til X11 (Xorg)
|
|
||||||
|
|
||||||
RustDesk understøtter ikke Wayland. Tjek [dette](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) for at konfigurere Xorg som standard GNOME-session.
|
|
||||||
|
|
||||||
## Wayland-support
|
|
||||||
|
|
||||||
Wayland ser ikke ud til at levere nogen API til at sende tastetryk til andre vinduer. Derfor bruger rustdesk et API fra et lavere niveau, nemlig `/dev/uinput`-enheden (Linux-kerneniveau).
|
|
||||||
|
|
||||||
Når wayland er den kontrollerede side, skal du starte på følgende måde:
|
|
||||||
```bash
|
|
||||||
# Start uinput service
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
**Bemærk**: Wayland-skærmoptagelse bruger forskellige grænseflader. RustDesk understøtter i øjeblikket kun org.freedesktop.portal.ScreenCast.
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# Not support
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Support
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
## Sådan bygger du med Docker
|
## Sådan bygger du med Docker
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -133,34 +133,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Wayland zu X11 (Xorg) ändern
|
|
||||||
|
|
||||||
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-Sitzung zu nutzen.
|
|
||||||
|
|
||||||
## Wayland-Unterstützung
|
|
||||||
|
|
||||||
Wayland scheint keine API für das Senden von Tastatureingaben an andere Fenster zu bieten. Daher verwendet RustDesk eine API von einer niedrigeren Ebene, nämlich dem Gerät `/dev/uinput` (Linux-Kernelebene).
|
|
||||||
|
|
||||||
Wenn Wayland die kontrollierte Seite ist, müssen Sie wie folgt vorgehen:
|
|
||||||
```bash
|
|
||||||
# Dienst uinput starten
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
**Hinweis**: Die Wayland-Bildschirmaufnahme verwendet verschiedene Schnittstellen. RustDesk unterstützt derzeit nur org.freedesktop.portal.ScreenCast.
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# Keine Unterstützung
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Unterstützung
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Auf Docker kompilieren
|
## Auf Docker kompilieren
|
||||||
|
|
||||||
Beginnen Sie damit, das Repository zu klonen und den Docker-Container zu bauen:
|
Beginnen Sie damit, das Repository zu klonen und den Docker-Container zu bauen:
|
||||||
|
@ -104,10 +104,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ŝanĝi Wayland por X11 (Xorg)
|
|
||||||
|
|
||||||
RustDesk ne subtenas Wayland. Kontrolu [tion](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) por agordi Xorg kiel defaŭlta sesio GNOME.
|
|
||||||
|
|
||||||
## Kiel kompili kun Docker
|
## Kiel kompili kun Docker
|
||||||
|
|
||||||
Komencu klonante la deponejon kaj kompilu la konteneron Docker:
|
Komencu klonante la deponejon kaj kompilu la konteneron Docker:
|
||||||
|
@ -113,34 +113,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cambia Wayland a X11 (Xorg)
|
|
||||||
|
|
||||||
RustDesk no soporta Wayland. Lee [esto](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.
|
|
||||||
|
|
||||||
## Soporte para Wayland
|
|
||||||
|
|
||||||
Wayland no parece proporcionar ninguna API para enviar pulsaciones de teclas a otras ventanas. Por lo tanto, rustdesk usa una API de nivel bajo, a saber, el dispositivo `/dev/uinput` (a nivel del kernel de Linux).
|
|
||||||
|
|
||||||
Cuando wayland esta del lado controlado, hay que iniciar de la siguiente manera:
|
|
||||||
```bash
|
|
||||||
# Empezar el servicio uinput
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
**Aviso**: La grabación de pantalla de Wayland utiliza diferentes interfaces. RustDesk actualmente sólo soporta org.freedesktop.portal.ScreenCast
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# No soportado
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Soportado
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Como compilar con Docker
|
## Como compilar con Docker
|
||||||
|
|
||||||
Empieza clonando el repositorio y compilando el contenedor de docker:
|
Empieza clonando el repositorio y compilando el contenedor de docker:
|
||||||
|
@ -112,10 +112,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### تغییر Wayland به (X11 (Xorg
|
|
||||||
|
|
||||||
راستدسک از Wayland پشتیبانی نمی کند. برای جایگزنی Xorg به عنوان پیشفرض GNOM، [اینجا](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) را کلیک کنید.
|
|
||||||
|
|
||||||
## نحوه ساخت با داکر
|
## نحوه ساخت با داکر
|
||||||
|
|
||||||
این مخزن Git را دریافت کنید و کانتینر را به روش زیر بسازید
|
این مخزن Git را دریافت کنید و کانتینر را به روش زیر بسازید
|
||||||
|
@ -104,10 +104,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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/) asettamalla Xorg oletus GNOME-istuntoon.
|
|
||||||
|
|
||||||
## Kuinka rakennetaan Dockerin kanssa
|
## Kuinka rakennetaan Dockerin kanssa
|
||||||
|
|
||||||
Aloita kloonaamalla tietovarasto ja rakentamalla docker-säiliö:
|
Aloita kloonaamalla tietovarasto ja rakentamalla docker-säiliö:
|
||||||
|
@ -104,10 +104,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
Exécution du cargo
|
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
|
## Comment construire avec Docker
|
||||||
|
|
||||||
Commencez par cloner le dépôt et construire le conteneur Docker :
|
Commencez par cloner le dépôt et construire le conteneur Docker :
|
||||||
|
@ -133,34 +133,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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 περιβάλλον.
|
|
||||||
|
|
||||||
## Υποστήριξη Wayland
|
|
||||||
|
|
||||||
Το Wayland προς το παρόν δεν διαθέτει κάποιο API το οποίο να στέλνει τα πατήματα πλήκτρων στα υπόλοιπα παράθυρα. Για τον λόγο αυτό, το Rustdesk χρησιμοποιεί ένα API από κατώτερο επίπεδο, όπως το `/dev/uinput` (Linux kernel level).
|
|
||||||
|
|
||||||
Σε περίπτωση που το Wayland είναι η ελεγχόμενη πλευρά, θα πρέπει να ξεκινήσετε με τον παρακάτω τρόπο:
|
|
||||||
```bash
|
|
||||||
# Start uinput service
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
**Σημείωση**: Η εγγραφή οθόνης του Wayland χρησιμοποιεί διαφορετικές διεπαφές. Το RustDesk προς το παρόν υποστηρίζει μόνο org.freedesktop.portal.ScreenCast.
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# Not support
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Support
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Πως να κάνετε build στο Docker
|
## Πως να κάνετε build στο Docker
|
||||||
|
|
||||||
Ξεκινήστε κλωνοποιώντας το αποθετήριο και κάνοντας build το docker container:
|
Ξεκινήστε κλωνοποιώντας το αποθετήριο και κάνοντας build το docker container:
|
||||||
|
@ -116,10 +116,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Válts Wayland-ról X11-re (Xorg)
|
|
||||||
|
|
||||||
A RustDesk nem támogatja a Waylendet. [Itt](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) található egy tutorial amelynek segítségével beállíthatod a Xorg-ot mint alap GNOME session.
|
|
||||||
|
|
||||||
## Hogyan építs Dockerrel
|
## Hogyan építs Dockerrel
|
||||||
|
|
||||||
Kezdjünk a repo clónozásával, majd pedig a Docker container megépítésével:
|
Kezdjünk a repo clónozásával, majd pedig a Docker container megépítésével:
|
||||||
|
@ -128,37 +128,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mengubah Wayland ke X11 (Xorg)
|
|
||||||
|
|
||||||
RustDesk tidak mendukung Wayland. Cek [ini](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) untuk mengonfigurasi Xorg sebagai sesi standar di GNOME.
|
|
||||||
|
|
||||||
## Kompatibilitas dengan Wayland
|
|
||||||
|
|
||||||
Sepertinya Wayland tidak memiliki API untuk mengirimkan ketukan tombol ke jendela lain. Maka dari itu, RustDesk menggunakan API dari level yang lebih rendah, lebih tepatnya perangkat `/dev/uinput` (linux kernel level)
|
|
||||||
|
|
||||||
Saat Wayland menjadi sisi yang dikendalikan atau sisi yang sedang diremote, kamu harus memulai dengan cara ini
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start uinput service
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
|
|
||||||
**Harap Diperhatikan**: Saat Perekaman layar menggunakan Wayland antarmuka (UI) yang ditampilkan akan berbeda. Untuk saat ini RustDesk hanya mendukung org.freedesktop.portal.ScreenCast.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# Not support
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Support
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cara Build dengan Docker
|
## Cara Build dengan Docker
|
||||||
|
|
||||||
Mulailah dengan melakukan kloning (clone) repositori dan build dengan docker container:
|
Mulailah dengan melakukan kloning (clone) repositori dan build dengan docker container:
|
||||||
|
@ -109,11 +109,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cambiare Wayland in X11 (Xorg)
|
|
||||||
|
|
||||||
RustDesk non supporta Wayland.
|
|
||||||
Controlla [qui](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) per configurare Xorg come sessione predefinita di GNOME.
|
|
||||||
|
|
||||||
## Come compilare con Docker
|
## Come compilare con Docker
|
||||||
|
|
||||||
Clona il repository e compila i container docker:
|
Clona il repository e compila i container docker:
|
||||||
|
@ -114,11 +114,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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でビルドする方法
|
||||||
|
|
||||||
リポジトリのクローンを作成し、Dockerコンテナを構築することから始めます。
|
リポジトリのクローンを作成し、Dockerコンテナを構築することから始めます。
|
||||||
|
@ -112,10 +112,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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에 빌드하는 방법
|
||||||
|
|
||||||
레포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다.
|
레포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다.
|
||||||
|
@ -103,10 +103,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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/) പരിശോധിക്കുക.
|
|
||||||
|
|
||||||
## ഡോക്കർ ഉപയോഗിച്ച് എങ്ങനെ നിർമ്മിക്കാം
|
## ഡോക്കർ ഉപയോഗിച്ച് എങ്ങനെ നിർമ്മിക്കാം
|
||||||
|
|
||||||
റെപ്പോസിറ്റോറി ക്ലോണുചെയ്ത് ഡോക്കർ കണ്ടെയ്നർ നിർമ്മിക്കുന്നതിലൂടെ ആരംഭിക്കുക:
|
റെപ്പോസിറ്റോറി ക്ലോണുചെയ്ത് ഡോക്കർ കണ്ടെയ്നർ നിർമ്മിക്കുന്നതിലൂടെ ആരംഭിക്കുക:
|
||||||
|
@ -130,34 +130,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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.
|
|
||||||
|
|
||||||
## Wayland support
|
|
||||||
|
|
||||||
Wayland lijkt geen API te bieden voor het verzenden van toetsaanslagen naar andere vensters. Daarom gebruikt de rustdesk een API van een lager niveau, namelijk het `/dev/uinput` apparaat (Linux kernel niveau).
|
|
||||||
|
|
||||||
Als wayland de gecontroleerde kant is, moet je op de volgende manier beginnen:
|
|
||||||
```bash
|
|
||||||
# Start uinput service
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
**Let op**: Wayland schermopname gebruikt verschillende interfaces. RustDesk ondersteunt momenteel alleen org.freedesktop.portal.ScreenCast.
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# Not support
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Support
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Bouwen met Docker
|
## Bouwen met Docker
|
||||||
|
|
||||||
Begin met het klonen van de repository en het bouwen van de docker container:
|
Begin met het klonen van de repository en het bouwen van de docker container:
|
||||||
|
@ -128,34 +128,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Zmień Wayland na X11 (Xorg)
|
|
||||||
|
|
||||||
RustDesk nie obsługuje Waylanda. Sprawdź [tutaj](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/), jak skonfigurować Xorg jako domyślną sesję GNOME.
|
|
||||||
|
|
||||||
## Wspracie Wayland
|
|
||||||
|
|
||||||
Wygląda na to, że Wayland nie wspiera żadnego API do wysyłania naciśnięć klawiszy do innych okien. Dlatego rustdesk używa API z niższego poziomu, urządzenia o nazwie `/dev/uinput` (poziom jądra Linux).
|
|
||||||
|
|
||||||
Gdy po stronie kontrolowanej pracuje Wayland, musisz uruchomić program w następujący sposób:
|
|
||||||
```bash
|
|
||||||
# Start uinput service
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
**Uwaga**: Nagrywanie ekranu Wayland wykorzystuje różne interfejsy. RustDesk obecnie obsługuje tylko org.freedesktop.portal.ScreenCast.
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# Not support
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Support
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Jak kompilować za pomocą Dockera
|
## Jak kompilować za pomocą Dockera
|
||||||
|
|
||||||
Rozpocznij od sklonowania repozytorium i stworzenia kontenera docker:
|
Rozpocznij od sklonowania repozytorium i stworzenia kontenera docker:
|
||||||
|
@ -104,10 +104,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mude Wayland para X11 (Xorg)
|
|
||||||
|
|
||||||
RustDesk não suporta Wayland. Veja [esse link](https://docs.fedoraproject.org/pt_BR/quick-docs/configuring-xorg-as-default-gnome-session/) para configurar o Xorg como a sessão padrão do GNOME.
|
|
||||||
|
|
||||||
## Como compilar com Docker
|
## Como compilar com Docker
|
||||||
|
|
||||||
Comece clonando o repositório e montando o container docker:
|
Comece clonando o repositório e montando o container docker:
|
||||||
|
@ -114,10 +114,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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
|
||||||
|
|
||||||
Начните с клонирования репозитория и создания docker-контейнера:
|
Начните с клонирования репозитория и создания docker-контейнера:
|
||||||
|
@ -138,34 +138,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Wayland'ı X11 (Xorg) Olarak Değiştirme
|
|
||||||
|
|
||||||
RustDesk, Wayland'ı desteklemez. Xorg'u GNOME oturumu olarak varsayılan olarak ayarlamak için [burayı](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) kontrol edin.
|
|
||||||
|
|
||||||
## Wayland Desteği
|
|
||||||
|
|
||||||
Wayland'ın diğer pencerelere tuş vuruşu göndermek için herhangi bir API sağlamadığı görünmektedir. Bu nedenle, RustDesk daha düşük bir seviyeden, yani Linux çekirdek seviyesindeki `/dev/uinput` cihazının API'sini kullanır.
|
|
||||||
|
|
||||||
Wayland tarafı kontrol edildiğinde, aşağıdaki şekilde başlatmanız gerekir:
|
|
||||||
```bash
|
|
||||||
# uinput servisini başlatın
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
**Uyarı**: Wayland ekran kaydı farklı arayüzler kullanır. RustDesk şu anda yalnızca org.freedesktop.portal.ScreenCast'ı destekler.
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# Desteklenmez
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Desteklenir
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Docker ile Derleme Nasıl Yapılır
|
## Docker ile Derleme Nasıl Yapılır
|
||||||
|
|
||||||
Öncelikle deposunu klonlayın ve Docker konteynerini oluşturun:
|
Öncelikle deposunu klonlayın ve Docker konteynerini oluşturun:
|
||||||
|
@ -131,10 +131,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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
|
||||||
|
|
||||||
Почніть з клонування сховища та створення docker-контейнера:
|
Почніть з клонування сховища та створення docker-контейнера:
|
||||||
|
@ -116,10 +116,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Chuyển từ Wayland sang X11 (Xorg)
|
|
||||||
|
|
||||||
RustDesk hiện không hỗ trợ Wayland. Hãy xem [đường linh ở đây](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) cách để cài đặt Xorg làm session mặc định của GNOME.
|
|
||||||
|
|
||||||
## Cách để build sử dụng Docker
|
## Cách để build sử dụng Docker
|
||||||
|
|
||||||
Bắt đầu bằng cách sao chép repo này về máy tính và build cái Docker cointainer:
|
Bắt đầu bằng cách sao chép repo này về máy tính và build cái Docker cointainer:
|
||||||
|
@ -134,39 +134,6 @@ mv libsciter-gtk.so target/debug
|
|||||||
VCPKG_ROOT=$HOME/vcpkg cargo run
|
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 session.
|
|
||||||
|
|
||||||
## Wayland 支持
|
|
||||||
|
|
||||||
Wayland 似乎没有提供任何将按键发送到其他窗口的 API. 因此, RustDesk 使用较低级别的 API, 即 `/dev/uinput` devices (Linux kernal level).
|
|
||||||
|
|
||||||
当 Wayland 是受控方时,您必须以下列方式开始操作:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start uinput service
|
|
||||||
$ sudo rustdesk --service
|
|
||||||
$ rustdesk
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notice**: Wayland 屏幕录制使用不同的接口. RustDesk 目前只支持 org.freedesktop.portal.ScreenCast.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dbus-send --session --print-reply \
|
|
||||||
--dest=org.freedesktop.portal.Desktop \
|
|
||||||
/org/freedesktop/portal/desktop \
|
|
||||||
org.freedesktop.DBus.Properties.Get \
|
|
||||||
string:org.freedesktop.portal.ScreenCast string:version
|
|
||||||
# Not support
|
|
||||||
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
|
||||||
# Support
|
|
||||||
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
|
||||||
variant uint32 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用 Docker 编译
|
## 使用 Docker 编译
|
||||||
|
|
||||||
克隆版本库并构建 Docker 容器:
|
克隆版本库并构建 Docker 容器:
|
||||||
|
@ -14,8 +14,8 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
|
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||||
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
|
||||||
import 'package:flutter_hbb/main.dart';
|
import 'package:flutter_hbb/main.dart';
|
||||||
|
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
||||||
import 'package:flutter_hbb/models/peer_model.dart';
|
import 'package:flutter_hbb/models/peer_model.dart';
|
||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||||
@ -53,6 +53,9 @@ int androidVersion = 0;
|
|||||||
int windowsBuildNumber = 0;
|
int windowsBuildNumber = 0;
|
||||||
DesktopType? desktopType;
|
DesktopType? desktopType;
|
||||||
|
|
||||||
|
bool get isMainDesktopWindow =>
|
||||||
|
desktopType == DesktopType.main || desktopType == DesktopType.cm;
|
||||||
|
|
||||||
/// Check if the app is running with single view mode.
|
/// Check if the app is running with single view mode.
|
||||||
bool isSingleViewApp() {
|
bool isSingleViewApp() {
|
||||||
return desktopType == DesktopType.cm;
|
return desktopType == DesktopType.cm;
|
||||||
@ -955,7 +958,7 @@ class CustomAlertDialog extends StatelessWidget {
|
|||||||
|
|
||||||
void msgBox(SessionID sessionId, String type, String title, String text,
|
void msgBox(SessionID sessionId, String type, String title, String text,
|
||||||
String link, OverlayDialogManager dialogManager,
|
String link, OverlayDialogManager dialogManager,
|
||||||
{bool? hasCancel, ReconnectHandle? reconnect}) {
|
{bool? hasCancel, ReconnectHandle? reconnect, int? reconnectTimeout}) {
|
||||||
dialogManager.dismissAll();
|
dialogManager.dismissAll();
|
||||||
List<Widget> buttons = [];
|
List<Widget> buttons = [];
|
||||||
bool hasOk = false;
|
bool hasOk = false;
|
||||||
@ -995,22 +998,21 @@ void msgBox(SessionID sessionId, String type, String title, String text,
|
|||||||
dialogManager.dismissAll();
|
dialogManager.dismissAll();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if (reconnect != null && title == "Connection Error") {
|
if (reconnect != null &&
|
||||||
|
title == "Connection Error" &&
|
||||||
|
reconnectTimeout != null) {
|
||||||
// `enabled` is used to disable the dialog button once the button is clicked.
|
// `enabled` is used to disable the dialog button once the button is clicked.
|
||||||
final enabled = true.obs;
|
final enabled = true.obs;
|
||||||
final button = Obx(
|
final button = Obx(() => _ReconnectCountDownButton(
|
||||||
() => dialogButton(
|
second: reconnectTimeout,
|
||||||
'Reconnect',
|
onPressed: enabled.isTrue
|
||||||
isOutline: true,
|
? () {
|
||||||
onPressed: enabled.isTrue
|
// Disable the button
|
||||||
? () {
|
enabled.value = false;
|
||||||
// Disable the button
|
reconnect(dialogManager, sessionId, false);
|
||||||
enabled.value = false;
|
}
|
||||||
reconnect(dialogManager, sessionId, false);
|
: null,
|
||||||
}
|
));
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
buttons.insert(0, button);
|
buttons.insert(0, button);
|
||||||
}
|
}
|
||||||
if (link.isNotEmpty) {
|
if (link.isNotEmpty) {
|
||||||
@ -1491,8 +1493,8 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
|
|||||||
late Offset position;
|
late Offset position;
|
||||||
late Size sz;
|
late Size sz;
|
||||||
late bool isMaximized;
|
late bool isMaximized;
|
||||||
bool isFullscreen = stateGlobal.fullscreen ||
|
bool isFullscreen = stateGlobal.fullscreen.isTrue ||
|
||||||
(Platform.isMacOS && stateGlobal.closeOnFullscreen);
|
(Platform.isMacOS && stateGlobal.closeOnFullscreen == true);
|
||||||
setFrameIfMaximized() {
|
setFrameIfMaximized() {
|
||||||
if (isMaximized) {
|
if (isMaximized) {
|
||||||
final pos = bind.getLocalFlutterOption(k: kWindowPrefix + type.name);
|
final pos = bind.getLocalFlutterOption(k: kWindowPrefix + type.name);
|
||||||
@ -1670,8 +1672,10 @@ Future<Offset?> _adjustRestoreMainWindowOffset(
|
|||||||
|
|
||||||
/// Restore window position and size on start
|
/// Restore window position and size on start
|
||||||
/// Note that windowId must be provided if it's subwindow
|
/// Note that windowId must be provided if it's subwindow
|
||||||
|
//
|
||||||
|
// display is used to set the offset of the window in individual display mode.
|
||||||
Future<bool> restoreWindowPosition(WindowType type,
|
Future<bool> restoreWindowPosition(WindowType type,
|
||||||
{int? windowId, String? peerId}) async {
|
{int? windowId, String? peerId, int? display}) async {
|
||||||
if (bind
|
if (bind
|
||||||
.mainGetEnv(key: "DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION")
|
.mainGetEnv(key: "DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION")
|
||||||
.isNotEmpty) {
|
.isNotEmpty) {
|
||||||
@ -1707,14 +1711,22 @@ Future<bool> restoreWindowPosition(WindowType type,
|
|||||||
debugPrint("no window position saved, ignoring position restoration");
|
debugPrint("no window position saved, ignoring position restoration");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (type == WindowType.RemoteDesktop &&
|
if (type == WindowType.RemoteDesktop) {
|
||||||
!isRemotePeerPos &&
|
if (!isRemotePeerPos && windowId != null) {
|
||||||
windowId != null) {
|
if (lpos.offsetWidth != null) {
|
||||||
if (lpos.offsetWidth != null) {
|
lpos.offsetWidth = lpos.offsetWidth! + windowId * kNewWindowOffset;
|
||||||
lpos.offsetWidth = lpos.offsetWidth! + windowId * 20;
|
}
|
||||||
|
if (lpos.offsetHeight != null) {
|
||||||
|
lpos.offsetHeight = lpos.offsetHeight! + windowId * kNewWindowOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (lpos.offsetHeight != null) {
|
if (display != null) {
|
||||||
lpos.offsetHeight = lpos.offsetHeight! + windowId * 20;
|
if (lpos.offsetWidth != null) {
|
||||||
|
lpos.offsetWidth = lpos.offsetWidth! + display * kNewWindowOffset;
|
||||||
|
}
|
||||||
|
if (lpos.offsetHeight != null) {
|
||||||
|
lpos.offsetHeight = lpos.offsetHeight! + display * kNewWindowOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2012,6 +2024,10 @@ connect(
|
|||||||
final idController = Get.find<IDTextEditingController>();
|
final idController = Get.find<IDTextEditingController>();
|
||||||
idController.text = formatID(id);
|
idController.text = formatID(id);
|
||||||
}
|
}
|
||||||
|
if (Get.isRegistered<TextEditingController>()){
|
||||||
|
final fieldTextEditingController = Get.find<TextEditingController>();
|
||||||
|
fieldTextEditingController.text = formatID(id);
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
id = id.replaceAll(' ', '');
|
id = id.replaceAll(' ', '');
|
||||||
@ -2605,3 +2621,183 @@ bool isChooseDisplayToOpenInNewWindow(PeerInfo pi, SessionID sessionId) =>
|
|||||||
pi.isSupportMultiDisplay &&
|
pi.isSupportMultiDisplay &&
|
||||||
useTextureRender &&
|
useTextureRender &&
|
||||||
bind.sessionGetDisplaysAsIndividualWindows(sessionId: sessionId) == 'Y';
|
bind.sessionGetDisplaysAsIndividualWindows(sessionId: sessionId) == 'Y';
|
||||||
|
|
||||||
|
Future<List<Rect>> getScreenListWayland() async {
|
||||||
|
final screenRectList = <Rect>[];
|
||||||
|
if (isMainDesktopWindow) {
|
||||||
|
for (var screen in await window_size.getScreenList()) {
|
||||||
|
final scale = kIgnoreDpi ? 1.0 : screen.scaleFactor;
|
||||||
|
double l = screen.frame.left;
|
||||||
|
double t = screen.frame.top;
|
||||||
|
double r = screen.frame.right;
|
||||||
|
double b = screen.frame.bottom;
|
||||||
|
final rect = Rect.fromLTRB(l / scale, t / scale, r / scale, b / scale);
|
||||||
|
screenRectList.add(rect);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final screenList = await rustDeskWinManager.call(
|
||||||
|
WindowType.Main, kWindowGetScreenList, '');
|
||||||
|
try {
|
||||||
|
for (var screen in jsonDecode(screenList.result) as List<dynamic>) {
|
||||||
|
final scale = kIgnoreDpi ? 1.0 : screen['scaleFactor'];
|
||||||
|
double l = screen['frame']['l'];
|
||||||
|
double t = screen['frame']['t'];
|
||||||
|
double r = screen['frame']['r'];
|
||||||
|
double b = screen['frame']['b'];
|
||||||
|
final rect = Rect.fromLTRB(l / scale, t / scale, r / scale, b / scale);
|
||||||
|
screenRectList.add(rect);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Failed to parse screenList: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return screenRectList;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Rect>> getScreenListNotWayland() async {
|
||||||
|
final screenRectList = <Rect>[];
|
||||||
|
final displays = bind.mainGetDisplays();
|
||||||
|
if (displays.isEmpty) {
|
||||||
|
return screenRectList;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (var display in jsonDecode(displays) as List<dynamic>) {
|
||||||
|
// to-do: scale factor ?
|
||||||
|
// final scale = kIgnoreDpi ? 1.0 : screen.scaleFactor;
|
||||||
|
double l = display['x'].toDouble();
|
||||||
|
double t = display['y'].toDouble();
|
||||||
|
double r = (display['x'] + display['w']).toDouble();
|
||||||
|
double b = (display['y'] + display['h']).toDouble();
|
||||||
|
screenRectList.add(Rect.fromLTRB(l, t, r, b));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Failed to parse displays: $e');
|
||||||
|
}
|
||||||
|
return screenRectList;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Rect>> getScreenRectList() async {
|
||||||
|
return bind.mainCurrentIsWayland()
|
||||||
|
? await getScreenListWayland()
|
||||||
|
: await getScreenListNotWayland();
|
||||||
|
}
|
||||||
|
|
||||||
|
openMonitorInTheSameTab(int i, FFI ffi, PeerInfo pi) {
|
||||||
|
final displays = i == kAllDisplayValue
|
||||||
|
? List.generate(pi.displays.length, (index) => index)
|
||||||
|
: [i];
|
||||||
|
bind.sessionSwitchDisplay(
|
||||||
|
sessionId: ffi.sessionId, value: Int32List.fromList(displays));
|
||||||
|
ffi.ffiModel.switchToNewDisplay(i, ffi.sessionId, ffi.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open new tab or window to show this monitor.
|
||||||
|
// For now just open new window.
|
||||||
|
//
|
||||||
|
// screenRect is used to move the new window to the specified screen and set fullscreen.
|
||||||
|
openMonitorInNewTabOrWindow(int i, String peerId, PeerInfo pi,
|
||||||
|
{Rect? screenRect}) {
|
||||||
|
final args = {
|
||||||
|
'window_id': stateGlobal.windowId,
|
||||||
|
'peer_id': peerId,
|
||||||
|
'display': i,
|
||||||
|
'display_count': pi.displays.length,
|
||||||
|
};
|
||||||
|
if (screenRect != null) {
|
||||||
|
args['screen_rect'] = {
|
||||||
|
'l': screenRect.left,
|
||||||
|
't': screenRect.top,
|
||||||
|
'r': screenRect.right,
|
||||||
|
'b': screenRect.bottom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
DesktopMultiWindow.invokeMethod(
|
||||||
|
kMainWindowId, kWindowEventOpenMonitorSession, jsonEncode(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
tryMoveToScreenAndSetFullscreen(Rect? screenRect) async {
|
||||||
|
if (screenRect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final wc = WindowController.fromWindowId(stateGlobal.windowId);
|
||||||
|
final curFrame = await wc.getFrame();
|
||||||
|
final frame =
|
||||||
|
Rect.fromLTWH(screenRect.left + 30, screenRect.top + 30, 600, 400);
|
||||||
|
if (stateGlobal.fullscreen.isTrue &&
|
||||||
|
curFrame.left <= frame.left &&
|
||||||
|
curFrame.top <= frame.top &&
|
||||||
|
curFrame.width >= frame.width &&
|
||||||
|
curFrame.height >= frame.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await wc.setFrame(frame);
|
||||||
|
// An duration is needed to avoid the window being restored after fullscreen.
|
||||||
|
Future.delayed(Duration(milliseconds: 300), () async {
|
||||||
|
stateGlobal.setFullscreen(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseParamScreenRect(Map<String, dynamic> params) {
|
||||||
|
Rect? screenRect;
|
||||||
|
if (params['screen_rect'] != null) {
|
||||||
|
double l = params['screen_rect']['l'];
|
||||||
|
double t = params['screen_rect']['t'];
|
||||||
|
double r = params['screen_rect']['r'];
|
||||||
|
double b = params['screen_rect']['b'];
|
||||||
|
screenRect = Rect.fromLTRB(l, t, r, b);
|
||||||
|
}
|
||||||
|
return screenRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReconnectCountDownButton extends StatefulWidget {
|
||||||
|
_ReconnectCountDownButton({
|
||||||
|
Key? key,
|
||||||
|
required this.second,
|
||||||
|
required this.onPressed,
|
||||||
|
}) : super(key: key);
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final int second;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_ReconnectCountDownButton> createState() =>
|
||||||
|
_ReconnectCountDownButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReconnectCountDownButtonState extends State<_ReconnectCountDownButton> {
|
||||||
|
late int _countdownSeconds = widget.second;
|
||||||
|
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_startCountdownTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startCountdownTimer() {
|
||||||
|
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||||
|
if (_countdownSeconds <= 0) {
|
||||||
|
timer.cancel();
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_countdownSeconds--;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return dialogButton(
|
||||||
|
'${translate('Reconnect')} (${_countdownSeconds}s)',
|
||||||
|
onPressed: widget.onPressed,
|
||||||
|
isOutline: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
191
flutter/lib/common/widgets/autocomplete.dart
Normal file
191
flutter/lib/common/widgets/autocomplete.dart
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||||
|
import '../../../models/platform_model.dart';
|
||||||
|
import 'package:flutter_hbb/models/peer_model.dart';
|
||||||
|
import 'package:flutter_hbb/common.dart';
|
||||||
|
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||||
|
|
||||||
|
|
||||||
|
Future<List<Peer>> getAllPeers() async {
|
||||||
|
Map<String, dynamic> recentPeers = jsonDecode(await bind.mainLoadRecentPeersSync());
|
||||||
|
Map<String, dynamic> lanPeers = jsonDecode(await bind.mainLoadLanPeersSync());
|
||||||
|
Map<String, dynamic> abPeers = jsonDecode(await bind.mainLoadAbSync());
|
||||||
|
Map<String, dynamic> groupPeers = jsonDecode(await bind.mainLoadGroupSync());
|
||||||
|
|
||||||
|
Map<String, dynamic> combinedPeers = {};
|
||||||
|
|
||||||
|
void _mergePeers(Map<String, dynamic> peers) {
|
||||||
|
if (peers.containsKey("peers")) {
|
||||||
|
dynamic peerData = peers["peers"];
|
||||||
|
|
||||||
|
if (peerData is String) {
|
||||||
|
try {
|
||||||
|
peerData = jsonDecode(peerData);
|
||||||
|
} catch (e) {
|
||||||
|
print("Error decoding peers: $e");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peerData is List) {
|
||||||
|
for (var peer in peerData) {
|
||||||
|
if (peer is Map && peer.containsKey("id")) {
|
||||||
|
String id = peer["id"];
|
||||||
|
if (!combinedPeers.containsKey(id)) {
|
||||||
|
combinedPeers[id] = peer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_mergePeers(recentPeers);
|
||||||
|
_mergePeers(lanPeers);
|
||||||
|
_mergePeers(abPeers);
|
||||||
|
_mergePeers(groupPeers);
|
||||||
|
|
||||||
|
List<Peer> parsedPeers = [];
|
||||||
|
|
||||||
|
for (var peer in combinedPeers.values) {
|
||||||
|
parsedPeers.add(Peer.fromJson(peer));
|
||||||
|
}
|
||||||
|
return parsedPeers;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AutocompletePeerTile extends StatefulWidget {
|
||||||
|
final VoidCallback onSelect;
|
||||||
|
final Peer peer;
|
||||||
|
|
||||||
|
const AutocompletePeerTile({
|
||||||
|
Key? key,
|
||||||
|
required this.onSelect,
|
||||||
|
required this.peer,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AutocompletePeerTileState createState() => _AutocompletePeerTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AutocompletePeerTileState extends State<AutocompletePeerTile>{
|
||||||
|
List _frontN<T>(List list, int n) {
|
||||||
|
if (list.length <= n) {
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return list.sublist(0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context){
|
||||||
|
final double _tileRadius = 5;
|
||||||
|
final name =
|
||||||
|
'${widget.peer.username}${widget.peer.username.isNotEmpty && widget.peer.hostname.isNotEmpty ? '@' : ''}${widget.peer.hostname}';
|
||||||
|
final greyStyle = TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
|
||||||
|
final child = GestureDetector(
|
||||||
|
onTap: () => widget.onSelect(),
|
||||||
|
child:
|
||||||
|
Container(
|
||||||
|
height: 42,
|
||||||
|
margin: EdgeInsets.only(bottom: 5),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: str2color('${widget.peer.id}${widget.peer.platform}', 0x7f),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(_tileRadius),
|
||||||
|
bottomLeft: Radius.circular(_tileRadius),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: 42,
|
||||||
|
height: null,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(6),
|
||||||
|
child: getPlatformImage(widget.peer.platform, size: 30)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(left: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(_tileRadius),
|
||||||
|
bottomRight: Radius.circular(_tileRadius),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.only(top: 2),
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.only(top: 2),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(top: 2),
|
||||||
|
child: Row(children: [
|
||||||
|
getOnline(8, widget.peer.online),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.peer.alias.isEmpty ? formatID(widget.peer.id) : widget.peer.alias,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
)),
|
||||||
|
!widget.peer.alias.isEmpty?
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 5, right: 5),
|
||||||
|
child: Text(
|
||||||
|
"(${widget.peer.id})",
|
||||||
|
style: greyStyle,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
])),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
name,
|
||||||
|
style: greyStyle,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
))),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)));
|
||||||
|
final colors =
|
||||||
|
_frontN(widget.peer.tags, 25).map((e) => gFFI.abModel.getTagColor(e)).toList();
|
||||||
|
return Tooltip(
|
||||||
|
message: isMobile
|
||||||
|
? ''
|
||||||
|
: widget.peer.tags.isNotEmpty
|
||||||
|
? '${translate('Tags')}: ${widget.peer.tags.join(', ')}'
|
||||||
|
: '',
|
||||||
|
child: Stack(children: [
|
||||||
|
child,
|
||||||
|
if (colors.isNotEmpty)
|
||||||
|
Positioned(
|
||||||
|
top: 5,
|
||||||
|
right: 10,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: TagPainter(radius: 3, colors: colors),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ import 'dart:math' as math;
|
|||||||
typedef PopupMenuEntryBuilder = Future<List<mod_menu.PopupMenuEntry<String>>>
|
typedef PopupMenuEntryBuilder = Future<List<mod_menu.PopupMenuEntry<String>>>
|
||||||
Function(BuildContext);
|
Function(BuildContext);
|
||||||
|
|
||||||
enum PeerUiType { grid, list }
|
enum PeerUiType { grid, tile, list }
|
||||||
|
|
||||||
final peerCardUiType = PeerUiType.grid.obs;
|
final peerCardUiType = PeerUiType.grid.obs;
|
||||||
|
|
||||||
|
@ -215,29 +215,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _createPeerViewTypeSwitch(BuildContext context) {
|
Widget _createPeerViewTypeSwitch(BuildContext context) {
|
||||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
return PeerViewDropdown();
|
||||||
final types = [PeerUiType.grid, PeerUiType.list];
|
|
||||||
|
|
||||||
return Obx(() => _hoverAction(
|
|
||||||
context: context,
|
|
||||||
onTap: () async {
|
|
||||||
final type = types
|
|
||||||
.elementAt(peerCardUiType.value == types.elementAt(0) ? 1 : 0);
|
|
||||||
await bind.setLocalFlutterOption(
|
|
||||||
k: 'peer-card-ui-type', v: type.index.toString());
|
|
||||||
peerCardUiType.value = type;
|
|
||||||
},
|
|
||||||
child: Tooltip(
|
|
||||||
message: peerCardUiType.value == PeerUiType.grid
|
|
||||||
? translate('List View')
|
|
||||||
: translate('Grid View'),
|
|
||||||
child: Icon(
|
|
||||||
peerCardUiType.value == PeerUiType.grid
|
|
||||||
? Icons.view_list_rounded
|
|
||||||
: Icons.grid_view_rounded,
|
|
||||||
size: 18,
|
|
||||||
color: textColor,
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _createMultiSelection() {
|
Widget _createMultiSelection() {
|
||||||
@ -777,6 +755,85 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PeerViewDropdown extends StatefulWidget {
|
||||||
|
const PeerViewDropdown({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PeerViewDropdown> createState() => _PeerViewDropdownState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PeerViewDropdownState extends State<PeerViewDropdown> {
|
||||||
|
RelativeRect menuPos = RelativeRect.fromLTRB(0, 0, 0, 0);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final List<PeerUiType> types = [PeerUiType.grid, PeerUiType.tile, PeerUiType.list];
|
||||||
|
final style = TextStyle(
|
||||||
|
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||||
|
fontSize: MenuConfig.fontSize,
|
||||||
|
fontWeight: FontWeight.normal);
|
||||||
|
List<PopupMenuEntry> items = List.empty(growable: true);
|
||||||
|
items.add(PopupMenuItem(
|
||||||
|
height: 36,
|
||||||
|
enabled: false,
|
||||||
|
child: Text(translate("Change view"), style: style)));
|
||||||
|
for (var e in PeerUiType.values) {
|
||||||
|
items.add(PopupMenuItem(
|
||||||
|
height: 36,
|
||||||
|
child: Obx(() => Center(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 36,
|
||||||
|
child: getRadio<PeerUiType>(
|
||||||
|
Text(translate(
|
||||||
|
types.indexOf(e) == 0 ? 'Big tiles' : types.indexOf(e) == 1 ? 'Small tiles' : 'List'
|
||||||
|
), style: style),
|
||||||
|
e,
|
||||||
|
peerCardUiType.value,
|
||||||
|
dense: true,
|
||||||
|
(PeerUiType? v) async {
|
||||||
|
if (v != null) {
|
||||||
|
peerCardUiType.value = v;
|
||||||
|
setState(() {});
|
||||||
|
await bind.setLocalFlutterOption(
|
||||||
|
k: "peer-card-ui-type",
|
||||||
|
v: peerCardUiType.value.index.toString(),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _hoverAction(
|
||||||
|
context: context,
|
||||||
|
child: Tooltip(
|
||||||
|
message: translate('Change view'),
|
||||||
|
child: Icon(
|
||||||
|
peerCardUiType.value == PeerUiType.grid
|
||||||
|
? Icons.grid_view_rounded
|
||||||
|
: peerCardUiType.value == PeerUiType.tile
|
||||||
|
? Icons.view_list_rounded
|
||||||
|
: Icons.view_agenda_rounded,
|
||||||
|
size: 18,
|
||||||
|
)),
|
||||||
|
onTapDown: (details) {
|
||||||
|
final x = details.globalPosition.dx;
|
||||||
|
final y = details.globalPosition.dy;
|
||||||
|
setState(() {
|
||||||
|
menuPos = RelativeRect.fromLTRB(x, y, x, y);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onTap: () => showMenu(
|
||||||
|
context: context,
|
||||||
|
position: menuPos,
|
||||||
|
items: items,
|
||||||
|
elevation: 8,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class PeerSortDropdown extends StatefulWidget {
|
class PeerSortDropdown extends StatefulWidget {
|
||||||
const PeerSortDropdown({super.key});
|
const PeerSortDropdown({super.key});
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:visibility_detector/visibility_detector.dart';
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../models/peer_model.dart';
|
import '../../models/peer_model.dart';
|
||||||
@ -188,12 +189,25 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
|||||||
onVisibilityChanged: onVisibilityChanged,
|
onVisibilityChanged: onVisibilityChanged,
|
||||||
child: widget.peerCardBuilder(peer),
|
child: widget.peerCardBuilder(peer),
|
||||||
);
|
);
|
||||||
|
final windowWidth = MediaQuery.of(context).size.width;
|
||||||
|
// `Provider.of<PeerTabModel>(context)` will causes infinete loop.
|
||||||
|
// Because `gFFI.peerTabModel.setCurrentTabCachedPeers(peers)` will trigger `notifyListeners()`.
|
||||||
|
//
|
||||||
|
// No need to listen the currentTab change event.
|
||||||
|
// Because the currentTab change event will trigger the peers change event,
|
||||||
|
// and the peers change event will trigger _buildPeersView().
|
||||||
|
final currentTab = Provider.of<PeerTabModel>(context, listen: false).currentTab;
|
||||||
|
final hideAbTagsPanel = bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty;
|
||||||
return isDesktop
|
return isDesktop
|
||||||
? Obx(
|
? Obx(
|
||||||
() => SizedBox(
|
() => SizedBox(
|
||||||
width: 220,
|
width: peerCardUiType.value != PeerUiType.list
|
||||||
|
? 220
|
||||||
|
: currentTab == PeerTabIndex.group.index || (currentTab == PeerTabIndex.ab.index && !hideAbTagsPanel)
|
||||||
|
? windowWidth - 390 :
|
||||||
|
windowWidth - 227,
|
||||||
height:
|
height:
|
||||||
peerCardUiType.value == PeerUiType.grid ? 140 : 42,
|
peerCardUiType.value == PeerUiType.grid ? 140 : peerCardUiType.value != PeerUiType.list ? 42 : 45,
|
||||||
child: visibilityChild,
|
child: visibilityChild,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -224,11 +224,8 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
// record
|
// record
|
||||||
var codecFormat = ffi.qualityMonitorModel.data.codecFormat;
|
|
||||||
if (!isDesktop &&
|
if (!isDesktop &&
|
||||||
(ffi.recordingModel.start ||
|
(ffi.recordingModel.start || (perms["recording"] != false))) {
|
||||||
(perms["recording"] != false &&
|
|
||||||
(codecFormat == "VP8" || codecFormat == "VP9")))) {
|
|
||||||
v.add(TTextMenu(
|
v.add(TTextMenu(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -535,5 +532,20 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
child: Text(translate('Show displays as individual windows'))));
|
child: Text(translate('Show displays as individual windows'))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final screenList = await getScreenRectList();
|
||||||
|
if (useTextureRender && pi.isSupportMultiDisplay && screenList.length > 1) {
|
||||||
|
final value = bind.sessionGetUseAllMyDisplaysForTheRemoteSession(
|
||||||
|
sessionId: ffi.sessionId) ==
|
||||||
|
'Y';
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
bind.sessionSetUseAllMyDisplaysForTheRemoteSession(
|
||||||
|
sessionId: sessionId, value: value ? 'Y' : '');
|
||||||
|
},
|
||||||
|
child: Text(translate('Use all my displays for the remote session'))));
|
||||||
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,10 @@ import 'dart:io';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
const int kMaxVirtualDisplayCount = 4;
|
||||||
|
const int kAllVirtualDisplay = -1;
|
||||||
|
|
||||||
const double kDesktopRemoteTabBarHeight = 28.0;
|
const double kDesktopRemoteTabBarHeight = 28.0;
|
||||||
const int kInvalidWindowId = -1;
|
const int kInvalidWindowId = -1;
|
||||||
@ -10,6 +14,15 @@ const int kMainWindowId = 0;
|
|||||||
|
|
||||||
const kAllDisplayValue = -1;
|
const kAllDisplayValue = -1;
|
||||||
|
|
||||||
|
const kKeyLegacyMode = 'legacy';
|
||||||
|
const kKeyMapMode = 'map';
|
||||||
|
const kKeyTranslateMode = 'translate';
|
||||||
|
|
||||||
|
const String kPlatformAdditionsIsWayland = "is_wayland";
|
||||||
|
const String kPlatformAdditionsHeadless = "headless";
|
||||||
|
const String kPlatformAdditionsIsInstalled = "is_installed";
|
||||||
|
const String kPlatformAdditionsVirtualDisplays = "virtual_displays";
|
||||||
|
|
||||||
const String kPeerPlatformWindows = "Windows";
|
const String kPeerPlatformWindows = "Windows";
|
||||||
const String kPeerPlatformLinux = "Linux";
|
const String kPeerPlatformLinux = "Linux";
|
||||||
const String kPeerPlatformMacOS = "Mac OS";
|
const String kPeerPlatformMacOS = "Mac OS";
|
||||||
@ -29,6 +42,7 @@ const String kAppTypeDesktopPortForward = "port forward";
|
|||||||
|
|
||||||
const String kWindowMainWindowOnTop = "main_window_on_top";
|
const String kWindowMainWindowOnTop = "main_window_on_top";
|
||||||
const String kWindowGetWindowInfo = "get_window_info";
|
const String kWindowGetWindowInfo = "get_window_info";
|
||||||
|
const String kWindowGetScreenList = "get_screen_list";
|
||||||
const String kWindowDisableGrabKeyboard = "disable_grab_keyboard";
|
const String kWindowDisableGrabKeyboard = "disable_grab_keyboard";
|
||||||
const String kWindowActionRebuild = "rebuild";
|
const String kWindowActionRebuild = "rebuild";
|
||||||
const String kWindowEventHide = "hide";
|
const String kWindowEventHide = "hide";
|
||||||
@ -64,7 +78,10 @@ const int kWindowMainId = 0;
|
|||||||
const String kPointerEventKindTouch = "touch";
|
const String kPointerEventKindTouch = "touch";
|
||||||
const String kPointerEventKindMouse = "mouse";
|
const String kPointerEventKindMouse = "mouse";
|
||||||
|
|
||||||
const String kKeyShowDisplaysAsIndividualWindows = 'displays_as_individual_windows';
|
const String kKeyShowDisplaysAsIndividualWindows =
|
||||||
|
'displays_as_individual_windows';
|
||||||
|
const String kKeyUseAllMyDisplaysForTheRemoteSession =
|
||||||
|
'use_all_my_displays_for_the_remote_session';
|
||||||
const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar';
|
const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar';
|
||||||
|
|
||||||
// the executable name of the portable version
|
// the executable name of the portable version
|
||||||
@ -84,9 +101,17 @@ const int kDesktopMaxDisplaySize = 3840;
|
|||||||
const double kDesktopFileTransferRowHeight = 30.0;
|
const double kDesktopFileTransferRowHeight = 30.0;
|
||||||
const double kDesktopFileTransferHeaderHeight = 25.0;
|
const double kDesktopFileTransferHeaderHeight = 25.0;
|
||||||
|
|
||||||
|
double kNewWindowOffset = Platform.isWindows
|
||||||
|
? 56.0
|
||||||
|
: Platform.isLinux
|
||||||
|
? 50.0
|
||||||
|
: Platform.isMacOS
|
||||||
|
? 30.0
|
||||||
|
: 50.0;
|
||||||
|
|
||||||
EdgeInsets get kDragToResizeAreaPadding =>
|
EdgeInsets get kDragToResizeAreaPadding =>
|
||||||
!kUseCompatibleUiMode && Platform.isLinux
|
!kUseCompatibleUiMode && Platform.isLinux
|
||||||
? stateGlobal.fullscreen || stateGlobal.isMaximized.value
|
? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value
|
||||||
? EdgeInsets.zero
|
? EdgeInsets.zero
|
||||||
: EdgeInsets.all(5.0)
|
: EdgeInsets.all(5.0)
|
||||||
: EdgeInsets.zero;
|
: EdgeInsets.zero;
|
||||||
|
@ -11,10 +11,12 @@ import 'package:flutter_hbb/models/state_model.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
import 'package:flutter_hbb/models/peer_model.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/formatter/id_formatter.dart';
|
import '../../common/formatter/id_formatter.dart';
|
||||||
import '../../common/widgets/peer_tab_page.dart';
|
import '../../common/widgets/peer_tab_page.dart';
|
||||||
|
import '../../common/widgets/autocomplete.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../widgets/button.dart';
|
import '../widgets/button.dart';
|
||||||
|
|
||||||
@ -35,12 +37,21 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
Timer? _updateTimer;
|
Timer? _updateTimer;
|
||||||
|
|
||||||
final RxBool _idInputFocused = false.obs;
|
final RxBool _idInputFocused = false.obs;
|
||||||
final FocusNode _idFocusNode = FocusNode();
|
|
||||||
|
|
||||||
var svcStopped = Get.find<RxBool>(tag: 'stop-service');
|
var svcStopped = Get.find<RxBool>(tag: 'stop-service');
|
||||||
var svcIsUsingPublicServer = true.obs;
|
var svcIsUsingPublicServer = true.obs;
|
||||||
|
|
||||||
bool isWindowMinimized = false;
|
bool isWindowMinimized = false;
|
||||||
|
List<Peer> peers = [];
|
||||||
|
List _frontN<T>(List list, int n) {
|
||||||
|
if (list.length <= n) {
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return list.sublist(0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool isPeersLoading = false;
|
||||||
|
bool isPeersLoaded = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -58,12 +69,6 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
_updateTimer = periodic_immediate(Duration(seconds: 1), () async {
|
_updateTimer = periodic_immediate(Duration(seconds: 1), () async {
|
||||||
updateStatus();
|
updateStatus();
|
||||||
});
|
});
|
||||||
_idFocusNode.addListener(() {
|
|
||||||
_idInputFocused.value = _idFocusNode.hasFocus;
|
|
||||||
// select all to faciliate removing text, just following the behavior of address input of chrome
|
|
||||||
_idController.selection = TextSelection(
|
|
||||||
baseOffset: 0, extentOffset: _idController.value.text.length);
|
|
||||||
});
|
|
||||||
Get.put<IDTextEditingController>(_idController);
|
Get.put<IDTextEditingController>(_idController);
|
||||||
windowManager.addListener(this);
|
windowManager.addListener(this);
|
||||||
}
|
}
|
||||||
@ -76,6 +81,9 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
if (Get.isRegistered<IDTextEditingController>()) {
|
if (Get.isRegistered<IDTextEditingController>()) {
|
||||||
Get.delete<IDTextEditingController>();
|
Get.delete<IDTextEditingController>();
|
||||||
}
|
}
|
||||||
|
if (Get.isRegistered<TextEditingController>()){
|
||||||
|
Get.delete<TextEditingController>();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,8 +150,20 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
connect(context, id, isFileTransfer: isFileTransfer);
|
connect(context, id, isFileTransfer: isFileTransfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchPeers() async {
|
||||||
|
setState(() {
|
||||||
|
isPeersLoading = true;
|
||||||
|
});
|
||||||
|
await Future.delayed(Duration(milliseconds: 100));
|
||||||
|
peers = await getAllPeers();
|
||||||
|
setState(() {
|
||||||
|
isPeersLoading = false;
|
||||||
|
isPeersLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// UI for the remote ID TextField.
|
/// UI for the remote ID TextField.
|
||||||
/// Search for a peer and connect to it if the id exists.
|
/// Search for a peer.
|
||||||
Widget _buildRemoteIDTextField(BuildContext context) {
|
Widget _buildRemoteIDTextField(BuildContext context) {
|
||||||
var w = Container(
|
var w = Container(
|
||||||
width: 320 + 20 * 2,
|
width: 320 + 20 * 2,
|
||||||
@ -171,36 +191,133 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Obx(
|
child:
|
||||||
() => TextField(
|
Autocomplete<Peer>(
|
||||||
maxLength: 90,
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
autocorrect: false,
|
if (textEditingValue.text == '') {
|
||||||
enableSuggestions: false,
|
return const Iterable<Peer>.empty();
|
||||||
keyboardType: TextInputType.visiblePassword,
|
}
|
||||||
focusNode: _idFocusNode,
|
else if (peers.isEmpty && !isPeersLoaded) {
|
||||||
style: const TextStyle(
|
Peer emptyPeer = Peer(
|
||||||
fontFamily: 'WorkSans',
|
id: '',
|
||||||
fontSize: 22,
|
username: '',
|
||||||
height: 1.4,
|
hostname: '',
|
||||||
),
|
alias: '',
|
||||||
maxLines: 1,
|
platform: '',
|
||||||
cursorColor:
|
tags: [],
|
||||||
Theme.of(context).textTheme.titleLarge?.color,
|
hash: '',
|
||||||
decoration: InputDecoration(
|
forceAlwaysRelay: false,
|
||||||
filled: false,
|
rdpPort: '',
|
||||||
counterText: '',
|
rdpUsername: '',
|
||||||
hintText: _idInputFocused.value
|
loginName: '',
|
||||||
? null
|
);
|
||||||
: translate('Enter Remote ID'),
|
return [emptyPeer];
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
}
|
||||||
horizontal: 15, vertical: 13)),
|
else {
|
||||||
controller: _idController,
|
String textWithoutSpaces = textEditingValue.text.replaceAll(" ", "");
|
||||||
inputFormatters: [IDTextInputFormatter()],
|
if (int.tryParse(textWithoutSpaces) != null) {
|
||||||
onSubmitted: (s) {
|
textEditingValue = TextEditingValue(
|
||||||
onConnect();
|
text: textWithoutSpaces,
|
||||||
},
|
selection: textEditingValue.selection,
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
|
String textToFind = textEditingValue.text.toLowerCase();
|
||||||
|
|
||||||
|
return peers.where((peer) =>
|
||||||
|
peer.id.toLowerCase().contains(textToFind) ||
|
||||||
|
peer.username.toLowerCase().contains(textToFind) ||
|
||||||
|
peer.hostname.toLowerCase().contains(textToFind) ||
|
||||||
|
peer.alias.toLowerCase().contains(textToFind))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fieldViewBuilder: (BuildContext context,
|
||||||
|
TextEditingController fieldTextEditingController,
|
||||||
|
FocusNode fieldFocusNode ,
|
||||||
|
VoidCallback onFieldSubmitted,
|
||||||
|
) {
|
||||||
|
fieldTextEditingController.text = _idController.text;
|
||||||
|
Get.put<TextEditingController>(fieldTextEditingController);
|
||||||
|
fieldFocusNode.addListener(() async {
|
||||||
|
_idInputFocused.value = fieldFocusNode.hasFocus;
|
||||||
|
if (fieldFocusNode.hasFocus && !isPeersLoading){
|
||||||
|
_fetchPeers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final textLength = fieldTextEditingController.value.text.length;
|
||||||
|
// select all to facilitate removing text, just following the behavior of address input of chrome
|
||||||
|
fieldTextEditingController.selection = TextSelection(baseOffset: 0, extentOffset: textLength);
|
||||||
|
return Obx(() =>
|
||||||
|
TextField(
|
||||||
|
maxLength: 90,
|
||||||
|
autocorrect: false,
|
||||||
|
enableSuggestions: false,
|
||||||
|
keyboardType: TextInputType.visiblePassword,
|
||||||
|
focusNode: fieldFocusNode,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: 'WorkSans',
|
||||||
|
fontSize: 22,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
cursorColor: Theme.of(context).textTheme.titleLarge?.color,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
filled: false,
|
||||||
|
counterText: '',
|
||||||
|
hintText: _idInputFocused.value
|
||||||
|
? null
|
||||||
|
: translate('Enter Remote ID'),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15, vertical: 13)),
|
||||||
|
controller: fieldTextEditingController,
|
||||||
|
inputFormatters: [IDTextInputFormatter()],
|
||||||
|
onChanged: (v) {
|
||||||
|
_idController.id = v;
|
||||||
|
},
|
||||||
|
));
|
||||||
|
},
|
||||||
|
onSelected: (option) {
|
||||||
|
setState(() {
|
||||||
|
_idController.id = option.id;
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<Peer> onSelected, Iterable<Peer> options) {
|
||||||
|
double maxHeight = options.length * 50;
|
||||||
|
maxHeight = maxHeight > 200 ? 200 : maxHeight;
|
||||||
|
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
child: Material(
|
||||||
|
elevation: 4,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: maxHeight,
|
||||||
|
maxWidth: 319,
|
||||||
|
),
|
||||||
|
child: peers.isEmpty && isPeersLoading
|
||||||
|
? Container(
|
||||||
|
height: 80,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 5),
|
||||||
|
child: ListView(
|
||||||
|
children: options.map((peer) => AutocompletePeerTile(onSelect: () => onSelected(peer), peer: peer)).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -329,8 +329,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
"Click to download", () async {
|
"Click to download", () async {
|
||||||
final Uri url = Uri.parse('https://rustdesk.com/download');
|
final Uri url = Uri.parse('https://rustdesk.com/download');
|
||||||
await launchUrl(url);
|
await launchUrl(url);
|
||||||
},
|
}, closeButton: true);
|
||||||
closeButton: true);
|
|
||||||
}
|
}
|
||||||
if (systemError.isNotEmpty) {
|
if (systemError.isNotEmpty) {
|
||||||
return buildInstallCard("", systemError, "", () {});
|
return buildInstallCard("", systemError, "", () {});
|
||||||
@ -379,16 +378,39 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
} else if (Platform.isLinux) {
|
} else if (Platform.isLinux) {
|
||||||
|
final LinuxCards = <Widget>[];
|
||||||
|
if (bind.isSelinuxEnforcing()) {
|
||||||
|
// Check is SELinux enforcing, but show user a tip of is SELinux enabled for simple.
|
||||||
|
final keyShowSelinuxHelpTip = "show-selinux-help-tip";
|
||||||
|
if (bind.mainGetLocalOption(key: keyShowSelinuxHelpTip) != 'N') {
|
||||||
|
LinuxCards.add(buildInstallCard(
|
||||||
|
"Warning", "selinux_tip", "", () async {},
|
||||||
|
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
|
||||||
|
help: 'Help',
|
||||||
|
link:
|
||||||
|
'https://rustdesk.com/docs/en/client/linux/#permissions-issue',
|
||||||
|
closeButton: true,
|
||||||
|
closeOption: keyShowSelinuxHelpTip,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (bind.mainCurrentIsWayland()) {
|
if (bind.mainCurrentIsWayland()) {
|
||||||
return buildInstallCard(
|
LinuxCards.add(buildInstallCard(
|
||||||
"Warning", "wayland_experiment_tip", "", () async {},
|
"Warning", "wayland_experiment_tip", "", () async {},
|
||||||
|
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
|
||||||
help: 'Help',
|
help: 'Help',
|
||||||
link: 'https://rustdesk.com/docs/en/manual/linux/#x11-required');
|
link: 'https://rustdesk.com/docs/en/manual/linux/#x11-required'));
|
||||||
} else if (bind.mainIsLoginWayland()) {
|
} else if (bind.mainIsLoginWayland()) {
|
||||||
return buildInstallCard("Warning",
|
LinuxCards.add(buildInstallCard("Warning",
|
||||||
"Login screen using Wayland is not supported", "", () async {},
|
"Login screen using Wayland is not supported", "", () async {},
|
||||||
|
marginTop: LinuxCards.isEmpty ? 20.0 : 5.0,
|
||||||
help: 'Help',
|
help: 'Help',
|
||||||
link: 'https://rustdesk.com/docs/en/manual/linux/#login-screen');
|
link: 'https://rustdesk.com/docs/en/manual/linux/#login-screen'));
|
||||||
|
}
|
||||||
|
if (LinuxCards.isNotEmpty) {
|
||||||
|
return Column(
|
||||||
|
children: LinuxCards,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Container();
|
return Container();
|
||||||
@ -396,18 +418,26 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
|
|
||||||
Widget buildInstallCard(String title, String content, String btnText,
|
Widget buildInstallCard(String title, String content, String btnText,
|
||||||
GestureTapCallback onPressed,
|
GestureTapCallback onPressed,
|
||||||
{String? help, String? link, bool? closeButton}) {
|
{double marginTop = 20.0, String? help, String? link, bool? closeButton, String? closeOption}) {
|
||||||
|
void closeCard() async {
|
||||||
void closeCard() {
|
if (closeOption != null) {
|
||||||
setState(() {
|
await bind.mainSetLocalOption(key: closeOption, value: 'N');
|
||||||
isCardClosed = true;
|
if (bind.mainGetLocalOption(key: closeOption) == 'N') {
|
||||||
});
|
setState(() {
|
||||||
|
isCardClosed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
isCardClosed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.only(top: 20),
|
margin: EdgeInsets.only(top: marginTop),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
@ -555,6 +585,22 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
Get.put<RxBool>(svcStopped, tag: 'stop-service');
|
Get.put<RxBool>(svcStopped, tag: 'stop-service');
|
||||||
rustDeskWinManager.registerActiveWindowListener(onActiveWindowChanged);
|
rustDeskWinManager.registerActiveWindowListener(onActiveWindowChanged);
|
||||||
|
|
||||||
|
screenToMap(window_size.Screen screen) => {
|
||||||
|
'frame': {
|
||||||
|
'l': screen.frame.left,
|
||||||
|
't': screen.frame.top,
|
||||||
|
'r': screen.frame.right,
|
||||||
|
'b': screen.frame.bottom,
|
||||||
|
},
|
||||||
|
'visibleFrame': {
|
||||||
|
'l': screen.visibleFrame.left,
|
||||||
|
't': screen.visibleFrame.top,
|
||||||
|
'r': screen.visibleFrame.right,
|
||||||
|
'b': screen.visibleFrame.bottom,
|
||||||
|
},
|
||||||
|
'scaleFactor': screen.scaleFactor,
|
||||||
|
};
|
||||||
|
|
||||||
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
|
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"[Main] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
"[Main] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
||||||
@ -563,24 +609,13 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
} else if (call.method == kWindowGetWindowInfo) {
|
} else if (call.method == kWindowGetWindowInfo) {
|
||||||
final screen = (await window_size.getWindowInfo()).screen;
|
final screen = (await window_size.getWindowInfo()).screen;
|
||||||
if (screen == null) {
|
if (screen == null) {
|
||||||
return "";
|
return '';
|
||||||
} else {
|
} else {
|
||||||
return jsonEncode({
|
return jsonEncode(screenToMap(screen));
|
||||||
'frame': {
|
|
||||||
'l': screen.frame.left,
|
|
||||||
't': screen.frame.top,
|
|
||||||
'r': screen.frame.right,
|
|
||||||
'b': screen.frame.bottom,
|
|
||||||
},
|
|
||||||
'visibleFrame': {
|
|
||||||
'l': screen.visibleFrame.left,
|
|
||||||
't': screen.visibleFrame.top,
|
|
||||||
'r': screen.visibleFrame.right,
|
|
||||||
'b': screen.visibleFrame.bottom,
|
|
||||||
},
|
|
||||||
'scaleFactor': screen.scaleFactor,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else if (call.method == kWindowGetScreenList) {
|
||||||
|
return jsonEncode(
|
||||||
|
(await window_size.getScreenList()).map(screenToMap).toList());
|
||||||
} else if (call.method == kWindowActionRebuild) {
|
} else if (call.method == kWindowActionRebuild) {
|
||||||
reloadCurrentWindow();
|
reloadCurrentWindow();
|
||||||
} else if (call.method == kWindowEventShow) {
|
} else if (call.method == kWindowEventShow) {
|
||||||
@ -613,8 +648,9 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
final peerId = args['peer_id'] as String;
|
final peerId = args['peer_id'] as String;
|
||||||
final display = args['display'] as int;
|
final display = args['display'] as int;
|
||||||
final displayCount = args['display_count'] as int;
|
final displayCount = args['display_count'] as int;
|
||||||
|
final screenRect = parseParamScreenRect(args);
|
||||||
await rustDeskWinManager.openMonitorSession(
|
await rustDeskWinManager.openMonitorSession(
|
||||||
windowId, peerId, display, displayCount);
|
windowId, peerId, display, displayCount, screenRect);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_uniLinksSubscription = listenUniLinks();
|
_uniLinksSubscription = listenUniLinks();
|
||||||
|
@ -1324,6 +1324,8 @@ class _DisplayState extends State<_Display> {
|
|||||||
if (useTextureRender) {
|
if (useTextureRender) {
|
||||||
children.add(otherRow('Show displays as individual windows',
|
children.add(otherRow('Show displays as individual windows',
|
||||||
kKeyShowDisplaysAsIndividualWindows));
|
kKeyShowDisplaysAsIndividualWindows));
|
||||||
|
children.add(otherRow('Use all my displays for the remote session',
|
||||||
|
kKeyUseAllMyDisplaysForTheRemoteSession));
|
||||||
}
|
}
|
||||||
return _Card(title: 'Other Default Options', children: children);
|
return _Card(title: 'Other Default Options', children: children);
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
late RxBool _keyboardEnabled;
|
late RxBool _keyboardEnabled;
|
||||||
final Map<int, RenderTexture> _renderTextures = {};
|
final Map<int, RenderTexture> _renderTextures = {};
|
||||||
|
|
||||||
final _blockableOverlayState = BlockableOverlayState();
|
var _blockableOverlayState = BlockableOverlayState();
|
||||||
|
|
||||||
final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode");
|
final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode");
|
||||||
|
|
||||||
@ -253,9 +253,9 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Toolbar = null,
|
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Toolbar = null,
|
||||||
setRemoteState: setState,
|
setRemoteState: setState,
|
||||||
);
|
);
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
bodyWidget() {
|
||||||
body: Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@ -281,7 +281,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
},
|
},
|
||||||
inputModel: _ffi.inputModel,
|
inputModel: _ffi.inputModel,
|
||||||
child: getBodyForDesktop(context))),
|
child: getBodyForDesktop(context))),
|
||||||
Obx(() => Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
_ffi.ffiModel.pi.isSet.isTrue &&
|
_ffi.ffiModel.pi.isSet.isTrue &&
|
||||||
_ffi.ffiModel.waitForFirstImage.isTrue
|
_ffi.ffiModel.waitForFirstImage.isTrue
|
||||||
@ -298,9 +298,34 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
: remoteToolbar(context),
|
: remoteToolbar(context),
|
||||||
_ffi.ffiModel.pi.isSet.isFalse ? emptyOverlay() : Offstage(),
|
_ffi.ffiModel.pi.isSet.isFalse ? emptyOverlay() : Offstage(),
|
||||||
],
|
],
|
||||||
)),
|
),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
|
body: Obx(() {
|
||||||
|
final imageReady = _ffi.ffiModel.pi.isSet.isTrue &&
|
||||||
|
_ffi.ffiModel.waitForFirstImage.isFalse;
|
||||||
|
if (imageReady) {
|
||||||
|
// `dismissAll()` is to ensure that the state is clean.
|
||||||
|
// It's ok to call dismissAll() here.
|
||||||
|
_ffi.dialogManager.dismissAll();
|
||||||
|
// Recreate the block state to refresh the state.
|
||||||
|
_blockableOverlayState = BlockableOverlayState();
|
||||||
|
_blockableOverlayState.applyFfi(_ffi);
|
||||||
|
// Block the whole `bodyWidget()` when dialog shows.
|
||||||
|
return BlockableOverlay(
|
||||||
|
underlying: bodyWidget(),
|
||||||
|
state: _blockableOverlayState,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// `_blockableOverlayState` is not recreated here.
|
||||||
|
// The toolbar's block state won't work properly when reconnecting, but that's okay.
|
||||||
|
return bodyWidget();
|
||||||
|
}
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,7 +702,8 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||||||
} else {
|
} else {
|
||||||
final key = cache.updateGetKey(scale);
|
final key = cache.updateGetKey(scale);
|
||||||
if (!cursor.cachedKeys.contains(key)) {
|
if (!cursor.cachedKeys.contains(key)) {
|
||||||
debugPrint("Register custom cursor with key $key (${cache.hotx},${cache.hoty})");
|
debugPrint(
|
||||||
|
"Register custom cursor with key $key (${cache.hotx},${cache.hoty})");
|
||||||
// [Safety]
|
// [Safety]
|
||||||
// It's ok to call async registerCursor in current synchronous context,
|
// It's ok to call async registerCursor in current synchronous context,
|
||||||
// because activating the cursor is also an async call and will always
|
// because activating the cursor is also an async call and will always
|
||||||
|
@ -48,6 +48,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
|
|
||||||
late ToolbarState _toolbarState;
|
late ToolbarState _toolbarState;
|
||||||
String? peerId;
|
String? peerId;
|
||||||
|
bool _isScreenRectSet = false;
|
||||||
|
int? _display;
|
||||||
|
|
||||||
var connectionMap = RxList<Widget>.empty(growable: true);
|
var connectionMap = RxList<Widget>.empty(growable: true);
|
||||||
|
|
||||||
@ -59,6 +61,10 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
final tabWindowId = params['tab_window_id'];
|
final tabWindowId = params['tab_window_id'];
|
||||||
final display = params['display'];
|
final display = params['display'];
|
||||||
final displays = params['displays'];
|
final displays = params['displays'];
|
||||||
|
final screenRect = parseParamScreenRect(params);
|
||||||
|
_isScreenRectSet = screenRect != null;
|
||||||
|
_display = display as int?;
|
||||||
|
tryMoveToScreenAndSetFullscreen(screenRect);
|
||||||
if (peerId != null) {
|
if (peerId != null) {
|
||||||
ConnectionTypeState.init(peerId!);
|
ConnectionTypeState.init(peerId!);
|
||||||
tabController.onSelected = (id) {
|
tabController.onSelected = (id) {
|
||||||
@ -115,11 +121,16 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
final tabWindowId = args['tab_window_id'];
|
final tabWindowId = args['tab_window_id'];
|
||||||
final display = args['display'];
|
final display = args['display'];
|
||||||
final displays = args['displays'];
|
final displays = args['displays'];
|
||||||
|
final screenRect = parseParamScreenRect(args);
|
||||||
windowOnTop(windowId());
|
windowOnTop(windowId());
|
||||||
|
tryMoveToScreenAndSetFullscreen(screenRect);
|
||||||
if (tabController.length == 0) {
|
if (tabController.length == 0) {
|
||||||
if (Platform.isMacOS && stateGlobal.closeOnFullscreen) {
|
// Show the hidden window.
|
||||||
|
if (Platform.isMacOS && stateGlobal.closeOnFullscreen == true) {
|
||||||
stateGlobal.setFullscreen(true);
|
stateGlobal.setFullscreen(true);
|
||||||
}
|
}
|
||||||
|
// Reset the state
|
||||||
|
stateGlobal.closeOnFullscreen = null;
|
||||||
}
|
}
|
||||||
ConnectionTypeState.init(id);
|
ConnectionTypeState.init(id);
|
||||||
_toolbarState.setShow(
|
_toolbarState.setShow(
|
||||||
@ -196,15 +207,18 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
_update_remote_count();
|
_update_remote_count();
|
||||||
return returnValue;
|
return returnValue;
|
||||||
});
|
});
|
||||||
Future.delayed(Duration.zero, () {
|
if (!_isScreenRectSet) {
|
||||||
restoreWindowPosition(
|
Future.delayed(Duration.zero, () {
|
||||||
WindowType.RemoteDesktop,
|
restoreWindowPosition(
|
||||||
windowId: windowId(),
|
WindowType.RemoteDesktop,
|
||||||
peerId: tabController.state.value.tabs.isEmpty
|
windowId: windowId(),
|
||||||
? null
|
peerId: tabController.state.value.tabs.isEmpty
|
||||||
: tabController.state.value.tabs[0].key,
|
? null
|
||||||
);
|
: tabController.state.value.tabs[0].key,
|
||||||
});
|
display: _display,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -451,6 +465,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
c++;
|
c++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loopCloseWindow();
|
loopCloseWindow();
|
||||||
}
|
}
|
||||||
ConnectionTypeState.delete(id);
|
ConnectionTypeState.delete(id);
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hbb/common/widgets/toolbar.dart';
|
import 'package:flutter_hbb/common/widgets/toolbar.dart';
|
||||||
import 'package:flutter_hbb/main.dart';
|
|
||||||
import 'package:flutter_hbb/models/chat_model.dart';
|
import 'package:flutter_hbb/models/chat_model.dart';
|
||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
||||||
@ -22,17 +20,12 @@ import 'package:desktop_multi_window/desktop_multi_window.dart';
|
|||||||
import 'package:window_size/window_size.dart' as window_size;
|
import 'package:window_size/window_size.dart' as window_size;
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/dialog.dart';
|
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../../common/shared_state.dart';
|
import '../../common/shared_state.dart';
|
||||||
import './popup_menu.dart';
|
import './popup_menu.dart';
|
||||||
import './kb_layout_type_chooser.dart';
|
import './kb_layout_type_chooser.dart';
|
||||||
|
|
||||||
const _kKeyLegacyMode = 'legacy';
|
|
||||||
const _kKeyMapMode = 'map';
|
|
||||||
const _kKeyTranslateMode = 'translate';
|
|
||||||
|
|
||||||
class ToolbarState {
|
class ToolbarState {
|
||||||
final kStoreKey = 'remoteMenubarState';
|
final kStoreKey = 'remoteMenubarState';
|
||||||
late RxBool show;
|
late RxBool show;
|
||||||
@ -353,10 +346,10 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
|||||||
|
|
||||||
int get windowId => stateGlobal.windowId;
|
int get windowId => stateGlobal.windowId;
|
||||||
|
|
||||||
bool get isFullscreen => stateGlobal.fullscreen;
|
|
||||||
void _setFullscreen(bool v) {
|
void _setFullscreen(bool v) {
|
||||||
stateGlobal.setFullscreen(v);
|
stateGlobal.setFullscreen(v);
|
||||||
setState(() {});
|
// stateGlobal.fullscreen is RxBool now, no need to call setState.
|
||||||
|
// setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
RxBool get show => widget.state.show;
|
RxBool get show => widget.state.show;
|
||||||
@ -480,7 +473,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
|||||||
toolbarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi));
|
toolbarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi));
|
||||||
toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi));
|
toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi));
|
||||||
}
|
}
|
||||||
toolbarItems.add(_RecordMenu(ffi: widget.ffi));
|
toolbarItems.add(_RecordMenu());
|
||||||
toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
|
toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -744,42 +737,14 @@ class _MonitorMenu extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open new tab or window to show this monitor.
|
|
||||||
// For now just open new window.
|
|
||||||
openMonitorInNewTabOrWindow(int i, PeerInfo pi) {
|
|
||||||
if (kWindowId == null) {
|
|
||||||
// unreachable
|
|
||||||
debugPrint('openMonitorInNewTabOrWindow, unreachable! kWindowId is null');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DesktopMultiWindow.invokeMethod(
|
|
||||||
kMainWindowId,
|
|
||||||
kWindowEventOpenMonitorSession,
|
|
||||||
jsonEncode({
|
|
||||||
'window_id': kWindowId!,
|
|
||||||
'peer_id': ffi.id,
|
|
||||||
'display': i,
|
|
||||||
'display_count': pi.displays.length,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
openMonitorInTheSameTab(int i, PeerInfo pi) {
|
|
||||||
final displays = i == kAllDisplayValue
|
|
||||||
? List.generate(pi.displays.length, (index) => index)
|
|
||||||
: [i];
|
|
||||||
bind.sessionSwitchDisplay(
|
|
||||||
sessionId: ffi.sessionId, value: Int32List.fromList(displays));
|
|
||||||
ffi.ffiModel.switchToNewDisplay(i, ffi.sessionId, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressed(int i, PeerInfo pi) {
|
onPressed(int i, PeerInfo pi) {
|
||||||
_menuDismissCallback(ffi);
|
_menuDismissCallback(ffi);
|
||||||
RxInt display = CurrentDisplayState.find(id);
|
RxInt display = CurrentDisplayState.find(id);
|
||||||
if (display.value != i) {
|
if (display.value != i) {
|
||||||
if (isChooseDisplayToOpenInNewWindow(pi, ffi.sessionId)) {
|
if (isChooseDisplayToOpenInNewWindow(pi, ffi.sessionId)) {
|
||||||
openMonitorInNewTabOrWindow(i, pi);
|
openMonitorInNewTabOrWindow(i, ffi.id, pi);
|
||||||
} else {
|
} else {
|
||||||
openMonitorInTheSameTab(i, pi);
|
openMonitorInTheSameTab(i, ffi, pi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -827,7 +792,7 @@ class ScreenAdjustor {
|
|||||||
required this.cbExitFullscreen,
|
required this.cbExitFullscreen,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool get isFullscreen => stateGlobal.fullscreen;
|
bool get isFullscreen => stateGlobal.fullscreen.isTrue;
|
||||||
int get windowId => stateGlobal.windowId;
|
int get windowId => stateGlobal.windowId;
|
||||||
|
|
||||||
adjustWindow(BuildContext context) {
|
adjustWindow(BuildContext context) {
|
||||||
@ -981,7 +946,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
cbExitFullscreen: () => widget.setFullscreen(false),
|
cbExitFullscreen: () => widget.setFullscreen(false),
|
||||||
);
|
);
|
||||||
|
|
||||||
bool get isFullscreen => stateGlobal.fullscreen;
|
|
||||||
int get windowId => stateGlobal.windowId;
|
int get windowId => stateGlobal.windowId;
|
||||||
Map<String, bool> get perms => widget.ffi.ffiModel.permissions;
|
Map<String, bool> get perms => widget.ffi.ffiModel.permissions;
|
||||||
PeerInfo get pi => widget.ffi.ffiModel.pi;
|
PeerInfo get pi => widget.ffi.ffiModel.pi;
|
||||||
@ -1014,6 +978,10 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
screenAdjustor: _screenAdjustor,
|
screenAdjustor: _screenAdjustor,
|
||||||
),
|
),
|
||||||
|
_VirtualDisplayMenu(
|
||||||
|
id: widget.id,
|
||||||
|
ffi: widget.ffi,
|
||||||
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
toggles(),
|
toggles(),
|
||||||
widget.pluginItem,
|
widget.pluginItem,
|
||||||
@ -1423,6 +1391,70 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _VirtualDisplayMenu extends StatefulWidget {
|
||||||
|
final String id;
|
||||||
|
final FFI ffi;
|
||||||
|
|
||||||
|
_VirtualDisplayMenu({
|
||||||
|
Key? key,
|
||||||
|
required this.id,
|
||||||
|
required this.ffi,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_VirtualDisplayMenu> createState() => _VirtualDisplayMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VirtualDisplayMenuState extends State<_VirtualDisplayMenu> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) {
|
||||||
|
return Offstage();
|
||||||
|
}
|
||||||
|
if (!widget.ffi.ffiModel.pi.isInstalled) {
|
||||||
|
return Offstage();
|
||||||
|
}
|
||||||
|
|
||||||
|
final virtualDisplays = widget.ffi.ffiModel.pi.virtualDisplays;
|
||||||
|
|
||||||
|
final children = <Widget>[];
|
||||||
|
for (var i = 0; i < kMaxVirtualDisplayCount; i++) {
|
||||||
|
children.add(CkbMenuButton(
|
||||||
|
value: virtualDisplays.contains(i + 1),
|
||||||
|
onChanged: (bool? value) async {
|
||||||
|
if (value != null) {
|
||||||
|
bind.sessionToggleVirtualDisplay(
|
||||||
|
sessionId: widget.ffi.sessionId, index: i + 1, on: value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text('${translate('Virtual display')} ${i + 1}'),
|
||||||
|
ffi: widget.ffi,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
children.add(Divider());
|
||||||
|
children.add(MenuButton(
|
||||||
|
onPressed: () {
|
||||||
|
bind.sessionToggleVirtualDisplay(
|
||||||
|
sessionId: widget.ffi.sessionId,
|
||||||
|
index: kAllVirtualDisplay,
|
||||||
|
on: false);
|
||||||
|
},
|
||||||
|
ffi: widget.ffi,
|
||||||
|
child: Text(translate('Plug out all')),
|
||||||
|
));
|
||||||
|
return _SubmenuButton(
|
||||||
|
ffi: widget.ffi,
|
||||||
|
menuChildren: children,
|
||||||
|
child: Text(translate("Virtual display")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _KeyboardMenu extends StatelessWidget {
|
class _KeyboardMenu extends StatelessWidget {
|
||||||
final String id;
|
final String id;
|
||||||
final FFI ffi;
|
final FFI ffi;
|
||||||
@ -1438,18 +1470,16 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var ffiModel = Provider.of<FfiModel>(context);
|
var ffiModel = Provider.of<FfiModel>(context);
|
||||||
if (!ffiModel.keyboard) return Offstage();
|
if (!ffiModel.keyboard) return Offstage();
|
||||||
|
// If use flutter to grab keys, we can only use one mode.
|
||||||
|
// Map mode and Legacy mode, at least one of them is supported.
|
||||||
String? modeOnly;
|
String? modeOnly;
|
||||||
if (stateGlobal.grabKeyboard) {
|
if (stateGlobal.grabKeyboard) {
|
||||||
if (bind.sessionIsKeyboardModeSupported(
|
if (bind.sessionIsKeyboardModeSupported(
|
||||||
sessionId: ffi.sessionId, mode: _kKeyMapMode)) {
|
sessionId: ffi.sessionId, mode: kKeyMapMode)) {
|
||||||
bind.sessionSetKeyboardMode(
|
modeOnly = kKeyMapMode;
|
||||||
sessionId: ffi.sessionId, value: _kKeyMapMode);
|
|
||||||
modeOnly = _kKeyMapMode;
|
|
||||||
} else if (bind.sessionIsKeyboardModeSupported(
|
} else if (bind.sessionIsKeyboardModeSupported(
|
||||||
sessionId: ffi.sessionId, mode: _kKeyLegacyMode)) {
|
sessionId: ffi.sessionId, mode: kKeyLegacyMode)) {
|
||||||
bind.sessionSetKeyboardMode(
|
modeOnly = kKeyLegacyMode;
|
||||||
sessionId: ffi.sessionId, value: _kKeyLegacyMode);
|
|
||||||
modeOnly = _kKeyLegacyMode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _IconSubmenuButton(
|
return _IconSubmenuButton(
|
||||||
@ -1471,13 +1501,13 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
keyboardMode(String? modeOnly) {
|
keyboardMode(String? modeOnly) {
|
||||||
return futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ??
|
return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ??
|
||||||
_kKeyLegacyMode;
|
kKeyLegacyMode;
|
||||||
}(), hasData: (data) {
|
}(), hasData: (data) {
|
||||||
final groupValue = data as String;
|
final groupValue = data as String;
|
||||||
List<InputModeMenu> modes = [
|
List<InputModeMenu> modes = [
|
||||||
InputModeMenu(key: _kKeyLegacyMode, menu: 'Legacy mode'),
|
InputModeMenu(key: kKeyLegacyMode, menu: 'Legacy mode'),
|
||||||
InputModeMenu(key: _kKeyMapMode, menu: 'Map mode'),
|
InputModeMenu(key: kKeyMapMode, menu: 'Map mode'),
|
||||||
InputModeMenu(key: _kKeyTranslateMode, menu: 'Translate mode'),
|
InputModeMenu(key: kKeyTranslateMode, menu: 'Translate mode'),
|
||||||
];
|
];
|
||||||
List<RdoMenuButton> list = [];
|
List<RdoMenuButton> list = [];
|
||||||
final enabled = !ffi.ffiModel.viewOnly;
|
final enabled = !ffi.ffiModel.viewOnly;
|
||||||
@ -1495,12 +1525,12 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pi.isWayland && mode.key != _kKeyMapMode) {
|
if (pi.isWayland && mode.key != kKeyMapMode) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var text = translate(mode.menu);
|
var text = translate(mode.menu);
|
||||||
if (mode.key == _kKeyTranslateMode) {
|
if (mode.key == kKeyTranslateMode) {
|
||||||
text = '$text beta';
|
text = '$text beta';
|
||||||
}
|
}
|
||||||
list.add(RdoMenuButton<String>(
|
list.add(RdoMenuButton<String>(
|
||||||
@ -1677,17 +1707,17 @@ class _VoiceCallMenu extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RecordMenu extends StatelessWidget {
|
class _RecordMenu extends StatelessWidget {
|
||||||
final FFI ffi;
|
const _RecordMenu({Key? key}) : super(key: key);
|
||||||
const _RecordMenu({Key? key, required this.ffi}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var ffiModel = Provider.of<FfiModel>(context);
|
var ffi = Provider.of<FfiModel>(context);
|
||||||
var recordingModel = Provider.of<RecordingModel>(context);
|
var recordingModel = Provider.of<RecordingModel>(context);
|
||||||
final visible =
|
final visible =
|
||||||
recordingModel.start || ffiModel.permissions['recording'] != false;
|
(recordingModel.start || ffi.permissions['recording'] != false) &&
|
||||||
|
ffi.pi.currentDisplay != kAllDisplayValue;
|
||||||
if (!visible) return Offstage();
|
if (!visible) return Offstage();
|
||||||
final menuButton = _IconMenuButton(
|
return _IconMenuButton(
|
||||||
assetName: 'assets/rec.svg',
|
assetName: 'assets/rec.svg',
|
||||||
tooltip: recordingModel.start
|
tooltip: recordingModel.start
|
||||||
? 'Stop session recording'
|
? 'Stop session recording'
|
||||||
@ -1700,14 +1730,6 @@ class _RecordMenu extends StatelessWidget {
|
|||||||
? _ToolbarTheme.hoverRedColor
|
? _ToolbarTheme.hoverRedColor
|
||||||
: _ToolbarTheme.hoverBlueColor,
|
: _ToolbarTheme.hoverBlueColor,
|
||||||
);
|
);
|
||||||
return ChangeNotifierProvider.value(
|
|
||||||
value: ffi.qualityMonitorModel,
|
|
||||||
child: Consumer<QualityMonitorModel>(
|
|
||||||
builder: (context, model, child) => Offstage(
|
|
||||||
// If already started, AV1->Hidden/Stop, Other->Start, same as actual
|
|
||||||
offstage: model.data.codecFormat == 'AV1',
|
|
||||||
child: menuButton,
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1722,7 +1744,7 @@ class _CloseMenu extends StatelessWidget {
|
|||||||
return _IconMenuButton(
|
return _IconMenuButton(
|
||||||
assetName: 'assets/close.svg',
|
assetName: 'assets/close.svg',
|
||||||
tooltip: 'Close',
|
tooltip: 'Close',
|
||||||
onPressed: () => clientClose(ffi.sessionId, ffi.dialogManager),
|
onPressed: () => closeConnection(id: id),
|
||||||
color: _ToolbarTheme.redColor,
|
color: _ToolbarTheme.redColor,
|
||||||
hoverColor: _ToolbarTheme.hoverRedColor,
|
hoverColor: _ToolbarTheme.hoverRedColor,
|
||||||
);
|
);
|
||||||
@ -2090,32 +2112,34 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildDraggable(context),
|
_buildDraggable(context),
|
||||||
TextButton(
|
Obx(() => TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.setFullscreen(!isFullscreen);
|
widget.setFullscreen(!isFullscreen.value);
|
||||||
setState(() {});
|
},
|
||||||
},
|
child: Tooltip(
|
||||||
child: Tooltip(
|
message: translate(
|
||||||
message: translate(isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'),
|
isFullscreen.isTrue ? 'Exit Fullscreen' : 'Fullscreen'),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
isFullscreen.isTrue
|
||||||
size: iconSize,
|
? Icons.fullscreen_exit
|
||||||
),
|
: Icons.fullscreen,
|
||||||
),
|
size: iconSize,
|
||||||
),
|
),
|
||||||
Offstage(
|
|
||||||
offstage: !isFullscreen,
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () => widget.setMinimize(),
|
|
||||||
child: Tooltip(
|
|
||||||
message: translate('Minimize'),
|
|
||||||
child: Icon(
|
|
||||||
Icons.remove,
|
|
||||||
size: iconSize,
|
|
||||||
),
|
),
|
||||||
),
|
)),
|
||||||
),
|
Obx(() => Offstage(
|
||||||
),
|
offstage: isFullscreen.isFalse,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => widget.setMinimize(),
|
||||||
|
child: Tooltip(
|
||||||
|
message: translate('Minimize'),
|
||||||
|
child: Icon(
|
||||||
|
Icons.remove,
|
||||||
|
size: iconSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => setState(() {
|
onPressed: () => setState(() {
|
||||||
widget.show.value = !widget.show.value;
|
widget.show.value = !widget.show.value;
|
||||||
|
@ -448,6 +448,7 @@ class DesktopTab extends StatelessWidget {
|
|||||||
isMainWindow: isMainWindow,
|
isMainWindow: isMainWindow,
|
||||||
tabType: tabType,
|
tabType: tabType,
|
||||||
state: state,
|
state: state,
|
||||||
|
tabController: controller,
|
||||||
tail: tail,
|
tail: tail,
|
||||||
showMinimize: showMinimize,
|
showMinimize: showMinimize,
|
||||||
showMaximize: showMaximize,
|
showMaximize: showMaximize,
|
||||||
@ -463,6 +464,7 @@ class WindowActionPanel extends StatefulWidget {
|
|||||||
final bool isMainWindow;
|
final bool isMainWindow;
|
||||||
final DesktopTabType tabType;
|
final DesktopTabType tabType;
|
||||||
final Rx<DesktopTabState> state;
|
final Rx<DesktopTabState> state;
|
||||||
|
final DesktopTabController tabController;
|
||||||
|
|
||||||
final bool showMinimize;
|
final bool showMinimize;
|
||||||
final bool showMaximize;
|
final bool showMaximize;
|
||||||
@ -475,6 +477,7 @@ class WindowActionPanel extends StatefulWidget {
|
|||||||
required this.isMainWindow,
|
required this.isMainWindow,
|
||||||
required this.tabType,
|
required this.tabType,
|
||||||
required this.state,
|
required this.state,
|
||||||
|
required this.tabController,
|
||||||
this.tail,
|
this.tail,
|
||||||
this.showMinimize = true,
|
this.showMinimize = true,
|
||||||
this.showMaximize = true,
|
this.showMaximize = true,
|
||||||
@ -580,19 +583,38 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
void onWindowClose() async {
|
void onWindowClose() async {
|
||||||
mainWindowClose() async => await windowManager.hide();
|
mainWindowClose() async => await windowManager.hide();
|
||||||
notMainWindowClose(WindowController controller) async {
|
notMainWindowClose(WindowController controller) async {
|
||||||
await controller.hide();
|
if (widget.tabController.length == 0) {
|
||||||
await Future.wait([
|
debugPrint("close emtpy multiwindow, hide");
|
||||||
rustDeskWinManager
|
await controller.hide();
|
||||||
.call(WindowType.Main, kWindowEventHide, {"id": kWindowId!}),
|
await rustDeskWinManager
|
||||||
widget.onClose?.call() ?? Future.microtask(() => null)
|
.call(WindowType.Main, kWindowEventHide, {"id": kWindowId!});
|
||||||
]);
|
} else {
|
||||||
|
debugPrint("close not emtpy multiwindow from taskbar");
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
await controller.show();
|
||||||
|
await controller.focus();
|
||||||
|
final res = await widget.onClose?.call() ?? true;
|
||||||
|
if (res) {
|
||||||
|
Future.delayed(Duration.zero, () async {
|
||||||
|
// onWindowClose will be called again to hide
|
||||||
|
await WindowController.fromWindowId(kWindowId!).close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ubuntu22.04 windowOnTop not work from taskbar
|
||||||
|
widget.tabController.clear();
|
||||||
|
Future.delayed(Duration.zero, () async {
|
||||||
|
// onWindowClose will be called again to hide
|
||||||
|
await WindowController.fromWindowId(kWindowId!).close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macOSWindowClose(
|
macOSWindowClose(
|
||||||
Future<void> Function() restoreFunc,
|
Future<bool> Function() checkFullscreen,
|
||||||
Future<bool> Function() checkFullscreen,
|
Future<void> Function() closeFunc,
|
||||||
Future<void> Function() closeFunc) async {
|
) async {
|
||||||
await restoreFunc();
|
|
||||||
_macOSCheckRestoreCounter = 0;
|
_macOSCheckRestoreCounter = 0;
|
||||||
_macOSCheckRestoreTimer =
|
_macOSCheckRestoreTimer =
|
||||||
Timer.periodic(Duration(milliseconds: 30), (timer) async {
|
Timer.periodic(Duration(milliseconds: 30), (timer) async {
|
||||||
@ -612,26 +634,38 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
}
|
}
|
||||||
// macOS specific workaround, the window is not hiding when in fullscreen.
|
// macOS specific workaround, the window is not hiding when in fullscreen.
|
||||||
if (Platform.isMacOS && await windowManager.isFullScreen()) {
|
if (Platform.isMacOS && await windowManager.isFullScreen()) {
|
||||||
stateGlobal.closeOnFullscreen = true;
|
stateGlobal.closeOnFullscreen ??= true;
|
||||||
|
await windowManager.setFullScreen(false);
|
||||||
await macOSWindowClose(
|
await macOSWindowClose(
|
||||||
() async => await windowManager.setFullScreen(false),
|
() async => await windowManager.isFullScreen(),
|
||||||
() async => await windowManager.isFullScreen(),
|
mainWindowClose,
|
||||||
mainWindowClose);
|
);
|
||||||
} else {
|
} else {
|
||||||
stateGlobal.closeOnFullscreen = false;
|
stateGlobal.closeOnFullscreen ??= false;
|
||||||
await mainWindowClose();
|
await mainWindowClose();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// it's safe to hide the subwindow
|
// it's safe to hide the subwindow
|
||||||
final controller = WindowController.fromWindowId(kWindowId!);
|
final controller = WindowController.fromWindowId(kWindowId!);
|
||||||
if (Platform.isMacOS && await controller.isFullScreen()) {
|
if (Platform.isMacOS) {
|
||||||
stateGlobal.closeOnFullscreen = true;
|
// onWindowClose() maybe called multiple times because of loopCloseWindow() in remote_tab_page.dart.
|
||||||
await macOSWindowClose(
|
// use ??= to make sure the value is set on first call.
|
||||||
() async => await controller.setFullscreen(false),
|
|
||||||
() async => await controller.isFullScreen(),
|
if (await widget.onClose?.call() ?? true) {
|
||||||
() async => await notMainWindowClose(controller));
|
if (await controller.isFullScreen()) {
|
||||||
|
stateGlobal.closeOnFullscreen ??= true;
|
||||||
|
await controller.setFullscreen(false);
|
||||||
|
stateGlobal.setFullscreen(false, procWnd: false);
|
||||||
|
await macOSWindowClose(
|
||||||
|
() async => await controller.isFullScreen(),
|
||||||
|
() async => await notMainWindowClose(controller),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
stateGlobal.closeOnFullscreen ??= false;
|
||||||
|
await notMainWindowClose(controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
stateGlobal.closeOnFullscreen = false;
|
|
||||||
await notMainWindowClose(controller);
|
await notMainWindowClose(controller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,8 +198,16 @@ void runMultiWindow(
|
|||||||
}
|
}
|
||||||
switch (appType) {
|
switch (appType) {
|
||||||
case kAppTypeDesktopRemote:
|
case kAppTypeDesktopRemote:
|
||||||
await restoreWindowPosition(WindowType.RemoteDesktop,
|
// If screen rect is set, the window will be moved to the target screen and then set fullscreen.
|
||||||
windowId: kWindowId!, peerId: argument['id'] as String?);
|
if (argument['screen_rect'] == null) {
|
||||||
|
// display can be used to control the offset of the window.
|
||||||
|
await restoreWindowPosition(
|
||||||
|
WindowType.RemoteDesktop,
|
||||||
|
windowId: kWindowId!,
|
||||||
|
peerId: argument['id'] as String?,
|
||||||
|
display: argument['display'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case kAppTypeDesktopFileTransfer:
|
case kAppTypeDesktopFileTransfer:
|
||||||
await restoreWindowPosition(WindowType.FileTransfer,
|
await restoreWindowPosition(WindowType.FileTransfer,
|
||||||
|
@ -6,10 +6,12 @@ import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:flutter_hbb/models/peer_model.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/login.dart';
|
import '../../common/widgets/login.dart';
|
||||||
import '../../common/widgets/peer_tab_page.dart';
|
import '../../common/widgets/peer_tab_page.dart';
|
||||||
|
import '../../common/widgets/autocomplete.dart';
|
||||||
import '../../consts.dart';
|
import '../../consts.dart';
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
@ -42,6 +44,16 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
|
|
||||||
/// Update url. If it's not null, means an update is available.
|
/// Update url. If it's not null, means an update is available.
|
||||||
var _updateUrl = '';
|
var _updateUrl = '';
|
||||||
|
List<Peer> peers = [];
|
||||||
|
List _frontN<T>(List list, int n) {
|
||||||
|
if (list.length <= n) {
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return list.sublist(0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool isPeersLoading = false;
|
||||||
|
bool isPeersLoaded = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -116,6 +128,18 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
color: Colors.white, fontWeight: FontWeight.bold))));
|
color: Colors.white, fontWeight: FontWeight.bold))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchPeers() async {
|
||||||
|
setState(() {
|
||||||
|
isPeersLoading = true;
|
||||||
|
});
|
||||||
|
await Future.delayed(Duration(milliseconds: 100));
|
||||||
|
peers = await getAllPeers();
|
||||||
|
setState(() {
|
||||||
|
isPeersLoading = false;
|
||||||
|
isPeersLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// UI for the remote ID TextField.
|
/// UI for the remote ID TextField.
|
||||||
/// Search for a peer and connect to it if the id exists.
|
/// Search for a peer and connect to it if the id exists.
|
||||||
Widget _buildRemoteIDTextField() {
|
Widget _buildRemoteIDTextField() {
|
||||||
@ -133,12 +157,69 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||||
child: AutoSizeTextField(
|
child: Autocomplete<Peer>(
|
||||||
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
|
if (textEditingValue.text == '') {
|
||||||
|
return const Iterable<Peer>.empty();
|
||||||
|
}
|
||||||
|
else if (peers.isEmpty && !isPeersLoaded) {
|
||||||
|
Peer emptyPeer = Peer(
|
||||||
|
id: '',
|
||||||
|
username: '',
|
||||||
|
hostname: '',
|
||||||
|
alias: '',
|
||||||
|
platform: '',
|
||||||
|
tags: [],
|
||||||
|
hash: '',
|
||||||
|
forceAlwaysRelay: false,
|
||||||
|
rdpPort: '',
|
||||||
|
rdpUsername: '',
|
||||||
|
loginName: '',
|
||||||
|
);
|
||||||
|
return [emptyPeer];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String textWithoutSpaces = textEditingValue.text.replaceAll(" ", "");
|
||||||
|
if (int.tryParse(textWithoutSpaces) != null) {
|
||||||
|
textEditingValue = TextEditingValue(
|
||||||
|
text: textWithoutSpaces,
|
||||||
|
selection: textEditingValue.selection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
String textToFind = textEditingValue.text.toLowerCase();
|
||||||
|
|
||||||
|
return peers.where((peer) =>
|
||||||
|
peer.id.toLowerCase().contains(textToFind) ||
|
||||||
|
peer.username.toLowerCase().contains(textToFind) ||
|
||||||
|
peer.hostname.toLowerCase().contains(textToFind) ||
|
||||||
|
peer.alias.toLowerCase().contains(textToFind))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fieldViewBuilder: (BuildContext context,
|
||||||
|
TextEditingController fieldTextEditingController,
|
||||||
|
FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) {
|
||||||
|
fieldTextEditingController.text = _idController.text;
|
||||||
|
fieldFocusNode.addListener(() async{
|
||||||
|
_idEmpty.value = fieldTextEditingController.text.isEmpty;
|
||||||
|
if (fieldFocusNode.hasFocus && !isPeersLoading){
|
||||||
|
_fetchPeers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final textLength = fieldTextEditingController.value.text.length;
|
||||||
|
// select all to facilitate removing text, just following the behavior of address input of chrome
|
||||||
|
fieldTextEditingController.selection = TextSelection(baseOffset: 0, extentOffset: textLength);
|
||||||
|
return AutoSizeTextField(
|
||||||
|
controller: fieldTextEditingController,
|
||||||
|
focusNode: fieldFocusNode,
|
||||||
minFontSize: 18,
|
minFontSize: 18,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
enableSuggestions: false,
|
enableSuggestions: false,
|
||||||
keyboardType: TextInputType.visiblePassword,
|
keyboardType: TextInputType.visiblePassword,
|
||||||
// keyboardType: TextInputType.number,
|
// keyboardType: TextInputType.number,
|
||||||
|
onChanged: (String text) {
|
||||||
|
_idController.id = text;
|
||||||
|
},
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'WorkSans',
|
fontFamily: 'WorkSans',
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -161,8 +242,42 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
color: MyTheme.darkGray,
|
color: MyTheme.darkGray,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
controller: _idController,
|
|
||||||
inputFormatters: [IDTextInputFormatter()],
|
inputFormatters: [IDTextInputFormatter()],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (option) {
|
||||||
|
setState(() {
|
||||||
|
_idController.id = option.id;
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<Peer> onSelected, Iterable<Peer> options) {
|
||||||
|
double maxHeight = options.length * 50;
|
||||||
|
maxHeight = maxHeight > 200 ? 200 : maxHeight;
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
child: Material(
|
||||||
|
elevation: 4,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: maxHeight,
|
||||||
|
maxWidth: 320,
|
||||||
|
),
|
||||||
|
child: peers.isEmpty && isPeersLoading
|
||||||
|
? Container(
|
||||||
|
height: 80,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
)))
|
||||||
|
: ListView(
|
||||||
|
padding: EdgeInsets.only(top: 5),
|
||||||
|
children: options.map((peer) => AutocompletePeerTile(onSelect: () => onSelected(peer), peer: peer)).toList(),
|
||||||
|
))))
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -170,7 +285,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
offstage: _idEmpty.value,
|
offstage: _idEmpty.value,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_idController.clear();
|
setState(() {
|
||||||
|
_idController.clear();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.clear, color: MyTheme.darkGray)),
|
icon: Icon(Icons.clear, color: MyTheme.darkGray)),
|
||||||
)),
|
)),
|
||||||
|
@ -235,7 +235,7 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
clientClose(sessionId, gFFI.dialogManager);
|
clientClose(sessionId, gFFI.dialogManager);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: getRawPointerAndKeyBody(Scaffold(
|
child: Scaffold(
|
||||||
// workaround for https://github.com/rustdesk/rustdesk/issues/3131
|
// workaround for https://github.com/rustdesk/rustdesk/issues/3131
|
||||||
floatingActionButtonLocation: keyboardIsVisible
|
floatingActionButtonLocation: keyboardIsVisible
|
||||||
? FABLocation(FloatingActionButtonLocation.endFloat, 0, -35)
|
? FABLocation(FloatingActionButtonLocation.endFloat, 0, -35)
|
||||||
@ -281,7 +281,7 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
: Offstage(),
|
: Offstage(),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
body: Overlay(
|
body: getRawPointerAndKeyBody(Overlay(
|
||||||
initialEntries: [
|
initialEntries: [
|
||||||
OverlayEntry(builder: (context) {
|
OverlayEntry(builder: (context) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -767,7 +767,9 @@ void showOptions(
|
|||||||
children.add(InkWell(
|
children.add(InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (i == cur) return;
|
if (i == cur) return;
|
||||||
bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: Int32List.fromList([i]));
|
gFFI.recordingModel.onClose();
|
||||||
|
bind.sessionSwitchDisplay(
|
||||||
|
sessionId: gFFI.sessionId, value: Int32List.fromList([i]));
|
||||||
gFFI.dialogManager.dismissAll();
|
gFFI.dialogManager.dismissAll();
|
||||||
},
|
},
|
||||||
child: Ink(
|
child: Ink(
|
||||||
|
@ -103,7 +103,7 @@ class ChatModel with ChangeNotifier {
|
|||||||
void setOverlayState(BlockableOverlayState blockableOverlayState) {
|
void setOverlayState(BlockableOverlayState blockableOverlayState) {
|
||||||
_blockableOverlayState = blockableOverlayState;
|
_blockableOverlayState = blockableOverlayState;
|
||||||
|
|
||||||
_blockableOverlayState!.addMiddleBlockedListener((v) {
|
_blockableOverlayState.addMiddleBlockedListener((v) {
|
||||||
if (!v) {
|
if (!v) {
|
||||||
isWindowFocus.value = false;
|
isWindowFocus.value = false;
|
||||||
if (isWindowFocus.value) {
|
if (isWindowFocus.value) {
|
||||||
@ -197,9 +197,9 @@ class ChatModel with ChangeNotifier {
|
|||||||
showChatWindowOverlay({Offset? chatInitPos}) {
|
showChatWindowOverlay({Offset? chatInitPos}) {
|
||||||
if (chatWindowOverlayEntry != null) return;
|
if (chatWindowOverlayEntry != null) return;
|
||||||
isWindowFocus.value = true;
|
isWindowFocus.value = true;
|
||||||
_blockableOverlayState?.setMiddleBlocked(true);
|
_blockableOverlayState.setMiddleBlocked(true);
|
||||||
|
|
||||||
final overlayState = _blockableOverlayState?.state;
|
final overlayState = _blockableOverlayState.state;
|
||||||
if (overlayState == null) return;
|
if (overlayState == null) return;
|
||||||
if (isMobile &&
|
if (isMobile &&
|
||||||
!gFFI.chatModel.currentKey.isOut && // not in remote page
|
!gFFI.chatModel.currentKey.isOut && // not in remote page
|
||||||
@ -212,7 +212,7 @@ class ChatModel with ChangeNotifier {
|
|||||||
onPointerDown: (_) {
|
onPointerDown: (_) {
|
||||||
if (!isWindowFocus.value) {
|
if (!isWindowFocus.value) {
|
||||||
isWindowFocus.value = true;
|
isWindowFocus.value = true;
|
||||||
_blockableOverlayState?.setMiddleBlocked(true);
|
_blockableOverlayState.setMiddleBlocked(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: DraggableChatWindow(
|
child: DraggableChatWindow(
|
||||||
@ -228,7 +228,7 @@ class ChatModel with ChangeNotifier {
|
|||||||
|
|
||||||
hideChatWindowOverlay() {
|
hideChatWindowOverlay() {
|
||||||
if (chatWindowOverlayEntry != null) {
|
if (chatWindowOverlayEntry != null) {
|
||||||
_blockableOverlayState?.setMiddleBlocked(false);
|
_blockableOverlayState.setMiddleBlocked(false);
|
||||||
chatWindowOverlayEntry!.remove();
|
chatWindowOverlayEntry!.remove();
|
||||||
chatWindowOverlayEntry = null;
|
chatWindowOverlayEntry = null;
|
||||||
return;
|
return;
|
||||||
|
@ -261,6 +261,7 @@ class FileController {
|
|||||||
required this.getOtherSideDirectoryData});
|
required this.getOtherSideDirectoryData});
|
||||||
|
|
||||||
String get homePath => options.value.home;
|
String get homePath => options.value.home;
|
||||||
|
void set homePath(String path) => options.value.home = path;
|
||||||
OverlayDialogManager? get dialogManager => rootState.target?.dialogManager;
|
OverlayDialogManager? get dialogManager => rootState.target?.dialogManager;
|
||||||
|
|
||||||
String get shortPath {
|
String get shortPath {
|
||||||
@ -376,6 +377,11 @@ class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void goToHomeDirectory() {
|
void goToHomeDirectory() {
|
||||||
|
if (isLocal) {
|
||||||
|
openDirectory(homePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
homePath = "";
|
||||||
openDirectory(homePath);
|
openDirectory(homePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
}, sessionId, peerId);
|
}, sessionId, peerId);
|
||||||
updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId);
|
updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId);
|
||||||
setConnectionType(peerId, data.secure, data.direct);
|
setConnectionType(peerId, data.secure, data.direct);
|
||||||
await handlePeerInfo(data.peerInfo, peerId);
|
await handlePeerInfo(data.peerInfo, peerId, true);
|
||||||
for (final element in data.cursorDataList) {
|
for (final element in data.cursorDataList) {
|
||||||
updateLastCursorId(element);
|
updateLastCursorId(element);
|
||||||
await handleCursorData(element);
|
await handleCursorData(element);
|
||||||
@ -245,9 +245,11 @@ class FfiModel with ChangeNotifier {
|
|||||||
if (name == 'msgbox') {
|
if (name == 'msgbox') {
|
||||||
handleMsgBox(evt, sessionId, peerId);
|
handleMsgBox(evt, sessionId, peerId);
|
||||||
} else if (name == 'peer_info') {
|
} else if (name == 'peer_info') {
|
||||||
handlePeerInfo(evt, peerId);
|
handlePeerInfo(evt, peerId, false);
|
||||||
} else if (name == 'sync_peer_info') {
|
} else if (name == 'sync_peer_info') {
|
||||||
handleSyncPeerInfo(evt, sessionId, peerId);
|
handleSyncPeerInfo(evt, sessionId, peerId);
|
||||||
|
} else if (name == 'sync_platform_additions') {
|
||||||
|
handlePlatformAdditions(evt, sessionId, peerId);
|
||||||
} else if (name == 'connection_ready') {
|
} else if (name == 'connection_ready') {
|
||||||
setConnectionType(
|
setConnectionType(
|
||||||
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
|
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
|
||||||
@ -430,14 +432,12 @@ class FfiModel with ChangeNotifier {
|
|||||||
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
|
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
|
||||||
final curDisplay = int.parse(evt['display']);
|
final curDisplay = int.parse(evt['display']);
|
||||||
|
|
||||||
// The message should be handled by the another UI session.
|
|
||||||
if (isChooseDisplayToOpenInNewWindow(_pi, sessionId)) {
|
|
||||||
if (curDisplay != _pi.currentDisplay) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_pi.currentDisplay != kAllDisplayValue) {
|
if (_pi.currentDisplay != kAllDisplayValue) {
|
||||||
|
if (bind.peerGetDefaultSessionsCount(id: peerId) > 1) {
|
||||||
|
if (curDisplay != _pi.currentDisplay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
_pi.currentDisplay = curDisplay;
|
_pi.currentDisplay = curDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,7 +514,9 @@ class FfiModel with ChangeNotifier {
|
|||||||
String link, bool hasRetry, OverlayDialogManager dialogManager,
|
String link, bool hasRetry, OverlayDialogManager dialogManager,
|
||||||
{bool? hasCancel}) {
|
{bool? hasCancel}) {
|
||||||
msgBox(sessionId, type, title, text, link, dialogManager,
|
msgBox(sessionId, type, title, text, link, dialogManager,
|
||||||
hasCancel: hasCancel, reconnect: reconnect);
|
hasCancel: hasCancel,
|
||||||
|
reconnect: reconnect,
|
||||||
|
reconnectTimeout: hasRetry ? _reconnects : null);
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
if (hasRetry) {
|
if (hasRetry) {
|
||||||
_timer = Timer(Duration(seconds: _reconnects), () {
|
_timer = Timer(Duration(seconds: _reconnects), () {
|
||||||
@ -530,6 +532,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
bool forceRelay) {
|
bool forceRelay) {
|
||||||
bind.sessionReconnect(sessionId: sessionId, forceRelay: forceRelay);
|
bind.sessionReconnect(sessionId: sessionId, forceRelay: forceRelay);
|
||||||
clearPermissions();
|
clearPermissions();
|
||||||
|
dialogManager.dismissAll();
|
||||||
dialogManager.showLoading(translate('Connecting...'),
|
dialogManager.showLoading(translate('Connecting...'),
|
||||||
onCancel: closeConnection);
|
onCancel: closeConnection);
|
||||||
}
|
}
|
||||||
@ -623,7 +626,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handle the peer info event based on [evt].
|
/// Handle the peer info event based on [evt].
|
||||||
handlePeerInfo(Map<String, dynamic> evt, String peerId) async {
|
handlePeerInfo(Map<String, dynamic> evt, String peerId, bool isCache) async {
|
||||||
cachedPeerData.peerInfo = evt;
|
cachedPeerData.peerInfo = evt;
|
||||||
|
|
||||||
// recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
|
// recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
|
||||||
@ -689,12 +692,12 @@ class FfiModel with ChangeNotifier {
|
|||||||
sessionId: sessionId, arg: 'view-only'));
|
sessionId: sessionId, arg: 'view-only'));
|
||||||
}
|
}
|
||||||
if (connType == ConnType.defaultConn) {
|
if (connType == ConnType.defaultConn) {
|
||||||
final platformDdditions = evt['platform_additions'];
|
final platformAdditions = evt['platform_additions'];
|
||||||
if (platformDdditions != null && platformDdditions != '') {
|
if (platformAdditions != null && platformAdditions != '') {
|
||||||
try {
|
try {
|
||||||
_pi.platformDdditions = json.decode(platformDdditions);
|
_pi.platformAdditions = json.decode(platformAdditions);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Failed to decode platformDdditions $e');
|
debugPrint('Failed to decode platformAdditions $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -702,7 +705,86 @@ class FfiModel with ChangeNotifier {
|
|||||||
_pi.isSet.value = true;
|
_pi.isSet.value = true;
|
||||||
stateGlobal.resetLastResolutionGroupValues(peerId);
|
stateGlobal.resetLastResolutionGroupValues(peerId);
|
||||||
|
|
||||||
|
if (isDesktop) {
|
||||||
|
checkDesktopKeyboardMode();
|
||||||
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
if (!isCache) {
|
||||||
|
tryUseAllMyDisplaysForTheRemoteSession(peerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDesktopKeyboardMode() async {
|
||||||
|
final curMode = await bind.sessionGetKeyboardMode(sessionId: sessionId);
|
||||||
|
if (curMode != null) {
|
||||||
|
if (bind.sessionIsKeyboardModeSupported(
|
||||||
|
sessionId: sessionId, mode: curMode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If current keyboard mode is not supported, change to another one.
|
||||||
|
|
||||||
|
if (stateGlobal.grabKeyboard) {
|
||||||
|
for (final mode in [kKeyMapMode, kKeyLegacyMode]) {
|
||||||
|
if (bind.sessionIsKeyboardModeSupported(
|
||||||
|
sessionId: sessionId, mode: mode)) {
|
||||||
|
bind.sessionSetKeyboardMode(sessionId: sessionId, value: mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (final mode in [kKeyMapMode, kKeyTranslateMode, kKeyLegacyMode]) {
|
||||||
|
if (bind.sessionIsKeyboardModeSupported(
|
||||||
|
sessionId: sessionId, mode: mode)) {
|
||||||
|
bind.sessionSetKeyboardMode(sessionId: sessionId, value: mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tryUseAllMyDisplaysForTheRemoteSession(String peerId) async {
|
||||||
|
if (bind.sessionGetUseAllMyDisplaysForTheRemoteSession(
|
||||||
|
sessionId: sessionId) !=
|
||||||
|
'Y') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_pi.isSupportMultiDisplay || _pi.displays.length <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final screenRectList = await getScreenRectList();
|
||||||
|
if (screenRectList.length <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// to-do: peer currentDisplay is the primary display, but the primary display may not be the first display.
|
||||||
|
// local primary display also may not be the first display.
|
||||||
|
//
|
||||||
|
// 0 is assumed to be the primary display here, for now.
|
||||||
|
|
||||||
|
// move to the first display and set fullscreen
|
||||||
|
bind.sessionSwitchDisplay(
|
||||||
|
sessionId: sessionId, value: Int32List.fromList([0]));
|
||||||
|
_pi.currentDisplay = 0;
|
||||||
|
try {
|
||||||
|
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
await tryMoveToScreenAndSetFullscreen(screenRectList[0]);
|
||||||
|
|
||||||
|
final length = _pi.displays.length < screenRectList.length
|
||||||
|
? _pi.displays.length
|
||||||
|
: screenRectList.length;
|
||||||
|
for (var i = 1; i < length; i++) {
|
||||||
|
openMonitorInNewTabOrWindow(i, peerId, _pi,
|
||||||
|
screenRect: screenRectList[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tryShowAndroidActionsOverlay({int delayMSecs = 10}) {
|
tryShowAndroidActionsOverlay({int delayMSecs = 10}) {
|
||||||
@ -780,6 +862,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
_pi.displays = newDisplays;
|
_pi.displays = newDisplays;
|
||||||
_pi.displaysCount.value = _pi.displays.length;
|
_pi.displaysCount.value = _pi.displays.length;
|
||||||
|
|
||||||
if (_pi.currentDisplay == kAllDisplayValue) {
|
if (_pi.currentDisplay == kAllDisplayValue) {
|
||||||
updateCurDisplay(sessionId);
|
updateCurDisplay(sessionId);
|
||||||
// to-do: What if the displays are changed?
|
// to-do: What if the displays are changed?
|
||||||
@ -814,8 +897,37 @@ class FfiModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePlatformAdditions(
|
||||||
|
Map<String, dynamic> evt, SessionID sessionId, String peerId) async {
|
||||||
|
final updateData = evt['platform_additions'] as String?;
|
||||||
|
if (updateData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateData.isEmpty) {
|
||||||
|
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
final updateJson = json.decode(updateData);
|
||||||
|
for (final key in updateJson.keys) {
|
||||||
|
_pi.platformAdditions[key] = updateJson[key];
|
||||||
|
}
|
||||||
|
if (!updateJson.contains(kPlatformAdditionsVirtualDisplays)) {
|
||||||
|
_pi.platformAdditions.remove(kPlatformAdditionsVirtualDisplays);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Failed to decode platformAdditions $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedPeerData.peerInfo['platform_additions'] =
|
||||||
|
json.encode(_pi.platformAdditions);
|
||||||
|
}
|
||||||
|
|
||||||
// Directly switch to the new display without waiting for the response.
|
// Directly switch to the new display without waiting for the response.
|
||||||
switchToNewDisplay(int display, SessionID sessionId, String peerId) {
|
switchToNewDisplay(int display, SessionID sessionId, String peerId) {
|
||||||
|
// VideoHandler creation is upon when video frames are received, so either caching commands(don't know next width/height) or stopping recording when switching displays.
|
||||||
|
parent.target?.recordingModel.onClose();
|
||||||
// no need to wait for the response
|
// no need to wait for the response
|
||||||
pi.currentDisplay = display;
|
pi.currentDisplay = display;
|
||||||
updateCurDisplay(sessionId);
|
updateCurDisplay(sessionId);
|
||||||
@ -824,7 +936,6 @@ class FfiModel with ChangeNotifier {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
parent.target?.recordingModel.onSwitchDisplay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
|
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
|
||||||
@ -1806,57 +1917,67 @@ class RecordingModel with ChangeNotifier {
|
|||||||
int? width = parent.target?.canvasModel.getDisplayWidth();
|
int? width = parent.target?.canvasModel.getDisplayWidth();
|
||||||
int? height = parent.target?.canvasModel.getDisplayHeight();
|
int? height = parent.target?.canvasModel.getDisplayHeight();
|
||||||
if (sessionId == null || width == null || height == null) return;
|
if (sessionId == null || width == null || height == null) return;
|
||||||
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
final pi = parent.target?.ffiModel.pi;
|
||||||
if (currentDisplay != kAllDisplayValue) {
|
if (pi == null) return;
|
||||||
bind.sessionRecordScreen(
|
final currentDisplay = pi.currentDisplay;
|
||||||
sessionId: sessionId,
|
if (currentDisplay == kAllDisplayValue) return;
|
||||||
start: true,
|
bind.sessionRecordScreen(
|
||||||
display: currentDisplay!,
|
sessionId: sessionId,
|
||||||
width: width,
|
start: true,
|
||||||
height: height);
|
display: currentDisplay,
|
||||||
}
|
width: width,
|
||||||
|
height: height);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() async {
|
toggle() async {
|
||||||
if (isIOS) return;
|
if (isIOS) return;
|
||||||
final sessionId = parent.target?.sessionId;
|
final sessionId = parent.target?.sessionId;
|
||||||
if (sessionId == null) return;
|
if (sessionId == null) return;
|
||||||
|
final pi = parent.target?.ffiModel.pi;
|
||||||
|
if (pi == null) return;
|
||||||
|
final currentDisplay = pi.currentDisplay;
|
||||||
|
if (currentDisplay == kAllDisplayValue) return;
|
||||||
_start = !_start;
|
_start = !_start;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
await bind.sessionRecordStatus(sessionId: sessionId, status: _start);
|
await _sendStatusMessage(sessionId, pi, _start);
|
||||||
if (_start) {
|
if (_start) {
|
||||||
final pi = parent.target?.ffiModel.pi;
|
sessionRefreshVideo(sessionId, pi);
|
||||||
if (pi != null) {
|
if (versionCmp(pi.version, '1.2.4') >= 0) {
|
||||||
sessionRefreshVideo(sessionId, pi);
|
// will not receive SwitchDisplay since 1.2.4
|
||||||
|
onSwitchDisplay();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
|
||||||
if (currentDisplay != kAllDisplayValue) {
|
|
||||||
bind.sessionRecordScreen(
|
|
||||||
sessionId: sessionId,
|
|
||||||
start: false,
|
|
||||||
display: currentDisplay!,
|
|
||||||
width: 0,
|
|
||||||
height: 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose() {
|
|
||||||
if (isIOS) return;
|
|
||||||
final sessionId = parent.target?.sessionId;
|
|
||||||
if (sessionId == null) return;
|
|
||||||
_start = false;
|
|
||||||
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
|
||||||
if (currentDisplay != kAllDisplayValue) {
|
|
||||||
bind.sessionRecordScreen(
|
bind.sessionRecordScreen(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
start: false,
|
start: false,
|
||||||
display: currentDisplay!,
|
display: currentDisplay,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0);
|
height: 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClose() async {
|
||||||
|
if (isIOS) return;
|
||||||
|
final sessionId = parent.target?.sessionId;
|
||||||
|
if (sessionId == null) return;
|
||||||
|
if (!_start) return;
|
||||||
|
_start = false;
|
||||||
|
final pi = parent.target?.ffiModel.pi;
|
||||||
|
if (pi == null) return;
|
||||||
|
final currentDisplay = pi.currentDisplay;
|
||||||
|
if (currentDisplay == kAllDisplayValue) return;
|
||||||
|
await _sendStatusMessage(sessionId, pi, false);
|
||||||
|
bind.sessionRecordScreen(
|
||||||
|
sessionId: sessionId,
|
||||||
|
start: false,
|
||||||
|
display: currentDisplay,
|
||||||
|
width: 0,
|
||||||
|
height: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendStatusMessage(SessionID sessionId, PeerInfo pi, bool status) async {
|
||||||
|
await bind.sessionRecordStatus(sessionId: sessionId, status: status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElevationModel with ChangeNotifier {
|
class ElevationModel with ChangeNotifier {
|
||||||
@ -2203,13 +2324,18 @@ class PeerInfo with ChangeNotifier {
|
|||||||
List<Display> displays = [];
|
List<Display> displays = [];
|
||||||
Features features = Features();
|
Features features = Features();
|
||||||
List<Resolution> resolutions = [];
|
List<Resolution> resolutions = [];
|
||||||
Map<String, dynamic> platformDdditions = {};
|
Map<String, dynamic> platformAdditions = {};
|
||||||
|
|
||||||
RxInt displaysCount = 0.obs;
|
RxInt displaysCount = 0.obs;
|
||||||
RxBool isSet = false.obs;
|
RxBool isSet = false.obs;
|
||||||
|
|
||||||
bool get isWayland => platformDdditions['is_wayland'] == true;
|
bool get isWayland => platformAdditions[kPlatformAdditionsIsWayland] == true;
|
||||||
bool get isHeadless => platformDdditions['headless'] == true;
|
bool get isHeadless => platformAdditions[kPlatformAdditionsHeadless] == true;
|
||||||
|
bool get isInstalled =>
|
||||||
|
platform != kPeerPlatformWindows ||
|
||||||
|
platformAdditions[kPlatformAdditionsIsInstalled] == true;
|
||||||
|
List<int> get virtualDisplays => List<int>.from(
|
||||||
|
platformAdditions[kPlatformAdditionsVirtualDisplays] ?? []);
|
||||||
|
|
||||||
bool get isSupportMultiDisplay => isDesktop && isSupportMultiUiSession;
|
bool get isSupportMultiDisplay => isDesktop && isSupportMultiUiSession;
|
||||||
|
|
||||||
@ -2237,7 +2363,7 @@ class PeerInfo with ChangeNotifier {
|
|||||||
if (currentDisplay == kAllDisplayValue) {
|
if (currentDisplay == kAllDisplayValue) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (currentDisplay > 0 && currentDisplay < displays.length) {
|
if (currentDisplay >= 0 && currentDisplay < displays.length) {
|
||||||
return displays[currentDisplay];
|
return displays[currentDisplay];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -11,7 +11,7 @@ enum SvcStatus { notReady, connecting, ready }
|
|||||||
class StateGlobal {
|
class StateGlobal {
|
||||||
int _windowId = -1;
|
int _windowId = -1;
|
||||||
bool grabKeyboard = false;
|
bool grabKeyboard = false;
|
||||||
bool _fullscreen = false;
|
final RxBool _fullscreen = false.obs;
|
||||||
bool _isMinimized = false;
|
bool _isMinimized = false;
|
||||||
final RxBool isMaximized = false.obs;
|
final RxBool isMaximized = false.obs;
|
||||||
final RxBool _showTabBar = true.obs;
|
final RxBool _showTabBar = true.obs;
|
||||||
@ -20,15 +20,15 @@ class StateGlobal {
|
|||||||
final RxBool showRemoteToolBar = false.obs;
|
final RxBool showRemoteToolBar = false.obs;
|
||||||
final svcStatus = SvcStatus.notReady.obs;
|
final svcStatus = SvcStatus.notReady.obs;
|
||||||
// Only used for macOS
|
// Only used for macOS
|
||||||
bool closeOnFullscreen = false;
|
bool? closeOnFullscreen;
|
||||||
|
|
||||||
// Use for desktop -> remote toolbar -> resolution
|
// Use for desktop -> remote toolbar -> resolution
|
||||||
final Map<String, Map<int, String?>> _lastResolutionGroupValues = {};
|
final Map<String, Map<int, String?>> _lastResolutionGroupValues = {};
|
||||||
|
|
||||||
int get windowId => _windowId;
|
int get windowId => _windowId;
|
||||||
bool get fullscreen => _fullscreen;
|
RxBool get fullscreen => _fullscreen;
|
||||||
bool get isMinimized => _isMinimized;
|
bool get isMinimized => _isMinimized;
|
||||||
double get tabBarHeight => fullscreen ? 0 : kDesktopRemoteTabBarHeight;
|
double get tabBarHeight => fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight;
|
||||||
RxBool get showTabBar => _showTabBar;
|
RxBool get showTabBar => _showTabBar;
|
||||||
RxDouble get resizeEdgeSize => _resizeEdgeSize;
|
RxDouble get resizeEdgeSize => _resizeEdgeSize;
|
||||||
RxDouble get windowBorderWidth => _windowBorderWidth;
|
RxDouble get windowBorderWidth => _windowBorderWidth;
|
||||||
@ -51,7 +51,7 @@ class StateGlobal {
|
|||||||
|
|
||||||
setWindowId(int id) => _windowId = id;
|
setWindowId(int id) => _windowId = id;
|
||||||
setMaximized(bool v) {
|
setMaximized(bool v) {
|
||||||
if (!_fullscreen) {
|
if (!_fullscreen.isTrue) {
|
||||||
if (isMaximized.value != v) {
|
if (isMaximized.value != v) {
|
||||||
isMaximized.value = v;
|
isMaximized.value = v;
|
||||||
_resizeEdgeSize.value =
|
_resizeEdgeSize.value =
|
||||||
@ -66,29 +66,27 @@ class StateGlobal {
|
|||||||
setMinimized(bool v) => _isMinimized = v;
|
setMinimized(bool v) => _isMinimized = v;
|
||||||
|
|
||||||
setFullscreen(bool v, {bool procWnd = true}) {
|
setFullscreen(bool v, {bool procWnd = true}) {
|
||||||
if (_fullscreen != v) {
|
if (_fullscreen.value != v) {
|
||||||
_fullscreen = v;
|
_fullscreen.value = v;
|
||||||
_showTabBar.value = !_fullscreen;
|
_showTabBar.value = !_fullscreen.value;
|
||||||
_resizeEdgeSize.value = fullscreen
|
_resizeEdgeSize.value = fullscreen.isTrue
|
||||||
? kFullScreenEdgeSize
|
? kFullScreenEdgeSize
|
||||||
: isMaximized.isTrue
|
: isMaximized.isTrue
|
||||||
? kMaximizeEdgeSize
|
? kMaximizeEdgeSize
|
||||||
: kWindowEdgeSize;
|
: kWindowEdgeSize;
|
||||||
print(
|
print(
|
||||||
"fullscreen: $fullscreen, resizeEdgeSize: ${_resizeEdgeSize.value}");
|
"fullscreen: $fullscreen, resizeEdgeSize: ${_resizeEdgeSize.value}");
|
||||||
_windowBorderWidth.value = fullscreen ? 0 : kWindowBorderWidth;
|
_windowBorderWidth.value = fullscreen.isTrue ? 0 : kWindowBorderWidth;
|
||||||
if (procWnd) {
|
if (procWnd) {
|
||||||
WindowController.fromWindowId(windowId)
|
final wc = WindowController.fromWindowId(windowId);
|
||||||
.setFullscreen(_fullscreen)
|
wc.setFullscreen(_fullscreen.isTrue).then((_) {
|
||||||
.then((_) {
|
|
||||||
// https://github.com/leanflutter/window_manager/issues/131#issuecomment-1111587982
|
// https://github.com/leanflutter/window_manager/issues/131#issuecomment-1111587982
|
||||||
if (Platform.isWindows && !v) {
|
if (Platform.isWindows && !v) {
|
||||||
Future.delayed(Duration.zero, () async {
|
Future.delayed(Duration.zero, () async {
|
||||||
final frame =
|
final frame = await wc.getFrame();
|
||||||
await WindowController.fromWindowId(windowId).getFrame();
|
|
||||||
final newRect = Rect.fromLTWH(
|
final newRect = Rect.fromLTWH(
|
||||||
frame.left, frame.top, frame.width + 1, frame.height + 1);
|
frame.left, frame.top, frame.width + 1, frame.height + 1);
|
||||||
await WindowController.fromWindowId(windowId).setFrame(newRect);
|
await wc.setFrame(newRect);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -69,8 +69,8 @@ class RustDeskMultiWindowManager {
|
|||||||
|
|
||||||
// This function must be called in the main window thread.
|
// This function must be called in the main window thread.
|
||||||
// Because the _remoteDesktopWindows is managed in that thread.
|
// Because the _remoteDesktopWindows is managed in that thread.
|
||||||
openMonitorSession(
|
openMonitorSession(int windowId, String peerId, int display, int displayCount,
|
||||||
int windowId, String peerId, int display, int displayCount) async {
|
Rect? screenRect) async {
|
||||||
if (_remoteDesktopWindows.length > 1) {
|
if (_remoteDesktopWindows.length > 1) {
|
||||||
for (final windowId in _remoteDesktopWindows) {
|
for (final windowId in _remoteDesktopWindows) {
|
||||||
if (await DesktopMultiWindow.invokeMethod(
|
if (await DesktopMultiWindow.invokeMethod(
|
||||||
@ -95,6 +95,14 @@ class RustDeskMultiWindowManager {
|
|||||||
'display': display,
|
'display': display,
|
||||||
'displays': displays,
|
'displays': displays,
|
||||||
};
|
};
|
||||||
|
if (screenRect != null) {
|
||||||
|
params['screen_rect'] = {
|
||||||
|
'l': screenRect.left,
|
||||||
|
't': screenRect.top,
|
||||||
|
'r': screenRect.right,
|
||||||
|
'b': screenRect.bottom,
|
||||||
|
};
|
||||||
|
}
|
||||||
await _newSession(
|
await _newSession(
|
||||||
false,
|
false,
|
||||||
WindowType.RemoteDesktop,
|
WindowType.RemoteDesktop,
|
||||||
@ -102,21 +110,34 @@ class RustDeskMultiWindowManager {
|
|||||||
peerId,
|
peerId,
|
||||||
_remoteDesktopWindows,
|
_remoteDesktopWindows,
|
||||||
jsonEncode(params),
|
jsonEncode(params),
|
||||||
|
screenRect: screenRect,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> newSessionWindow(
|
Future<int> newSessionWindow(
|
||||||
WindowType type, String remoteId, String msg, List<int> windows) async {
|
WindowType type,
|
||||||
|
String remoteId,
|
||||||
|
String msg,
|
||||||
|
List<int> windows,
|
||||||
|
bool withScreenRect,
|
||||||
|
) async {
|
||||||
final windowController = await DesktopMultiWindow.createWindow(msg);
|
final windowController = await DesktopMultiWindow.createWindow(msg);
|
||||||
final windowId = windowController.windowId;
|
final windowId = windowController.windowId;
|
||||||
windowController
|
if (!withScreenRect) {
|
||||||
..setFrame(
|
windowController
|
||||||
const Offset(0, 0) & Size(1280 + windowId * 20, 720 + windowId * 20))
|
..setFrame(const Offset(0, 0) &
|
||||||
..center()
|
Size(1280 + windowId * 20, 720 + windowId * 20))
|
||||||
..setTitle(getWindowNameWithId(
|
..center()
|
||||||
|
..setTitle(getWindowNameWithId(
|
||||||
|
remoteId,
|
||||||
|
overrideType: type,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
windowController.setTitle(getWindowNameWithId(
|
||||||
remoteId,
|
remoteId,
|
||||||
overrideType: type,
|
overrideType: type,
|
||||||
));
|
));
|
||||||
|
}
|
||||||
if (Platform.isMacOS) {
|
if (Platform.isMacOS) {
|
||||||
Future.microtask(() => windowController.show());
|
Future.microtask(() => windowController.show());
|
||||||
}
|
}
|
||||||
@ -131,11 +152,13 @@ class RustDeskMultiWindowManager {
|
|||||||
String methodName,
|
String methodName,
|
||||||
String remoteId,
|
String remoteId,
|
||||||
List<int> windows,
|
List<int> windows,
|
||||||
String msg,
|
String msg, {
|
||||||
) async {
|
Rect? screenRect,
|
||||||
|
}) async {
|
||||||
if (openInTabs) {
|
if (openInTabs) {
|
||||||
if (windows.isEmpty) {
|
if (windows.isEmpty) {
|
||||||
final windowId = await newSessionWindow(type, remoteId, msg, windows);
|
final windowId = await newSessionWindow(
|
||||||
|
type, remoteId, msg, windows, screenRect != null);
|
||||||
return MultiWindowCallResult(windowId, null);
|
return MultiWindowCallResult(windowId, null);
|
||||||
} else {
|
} else {
|
||||||
return call(type, methodName, msg);
|
return call(type, methodName, msg);
|
||||||
@ -144,8 +167,10 @@ class RustDeskMultiWindowManager {
|
|||||||
if (_inactiveWindows.isNotEmpty) {
|
if (_inactiveWindows.isNotEmpty) {
|
||||||
for (final windowId in windows) {
|
for (final windowId in windows) {
|
||||||
if (_inactiveWindows.contains(windowId)) {
|
if (_inactiveWindows.contains(windowId)) {
|
||||||
await restoreWindowPosition(type,
|
if (screenRect == null) {
|
||||||
windowId: windowId, peerId: remoteId);
|
await restoreWindowPosition(type,
|
||||||
|
windowId: windowId, peerId: remoteId);
|
||||||
|
}
|
||||||
await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
|
await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
|
||||||
WindowController.fromWindowId(windowId).show();
|
WindowController.fromWindowId(windowId).show();
|
||||||
registerActiveWindow(windowId);
|
registerActiveWindow(windowId);
|
||||||
@ -153,7 +178,8 @@ class RustDeskMultiWindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final windowId = await newSessionWindow(type, remoteId, msg, windows);
|
final windowId = await newSessionWindow(
|
||||||
|
type, remoteId, msg, windows, screenRect != null);
|
||||||
return MultiWindowCallResult(windowId, null);
|
return MultiWindowCallResult(windowId, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,7 @@ message PeerInfo {
|
|||||||
SupportedEncoding encoding = 10;
|
SupportedEncoding encoding = 10;
|
||||||
SupportedResolutions resolutions = 11;
|
SupportedResolutions resolutions = 11;
|
||||||
// Use JSON's key-value format which is friendly for peer to handle.
|
// Use JSON's key-value format which is friendly for peer to handle.
|
||||||
|
// NOTE: Only support one-level dictionaries (for peer to update), and the key is of type string.
|
||||||
string platform_additions = 12;
|
string platform_additions = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,6 +499,11 @@ message CaptureDisplays {
|
|||||||
repeated int32 set = 3;
|
repeated int32 set = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ToggleVirtualDisplay {
|
||||||
|
int32 display = 1;
|
||||||
|
bool on = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message PermissionInfo {
|
message PermissionInfo {
|
||||||
enum Permission {
|
enum Permission {
|
||||||
Keyboard = 0;
|
Keyboard = 0;
|
||||||
@ -697,6 +703,7 @@ message Misc {
|
|||||||
bool client_record_status = 29;
|
bool client_record_status = 29;
|
||||||
CaptureDisplays capture_displays = 30;
|
CaptureDisplays capture_displays = 30;
|
||||||
int32 refresh_video_display = 31;
|
int32 refresh_video_display = 31;
|
||||||
|
ToggleVirtualDisplay toggle_virtual_display = 32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,6 +290,12 @@ pub struct PeerConfig {
|
|||||||
skip_serializing_if = "String::is_empty"
|
skip_serializing_if = "String::is_empty"
|
||||||
)]
|
)]
|
||||||
pub displays_as_individual_windows: String,
|
pub displays_as_individual_windows: String,
|
||||||
|
#[serde(
|
||||||
|
default = "PeerConfig::default_use_all_my_displays_for_the_remote_session",
|
||||||
|
deserialize_with = "PeerConfig::deserialize_use_all_my_displays_for_the_remote_session",
|
||||||
|
skip_serializing_if = "String::is_empty"
|
||||||
|
)]
|
||||||
|
pub use_all_my_displays_for_the_remote_session: String,
|
||||||
|
|
||||||
#[serde(
|
#[serde(
|
||||||
default,
|
default,
|
||||||
@ -335,6 +341,8 @@ impl Default for PeerConfig {
|
|||||||
view_only: Default::default(),
|
view_only: Default::default(),
|
||||||
reverse_mouse_wheel: Self::default_reverse_mouse_wheel(),
|
reverse_mouse_wheel: Self::default_reverse_mouse_wheel(),
|
||||||
displays_as_individual_windows: Self::default_displays_as_individual_windows(),
|
displays_as_individual_windows: Self::default_displays_as_individual_windows(),
|
||||||
|
use_all_my_displays_for_the_remote_session:
|
||||||
|
Self::default_use_all_my_displays_for_the_remote_session(),
|
||||||
custom_resolutions: Default::default(),
|
custom_resolutions: Default::default(),
|
||||||
options: Self::default_options(),
|
options: Self::default_options(),
|
||||||
ui_flutter: Default::default(),
|
ui_flutter: Default::default(),
|
||||||
@ -561,7 +569,7 @@ impl Config {
|
|||||||
|
|
||||||
pub fn get_home() -> PathBuf {
|
pub fn get_home() -> PathBuf {
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
return Self::path(APP_HOME_DIR.read().unwrap().as_str());
|
return PathBuf::from(APP_HOME_DIR.read().unwrap().as_str());
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
{
|
{
|
||||||
if let Some(path) = dirs_next::home_dir() {
|
if let Some(path) = dirs_next::home_dir() {
|
||||||
@ -615,6 +623,13 @@ impl Config {
|
|||||||
std::fs::create_dir_all(&path).ok();
|
std::fs::create_dir_all(&path).ok();
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
{
|
||||||
|
let mut path = Self::get_home();
|
||||||
|
path.push(format!("{}/Logs", *APP_NAME.read().unwrap()));
|
||||||
|
std::fs::create_dir_all(&path).ok();
|
||||||
|
return path;
|
||||||
|
}
|
||||||
if let Some(path) = Self::path("").parent() {
|
if let Some(path) = Self::path("").parent() {
|
||||||
let mut path: PathBuf = path.into();
|
let mut path: PathBuf = path.into();
|
||||||
path.push("log");
|
path.push("log");
|
||||||
@ -1156,6 +1171,11 @@ impl PeerConfig {
|
|||||||
deserialize_displays_as_individual_windows,
|
deserialize_displays_as_individual_windows,
|
||||||
UserDefaultConfig::read().get("displays_as_individual_windows")
|
UserDefaultConfig::read().get("displays_as_individual_windows")
|
||||||
);
|
);
|
||||||
|
serde_field_string!(
|
||||||
|
default_use_all_my_displays_for_the_remote_session,
|
||||||
|
deserialize_use_all_my_displays_for_the_remote_session,
|
||||||
|
UserDefaultConfig::read().get("use_all_my_displays_for_the_remote_session")
|
||||||
|
);
|
||||||
|
|
||||||
fn default_custom_image_quality() -> Vec<i32> {
|
fn default_custom_image_quality() -> Vec<i32> {
|
||||||
let f: f64 = UserDefaultConfig::read()
|
let f: f64 = UserDefaultConfig::read()
|
||||||
|
@ -19,7 +19,7 @@ cfg-if = "1.0"
|
|||||||
num_cpus = "1.15"
|
num_cpus = "1.15"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
hbb_common = { path = "../hbb_common" }
|
hbb_common = { path = "../hbb_common" }
|
||||||
webm = "1.0"
|
webm = { git = "https://github.com/21pages/rust-webm" }
|
||||||
|
|
||||||
[dependencies.winapi]
|
[dependencies.winapi]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
|
@ -362,13 +362,14 @@ pub fn check_config_process() {
|
|||||||
let f = || {
|
let f = || {
|
||||||
// Clear to avoid checking process errors
|
// Clear to avoid checking process errors
|
||||||
// But when the program is just started, the configuration file has not been updated, and the new connection will read an empty configuration
|
// But when the program is just started, the configuration file has not been updated, and the new connection will read an empty configuration
|
||||||
|
// TODO: --server start multi times on windows startup, which will clear the last config and cause concurrent file writing
|
||||||
HwCodecConfig::clear();
|
HwCodecConfig::clear();
|
||||||
if let Ok(exe) = std::env::current_exe() {
|
if let Ok(exe) = std::env::current_exe() {
|
||||||
if let Some(_) = exe.file_name().to_owned() {
|
if let Some(_) = exe.file_name().to_owned() {
|
||||||
let arg = "--check-hwcodec-config";
|
let arg = "--check-hwcodec-config";
|
||||||
if let Ok(mut child) = std::process::Command::new(exe).arg(arg).spawn() {
|
if let Ok(mut child) = std::process::Command::new(exe).arg(arg).spawn() {
|
||||||
// wait up to 10 seconds
|
// wait up to 30 seconds, it maybe slow on windows startup for poorly performing machines
|
||||||
for _ in 0..10 {
|
for _ in 0..30 {
|
||||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
if let Ok(Some(_)) = child.try_wait() {
|
if let Ok(Some(_)) = child.try_wait() {
|
||||||
break;
|
break;
|
||||||
|
@ -49,9 +49,12 @@ impl RecorderContext {
|
|||||||
}
|
}
|
||||||
let file = if self.server { "s" } else { "c" }.to_string()
|
let file = if self.server { "s" } else { "c" }.to_string()
|
||||||
+ &self.id.clone()
|
+ &self.id.clone()
|
||||||
+ &chrono::Local::now().format("_%Y%m%d%H%M%S_").to_string()
|
+ &chrono::Local::now().format("_%Y%m%d%H%M%S%3f_").to_string()
|
||||||
+ &self.format.to_string()
|
+ &self.format.to_string().to_lowercase()
|
||||||
+ if self.format == CodecFormat::VP9 || self.format == CodecFormat::VP8 {
|
+ if self.format == CodecFormat::VP9
|
||||||
|
|| self.format == CodecFormat::VP8
|
||||||
|
|| self.format == CodecFormat::AV1
|
||||||
|
{
|
||||||
".webm"
|
".webm"
|
||||||
} else {
|
} else {
|
||||||
".mp4"
|
".mp4"
|
||||||
@ -83,6 +86,7 @@ pub enum RecordState {
|
|||||||
pub struct Recorder {
|
pub struct Recorder {
|
||||||
pub inner: Box<dyn RecorderApi>,
|
pub inner: Box<dyn RecorderApi>,
|
||||||
ctx: RecorderContext,
|
ctx: RecorderContext,
|
||||||
|
pts: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Recorder {
|
impl Deref for Recorder {
|
||||||
@ -101,19 +105,18 @@ impl DerefMut for Recorder {
|
|||||||
|
|
||||||
impl Recorder {
|
impl Recorder {
|
||||||
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
||||||
if ctx.format == CodecFormat::AV1 {
|
|
||||||
bail!("not support av1 recording");
|
|
||||||
}
|
|
||||||
ctx.set_filename()?;
|
ctx.set_filename()?;
|
||||||
let recorder = match ctx.format {
|
let recorder = match ctx.format {
|
||||||
CodecFormat::VP8 | CodecFormat::VP9 => Recorder {
|
CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => Recorder {
|
||||||
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
||||||
ctx,
|
ctx,
|
||||||
|
pts: None,
|
||||||
},
|
},
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
_ => Recorder {
|
_ => Recorder {
|
||||||
inner: Box::new(HwRecorder::new(ctx.clone())?),
|
inner: Box::new(HwRecorder::new(ctx.clone())?),
|
||||||
ctx,
|
ctx,
|
||||||
|
pts: None,
|
||||||
},
|
},
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
_ => bail!("unsupported codec type"),
|
_ => bail!("unsupported codec type"),
|
||||||
@ -125,13 +128,16 @@ impl Recorder {
|
|||||||
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
||||||
ctx.set_filename()?;
|
ctx.set_filename()?;
|
||||||
self.inner = match ctx.format {
|
self.inner = match ctx.format {
|
||||||
CodecFormat::VP8 | CodecFormat::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
|
CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1 => {
|
||||||
|
Box::new(WebmRecorder::new(ctx.clone())?)
|
||||||
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
_ => bail!("unsupported codec type"),
|
_ => bail!("unsupported codec type"),
|
||||||
};
|
};
|
||||||
self.ctx = ctx;
|
self.ctx = ctx;
|
||||||
|
self.pts = None;
|
||||||
self.send_state(RecordState::NewFile(self.ctx.filename.clone()));
|
self.send_state(RecordState::NewFile(self.ctx.filename.clone()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -153,7 +159,10 @@ impl Recorder {
|
|||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
vp8s.frames.iter().map(|f| self.write_video(f)).count();
|
for f in vp8s.frames.iter() {
|
||||||
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
video_frame::Union::Vp9s(vp9s) => {
|
video_frame::Union::Vp9s(vp9s) => {
|
||||||
if self.ctx.format != CodecFormat::VP9 {
|
if self.ctx.format != CodecFormat::VP9 {
|
||||||
@ -162,7 +171,22 @@ impl Recorder {
|
|||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
vp9s.frames.iter().map(|f| self.write_video(f)).count();
|
for f in vp9s.frames.iter() {
|
||||||
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
video_frame::Union::Av1s(av1s) => {
|
||||||
|
if self.ctx.format != CodecFormat::AV1 {
|
||||||
|
self.change(RecorderContext {
|
||||||
|
format: CodecFormat::AV1,
|
||||||
|
..self.ctx.clone()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
for f in av1s.frames.iter() {
|
||||||
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
video_frame::Union::H264s(h264s) => {
|
video_frame::Union::H264s(h264s) => {
|
||||||
@ -172,8 +196,9 @@ impl Recorder {
|
|||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
if self.ctx.format == CodecFormat::H264 {
|
for f in h264s.frames.iter() {
|
||||||
h264s.frames.iter().map(|f| self.write_video(f)).count();
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
@ -184,8 +209,9 @@ impl Recorder {
|
|||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
if self.ctx.format == CodecFormat::H265 {
|
for f in h265s.frames.iter() {
|
||||||
h265s.frames.iter().map(|f| self.write_video(f)).count();
|
self.check_pts(f.pts)?;
|
||||||
|
self.write_video(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => bail!("unsupported frame type"),
|
_ => bail!("unsupported frame type"),
|
||||||
@ -194,6 +220,17 @@ impl Recorder {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_pts(&mut self, pts: i64) -> ResultType<()> {
|
||||||
|
// https://stackoverflow.com/questions/76379101/how-to-create-one-playable-webm-file-from-two-different-video-tracks-with-same-c
|
||||||
|
let old_pts = self.pts;
|
||||||
|
self.pts = Some(pts);
|
||||||
|
if old_pts.clone().unwrap_or_default() > pts {
|
||||||
|
log::info!("pts {:?}->{}, change record filename", old_pts, pts);
|
||||||
|
self.change(self.ctx.clone())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn send_state(&self, state: RecordState) {
|
fn send_state(&self, state: RecordState) {
|
||||||
self.ctx.tx.as_ref().map(|tx| tx.send(state));
|
self.ctx.tx.as_ref().map(|tx| tx.send(state));
|
||||||
}
|
}
|
||||||
@ -230,10 +267,19 @@ impl RecorderApi for WebmRecorder {
|
|||||||
None,
|
None,
|
||||||
if ctx.format == CodecFormat::VP9 {
|
if ctx.format == CodecFormat::VP9 {
|
||||||
mux::VideoCodecId::VP9
|
mux::VideoCodecId::VP9
|
||||||
} else {
|
} else if ctx.format == CodecFormat::VP8 {
|
||||||
mux::VideoCodecId::VP8
|
mux::VideoCodecId::VP8
|
||||||
|
} else {
|
||||||
|
mux::VideoCodecId::AV1
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
if ctx.format == CodecFormat::AV1 {
|
||||||
|
// [129, 8, 12, 0] in 3.6.0, but zero works
|
||||||
|
let codec_private = vec![0, 0, 0, 0];
|
||||||
|
if !webm.set_codec_private(vt.track_number(), &codec_private) {
|
||||||
|
bail!("Failed to set codec private");
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(WebmRecorder {
|
Ok(WebmRecorder {
|
||||||
vt,
|
vt,
|
||||||
webm: Some(webm),
|
webm: Some(webm),
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub mod win10;
|
pub mod win10;
|
||||||
|
use hbb_common::ResultType;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use hbb_common::lazy_static;
|
use hbb_common::{bail, lazy_static};
|
||||||
use hbb_common::{bail, ResultType};
|
#[cfg(windows)]
|
||||||
use std::path::Path;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@ -33,18 +34,25 @@ pub fn download_driver() -> ResultType<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[cfg(windows)]
|
||||||
pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
|
fn get_driver_install_abs_path() -> ResultType<PathBuf> {
|
||||||
#[cfg(windows)]
|
|
||||||
let install_path = win10::DRIVER_INSTALL_PATH;
|
let install_path = win10::DRIVER_INSTALL_PATH;
|
||||||
#[cfg(not(windows))]
|
let exe_file = std::env::current_exe()?;
|
||||||
let install_path = "";
|
let abs_path = match exe_file.parent() {
|
||||||
|
Some(cur_dir) => cur_dir.join(install_path),
|
||||||
let abs_path = Path::new(install_path).canonicalize()?;
|
None => bail!(
|
||||||
|
"Invalid exe parent for {}",
|
||||||
|
exe_file.to_string_lossy().as_ref()
|
||||||
|
),
|
||||||
|
};
|
||||||
if !abs_path.exists() {
|
if !abs_path.exists() {
|
||||||
bail!("{} not exists", install_path)
|
bail!("{} not exists", install_path)
|
||||||
}
|
}
|
||||||
|
Ok(abs_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
unsafe {
|
unsafe {
|
||||||
{
|
{
|
||||||
@ -54,6 +62,7 @@ pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
|
|||||||
bail!("{}", e);
|
bail!("{}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let abs_path = get_driver_install_abs_path()?;
|
||||||
let full_install_path: Vec<u16> = abs_path
|
let full_install_path: Vec<u16> = abs_path
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -76,19 +85,10 @@ pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn uninstall_driver(_reboot_required: &mut bool) -> ResultType<()> {
|
pub fn uninstall_driver(_reboot_required: &mut bool) -> ResultType<()> {
|
||||||
#[cfg(windows)]
|
|
||||||
let install_path = win10::DRIVER_INSTALL_PATH;
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
let install_path = "";
|
|
||||||
|
|
||||||
let abs_path = Path::new(install_path).canonicalize()?;
|
|
||||||
if !abs_path.exists() {
|
|
||||||
bail!("{} not exists", install_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
unsafe {
|
unsafe {
|
||||||
{
|
{
|
||||||
|
let abs_path = get_driver_install_abs_path()?;
|
||||||
let full_install_path: Vec<u16> = abs_path
|
let full_install_path: Vec<u16> = abs_path
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -999,16 +999,19 @@ pub struct VideoHandler {
|
|||||||
pub rgb: ImageRgb,
|
pub rgb: ImageRgb,
|
||||||
recorder: Arc<Mutex<Option<Recorder>>>,
|
recorder: Arc<Mutex<Option<Recorder>>>,
|
||||||
record: bool,
|
record: bool,
|
||||||
|
_display: usize, // useful for debug
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoHandler {
|
impl VideoHandler {
|
||||||
/// Create a new video handler.
|
/// Create a new video handler.
|
||||||
pub fn new() -> Self {
|
pub fn new(_display: usize) -> Self {
|
||||||
|
log::info!("new video handler for display #{_display}");
|
||||||
VideoHandler {
|
VideoHandler {
|
||||||
decoder: Decoder::new(),
|
decoder: Decoder::new(),
|
||||||
rgb: ImageRgb::new(ImageFormat::ARGB, crate::DST_STRIDE_RGBA),
|
rgb: ImageRgb::new(ImageFormat::ARGB, crate::DST_STRIDE_RGBA),
|
||||||
recorder: Default::default(),
|
recorder: Default::default(),
|
||||||
record: false,
|
record: false,
|
||||||
|
_display,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1207,7 +1210,7 @@ impl LoginConfigHandler {
|
|||||||
self.save_config(config);
|
self.save_config(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save reverse mouse wheel ("", "Y") to the current config.
|
/// Save "displays_as_individual_windows" ("", "Y") to the current config.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
@ -1218,6 +1221,17 @@ impl LoginConfigHandler {
|
|||||||
self.save_config(config);
|
self.save_config(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Save "use_all_my_displays_for_the_remote_session" ("", "Y") to the current config.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `value` - The "use_all_my_displays_for_the_remote_session" value ("", "Y").
|
||||||
|
pub fn save_use_all_my_displays_for_the_remote_session(&mut self, value: String) {
|
||||||
|
let mut config = self.load_config();
|
||||||
|
config.use_all_my_displays_for_the_remote_session = value;
|
||||||
|
self.save_config(config);
|
||||||
|
}
|
||||||
|
|
||||||
/// Save scroll style to the current config.
|
/// Save scroll style to the current config.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -1889,7 +1903,7 @@ where
|
|||||||
if handler_controller_map.len() <= display {
|
if handler_controller_map.len() <= display {
|
||||||
for _i in handler_controller_map.len()..=display {
|
for _i in handler_controller_map.len()..=display {
|
||||||
handler_controller_map.push(VideoHandlerController {
|
handler_controller_map.push(VideoHandlerController {
|
||||||
handler: VideoHandler::new(),
|
handler: VideoHandler::new(_i),
|
||||||
count: 0,
|
count: 0,
|
||||||
duration: std::time::Duration::ZERO,
|
duration: std::time::Duration::ZERO,
|
||||||
skip_beginning: 0,
|
skip_beginning: 0,
|
||||||
@ -1949,6 +1963,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MediaData::RecordScreen(start, display, w, h, id) => {
|
MediaData::RecordScreen(start, display, w, h, id) => {
|
||||||
|
log::info!("record screen command: start:{start}, display:{display}");
|
||||||
if handler_controller_map.len() == 1 {
|
if handler_controller_map.len() == 1 {
|
||||||
// Compatible with the sciter version(single ui session).
|
// Compatible with the sciter version(single ui session).
|
||||||
// For the sciter version, there're no multi-ui-sessions for one connection.
|
// For the sciter version, there're no multi-ui-sessions for one connection.
|
||||||
|
@ -1531,6 +1531,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
Some(message::Union::PeerInfo(pi)) => {
|
Some(message::Union::PeerInfo(pi)) => {
|
||||||
self.handler.set_displays(&pi.displays);
|
self.handler.set_displays(&pi.displays);
|
||||||
|
self.handler.set_platform_additions(&pi.platform_additions);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ impl Drop for SimpleCallOnReturn {
|
|||||||
pub fn global_init() -> bool {
|
pub fn global_init() -> bool {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
if !*IS_X11 {
|
if !crate::platform::linux::is_x11() {
|
||||||
crate::server::wayland::init();
|
crate::server::wayland::init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -956,7 +956,10 @@ pub async fn post_request_sync(url: String, body: String, header: &str) -> Resul
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn make_privacy_mode_msg_with_details(state: back_notification::PrivacyModeState, details: String) -> Message {
|
pub fn make_privacy_mode_msg_with_details(
|
||||||
|
state: back_notification::PrivacyModeState,
|
||||||
|
details: String,
|
||||||
|
) -> Message {
|
||||||
let mut misc = Misc::new();
|
let mut misc = Misc::new();
|
||||||
let mut back_notification = BackNotification {
|
let mut back_notification = BackNotification {
|
||||||
details,
|
details,
|
||||||
@ -990,17 +993,6 @@ pub fn get_supported_keyboard_modes(version: i64) -> Vec<KeyboardMode> {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
pub static ref IS_X11: bool = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
pub static ref IS_X11: bool = hbb_common::platform::linux::is_x11_or_headless();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> String {
|
pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> String {
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
let mut fd_json = serde_json::Map::new();
|
let mut fd_json = serde_json::Map::new();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(windows)]
|
||||||
use crate::client::translate;
|
use crate::client::translate;
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
@ -691,6 +691,13 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_platform_additions(&self, data: &str) {
|
||||||
|
self.push_event(
|
||||||
|
"sync_platform_additions",
|
||||||
|
vec![("platform_additions", &data)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn on_connected(&self, _conn_type: ConnType) {}
|
fn on_connected(&self, _conn_type: ConnType) {}
|
||||||
|
|
||||||
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
|
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
|
||||||
@ -1377,6 +1384,9 @@ pub fn get_cur_session() -> Option<FlutterSession> {
|
|||||||
|
|
||||||
// sessions mod is used to avoid the big lock of sessions' map.
|
// sessions mod is used to avoid the big lock of sessions' map.
|
||||||
pub mod sessions {
|
pub mod sessions {
|
||||||
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
@ -1441,12 +1451,44 @@ pub mod sessions {
|
|||||||
let mut remove_peer_key = None;
|
let mut remove_peer_key = None;
|
||||||
for (peer_key, s) in SESSIONS.write().unwrap().iter_mut() {
|
for (peer_key, s) in SESSIONS.write().unwrap().iter_mut() {
|
||||||
let mut write_lock = s.ui_handler.session_handlers.write().unwrap();
|
let mut write_lock = s.ui_handler.session_handlers.write().unwrap();
|
||||||
if write_lock.remove(id).is_some() {
|
let remove_ret = write_lock.remove(id);
|
||||||
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
|
if remove_ret.is_some() {
|
||||||
if write_lock.is_empty() {
|
if write_lock.is_empty() {
|
||||||
remove_peer_key = Some(peer_key.clone());
|
remove_peer_key = Some(peer_key.clone());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
match remove_ret {
|
||||||
|
Some(_) => {
|
||||||
|
if write_lock.is_empty() {
|
||||||
|
remove_peer_key = Some(peer_key.clone());
|
||||||
|
} else {
|
||||||
|
// Set capture displays if some are not used any more.
|
||||||
|
let mut remains_displays = HashSet::new();
|
||||||
|
for (_, h) in write_lock.iter() {
|
||||||
|
remains_displays.extend(
|
||||||
|
h.renderer
|
||||||
|
.map_display_sessions
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.keys()
|
||||||
|
.cloned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !remains_displays.is_empty() {
|
||||||
|
s.capture_displays(
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
remains_displays.iter().map(|d| *d as i32).collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SESSIONS.write().unwrap().remove(&remove_peer_key?)
|
SESSIONS.write().unwrap().remove(&remove_peer_key?)
|
||||||
}
|
}
|
||||||
|
@ -39,11 +39,15 @@ fn initialize(app_dir: &str) {
|
|||||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
|
// flexi_logger can't work when android_logger initialized.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
android_logger::init_once(
|
android_logger::init_once(
|
||||||
android_logger::Config::default()
|
android_logger::Config::default()
|
||||||
.with_max_level(log::LevelFilter::Debug) // limit log level
|
.with_max_level(log::LevelFilter::Debug) // limit log level
|
||||||
.with_tag("ffi"), // logs will show under mytag tag
|
.with_tag("ffi"), // logs will show under mytag tag
|
||||||
);
|
);
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
hbb_common::init_log(false, "");
|
||||||
#[cfg(feature = "mediacodec")]
|
#[cfg(feature = "mediacodec")]
|
||||||
scrap::mediacodec::check_mediacodec();
|
scrap::mediacodec::check_mediacodec();
|
||||||
crate::common::test_rendezvous_server();
|
crate::common::test_rendezvous_server();
|
||||||
@ -206,6 +210,7 @@ pub fn session_reconnect(session_id: SessionID, force_relay: bool) {
|
|||||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.reconnect(force_relay);
|
session.reconnect(force_relay);
|
||||||
}
|
}
|
||||||
|
session_on_waiting_for_image_dialog_show(session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_toggle_option(session_id: SessionID, value: String) {
|
pub fn session_toggle_option(session_id: SessionID, value: String) {
|
||||||
@ -339,7 +344,9 @@ pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_displays_as_individual_windows(session_id: SessionID) -> SyncReturn<Option<String>> {
|
pub fn session_get_displays_as_individual_windows(
|
||||||
|
session_id: SessionID,
|
||||||
|
) -> SyncReturn<Option<String>> {
|
||||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
SyncReturn(Some(session.get_displays_as_individual_windows()))
|
SyncReturn(Some(session.get_displays_as_individual_windows()))
|
||||||
} else {
|
} else {
|
||||||
@ -353,6 +360,27 @@ pub fn session_set_displays_as_individual_windows(session_id: SessionID, value:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_get_use_all_my_displays_for_the_remote_session(
|
||||||
|
session_id: SessionID,
|
||||||
|
) -> SyncReturn<Option<String>> {
|
||||||
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
|
SyncReturn(Some(
|
||||||
|
session.get_use_all_my_displays_for_the_remote_session(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
SyncReturn(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_set_use_all_my_displays_for_the_remote_session(
|
||||||
|
session_id: SessionID,
|
||||||
|
value: String,
|
||||||
|
) {
|
||||||
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
|
session.save_use_all_my_displays_for_the_remote_session(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32>> {
|
pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32>> {
|
||||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_custom_image_quality())
|
Some(session.get_custom_image_quality())
|
||||||
@ -931,6 +959,25 @@ pub fn main_load_recent_peers_sync() -> SyncReturn<String> {
|
|||||||
SyncReturn("".to_string())
|
SyncReturn("".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main_load_lan_peers_sync() -> SyncReturn<String> {
|
||||||
|
let data = HashMap::from([
|
||||||
|
("name", "load_lan_peers".to_owned()),
|
||||||
|
(
|
||||||
|
"peers",
|
||||||
|
serde_json::to_string(&get_lan_peers()).unwrap_or_default(),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
return SyncReturn(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main_load_ab_sync() -> SyncReturn<String> {
|
||||||
|
return SyncReturn(serde_json::to_string(&config::Ab::load()).unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main_load_group_sync() -> SyncReturn<String> {
|
||||||
|
return SyncReturn(serde_json::to_string(&config::Group::load()).unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main_load_recent_peers_for_ab(filter: String) -> String {
|
pub fn main_load_recent_peers_for_ab(filter: String) -> String {
|
||||||
let id_filters = serde_json::from_str::<Vec<String>>(&filter).unwrap_or_default();
|
let id_filters = serde_json::from_str::<Vec<String>>(&filter).unwrap_or_default();
|
||||||
let id_filters = if id_filters.is_empty() {
|
let id_filters = if id_filters.is_empty() {
|
||||||
@ -1072,6 +1119,29 @@ pub fn main_get_main_display() -> SyncReturn<String> {
|
|||||||
SyncReturn(display_info)
|
SyncReturn(display_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main_get_displays() -> SyncReturn<String> {
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
let display_info = "".to_owned();
|
||||||
|
#[cfg(not(target_os = "ios"))]
|
||||||
|
let mut display_info = "".to_owned();
|
||||||
|
#[cfg(not(target_os = "ios"))]
|
||||||
|
if let Ok(displays) = crate::display_service::try_get_displays() {
|
||||||
|
let displays = displays
|
||||||
|
.iter()
|
||||||
|
.map(|d| {
|
||||||
|
HashMap::from([
|
||||||
|
("x", d.origin().0),
|
||||||
|
("y", d.origin().1),
|
||||||
|
("w", d.width() as i32),
|
||||||
|
("h", d.height() as i32),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
display_info = serde_json::to_string(&displays).unwrap_or_default();
|
||||||
|
}
|
||||||
|
SyncReturn(display_info)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_add_port_forward(
|
pub fn session_add_port_forward(
|
||||||
session_id: SessionID,
|
session_id: SessionID,
|
||||||
local_port: i32,
|
local_port: i32,
|
||||||
@ -1334,6 +1404,12 @@ pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
|
|||||||
super::flutter::session_on_waiting_for_image_dialog_show(session_id);
|
super::flutter::session_on_waiting_for_image_dialog_show(session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_toggle_virtual_display(session_id: SessionID, index: i32, on: bool) {
|
||||||
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
|
session.toggle_virtual_display(index, on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main_set_home_dir(_home: String) {
|
pub fn main_set_home_dir(_home: String) {
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
{
|
{
|
||||||
@ -1518,7 +1594,7 @@ pub fn main_is_installed() -> SyncReturn<bool> {
|
|||||||
|
|
||||||
pub fn main_start_grab_keyboard() -> SyncReturn<bool> {
|
pub fn main_start_grab_keyboard() -> SyncReturn<bool> {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
if !*crate::common::IS_X11 {
|
if !crate::platform::linux::is_x11() {
|
||||||
return SyncReturn(false);
|
return SyncReturn(false);
|
||||||
}
|
}
|
||||||
crate::keyboard::client::start_grab_loop();
|
crate::keyboard::client::start_grab_loop();
|
||||||
@ -1869,6 +1945,17 @@ pub fn is_support_multi_ui_session(version: String) -> SyncReturn<bool> {
|
|||||||
SyncReturn(crate::common::is_support_multi_ui_session(&version))
|
SyncReturn(crate::common::is_support_multi_ui_session(&version))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_selinux_enforcing() -> SyncReturn<bool> {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
SyncReturn(crate::platform::linux::is_selinux_enforcing())
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
{
|
||||||
|
SyncReturn(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
pub mod server_side {
|
pub mod server_side {
|
||||||
use hbb_common::{config, log};
|
use hbb_common::{config, log};
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"),
|
("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"),
|
||||||
("Open in new window", "在新的窗口中打开"),
|
("Open in new window", "在新的窗口中打开"),
|
||||||
("Show displays as individual windows", "在单个窗口中打开显示器"),
|
("Show displays as individual windows", "在单个窗口中打开显示器"),
|
||||||
|
("Use all my displays for the remote session", "将我的所有显示器用于远程会话"),
|
||||||
|
("selinux_tip", "SELinux 处于启用状态,RustDesk 可能无法作为被控正常运行。"),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", "虚拟显示器"),
|
||||||
|
("Plug out all", "拔出所有"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -555,14 +555,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Check for software update on startup", "Kontrola aktualizace softwaru při spuštění"),
|
("Check for software update on startup", "Kontrola aktualizace softwaru při spuštění"),
|
||||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Aktualizujte prosím RustDesk Server Pro na verzi {} nebo novější!"),
|
("upgrade_rustdesk_server_pro_to_{}_tip", "Aktualizujte prosím RustDesk Server Pro na verzi {} nebo novější!"),
|
||||||
("pull_group_failed_tip", "Nepodařilo se obnovit skupinu"),
|
("pull_group_failed_tip", "Nepodařilo se obnovit skupinu"),
|
||||||
("Filter by intersection", ""),
|
("Filter by intersection", "Filtrovat podle průsečíku"),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", "Odstranit tapetu během příchozích relací"),
|
||||||
("Test", ""),
|
("Test", "Test"),
|
||||||
("switch_display_elevated_connections_tip", ""),
|
("switch_display_elevated_connections_tip", "Přepnutí na jinou než primární obrazovku není podporováno ve zvýšeném režimu, pokud existuje více připojení. Pokud chcete ovládat více obrazovek, zkuste to po instalaci znovu."),
|
||||||
("display_is_plugged_out_msg", ""),
|
("display_is_plugged_out_msg", "Obrazovka je odpojena, přepněte na první obrazovku."),
|
||||||
("No displays", ""),
|
("No displays", "Žádné obrazovky"),
|
||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", "Přepnout na primární obrazovku, protože více obrazovek není podporováno ve zvýšeném režimu."),
|
||||||
("Open in new window", ""),
|
("Open in new window", "Otevřít v novém okně"),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", "Zobrazit obrazovky jako jednotlivá okna"),
|
||||||
|
("Use all my displays for the remote session", "Použít všechny mé obrazovky pro vzdálenou relaci"),
|
||||||
|
("selinux_tip", "Na vašem zařízení je povolen SELinux, což může bránit správnému běhu RustDesku jako řízené strany."),
|
||||||
|
("Change view", "Změnit pohled"),
|
||||||
|
("Big tiles", "Velké dlaždice"),
|
||||||
|
("Small tiles", "Malé dlaždice"),
|
||||||
|
("List", "Seznam"),
|
||||||
|
("Virtual display", "Virtuální obrazovka"),
|
||||||
|
("Plug out all", "Odpojit všechny"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -440,7 +440,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Strong", "Stark"),
|
("Strong", "Stark"),
|
||||||
("Switch Sides", "Seiten wechseln"),
|
("Switch Sides", "Seiten wechseln"),
|
||||||
("Please confirm if you want to share your desktop?", "Bitte bestätigen Sie, wenn Sie Ihren Desktop freigeben möchten."),
|
("Please confirm if you want to share your desktop?", "Bitte bestätigen Sie, wenn Sie Ihren Desktop freigeben möchten."),
|
||||||
("Display", "Anzeige"),
|
("Display", "Bildschirm"),
|
||||||
("Default View Style", "Standard-Ansichtsstil"),
|
("Default View Style", "Standard-Ansichtsstil"),
|
||||||
("Default Scroll Style", "Standard-Scroll-Stil"),
|
("Default Scroll Style", "Standard-Scroll-Stil"),
|
||||||
("Default Image Quality", "Standard-Bildqualität"),
|
("Default Image Quality", "Standard-Bildqualität"),
|
||||||
@ -476,7 +476,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty Password", "Leeres Passwort"),
|
("Empty Password", "Leeres Passwort"),
|
||||||
("Me", "Ich"),
|
("Me", "Ich"),
|
||||||
("identical_file_tip", "Diese Datei ist identisch mit der Datei der Gegenstelle."),
|
("identical_file_tip", "Diese Datei ist identisch mit der Datei der Gegenstelle."),
|
||||||
("show_monitors_tip", "Monitore in der Symbolleiste anzeigen"),
|
("show_monitors_tip", "Bildschirme in der Symbolleiste anzeigen"),
|
||||||
("View Mode", "Ansichtsmodus"),
|
("View Mode", "Ansichtsmodus"),
|
||||||
("login_linux_tip", "Sie müssen sich an einem entfernten Linux-Konto anmelden, um eine X-Desktop-Sitzung zu eröffnen."),
|
("login_linux_tip", "Sie müssen sich an einem entfernten Linux-Konto anmelden, um eine X-Desktop-Sitzung zu eröffnen."),
|
||||||
("verify_rustdesk_password_tip", "RustDesk-Passwort bestätigen"),
|
("verify_rustdesk_password_tip", "RustDesk-Passwort bestätigen"),
|
||||||
@ -558,11 +558,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", "Nach Schnittmenge filtern"),
|
("Filter by intersection", "Nach Schnittmenge filtern"),
|
||||||
("Remove wallpaper during incoming sessions", "Hintergrundbild während eingehender Sitzungen entfernen"),
|
("Remove wallpaper during incoming sessions", "Hintergrundbild während eingehender Sitzungen entfernen"),
|
||||||
("Test", "Test"),
|
("Test", "Test"),
|
||||||
("switch_display_elevated_connections_tip", "Das Umschalten auf eine nicht primäre Anzeige wird mit erhöhten Rechten nicht unterstützt, wenn mehrere Verbindungen bestehen. Bitte versuchen Sie es nach der Installation erneut, wenn Sie mehrere Anzeigen steuern möchten."),
|
("switch_display_elevated_connections_tip", "Das Umschalten auf einen sekundären Bildschirm wird mit erhöhten Rechten nicht unterstützt, wenn mehrere Verbindungen bestehen. Bitte versuchen Sie es nach der Installation erneut, wenn Sie mehrere Bildschirme steuern möchten."),
|
||||||
("display_is_plugged_out_msg", "Das Anzeigegerät ist nicht angeschlossen, schalten Sie auf das erste Anzeigegerät um."),
|
("display_is_plugged_out_msg", "Der Bildschirm ist nicht angeschlossen, schalten Sie auf den ersten Bildschirm um."),
|
||||||
("No displays", "Keine Anzeigegeräte"),
|
("No displays", "Keine Bildschirme"),
|
||||||
("elevated_switch_display_msg", "Wechseln Sie zur primären Anzeige, da die Mehrfachanzeige im erweiterten Modus nicht unterstützt wird."),
|
("elevated_switch_display_msg", "Wechseln Sie zum primären Bildschirm, da mehrere Bildschirme im erweiterten Modus nicht unterstützt werden."),
|
||||||
("Open in new window", "In einem neuen Fenster öffnen"),
|
("Open in new window", "In einem neuen Fenster öffnen"),
|
||||||
("Show displays as individual windows", "Anzeigen als einzelne Fenster darstellen"),
|
("Show displays as individual windows", "Jeden Bildschirm in einem eigenen Fenster anzeigen"),
|
||||||
|
("Use all my displays for the remote session", "Alle meine Bildschirme für die Fernsitzung verwenden"),
|
||||||
|
("selinux_tip", "SELinux ist auf Ihrem Gerät aktiviert, was dazu führen kann, dass RustDesk als kontrollierte Seite nicht richtig läuft."),
|
||||||
|
("Change view", "Ansicht ändern"),
|
||||||
|
("Big tiles", "Große Kacheln"),
|
||||||
|
("Small tiles", "Kleine Kacheln"),
|
||||||
|
("List", "Liste"),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("pull_group_failed_tip", "Failed to refresh group"),
|
("pull_group_failed_tip", "Failed to refresh group"),
|
||||||
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
|
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
|
||||||
("switch_display_elevated_connections_tip", "Switching to non-primary display is not supported in the elevated mode when there are multiple connections. Please try again after installation if you want to control multiple displays."),
|
("switch_display_elevated_connections_tip", "Switching to non-primary display is not supported in the elevated mode when there are multiple connections. Please try again after installation if you want to control multiple displays."),
|
||||||
("display_is_plugged_out_msg", "The diplay is plugged out, switch to the first display."),
|
("display_is_plugged_out_msg", "The display is plugged out, switch to the first display."),
|
||||||
("elevated_switch_display_msg", "Switch to the primary display because multiple display is not supported in elevated mode."),
|
("elevated_switch_display_msg", "Switch to the primary display because multiple displays are not supported in elevated mode."),
|
||||||
|
("selinux_tip", "SELinux is enabled on your device, which may prevent RustDesk from running properly as controlled side."),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -555,14 +555,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Check for software update on startup", "Comprobar actualización al iniciar"),
|
("Check for software update on startup", "Comprobar actualización al iniciar"),
|
||||||
("upgrade_rustdesk_server_pro_to_{}_tip", "¡Por favor, actualiza RustDesk Server Pro a la versión {} o superior"),
|
("upgrade_rustdesk_server_pro_to_{}_tip", "¡Por favor, actualiza RustDesk Server Pro a la versión {} o superior"),
|
||||||
("pull_group_failed_tip", "No se ha podido refrescar el grupo"),
|
("pull_group_failed_tip", "No se ha podido refrescar el grupo"),
|
||||||
("Filter by intersection", ""),
|
("Filter by intersection", "Filtrar por intersección"),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", "Quitar el fonde de pantalla durante sesiones entrantes"),
|
||||||
("Test", ""),
|
("Test", "Probar"),
|
||||||
("switch_display_elevated_connections_tip", ""),
|
("switch_display_elevated_connections_tip", "Cambiar a una pantalla no principal no está soportado en el modo elevado cuando hay múltiples conexiones. Por favor, inténtalo de nuevo tras la instalación si quieres controlar múltiples pantallas."),
|
||||||
("display_is_plugged_out_msg", ""),
|
("display_is_plugged_out_msg", "La pantalla está desconectada, cambia a la principal."),
|
||||||
("No displays", ""),
|
("No displays", "No hay pantallas"),
|
||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", "Cambiar a la pantalla principal porque mútliples pantallas no están soportadas en modo elevado."),
|
||||||
("Open in new window", ""),
|
("Open in new window", "Abrir en una nueva ventana"),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", "Mostrar pantallas como ventanas individuales"),
|
||||||
|
("Use all my displays for the remote session", "Usar todas mis pantallas para la sesión remota"),
|
||||||
|
("selinux_tip", "SELinux está activado en tu dispositivo, lo que puede hacer que RustDesk no se ejecute correctamente como lado controlado."),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Home", ""),
|
("Home", ""),
|
||||||
("Audio Input", "Input Audio"),
|
("Audio Input", "Input Audio"),
|
||||||
("Enhancements", "Peningkatan"),
|
("Enhancements", "Peningkatan"),
|
||||||
("Hardware Codec", "Codec Perangkat Keras"),
|
("Hardware Codec", "Kodek Perangkat Keras"),
|
||||||
("Adaptive bitrate", "Kecepatan Bitrate Adaptif"),
|
("Adaptive bitrate", "Kecepatan Bitrate Adaptif"),
|
||||||
("ID Server", "Server ID"),
|
("ID Server", "Server ID"),
|
||||||
("Relay Server", "Server Relay"),
|
("Relay Server", "Server Relay"),
|
||||||
@ -352,7 +352,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Dark", "Gelap"),
|
("Dark", "Gelap"),
|
||||||
("Light", "Terang"),
|
("Light", "Terang"),
|
||||||
("Follow System", "Ikuti Sistem"),
|
("Follow System", "Ikuti Sistem"),
|
||||||
("Enable hardware codec", "Aktifkan codec perangkat keras"),
|
("Enable hardware codec", "Aktifkan kodek perangkat keras"),
|
||||||
("Unlock Security Settings", "Buka Keamanan Pengaturan"),
|
("Unlock Security Settings", "Buka Keamanan Pengaturan"),
|
||||||
("Enable Audio", "Aktifkan Audio"),
|
("Enable Audio", "Aktifkan Audio"),
|
||||||
("Unlock Network Settings", "Buka Keamanan Pengaturan Jaringan"),
|
("Unlock Network Settings", "Buka Keamanan Pengaturan Jaringan"),
|
||||||
@ -369,7 +369,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Unpin Toolbar", "Batal sematkan Toolbar"),
|
("Unpin Toolbar", "Batal sematkan Toolbar"),
|
||||||
("Recording", "Perekaman"),
|
("Recording", "Perekaman"),
|
||||||
("Directory", "Direktori"),
|
("Directory", "Direktori"),
|
||||||
("Automatically record incoming sessions", "Secara otomatis merekam sesi masuk"),
|
("Automatically record incoming sessions", "Otomatis merekam sesi masuk"),
|
||||||
("Change", "Ubah"),
|
("Change", "Ubah"),
|
||||||
("Start session recording", "Mulai sesi perekaman"),
|
("Start session recording", "Mulai sesi perekaman"),
|
||||||
("Stop session recording", "Hentikan sesi perekaman"),
|
("Stop session recording", "Hentikan sesi perekaman"),
|
||||||
@ -444,7 +444,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Default View Style", "Gaya Tampilan Default"),
|
("Default View Style", "Gaya Tampilan Default"),
|
||||||
("Default Scroll Style", "Gaya Scroll Default"),
|
("Default Scroll Style", "Gaya Scroll Default"),
|
||||||
("Default Image Quality", "Kualitas Gambar Default"),
|
("Default Image Quality", "Kualitas Gambar Default"),
|
||||||
("Default Codec", "Codec default"),
|
("Default Codec", "Kodek default"),
|
||||||
("Bitrate", "Bitrate"),
|
("Bitrate", "Bitrate"),
|
||||||
("FPS", "FPS"),
|
("FPS", "FPS"),
|
||||||
("Auto", "Otomatis"),
|
("Auto", "Otomatis"),
|
||||||
@ -454,9 +454,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Stop voice call", "Hentikan panggilan suara"),
|
("Stop voice call", "Hentikan panggilan suara"),
|
||||||
("relay_hint_tip", "Tidak memungkinkan untuk terhubung secara langsung; anda bisa mencoba terhubung via relay. Selain itu, jika ingin menggunakan relay pada percobaan pertama, silahkan tambah akhiran \"/r\" pada ID atau pilih \"Selalu terhubung via relay\" di pilihan sesi terbaru."),
|
("relay_hint_tip", "Tidak memungkinkan untuk terhubung secara langsung; anda bisa mencoba terhubung via relay. Selain itu, jika ingin menggunakan relay pada percobaan pertama, silahkan tambah akhiran \"/r\" pada ID atau pilih \"Selalu terhubung via relay\" di pilihan sesi terbaru."),
|
||||||
("Reconnect", "Menyambungkan ulang"),
|
("Reconnect", "Menyambungkan ulang"),
|
||||||
("Codec", "Codec"),
|
("Codec", "Kodek"),
|
||||||
("Resolution", "Resolusi"),
|
("Resolution", "Resolusi"),
|
||||||
("No transfers in progress", "Tidak ada transfer data yang sedang berlangsung"),
|
("No transfers in progress", "Tidak ada proses transfer data"),
|
||||||
("Set one-time password length", "Atur panjang kata sandi sekali pakai"),
|
("Set one-time password length", "Atur panjang kata sandi sekali pakai"),
|
||||||
("install_cert_tip", "Install sertifikat RustDesk"),
|
("install_cert_tip", "Install sertifikat RustDesk"),
|
||||||
("confirm_install_cert_tip", "Ini adalah sertifikat pengujian RustDesk, yang dapat dipercaya. Sertifikat ini akan digunakan untuk menginstal driver RustDesk saat diperlukan"),
|
("confirm_install_cert_tip", "Ini adalah sertifikat pengujian RustDesk, yang dapat dipercaya. Sertifikat ini akan digunakan untuk menginstal driver RustDesk saat diperlukan"),
|
||||||
@ -541,7 +541,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("HSV Color", "Warna HSV"),
|
("HSV Color", "Warna HSV"),
|
||||||
("Installation Successful!", "Instalasi berhasil!"),
|
("Installation Successful!", "Instalasi berhasil!"),
|
||||||
("Installation failed!", "Instalasi gagal!"),
|
("Installation failed!", "Instalasi gagal!"),
|
||||||
("Reverse mouse wheel", "Balikkan arah scroll mouse!"),
|
("Reverse mouse wheel", "Balikkan arah scroll mouse"),
|
||||||
("{} sessions", "sesi {}"),
|
("{} sessions", "sesi {}"),
|
||||||
("scam_title", "Kemungkinan Anda Sedang DITIPU!"),
|
("scam_title", "Kemungkinan Anda Sedang DITIPU!"),
|
||||||
("scam_text1", "Jika Anda sedang berbicara di telepon dengan seseorang yang TIDAK dikenal dan mereka meminta anda untuk menggunakan RustDesk, jangan lanjutkan dan segera tutup panggilan."),
|
("scam_text1", "Jika Anda sedang berbicara di telepon dengan seseorang yang TIDAK dikenal dan mereka meminta anda untuk menggunakan RustDesk, jangan lanjutkan dan segera tutup panggilan."),
|
||||||
@ -563,6 +563,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("No displays", "Tidak ada tampilan"),
|
("No displays", "Tidak ada tampilan"),
|
||||||
("elevated_switch_display_msg", "Pindah ke tampilan utama, pada mode elevasi, pengggunaan lebih dari satu layar tidak diizinkan"),
|
("elevated_switch_display_msg", "Pindah ke tampilan utama, pada mode elevasi, pengggunaan lebih dari satu layar tidak diizinkan"),
|
||||||
("Open in new window", "Buka di jendela baru"),
|
("Open in new window", "Buka di jendela baru"),
|
||||||
("Show displays as individual windows", "Tampilkan layar sebagai jendela terpisah"),
|
("Show displays as individual windows", "Tampilkan dengan jendela terpisah"),
|
||||||
|
("Use all my displays for the remote session", "Gunakan semua layar untuk sesi remote"),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", "Sesuaikan tampilan"),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", "Tampilan virtual"),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Audio Input Device", "Dispositivo ingresso audio"),
|
("Audio Input Device", "Dispositivo ingresso audio"),
|
||||||
("Use IP Whitelisting", "Usa elenco IP autorizzati"),
|
("Use IP Whitelisting", "Usa elenco IP autorizzati"),
|
||||||
("Network", "Rete"),
|
("Network", "Rete"),
|
||||||
("Enable RDP", "Abilita RDP"),
|
|
||||||
("Pin Toolbar", "Blocca barra strumenti"),
|
("Pin Toolbar", "Blocca barra strumenti"),
|
||||||
("Unpin Toolbar", "Sblocca barra strumenti"),
|
("Unpin Toolbar", "Sblocca barra strumenti"),
|
||||||
("Recording", "Registrazione"),
|
("Recording", "Registrazione"),
|
||||||
@ -565,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", "Passo allo schermo principale perché in modalità elevata non sono supportati più schermi."),
|
("elevated_switch_display_msg", "Passo allo schermo principale perché in modalità elevata non sono supportati più schermi."),
|
||||||
("Open in new window", "Apri in una nuova finestra"),
|
("Open in new window", "Apri in una nuova finestra"),
|
||||||
("Show displays as individual windows", "Visualizza schermi come finestre individuali"),
|
("Show displays as individual windows", "Visualizza schermi come finestre individuali"),
|
||||||
|
("Use all my displays for the remote session", "Usa tutti gli schermi per la sessione remota"),
|
||||||
|
("selinux_tip", "In questo dispositivo è abilitato SELinux, che potrebbe impedire il corretto funzionamento di RustDesk come lato controllato."),
|
||||||
|
("Change view", "Modifica vista"),
|
||||||
|
("Big tiles", "Icone grandi"),
|
||||||
|
("Small tiles", "Icone piccole"),
|
||||||
|
("List", "Elenco"),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", "Pārslēdzieties uz primāro displeju, jo paaugstinātajā režīmā netiek atbalstīti vairāki displeji."),
|
("elevated_switch_display_msg", "Pārslēdzieties uz primāro displeju, jo paaugstinātajā režīmā netiek atbalstīti vairāki displeji."),
|
||||||
("Open in new window", "Atvērt jaunā logā"),
|
("Open in new window", "Atvērt jaunā logā"),
|
||||||
("Show displays as individual windows", "Rādīt displejus kā atsevišķus logus"),
|
("Show displays as individual windows", "Rādīt displejus kā atsevišķus logus"),
|
||||||
|
("Use all my displays for the remote session", "Izmantot visus manus displejus attālajai sesijai"),
|
||||||
|
("selinux_tip", "Jūsu ierīcē ir iespējots SELinux, kas var neļaut RustDesk pareizi darboties kā kontrolētajai pusei."),
|
||||||
|
("Change view", "Mainīt skatu"),
|
||||||
|
("Big tiles", "Lielas flīzes"),
|
||||||
|
("Small tiles", "Mazas flīzes"),
|
||||||
|
("List", "Saraksts"),
|
||||||
|
("Virtual display", "Virtuālais displejs"),
|
||||||
|
("Plug out all", "Atvienot visu"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -555,14 +555,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Check for software update on startup", "Checken voor updates bij opstarten"),
|
("Check for software update on startup", "Checken voor updates bij opstarten"),
|
||||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Upgrade RustDesk Server Pro naar versie {} of nieuwer!"),
|
("upgrade_rustdesk_server_pro_to_{}_tip", "Upgrade RustDesk Server Pro naar versie {} of nieuwer!"),
|
||||||
("pull_group_failed_tip", "Vernieuwen van groep mislukt"),
|
("pull_group_failed_tip", "Vernieuwen van groep mislukt"),
|
||||||
("Filter by intersection", ""),
|
("Filter by intersection", "Filter op kruising"),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", "Achtergrond verwijderen tijdens inkomende sessies"),
|
||||||
("Test", ""),
|
("Test", "Test"),
|
||||||
("switch_display_elevated_connections_tip", ""),
|
("switch_display_elevated_connections_tip", "Overschakelen naar een niet-hoofdbeeldscherm wordt niet ondersteund in de verhoogde modus wanneer er meerdere verbindingen zijn. Probeer het opnieuw na de installatie als je meerdere schermen wilt beheren."),
|
||||||
("display_is_plugged_out_msg", ""),
|
("display_is_plugged_out_msg", "Beeldscherm is uitgeschakeld, schakel over naar het primaire beeldscherm."),
|
||||||
("No displays", ""),
|
("No displays", "Geen beeldschermen"),
|
||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", "Schakel over naar het primaire beeldscherm, aangezien meerdere beeldschermen niet worden ondersteund in de modus met verhoogde rechten."),
|
||||||
("Open in new window", ""),
|
("Open in new window", "Open in een nieuw venster"),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", "Beeldschermen weergeven als afzonderlijke vensters"),
|
||||||
|
("Use all my displays for the remote session", "Gebruik al mijn beeldschermen voor de externe sessie"),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", "Przełącz się na ekran główny, ponieważ wyświetlanie kilku ekranów nie jest obsługiwane przy podniesionych uprawnieniach."),
|
("elevated_switch_display_msg", "Przełącz się na ekran główny, ponieważ wyświetlanie kilku ekranów nie jest obsługiwane przy podniesionych uprawnieniach."),
|
||||||
("Open in new window", "Otwórz w nowym oknie"),
|
("Open in new window", "Otwórz w nowym oknie"),
|
||||||
("Show displays as individual windows", "Pokaż ekrany w osobnych oknach"),
|
("Show displays as individual windows", "Pokaż ekrany w osobnych oknach"),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", "Переключитесь на основной дисплей, поскольку в режиме повышенных прав несколько дисплеев не поддерживаются."),
|
("elevated_switch_display_msg", "Переключитесь на основной дисплей, поскольку в режиме повышенных прав несколько дисплеев не поддерживаются."),
|
||||||
("Open in new window", "Открыть в новом окне"),
|
("Open in new window", "Открыть в новом окне"),
|
||||||
("Show displays as individual windows", "Показывать дисплеи в отдельных окнах"),
|
("Show displays as individual windows", "Показывать дисплеи в отдельных окнах"),
|
||||||
|
("Use all my displays for the remote session", "Использовать все мои дисплеи для удалённого сеанса"),
|
||||||
|
("selinux_tip", "На вашем устройстве включён SELinux, что может помешать правильной работе RustDesk на контролируемой стороне."),
|
||||||
|
("Change view", "Вид"),
|
||||||
|
("Big tiles", "Большие значки"),
|
||||||
|
("Small tiles", "Маленькие значки"),
|
||||||
|
("List", "Список"),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
112
src/lang/ua.rs
112
src/lang/ua.rs
@ -138,7 +138,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Please try later", "Будь ласка, спробуйте пізніше"),
|
("Please try later", "Будь ласка, спробуйте пізніше"),
|
||||||
("Remote desktop is offline", "Віддалена стільниця не в мережі"),
|
("Remote desktop is offline", "Віддалена стільниця не в мережі"),
|
||||||
("Key mismatch", "Невідповідність ключів"),
|
("Key mismatch", "Невідповідність ключів"),
|
||||||
("Timeout", "Тайм-аут"),
|
("Timeout", "Час очікування"),
|
||||||
("Failed to connect to relay server", "Не вдалося підключитися до сервера реле"),
|
("Failed to connect to relay server", "Не вдалося підключитися до сервера реле"),
|
||||||
("Failed to connect via rendezvous server", "Не вдалося підключитися через проміжний сервер"),
|
("Failed to connect via rendezvous server", "Не вдалося підключитися через проміжний сервер"),
|
||||||
("Failed to connect via relay server", "Не вдалося підключитися через сервер реле"),
|
("Failed to connect via relay server", "Не вдалося підключитися через сервер реле"),
|
||||||
@ -223,7 +223,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Verification code", "Код підтвердження"),
|
("Verification code", "Код підтвердження"),
|
||||||
("verification_tip", "Виявлено новий пристрій, код підтвердження надіслано на зареєстровану email-адресу, введіть код підтвердження для продовження авторизації."),
|
("verification_tip", "Виявлено новий пристрій, код підтвердження надіслано на зареєстровану email-адресу, введіть код підтвердження для продовження авторизації."),
|
||||||
("Logout", "Вийти"),
|
("Logout", "Вийти"),
|
||||||
("Tags", "Ключові слова"),
|
("Tags", "Теги"),
|
||||||
("Search ID", "Пошук за ID"),
|
("Search ID", "Пошук за ID"),
|
||||||
("whitelist_sep", "Розділені комою, крапкою з комою, пробілом або новим рядком"),
|
("whitelist_sep", "Розділені комою, крапкою з комою, пробілом або новим рядком"),
|
||||||
("Add ID", "Додати ID"),
|
("Add ID", "Додати ID"),
|
||||||
@ -231,7 +231,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Unselect all tags", "Скасувати вибір усіх тегів"),
|
("Unselect all tags", "Скасувати вибір усіх тегів"),
|
||||||
("Network error", "Помилка мережі"),
|
("Network error", "Помилка мережі"),
|
||||||
("Username missed", "Імʼя користувача відсутнє"),
|
("Username missed", "Імʼя користувача відсутнє"),
|
||||||
("Password missed", "Забули пароль"),
|
("Password missed", "Пароль відсутній"),
|
||||||
("Wrong credentials", "Неправильні дані"),
|
("Wrong credentials", "Неправильні дані"),
|
||||||
("The verification code is incorrect or has expired", "Код підтвердження некоректний або протермінований"),
|
("The verification code is incorrect or has expired", "Код підтвердження некоректний або протермінований"),
|
||||||
("Edit Tag", "Редагувати тег"),
|
("Edit Tag", "Редагувати тег"),
|
||||||
@ -269,7 +269,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Reset canvas", "Відновлення полотна"),
|
("Reset canvas", "Відновлення полотна"),
|
||||||
("No permission of file transfer", "Немає дозволу на передачу файлів"),
|
("No permission of file transfer", "Немає дозволу на передачу файлів"),
|
||||||
("Note", "Примітка"),
|
("Note", "Примітка"),
|
||||||
("Connection", "Зʼєднання"),
|
("Connection", "Підключення"),
|
||||||
("Share Screen", "Поділитися екраном"),
|
("Share Screen", "Поділитися екраном"),
|
||||||
("Chat", "Чат"),
|
("Chat", "Чат"),
|
||||||
("Total", "Всього"),
|
("Total", "Всього"),
|
||||||
@ -516,53 +516,61 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Exit", "Вийти"),
|
("Exit", "Вийти"),
|
||||||
("Open", "Відкрити"),
|
("Open", "Відкрити"),
|
||||||
("logout_tip", "Ви впевнені, що хочете вилогуватися?"),
|
("logout_tip", "Ви впевнені, що хочете вилогуватися?"),
|
||||||
("Service", ""),
|
("Service", "Служба"),
|
||||||
("Start", ""),
|
("Start", "Запустити"),
|
||||||
("Stop", ""),
|
("Stop", "Зупинити"),
|
||||||
("exceed_max_devices", ""),
|
("exceed_max_devices", "У вас максимальна кількість керованих пристроїв."),
|
||||||
("Sync with recent sessions", ""),
|
("Sync with recent sessions", "Синхронізація з нещодавніми сеансами"),
|
||||||
("Sort tags", ""),
|
("Sort tags", "Сортувати теги"),
|
||||||
("Open connection in new tab", ""),
|
("Open connection in new tab", "Відкрити підключення в новій вкладці"),
|
||||||
("Move tab to new window", ""),
|
("Move tab to new window", "Перемістити вкладку до нового вікна"),
|
||||||
("Can not be empty", ""),
|
("Can not be empty", "Не може бути порожнім"),
|
||||||
("Already exists", ""),
|
("Already exists", "Вже існує"),
|
||||||
("Change Password", ""),
|
("Change Password", "Змінити пароль"),
|
||||||
("Refresh Password", ""),
|
("Refresh Password", "Оновити пароль"),
|
||||||
("ID", ""),
|
("ID", "ID"),
|
||||||
("Grid View", ""),
|
("Grid View", "Перегляд ґраткою"),
|
||||||
("List View", ""),
|
("List View", "Перегляд списком"),
|
||||||
("Select", ""),
|
("Select", "Вибрати"),
|
||||||
("Toggle Tags", ""),
|
("Toggle Tags", "Видимість тегів"),
|
||||||
("pull_ab_failed_tip", ""),
|
("pull_ab_failed_tip", "Не вдалося оновити адресну книгу"),
|
||||||
("push_ab_failed_tip", ""),
|
("push_ab_failed_tip", "Не вдалося синхронізувати адресну книгу"),
|
||||||
("synced_peer_readded_tip", ""),
|
("synced_peer_readded_tip", "Пристрої з нещодавніх сеансів будуть синхронізовані з адресною книгою"),
|
||||||
("Change Color", ""),
|
("Change Color", "Змінити колір"),
|
||||||
("Primary Color", ""),
|
("Primary Color", "Основний колір"),
|
||||||
("HSV Color", ""),
|
("HSV Color", "Колір HSV"),
|
||||||
("Installation Successful!", ""),
|
("Installation Successful!", "Успішне встановлення!"),
|
||||||
("Installation failed!", ""),
|
("Installation failed!", "Невдале встановлення!"),
|
||||||
("Reverse mouse wheel", ""),
|
("Reverse mouse wheel", "Зворотній напрям прокрутки"),
|
||||||
("{} sessions", ""),
|
("{} sessions", "{} сеансів"),
|
||||||
("scam_title", ""),
|
("scam_title", "Вас можуть ОБМАНУТИ!"),
|
||||||
("scam_text1", ""),
|
("scam_text1", "Якщо ви розмовляєте по телефону з кимось, кого НЕ ЗНАЄТЕ чи кому НЕ ДОВІРЯЄТЕ, і ця особа хоче, щоб ви використали RustDesk та запустили службу, не робіть цього та негайно завершіть дзвінок."),
|
||||||
("scam_text2", ""),
|
("scam_text2", "Ймовірно, ви маєте справу з шахраєм, що намагається викрасти ваші гроші чи особисті дані."),
|
||||||
("Don't show again", ""),
|
("Don't show again", "Не показувати знову"),
|
||||||
("I Agree", ""),
|
("I Agree", "Я погоджуюсь"),
|
||||||
("Decline", ""),
|
("Decline", "Я не погоджуюсь"),
|
||||||
("Timeout in minutes", ""),
|
("Timeout in minutes", "Час очікування в хвилинах"),
|
||||||
("auto_disconnect_option_tip", ""),
|
("auto_disconnect_option_tip", "Автоматично завершувати вхідні сеанси в разі неактивності користувача"),
|
||||||
("Connection failed due to inactivity", ""),
|
("Connection failed due to inactivity", "Автоматично відключено через неактивність"),
|
||||||
("Check for software update on startup", ""),
|
("Check for software update on startup", "Перевіряти оновлення під час запуску"),
|
||||||
("upgrade_rustdesk_server_pro_to_{}_tip", ""),
|
("upgrade_rustdesk_server_pro_to_{}_tip", "Будь ласка, оновіть RustDesk Server Pro до версії {} чи новіше!"),
|
||||||
("pull_group_failed_tip", ""),
|
("pull_group_failed_tip", "Не вдалося оновити групу"),
|
||||||
("Filter by intersection", ""),
|
("Filter by intersection", "Фільтр за збігом"),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", "Прибирати шпалеру під час вхідних сеансів"),
|
||||||
("Test", ""),
|
("Test", "Тест"),
|
||||||
("switch_display_elevated_connections_tip", ""),
|
("switch_display_elevated_connections_tip", "В режимі розширених прав, коли є декілька підключень, не підтримується перемикання на неосновний дисплей. Якщо вам потрібен контроль декількох дисплеїв, будь ласка, спробуйте ще раз після встановлення"),
|
||||||
("display_is_plugged_out_msg", ""),
|
("display_is_plugged_out_msg", "Дисплей відключено, перемкніться на перший дисплей"),
|
||||||
("No displays", ""),
|
("No displays", "Відсутні дисплеї"),
|
||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", "Перемкніться на основний дисплей, оскільки в режимі розширених прав одночасне використання декілька дисплеїв не підтримуються."),
|
||||||
("Open in new window", ""),
|
("Open in new window", "Відкрити в новому вікні"),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", "Відображати дисплеї в якості окремих вікон"),
|
||||||
|
("Use all my displays for the remote session", "Використовувати всі мої дисплеї для віддаленого сеансу"),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -564,5 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("elevated_switch_display_msg", ""),
|
("elevated_switch_display_msg", ""),
|
||||||
("Open in new window", ""),
|
("Open in new window", ""),
|
||||||
("Show displays as individual windows", ""),
|
("Show displays as individual windows", ""),
|
||||||
|
("Use all my displays for the remote session", ""),
|
||||||
|
("selinux_tip", ""),
|
||||||
|
("Change view", ""),
|
||||||
|
("Big tiles", ""),
|
||||||
|
("Small tiles", ""),
|
||||||
|
("List", ""),
|
||||||
|
("Virtual display", ""),
|
||||||
|
("Plug out all", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,10 @@ type Xdo = *const c_void;
|
|||||||
pub const PA_SAMPLE_RATE: u32 = 48000;
|
pub const PA_SAMPLE_RATE: u32 = 48000;
|
||||||
static mut UNMODIFIED: bool = true;
|
static mut UNMODIFIED: bool = true;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref IS_X11: bool = hbb_common::platform::linux::is_x11_or_headless();
|
||||||
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static XDO: RefCell<Xdo> = RefCell::new(unsafe { xdo_new(std::ptr::null()) });
|
static XDO: RefCell<Xdo> = RefCell::new(unsafe { xdo_new(std::ptr::null()) });
|
||||||
static DISPLAY: RefCell<*mut c_void> = RefCell::new(unsafe { XOpenDisplay(std::ptr::null())});
|
static DISPLAY: RefCell<*mut c_void> = RefCell::new(unsafe { XOpenDisplay(std::ptr::null())});
|
||||||
@ -1360,3 +1364,26 @@ impl Drop for WallPaperRemover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_x11() -> bool {
|
||||||
|
*IS_X11
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_selinux_enforcing() -> bool {
|
||||||
|
match run_cmds("getenforce") {
|
||||||
|
Ok(output) => output.trim() == "Enforcing",
|
||||||
|
Err(_) => match run_cmds("sestatus") {
|
||||||
|
Ok(output) => {
|
||||||
|
for line in output.lines() {
|
||||||
|
if line.contains("Current mode:") {
|
||||||
|
return line.contains("enforcing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Err(_) => false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user