Merge pull request #6199 from fufesou/feat/windows_virtual_displays

feat, win virtual display
This commit is contained in:
RustDesk 2023-10-28 02:03:24 +08:00 committed by GitHub
commit cef782c388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 403 additions and 102 deletions

View File

@ -32,7 +32,7 @@ import 'package:flutter_hbb/common/widgets/peer_card.dart';
for (var peer in peerData) { for (var peer in peerData) {
if (peer is Map && peer.containsKey("id")) { if (peer is Map && peer.containsKey("id")) {
String id = peer["id"]; String id = peer["id"];
if (id != null && !combinedPeers.containsKey(id)) { if (!combinedPeers.containsKey(id)) {
combinedPeers[id] = peer; combinedPeers[id] = peer;
} }
} }

View File

@ -5,6 +5,9 @@ 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'; 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;
const int kMainWindowId = 0; const int kMainWindowId = 0;
@ -15,6 +18,11 @@ const kKeyLegacyMode = 'legacy';
const kKeyMapMode = 'map'; const kKeyMapMode = 'map';
const kKeyTranslateMode = 'translate'; 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";

View File

@ -978,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,
@ -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 { class _KeyboardMenu extends StatelessWidget {
final String id; final String id;
final FFI ffi; final FFI ffi;

View File

@ -248,6 +248,8 @@ class FfiModel with ChangeNotifier {
handlePeerInfo(evt, peerId, false); 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');
@ -895,6 +897,33 @@ 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. // 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; RxInt displaysCount = 0.obs;
RxBool isSet = false.obs; RxBool isSet = false.obs;
bool get isWayland => platformAdditions['is_wayland'] == true; bool get isWayland => platformAdditions[kPlatformAdditionsIsWayland] == true;
bool get isHeadless => platformAdditions['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;

View File

@ -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;
} }
} }

View File

@ -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()

View File

@ -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);
} }
_ => {} _ => {}
} }

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 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) {

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); 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"))]
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -566,7 +566,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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"), ("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."), ("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"), ("Virtual display", ""),
("Plug out all", ""),
(" view", "Mainīt skatu"),
("Big tiles", "Lielas flīzes"), ("Big tiles", "Lielas flīzes"),
("Small tiles", "Mazas flīzes"), ("Small tiles", "Mazas flīzes"),
("List", "Saraksts"), ("List", "Saraksts"),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,9 +53,10 @@ use std::{
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
use system_shutdown; use system_shutdown;
#[cfg(all(windows, feature = "virtual_display_driver"))]
use crate::virtual_display_manager;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
use std::collections::HashSet; use std::collections::HashSet;
pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>; pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -1031,9 +1032,10 @@ impl Connection {
pi.hostname = DEVICE_NAME.lock().unwrap().clone(); pi.hostname = DEVICE_NAME.lock().unwrap().clone();
pi.platform = "Android".into(); pi.platform = "Android".into();
} }
#[cfg(any(target_os = "linux", target_os = "windows"))]
let mut platform_additions = serde_json::Map::new();
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
let mut platform_additions = serde_json::Map::new();
if crate::platform::current_is_wayland() { if crate::platform::current_is_wayland() {
platform_additions.insert("is_wayland".into(), json!(true)); platform_additions.insert("is_wayland".into(), json!(true));
} }
@ -1044,12 +1046,27 @@ impl Connection {
platform_additions.insert("headless".into(), json!(true)); platform_additions.insert("headless".into(), json!(true));
} }
} }
if !platform_additions.is_empty() { }
pi.platform_additions = #[cfg(target_os = "windows")]
serde_json::to_string(&platform_additions).unwrap_or("".into()); {
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(); pi.encoding = Some(scrap::codec::Encoder::supported_encoding()).into();
if self.port_forward_socket.is_some() { if self.port_forward_socket.is_some() {
@ -1962,6 +1979,10 @@ impl Connection {
let set = displays.set.iter().map(|d| *d as usize).collect::<Vec<_>>(); let set = displays.set.iter().map(|d| *d as usize).collect::<Vec<_>>();
self.capture_displays(&add, &sub, &set).await; 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)) => { Some(misc::Union::ChatMessage(c)) => {
self.send_to_cm(ipc::Data::ChatMessage { text: c.text }); self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
self.chat_unanswered = true; self.chat_unanswered = true;
@ -2214,6 +2235,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")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
fn change_resolution(&mut self, r: &Resolution) { fn change_resolution(&mut self, r: &Resolution) {
if self.keyboard { if self.keyboard {
@ -2222,7 +2262,7 @@ impl Connection {
let name = display.name(); let name = display.name();
#[cfg(all(windows, feature = "virtual_display_driver"))] #[cfg(all(windows, feature = "virtual_display_driver"))]
if let Some(_ok) = if let Some(_ok) =
crate::virtual_display_manager::change_resolution_if_is_virtual_display( virtual_display_manager::change_resolution_if_is_virtual_display(
&name, &name,
r.width as _, r.width as _,
r.height as _, r.height as _,
@ -2918,7 +2958,7 @@ mod raii {
} }
#[cfg(all(windows, feature = "virtual_display_driver"))] #[cfg(all(windows, feature = "virtual_display_driver"))]
if active_conns_lock.is_empty() { if active_conns_lock.is_empty() {
display_service::try_plug_out_virtual_display(); let _ = virtual_display_manager::reset_all();
} }
#[cfg(all(windows))] #[cfg(all(windows))]
if active_conns_lock.is_empty() { if active_conns_lock.is_empty() {

View File

@ -12,6 +12,8 @@ use scrap::Display;
pub const NAME: &'static str = "display"; pub const NAME: &'static str = "display";
const DUMMY_DISPLAY_SIDE_MAX_SIZE: usize = 1024;
struct ChangedResolution { struct ChangedResolution {
original: (i32, i32), original: (i32, i32),
changed: (i32, i32), changed: (i32, i32),
@ -154,6 +156,20 @@ fn displays_to_msg(displays: Vec<DisplayInfo>) -> Message {
..Default::default() ..Default::default()
}; };
pi.displays = displays.clone(); 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. // current_display should not be used in server.
// It is set to 0 for compatibility with old clients. // It is set to 0 for compatibility with old clients.
pi.current_display = 0; pi.current_display = 0;
@ -168,11 +184,6 @@ fn check_get_displays_changed_msg() -> Option<Message> {
Some(displays_to_msg(displays)) 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<()> { fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
while sp.ok() { while sp.ok() {
sp.snapshot(|sps| { sp.snapshot(|sps| {
@ -312,9 +323,18 @@ fn no_displays(displays: &Vec<Display>) -> bool {
true true
} else if display_len == 1 { } else if display_len == 1 {
let display = &displays[0]; let display = &displays[0];
let dummy_display_side_max_size = 800; if display.width() > DUMMY_DISPLAY_SIDE_MAX_SIZE
display.width() <= dummy_display_side_max_size || display.height() > 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 { } else {
false false
} }

View File

@ -255,7 +255,7 @@ pub fn test_create_capturer(
) -> String { ) -> String {
let test_begin = Instant::now(); let test_begin = Instant::now();
loop { loop {
let err = match try_get_displays() { let err = match Display::all() {
Ok(mut displays) => { Ok(mut displays) => {
if displays.len() <= display_idx { if displays.len() <= display_idx {
anyhow!( 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 _ { if test_begin.elapsed().as_millis() >= timeout_millis as _ {
return err.to_string(); 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(); let ndisplay = displays.len();
if ndisplay <= current { if ndisplay <= current {
bail!( bail!(
@ -761,52 +761,6 @@ pub fn refresh() {
Display::refresh_size(); 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)] #[cfg(windows)]
fn start_uac_elevation_check() { fn start_uac_elevation_check() {
static START: Once = Once::new(); 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) { fn on_connected(&self, conn_type: ConnType) {
match conn_type { match conn_type {
ConnType::RDP => {} ConnType::RDP => {}

View File

@ -237,11 +237,19 @@ impl<T: InvokeUiSession> Session<T> {
} }
pub fn get_displays_as_individual_windows(&self) -> String { 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 { 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) { 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) { 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) { 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) { 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"))] #[cfg(not(feature = "flutter"))]
pub fn refresh_video(&self, _display: i32) { pub fn refresh_video(&self, _display: i32) {
self.send(Data::Message(LoginConfigHandler::refresh())); 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 switch_display(&self, display: &SwitchDisplay);
fn set_peer_info(&self, peer_info: &PeerInfo); // flutter fn set_peer_info(&self, peer_info: &PeerInfo); // flutter
fn set_displays(&self, displays: &Vec<DisplayInfo>); fn set_displays(&self, displays: &Vec<DisplayInfo>);
fn set_platform_additions(&self, data: &str);
fn on_connected(&self, conn_type: ConnType); fn on_connected(&self, conn_type: ConnType);
fn update_privacy_mode(&self); fn update_privacy_mode(&self);
fn set_permission(&self, name: &str, value: bool); fn set_permission(&self, name: &str, value: bool);

View File

@ -18,10 +18,18 @@ lazy_static::lazy_static! {
struct VirtualDisplayManager { struct VirtualDisplayManager {
headless_index_name: Option<(u32, String)>, headless_index_name: Option<(u32, String)>,
peer_index_name: HashMap<u32, String>, peer_index_name: HashMap<u32, String>,
is_driver_installed: bool,
} }
impl VirtualDisplayManager { 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 let Err(e) = virtual_display::create_device() {
if !e.to_string().contains("Device is already created") { if !e.to_string().contains("Device is already created") {
bail!("Create device failed {}", e); bail!("Create device failed {}", e);
@ -29,9 +37,8 @@ impl VirtualDisplayManager {
} }
// Reboot is not required for this case. // Reboot is not required for this case.
let mut _reboot_required = false; let mut _reboot_required = false;
allow_err!(virtual_display::install_update_driver( virtual_display::install_update_driver(&mut _reboot_required)?;
&mut _reboot_required self.is_driver_installed = true;
));
Ok(()) Ok(())
} }
@ -48,7 +55,7 @@ impl VirtualDisplayManager {
pub fn plug_in_headless() -> ResultType<()> { pub fn plug_in_headless() -> ResultType<()> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
VirtualDisplayManager::prepare_driver()?; manager.prepare_driver()?;
let modes = [virtual_display::MonitorMode { let modes = [virtual_display::MonitorMode {
width: 1920, width: 1920,
height: 1080, height: 1080,
@ -93,9 +100,55 @@ fn get_new_device_name(device_names: &HashSet<String>) -> String {
"".to_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();
manager.install_update_driver()?;
manager.peer_index_name.clear();
manager.headless_index_name = None;
Ok(())
}
pub fn plug_in_peer_request(modes: Vec<Vec<virtual_display::MonitorMode>>) -> ResultType<Vec<u32>> { pub fn plug_in_peer_request(modes: Vec<Vec<virtual_display::MonitorMode>>) -> ResultType<Vec<u32>> {
let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap();
VirtualDisplayManager::prepare_driver()?; manager.prepare_driver()?;
let mut indices: Vec<u32> = Vec::new(); let mut indices: Vec<u32> = Vec::new();
for m in modes.iter() { for m in modes.iter() {
@ -119,9 +172,9 @@ pub fn plug_in_peer_request(modes: Vec<Vec<virtual_display::MonitorMode>>) -> Re
Ok(indices) 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(); 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) { if manager.peer_index_name.contains_key(idx) {
allow_err!(virtual_display::plug_out_monitor(*idx)); allow_err!(virtual_display::plug_out_monitor(*idx));
manager.peer_index_name.remove(idx); manager.peer_index_name.remove(idx);