Merge remote-tracking branch 'rd/master' into feat/x11/clipboard-file/init

Signed-off-by: ClSlaid <cailue@bupt.edu.cn>
This commit is contained in:
ClSlaid 2023-10-28 19:56:19 +08:00
commit 251245d315
No known key found for this signature in database
GPG Key ID: E0A5F564C51C056E
56 changed files with 450 additions and 136 deletions

View File

@ -6,10 +6,10 @@
<a href="#file-structure">Struktura</a>
<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>
<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>
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)
@ -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++
- 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
- Linux/MacOS: vcpkg install libvpx libyuv opus aom
@ -127,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
```
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
target/debug/rustdesk
@ -139,7 +139,7 @@ Nebo, pokud spouštíte variantu pro vydání:
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ů

View File

@ -32,7 +32,7 @@ import 'package:flutter_hbb/common/widgets/peer_card.dart';
for (var peer in peerData) {
if (peer is Map && peer.containsKey("id")) {
String id = peer["id"];
if (id != null && !combinedPeers.containsKey(id)) {
if (!combinedPeers.containsKey(id)) {
combinedPeers[id] = peer;
}
}
@ -55,12 +55,12 @@ import 'package:flutter_hbb/common/widgets/peer_card.dart';
}
class AutocompletePeerTile extends StatefulWidget {
final IDTextEditingController idController;
final VoidCallback onSelect;
final Peer peer;
const AutocompletePeerTile({
Key? key,
required this.idController,
required this.onSelect,
required this.peer,
}) : super(key: key);
@ -85,12 +85,7 @@ class _AutocompletePeerTileState extends State<AutocompletePeerTile>{
fontSize: 11,
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
final child = GestureDetector(
onTap: () {
setState(() {
widget.idController.id = widget.peer.id;
FocusScope.of(context).unfocus();
});
},
onTap: () => widget.onSelect(),
child:
Container(
height: 42,

View File

@ -190,14 +190,20 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
child: widget.peerCardBuilder(peer),
);
final windowWidth = MediaQuery.of(context).size.width;
final model = Provider.of<PeerTabModel>(context);
// `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
? Obx(
() => SizedBox(
width: peerCardUiType.value != PeerUiType.list
? 220
: model.currentTab == PeerTabIndex.group.index || (model.currentTab == PeerTabIndex.ab.index && !hideAbTagsPanel)
: currentTab == PeerTabIndex.group.index || (currentTab == PeerTabIndex.ab.index && !hideAbTagsPanel)
? windowWidth - 390 :
windowWidth - 227,
height:

View File

@ -5,6 +5,9 @@ import 'package:flutter_hbb/common.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 int kInvalidWindowId = -1;
const int kMainWindowId = 0;
@ -15,6 +18,11 @@ 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 kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS";

View File

@ -277,6 +277,12 @@ class _ConnectionPageState extends State<ConnectionPage>
},
));
},
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;
@ -304,7 +310,7 @@ class _ConnectionPageState extends State<ConnectionPage>
: Padding(
padding: const EdgeInsets.only(top: 5),
child: ListView(
children: options.map((peer) => AutocompletePeerTile(idController: _idController, peer: peer)).toList(),
children: options.map((peer) => AutocompletePeerTile(onSelect: () => onSelected(peer), peer: peer)).toList(),
),
),
),

View File

@ -978,6 +978,10 @@ class _DisplayMenuState extends State<_DisplayMenu> {
ffi: widget.ffi,
screenAdjustor: _screenAdjustor,
),
_VirtualDisplayMenu(
id: widget.id,
ffi: widget.ffi,
),
Divider(),
toggles(),
widget.pluginItem,
@ -1387,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 {
final String id;
final FFI ffi;

View File

@ -245,6 +245,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
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;
@ -268,7 +274,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
)))
: ListView(
padding: EdgeInsets.only(top: 5),
children: options.map((peer) => AutocompletePeerTile(idController: _idController, peer: peer)).toList(),
children: options.map((peer) => AutocompletePeerTile(onSelect: () => onSelected(peer), peer: peer)).toList(),
))))
);
},

View File

@ -248,6 +248,8 @@ class FfiModel with ChangeNotifier {
handlePeerInfo(evt, peerId, false);
} else if (name == 'sync_peer_info') {
handleSyncPeerInfo(evt, sessionId, peerId);
} else if (name == 'sync_platform_additions') {
handlePlatformAdditions(evt, sessionId, peerId);
} else if (name == 'connection_ready') {
setConnectionType(
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
@ -895,6 +897,33 @@ class FfiModel with ChangeNotifier {
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.
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.
@ -2300,8 +2329,13 @@ class PeerInfo with ChangeNotifier {
RxInt displaysCount = 0.obs;
RxBool isSet = false.obs;
bool get isWayland => platformAdditions['is_wayland'] == true;
bool get isHeadless => platformAdditions['headless'] == true;
bool get isWayland => platformAdditions[kPlatformAdditionsIsWayland] == 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;

View File

@ -102,6 +102,7 @@ message PeerInfo {
SupportedEncoding encoding = 10;
SupportedResolutions resolutions = 11;
// 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;
}
@ -498,6 +499,11 @@ message CaptureDisplays {
repeated int32 set = 3;
}
message ToggleVirtualDisplay {
int32 display = 1;
bool on = 2;
}
message PermissionInfo {
enum Permission {
Keyboard = 0;
@ -697,6 +703,7 @@ message Misc {
bool client_record_status = 29;
CaptureDisplays capture_displays = 30;
int32 refresh_video_display = 31;
ToggleVirtualDisplay toggle_virtual_display = 32;
}
}

View File

@ -1,9 +1,10 @@
#[cfg(windows)]
pub mod win10;
use hbb_common::ResultType;
#[cfg(windows)]
use hbb_common::lazy_static;
use hbb_common::{bail, ResultType};
use std::path::Path;
use hbb_common::{bail, lazy_static};
#[cfg(windows)]
use std::path::PathBuf;
#[cfg(windows)]
use std::sync::Mutex;
@ -33,18 +34,25 @@ pub fn download_driver() -> ResultType<()> {
Ok(())
}
#[no_mangle]
pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
#[cfg(windows)]
#[cfg(windows)]
fn get_driver_install_abs_path() -> ResultType<PathBuf> {
let install_path = win10::DRIVER_INSTALL_PATH;
#[cfg(not(windows))]
let install_path = "";
let abs_path = Path::new(install_path).canonicalize()?;
let exe_file = std::env::current_exe()?;
let abs_path = match exe_file.parent() {
Some(cur_dir) => cur_dir.join(install_path),
None => bail!(
"Invalid exe parent for {}",
exe_file.to_string_lossy().as_ref()
),
};
if !abs_path.exists() {
bail!("{} not exists", install_path)
}
Ok(abs_path)
}
#[no_mangle]
pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
#[cfg(windows)]
unsafe {
{
@ -54,6 +62,7 @@ pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
bail!("{}", e);
}
let abs_path = get_driver_install_abs_path()?;
let full_install_path: Vec<u16> = abs_path
.to_string_lossy()
.as_ref()
@ -76,19 +85,10 @@ pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> {
#[no_mangle]
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)]
unsafe {
{
let abs_path = get_driver_install_abs_path()?;
let full_install_path: Vec<u16> = abs_path
.to_string_lossy()
.as_ref()

View File

@ -1534,6 +1534,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
Some(message::Union::PeerInfo(pi)) => {
self.handler.set_displays(&pi.displays);
self.handler.set_platform_additions(&pi.platform_additions);
}
_ => {}
}

View File

@ -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 msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {

View File

@ -1401,6 +1401,12 @@ pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
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) {
#[cfg(any(target_os = "android", target_os = "ios"))]
{

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", "虚拟显示器"),
("Plug out all", "拔出所有"),
].iter().cloned().collect();
}

View File

@ -565,10 +565,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Open in new window", "Otevřít v novém okně"),
("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", ""),
("Change view", ""),
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("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();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -566,9 +566,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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", ""),
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Change view", "Ansicht ändern"),
("Big tiles", "Große Kacheln"),
("Small tiles", "Kleine Kacheln"),
("List", "Liste"),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("selinux_tip", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -566,9 +566,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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", ""),
("Change view", "Sesuaikan tampilan"),
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", "Tampilan virtual"),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -565,11 +565,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Open in new window", "Apri in una nuova finestra"),
("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", ""),
("selinux_tip", "In questo dispositivo è abilitato SELinux, che potrebbe impedire il corretto funzionamento di RustDesk come lato controllato."),
("Change view", ""),
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Change view", "Modifica vista"),
("Big tiles", "Icone grandi"),
("Small tiles", "Icone piccole"),
("List", "Elenco"),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -566,9 +566,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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", ""),
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("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();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", "Большие значки"),
("Small tiles", "Маленькие значки"),
("List", "Список"),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -570,5 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Big tiles", ""),
("Small tiles", ""),
("List", ""),
("Virtual display", ""),
("Plug out all", ""),
].iter().cloned().collect();
}

View File

@ -1838,7 +1838,7 @@ pub fn uninstall_cert() -> ResultType<()> {
}
mod cert {
use hbb_common::{allow_err, bail, log, ResultType};
use hbb_common::{bail, log, ResultType};
use std::{ffi::OsStr, io::Error, os::windows::ffi::OsStrExt, path::Path, str::from_utf8};
use winapi::{
shared::{

View File

@ -53,9 +53,10 @@ use std::{
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use system_shutdown;
#[cfg(all(windows, feature = "virtual_display_driver"))]
use crate::virtual_display_manager;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use std::collections::HashSet;
pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
lazy_static::lazy_static! {
@ -1031,9 +1032,10 @@ impl Connection {
pi.hostname = DEVICE_NAME.lock().unwrap().clone();
pi.platform = "Android".into();
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
let mut platform_additions = serde_json::Map::new();
#[cfg(target_os = "linux")]
{
let mut platform_additions = serde_json::Map::new();
if crate::platform::current_is_wayland() {
platform_additions.insert("is_wayland".into(), json!(true));
}
@ -1044,12 +1046,27 @@ impl Connection {
platform_additions.insert("headless".into(), json!(true));
}
}
if !platform_additions.is_empty() {
pi.platform_additions =
serde_json::to_string(&platform_additions).unwrap_or("".into());
}
#[cfg(target_os = "windows")]
{
platform_additions.insert(
"is_installed".into(),
json!(crate::platform::is_installed()),
);
#[cfg(feature = "virtual_display_driver")]
if crate::platform::is_installed() {
let virtual_displays = virtual_display_manager::get_virtual_displays();
if !virtual_displays.is_empty() {
platform_additions.insert("virtual_displays".into(), json!(&virtual_displays));
}
}
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
if !platform_additions.is_empty() {
pi.platform_additions = serde_json::to_string(&platform_additions).unwrap_or("".into());
}
pi.encoding = Some(scrap::codec::Encoder::supported_encoding()).into();
if self.port_forward_socket.is_some() {
@ -1963,6 +1980,10 @@ impl Connection {
let set = displays.set.iter().map(|d| *d as usize).collect::<Vec<_>>();
self.capture_displays(&add, &sub, &set).await;
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
Some(misc::Union::ToggleVirtualDisplay(t)) => {
self.toggle_virtual_display(t).await;
}
Some(misc::Union::ChatMessage(c)) => {
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
self.chat_unanswered = true;
@ -2215,6 +2236,25 @@ impl Connection {
}
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
async fn toggle_virtual_display(&mut self, t: ToggleVirtualDisplay) {
if t.on {
if let Err(e) = virtual_display_manager::plug_in_index_modes(t.display as _, Vec::new())
{
log::error!("Failed to plug in virtual display: {}", e);
}
} else {
let indices = if t.display == -1 {
virtual_display_manager::get_virtual_displays()
} else {
vec![t.display as _]
};
if let Err(e) = virtual_display_manager::plug_out_peer_request(&indices) {
log::error!("Failed to plug out virtual display {:?}: {}", &indices, e);
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn change_resolution(&mut self, r: &Resolution) {
if self.keyboard {
@ -2223,7 +2263,7 @@ impl Connection {
let name = display.name();
#[cfg(all(windows, feature = "virtual_display_driver"))]
if let Some(_ok) =
crate::virtual_display_manager::change_resolution_if_is_virtual_display(
virtual_display_manager::change_resolution_if_is_virtual_display(
&name,
r.width as _,
r.height as _,
@ -2919,7 +2959,7 @@ mod raii {
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
if active_conns_lock.is_empty() {
display_service::try_plug_out_virtual_display();
let _ = virtual_display_manager::reset_all();
}
#[cfg(all(windows))]
if active_conns_lock.is_empty() {

View File

@ -12,6 +12,8 @@ use scrap::Display;
pub const NAME: &'static str = "display";
const DUMMY_DISPLAY_SIDE_MAX_SIZE: usize = 1024;
struct ChangedResolution {
original: (i32, i32),
changed: (i32, i32),
@ -154,6 +156,20 @@ fn displays_to_msg(displays: Vec<DisplayInfo>) -> Message {
..Default::default()
};
pi.displays = displays.clone();
#[cfg(all(windows, feature = "virtual_display_driver"))]
if crate::platform::is_installed() {
let virtual_displays = crate::virtual_display_manager::get_virtual_displays();
if !virtual_displays.is_empty() {
let mut platform_additions = serde_json::Map::new();
platform_additions.insert(
"virtual_displays".into(),
serde_json::json!(&virtual_displays),
);
pi.platform_additions = serde_json::to_string(&platform_additions).unwrap_or("".into());
}
}
// current_display should not be used in server.
// It is set to 0 for compatibility with old clients.
pi.current_display = 0;
@ -168,11 +184,6 @@ fn check_get_displays_changed_msg() -> Option<Message> {
Some(displays_to_msg(displays))
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
pub fn try_plug_out_virtual_display() {
let _res = virtual_display_manager::plug_out_headless();
}
fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
while sp.ok() {
sp.snapshot(|sps| {
@ -312,9 +323,18 @@ fn no_displays(displays: &Vec<Display>) -> bool {
true
} else if display_len == 1 {
let display = &displays[0];
let dummy_display_side_max_size = 800;
display.width() <= dummy_display_side_max_size
&& display.height() <= dummy_display_side_max_size
if display.width() > DUMMY_DISPLAY_SIDE_MAX_SIZE
|| display.height() > DUMMY_DISPLAY_SIDE_MAX_SIZE
{
return false;
}
let any_real = crate::platform::resolutions(&display.name())
.iter()
.any(|r| {
(r.height as usize) > DUMMY_DISPLAY_SIDE_MAX_SIZE
|| (r.width as usize) > DUMMY_DISPLAY_SIDE_MAX_SIZE
});
!any_real
} else {
false
}

View File

@ -255,7 +255,7 @@ pub fn test_create_capturer(
) -> String {
let test_begin = Instant::now();
loop {
let err = match try_get_displays() {
let err = match Display::all() {
Ok(mut displays) => {
if displays.len() <= display_idx {
anyhow!(
@ -271,7 +271,7 @@ pub fn test_create_capturer(
}
}
}
Err(e) => e,
Err(e) => e.into(),
};
if test_begin.elapsed().as_millis() >= timeout_millis as _ {
return err.to_string();
@ -332,7 +332,7 @@ fn get_capturer(
}
}
let mut displays = try_get_displays()?;
let mut displays = Display::all()?;
let ndisplay = displays.len();
if ndisplay <= current {
bail!(
@ -761,52 +761,6 @@ pub fn refresh() {
Display::refresh_size();
}
#[inline]
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
fn try_get_displays() -> ResultType<Vec<Display>> {
Ok(Display::all()?)
}
#[inline]
#[cfg(all(windows, feature = "virtual_display_driver"))]
fn no_displays(displays: &Vec<Display>) -> bool {
let display_len = displays.len();
if display_len == 0 {
true
} else if display_len == 1 {
let display = &displays[0];
let dummy_display_side_max_size = 800;
if display.width() > dummy_display_side_max_size
|| display.height() > dummy_display_side_max_size
{
return false;
}
let any_real = crate::platform::resolutions(&display.name())
.iter()
.any(|r| {
(r.height as usize) > dummy_display_side_max_size
|| (r.width as usize) > dummy_display_side_max_size
});
!any_real
} else {
false
}
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
fn try_get_displays() -> ResultType<Vec<Display>> {
// let mut displays = Display::all()?;
// if no_displays(&displays) {
// log::debug!("no displays, create virtual display");
// if let Err(e) = virtual_display_manager::plug_in_headless() {
// log::error!("plug in headless failed {}", e);
// } else {
// displays = Display::all()?;
// }
// }
Ok(Display::all()?)
}
#[cfg(windows)]
fn start_uac_elevation_check() {
static START: Once = Once::new();

View File

@ -254,6 +254,10 @@ impl InvokeUiSession for SciterHandler {
);
}
fn set_platform_additions(&self, _data: &str) {
// Ignore for sciter version.
}
fn on_connected(&self, conn_type: ConnType) {
match conn_type {
ConnType::RDP => {}

View File

@ -237,11 +237,19 @@ impl<T: InvokeUiSession> Session<T> {
}
pub fn get_displays_as_individual_windows(&self) -> String {
self.lc.read().unwrap().displays_as_individual_windows.clone()
self.lc
.read()
.unwrap()
.displays_as_individual_windows
.clone()
}
pub fn get_use_all_my_displays_for_the_remote_session(&self) -> String {
self.lc.read().unwrap().use_all_my_displays_for_the_remote_session.clone()
self.lc
.read()
.unwrap()
.use_all_my_displays_for_the_remote_session
.clone()
}
pub fn save_reverse_mouse_wheel(&self, value: String) {
@ -249,11 +257,17 @@ impl<T: InvokeUiSession> Session<T> {
}
pub fn save_displays_as_individual_windows(&self, value: String) {
self.lc.write().unwrap().save_displays_as_individual_windows(value);
self.lc
.write()
.unwrap()
.save_displays_as_individual_windows(value);
}
pub fn save_use_all_my_displays_for_the_remote_session(&self, value: String) {
self.lc.write().unwrap().save_use_all_my_displays_for_the_remote_session(value);
self.lc
.write()
.unwrap()
.save_use_all_my_displays_for_the_remote_session(value);
}
pub fn save_view_style(&self, value: String) {
@ -310,6 +324,18 @@ impl<T: InvokeUiSession> Session<T> {
}
}
pub fn toggle_virtual_display(&self, index: i32, on: bool) {
let mut misc = Misc::new();
misc.set_toggle_virtual_display(ToggleVirtualDisplay {
display: index,
on,
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_misc(misc);
self.send(Data::Message(msg_out));
}
#[cfg(not(feature = "flutter"))]
pub fn refresh_video(&self, _display: i32) {
self.send(Data::Message(LoginConfigHandler::refresh()));
@ -1175,6 +1201,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn switch_display(&self, display: &SwitchDisplay);
fn set_peer_info(&self, peer_info: &PeerInfo); // flutter
fn set_displays(&self, displays: &Vec<DisplayInfo>);
fn set_platform_additions(&self, data: &str);
fn on_connected(&self, conn_type: ConnType);
fn update_privacy_mode(&self);
fn set_permission(&self, name: &str, value: bool);

View File

@ -18,10 +18,18 @@ lazy_static::lazy_static! {
struct VirtualDisplayManager {
headless_index_name: Option<(u32, String)>,
peer_index_name: HashMap<u32, String>,
is_driver_installed: bool,
}
impl VirtualDisplayManager {
fn prepare_driver() -> ResultType<()> {
fn prepare_driver(&mut self) -> ResultType<()> {
if !self.is_driver_installed {
self.install_update_driver()?;
}
Ok(())
}
fn install_update_driver(&mut self) -> ResultType<()> {
if let Err(e) = virtual_display::create_device() {
if !e.to_string().contains("Device is already created") {
bail!("Create device failed {}", e);
@ -29,9 +37,8 @@ impl VirtualDisplayManager {
}
// Reboot is not required for this case.
let mut _reboot_required = false;
allow_err!(virtual_display::install_update_driver(
&mut _reboot_required
));
virtual_display::install_update_driver(&mut _reboot_required)?;
self.is_driver_installed = true;
Ok(())
}
@ -48,7 +55,7 @@ impl VirtualDisplayManager {
pub fn plug_in_headless() -> ResultType<()> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
VirtualDisplayManager::prepare_driver()?;
manager.prepare_driver()?;
let modes = [virtual_display::MonitorMode {
width: 1920,
height: 1080,
@ -93,9 +100,55 @@ fn get_new_device_name(device_names: &HashSet<String>) -> String {
"".to_string()
}
pub fn get_virtual_displays() -> Vec<u32> {
VIRTUAL_DISPLAY_MANAGER
.lock()
.unwrap()
.peer_index_name
.keys()
.cloned()
.collect()
}
pub fn plug_in_index_modes(
idx: u32,
mut modes: Vec<virtual_display::MonitorMode>,
) -> ResultType<()> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
manager.prepare_driver()?;
if !manager.peer_index_name.contains_key(&idx) {
let device_names = windows::get_device_names();
if modes.is_empty() {
modes.push(virtual_display::MonitorMode {
width: 1920,
height: 1080,
sync: 60,
});
}
match VirtualDisplayManager::plug_in_monitor(idx, modes.as_slice()) {
Ok(_) => {
let device_name = get_new_device_name(&device_names);
manager.peer_index_name.insert(idx, device_name);
}
Err(e) => {
log::error!("Plug in monitor failed {}", e);
}
}
}
Ok(())
}
pub fn reset_all() -> ResultType<()> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
if !manager.peer_index_name.is_empty() || manager.headless_index_name.is_some() {
manager.install_update_driver()?;
}
Ok(())
}
pub fn plug_in_peer_request(modes: Vec<Vec<virtual_display::MonitorMode>>) -> ResultType<Vec<u32>> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
VirtualDisplayManager::prepare_driver()?;
manager.prepare_driver()?;
let mut indices: Vec<u32> = Vec::new();
for m in modes.iter() {
@ -119,9 +172,9 @@ pub fn plug_in_peer_request(modes: Vec<Vec<virtual_display::MonitorMode>>) -> Re
Ok(indices)
}
pub fn plug_out_peer_request(modes: &[u32]) -> ResultType<()> {
pub fn plug_out_peer_request(indices: &[u32]) -> ResultType<()> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
for idx in modes.iter() {
for idx in indices.iter() {
if manager.peer_index_name.contains_key(idx) {
allow_err!(virtual_display::plug_out_monitor(*idx));
manager.peer_index_name.remove(idx);