mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-01-20 17:13:00 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
45a9e54631
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -6602,7 +6602,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "wallpaper"
|
name = "wallpaper"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
source = "git+https://github.com/21pages/wallpaper.rs#2bbb70acd93be179c69cb96cb8c3dda487e6f5fd"
|
source = "git+https://github.com/21pages/wallpaper.rs#ce4a0cd3f58327c7cc44d15a63706fb0c022bacf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"enquote",
|
"enquote",
|
||||||
|
@ -1054,7 +1054,7 @@ Widget msgboxIcon(String type) {
|
|||||||
if (type == 'on-uac' || type == 'on-foreground-elevated') {
|
if (type == 'on-uac' || type == 'on-foreground-elevated') {
|
||||||
iconData = Icons.admin_panel_settings;
|
iconData = Icons.admin_panel_settings;
|
||||||
}
|
}
|
||||||
if (type == "info") {
|
if (type.contains('info')) {
|
||||||
iconData = Icons.info;
|
iconData = Icons.info;
|
||||||
}
|
}
|
||||||
if (iconData != null) {
|
if (iconData != null) {
|
||||||
@ -2317,7 +2317,7 @@ Widget dialogButton(String text,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int version_cmp(String v1, String v2) {
|
int versionCmp(String v1, String v2) {
|
||||||
return bind.versionToNumber(v: v1) - bind.versionToNumber(v: v2);
|
return bind.versionToNumber(v: v1) - bind.versionToNumber(v: v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2589,3 +2589,18 @@ String getDesktopTabLabel(String peerId, String alias) {
|
|||||||
}
|
}
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionRefreshVideo(SessionID sessionId, PeerInfo pi) async {
|
||||||
|
if (pi.currentDisplay == kAllDisplayValue) {
|
||||||
|
for (int i = 0; i < pi.displays.length; i++) {
|
||||||
|
await bind.sessionRefresh(sessionId: sessionId, display: i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await bind.sessionRefresh(sessionId: sessionId, display: pi.currentDisplay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isChooseDisplayToOpenInNewWindow(PeerInfo pi, SessionID sessionId) =>
|
||||||
|
pi.isSupportMultiDisplay &&
|
||||||
|
bind.sessionGetDisplaysAsIndividualWindows(sessionId: sessionId) == 'Y';
|
||||||
|
|
||||||
|
@ -1295,7 +1295,7 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
|
|||||||
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
|
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
bool notShowFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
|
bool notShowFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
|
||||||
version_cmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
|
versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
|
||||||
|
|
||||||
final content = customImageQualityWidget(
|
final content = customImageQualityWidget(
|
||||||
initQuality: qualityInitValue,
|
initQuality: qualityInitValue,
|
||||||
|
@ -600,7 +600,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
await _openNewConnInAction(id, 'Open in New Tab', kOptionOpenInTabs);
|
await _openNewConnInAction(id, 'Open in New Tab', kOptionOpenInTabs);
|
||||||
|
|
||||||
_openInWindowsAction(String id) async => await _openNewConnInAction(
|
_openInWindowsAction(String id) async => await _openNewConnInAction(
|
||||||
id, 'Open in New Window', kOptionOpenInWindows);
|
id, 'Open in new window', kOptionOpenInWindows);
|
||||||
|
|
||||||
_openNewConnInOptAction(String id) async =>
|
_openNewConnInOptAction(String id) async =>
|
||||||
mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs)
|
mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs)
|
||||||
|
@ -90,7 +90,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||||||
v.add(
|
v.add(
|
||||||
TTextMenu(
|
TTextMenu(
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')),
|
Text(translate(pi.isHeadless ? 'OS Account' : 'OS Password')),
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage: isDesktop,
|
offstage: isDesktop,
|
||||||
child: Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12),
|
child: Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12),
|
||||||
@ -99,13 +99,13 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||||||
trailingIcon: Transform.scale(
|
trailingIcon: Transform.scale(
|
||||||
scale: 0.8,
|
scale: 0.8,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => pi.is_headless
|
onTap: () => pi.isHeadless
|
||||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||||
: handleOsPasswordEditIcon(sessionId, ffi.dialogManager),
|
: handleOsPasswordEditIcon(sessionId, ffi.dialogManager),
|
||||||
child: Icon(Icons.edit),
|
child: Icon(Icons.edit),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () => pi.is_headless
|
onPressed: () => pi.isHeadless
|
||||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||||
: handleOsPasswordAction(sessionId, ffi.dialogManager),
|
: handleOsPasswordAction(sessionId, ffi.dialogManager),
|
||||||
),
|
),
|
||||||
@ -208,7 +208,8 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||||||
ffiModel.keyboard &&
|
ffiModel.keyboard &&
|
||||||
pi.platform != kPeerPlatformAndroid &&
|
pi.platform != kPeerPlatformAndroid &&
|
||||||
pi.platform != kPeerPlatformMacOS &&
|
pi.platform != kPeerPlatformMacOS &&
|
||||||
version_cmp(pi.version, '1.2.0') >= 0) {
|
versionCmp(pi.version, '1.2.0') >= 0 &&
|
||||||
|
bind.peerGetDefaultSessionsCount(id: id) == 1) {
|
||||||
v.add(TTextMenu(
|
v.add(TTextMenu(
|
||||||
child: Text(translate('Switch Sides')),
|
child: Text(translate('Switch Sides')),
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
@ -217,8 +218,9 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
|||||||
// refresh
|
// refresh
|
||||||
if (pi.version.isNotEmpty) {
|
if (pi.version.isNotEmpty) {
|
||||||
v.add(TTextMenu(
|
v.add(TTextMenu(
|
||||||
child: Text(translate('Refresh')),
|
child: Text(translate('Refresh')),
|
||||||
onPressed: () => bind.sessionRefresh(sessionId: sessionId)));
|
onPressed: () => sessionRefreshVideo(sessionId, pi),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
// record
|
// record
|
||||||
var codecFormat = ffi.qualityMonitorModel.data.codecFormat;
|
var codecFormat = ffi.qualityMonitorModel.data.codecFormat;
|
||||||
@ -377,7 +379,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
// show remote cursor
|
// show remote cursor
|
||||||
if (pi.platform != kPeerPlatformAndroid &&
|
if (pi.platform != kPeerPlatformAndroid &&
|
||||||
!ffi.canvasModel.cursorEmbedded &&
|
!ffi.canvasModel.cursorEmbedded &&
|
||||||
!pi.is_wayland) {
|
!pi.isWayland) {
|
||||||
final state = ShowRemoteCursorState.find(id);
|
final state = ShowRemoteCursorState.find(id);
|
||||||
final enabled = !ffiModel.viewOnly;
|
final enabled = !ffiModel.viewOnly;
|
||||||
final option = 'show-remote-cursor';
|
final option = 'show-remote-cursor';
|
||||||
@ -488,7 +490,8 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
value: rxValue.value,
|
value: rxValue.value,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
if (ffiModel.pi.currentDisplay != 0) {
|
if (ffiModel.pi.currentDisplay != 0 &&
|
||||||
|
ffiModel.pi.currentDisplay != kAllDisplayValue) {
|
||||||
msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info',
|
msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info',
|
||||||
'Please switch to Display 1 first', '', ffi.dialogManager);
|
'Please switch to Display 1 first', '', ffi.dialogManager);
|
||||||
return;
|
return;
|
||||||
@ -512,5 +515,23 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
},
|
},
|
||||||
child: Text(translate('Swap control-command key'))));
|
child: Text(translate('Swap control-command key'))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pi.isSupportMultiDisplay &&
|
||||||
|
PrivacyModeState.find(id).isFalse &&
|
||||||
|
pi.displaysCount.value > 1 &&
|
||||||
|
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
|
||||||
|
final value =
|
||||||
|
bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) ==
|
||||||
|
'Y';
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
bind.sessionSetDisplaysAsIndividualWindows(
|
||||||
|
sessionId: sessionId, value: value ? 'Y' : '');
|
||||||
|
},
|
||||||
|
child: Text(translate('Show displays as individual windows'))));
|
||||||
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,14 @@ 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';
|
||||||
|
|
||||||
|
const bool kOpenSamePeerInNewWindow = true;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
const kAllDisplayValue = -1;
|
||||||
|
|
||||||
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";
|
||||||
@ -37,11 +41,13 @@ const String kWindowEventNewRemoteDesktop = "new_remote_desktop";
|
|||||||
const String kWindowEventNewFileTransfer = "new_file_transfer";
|
const String kWindowEventNewFileTransfer = "new_file_transfer";
|
||||||
const String kWindowEventNewPortForward = "new_port_forward";
|
const String kWindowEventNewPortForward = "new_port_forward";
|
||||||
const String kWindowEventActiveSession = "active_session";
|
const String kWindowEventActiveSession = "active_session";
|
||||||
|
const String kWindowEventActiveDisplaySession = "active_display_session";
|
||||||
const String kWindowEventGetRemoteList = "get_remote_list";
|
const String kWindowEventGetRemoteList = "get_remote_list";
|
||||||
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
||||||
|
|
||||||
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
|
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
|
||||||
const String kWindowEventGetCachedSessionData = "get_cached_session_data";
|
const String kWindowEventGetCachedSessionData = "get_cached_session_data";
|
||||||
|
const String kWindowEventOpenMonitorSession = "open_monitor_session";
|
||||||
|
|
||||||
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
|
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
|
||||||
const String kOptionOpenInTabs = "allow-open-in-tabs";
|
const String kOptionOpenInTabs = "allow-open-in-tabs";
|
||||||
@ -60,6 +66,9 @@ 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 kKeyShowMonitorsToolbar = 'show_monitors_toolbar';
|
||||||
|
|
||||||
// the executable name of the portable version
|
// the executable name of the portable version
|
||||||
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
|
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
|
||||||
|
|
||||||
|
@ -187,12 +187,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
? Theme.of(context).scaffoldBackgroundColor
|
? Theme.of(context).scaffoldBackgroundColor
|
||||||
: Theme.of(context).colorScheme.background,
|
: Theme.of(context).colorScheme.background,
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: translate('Settings'),
|
message: translate('Settings'),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.more_vert_outlined,
|
Icons.more_vert_outlined,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onHover: (value) => hover.value = value,
|
onHover: (value) => hover.value = value,
|
||||||
@ -256,27 +256,27 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
child: Obx(() => RotatedBox(
|
child: Obx(() => RotatedBox(
|
||||||
quarterTurns: 2,
|
quarterTurns: 2,
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: translate('Refresh Password'),
|
message: translate('Refresh Password'),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.refresh,
|
Icons.refresh,
|
||||||
color: refreshHover.value
|
color: refreshHover.value
|
||||||
? textColor
|
? textColor
|
||||||
: Color(0xFFDDDDDD),
|
: Color(0xFFDDDDDD),
|
||||||
size: 22,
|
size: 22,
|
||||||
))
|
)))),
|
||||||
)),
|
|
||||||
onHover: (value) => refreshHover.value = value,
|
onHover: (value) => refreshHover.value = value,
|
||||||
).marginOnly(right: 8, top: 4),
|
).marginOnly(right: 8, top: 4),
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => Tooltip(
|
() => Tooltip(
|
||||||
message: translate('Change Password'),
|
message: translate('Change Password'),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.edit,
|
Icons.edit,
|
||||||
color:
|
color: editHover.value
|
||||||
editHover.value ? textColor : Color(0xFFDDDDDD),
|
? textColor
|
||||||
size: 22,
|
: Color(0xFFDDDDDD),
|
||||||
)).marginOnly(right: 8, top: 4),
|
size: 22,
|
||||||
|
)).marginOnly(right: 8, top: 4),
|
||||||
),
|
),
|
||||||
onTap: () => DesktopSettingPage.switch2page(1),
|
onTap: () => DesktopSettingPage.switch2page(1),
|
||||||
onHover: (value) => editHover.value = value,
|
onHover: (value) => editHover.value = value,
|
||||||
@ -604,8 +604,17 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
debugPrint("Failed to parse window id '${call.arguments}': $e");
|
debugPrint("Failed to parse window id '${call.arguments}': $e");
|
||||||
}
|
}
|
||||||
if (windowId != null) {
|
if (windowId != null) {
|
||||||
await rustDeskWinManager.moveTabToNewWindow(windowId, args[1], args[2]);
|
await rustDeskWinManager.moveTabToNewWindow(
|
||||||
|
windowId, args[1], args[2]);
|
||||||
}
|
}
|
||||||
|
} else if (call.method == kWindowEventOpenMonitorSession) {
|
||||||
|
final args = jsonDecode(call.arguments);
|
||||||
|
final windowId = args['window_id'] as int;
|
||||||
|
final peerId = args['peer_id'] as String;
|
||||||
|
final display = args['display'] as int;
|
||||||
|
final displayCount = args['display_count'] as int;
|
||||||
|
await rustDeskWinManager.openMonitorSession(
|
||||||
|
windowId, peerId, display, displayCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_uniLinksSubscription = listenUniLinks();
|
_uniLinksSubscription = listenUniLinks();
|
||||||
|
@ -323,24 +323,7 @@ class _GeneralState extends State<_General> {
|
|||||||
'enable-confirm-closing-tabs',
|
'enable-confirm-closing-tabs',
|
||||||
isServer: false),
|
isServer: false),
|
||||||
_OptionCheckBox(context, 'Adaptive bitrate', 'enable-abr'),
|
_OptionCheckBox(context, 'Adaptive bitrate', 'enable-abr'),
|
||||||
if (Platform.isWindows || Platform.isLinux)
|
wallpaper(),
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: _OptionCheckBox(
|
|
||||||
context,
|
|
||||||
'Remove wallpaper during incoming sessions',
|
|
||||||
'allow-remove-wallpaper'),
|
|
||||||
),
|
|
||||||
_CountDownButton(
|
|
||||||
text: 'Test',
|
|
||||||
second: 5,
|
|
||||||
onPressed: () {
|
|
||||||
bind.mainTestWallpaper(second: 5);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
_OptionCheckBox(
|
_OptionCheckBox(
|
||||||
context,
|
context,
|
||||||
'Open connection in new tab',
|
'Open connection in new tab',
|
||||||
@ -367,6 +350,42 @@ class _GeneralState extends State<_General> {
|
|||||||
return _Card(title: 'Other', children: children);
|
return _Card(title: 'Other', children: children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget wallpaper() {
|
||||||
|
return futureBuilder(future: () async {
|
||||||
|
final support = await bind.mainSupportRemoveWallpaper();
|
||||||
|
return support;
|
||||||
|
}(), hasData: (data) {
|
||||||
|
if (data is bool && data == true) {
|
||||||
|
final option = 'allow-remove-wallpaper';
|
||||||
|
bool value = mainGetBoolOptionSync(option);
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: _OptionCheckBox(
|
||||||
|
context,
|
||||||
|
'Remove wallpaper during incoming sessions',
|
||||||
|
option,
|
||||||
|
update: () {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (value)
|
||||||
|
_CountDownButton(
|
||||||
|
text: 'Test',
|
||||||
|
second: 5,
|
||||||
|
onPressed: () {
|
||||||
|
bind.mainTestWallpaper(second: 5);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Offstage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Widget hwcodec() {
|
Widget hwcodec() {
|
||||||
return Offstage(
|
return Offstage(
|
||||||
offstage: !bind.mainHasHwcodec(),
|
offstage: !bind.mainHasHwcodec(),
|
||||||
@ -1289,7 +1308,7 @@ class _DisplayState extends State<_Display> {
|
|||||||
Widget other(BuildContext context) {
|
Widget other(BuildContext context) {
|
||||||
return _Card(title: 'Other Default Options', children: [
|
return _Card(title: 'Other Default Options', children: [
|
||||||
otherRow('View Mode', 'view_only'),
|
otherRow('View Mode', 'view_only'),
|
||||||
otherRow('show_monitors_tip', 'show_monitors_toolbar'),
|
otherRow('show_monitors_tip', kKeyShowMonitorsToolbar),
|
||||||
otherRow('Collapse toolbar', 'collapse_toolbar'),
|
otherRow('Collapse toolbar', 'collapse_toolbar'),
|
||||||
otherRow('Show remote cursor', 'show_remote_cursor'),
|
otherRow('Show remote cursor', 'show_remote_cursor'),
|
||||||
otherRow('Zoom cursor', 'zoom-cursor'),
|
otherRow('Zoom cursor', 'zoom-cursor'),
|
||||||
@ -1300,6 +1319,8 @@ class _DisplayState extends State<_Display> {
|
|||||||
otherRow('Lock after session end', 'lock_after_session_end'),
|
otherRow('Lock after session end', 'lock_after_session_end'),
|
||||||
otherRow('Privacy mode', 'privacy_mode'),
|
otherRow('Privacy mode', 'privacy_mode'),
|
||||||
otherRow('Reverse mouse wheel', 'reverse_mouse_wheel'),
|
otherRow('Reverse mouse wheel', 'reverse_mouse_wheel'),
|
||||||
|
otherRow('Show displays as individual windows',
|
||||||
|
kKeyShowDisplaysAsIndividualWindows),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import '../widgets/tabbar_widget.dart';
|
|||||||
|
|
||||||
final SimpleWrapper<bool> _firstEnterImage = SimpleWrapper(false);
|
final SimpleWrapper<bool> _firstEnterImage = SimpleWrapper(false);
|
||||||
|
|
||||||
|
// Used to skip session close if "move to new window" is clicked.
|
||||||
final Map<String, bool> closeSessionOnDispose = {};
|
final Map<String, bool> closeSessionOnDispose = {};
|
||||||
|
|
||||||
class RemotePage extends StatefulWidget {
|
class RemotePage extends StatefulWidget {
|
||||||
@ -36,6 +37,8 @@ class RemotePage extends StatefulWidget {
|
|||||||
required this.id,
|
required this.id,
|
||||||
required this.sessionId,
|
required this.sessionId,
|
||||||
required this.tabWindowId,
|
required this.tabWindowId,
|
||||||
|
required this.display,
|
||||||
|
required this.displays,
|
||||||
required this.password,
|
required this.password,
|
||||||
required this.toolbarState,
|
required this.toolbarState,
|
||||||
required this.tabController,
|
required this.tabController,
|
||||||
@ -46,6 +49,8 @@ class RemotePage extends StatefulWidget {
|
|||||||
final String id;
|
final String id;
|
||||||
final SessionID? sessionId;
|
final SessionID? sessionId;
|
||||||
final int? tabWindowId;
|
final int? tabWindowId;
|
||||||
|
final int? display;
|
||||||
|
final List<int>? displays;
|
||||||
final String? password;
|
final String? password;
|
||||||
final ToolbarState toolbarState;
|
final ToolbarState toolbarState;
|
||||||
final String? switchUuid;
|
final String? switchUuid;
|
||||||
@ -73,7 +78,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
late RxBool _zoomCursor;
|
late RxBool _zoomCursor;
|
||||||
late RxBool _remoteCursorMoved;
|
late RxBool _remoteCursorMoved;
|
||||||
late RxBool _keyboardEnabled;
|
late RxBool _keyboardEnabled;
|
||||||
late RenderTexture _renderTexture;
|
final Map<int, RenderTexture> _renderTextures = {};
|
||||||
|
|
||||||
final _blockableOverlayState = BlockableOverlayState();
|
final _blockableOverlayState = BlockableOverlayState();
|
||||||
|
|
||||||
@ -109,6 +114,8 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
switchUuid: widget.switchUuid,
|
switchUuid: widget.switchUuid,
|
||||||
forceRelay: widget.forceRelay,
|
forceRelay: widget.forceRelay,
|
||||||
tabWindowId: widget.tabWindowId,
|
tabWindowId: widget.tabWindowId,
|
||||||
|
display: widget.display,
|
||||||
|
displays: widget.displays,
|
||||||
);
|
);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||||
@ -118,9 +125,6 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
if (!Platform.isLinux) {
|
if (!Platform.isLinux) {
|
||||||
Wakelock.enable();
|
Wakelock.enable();
|
||||||
}
|
}
|
||||||
// Register texture.
|
|
||||||
_renderTexture = RenderTexture();
|
|
||||||
_renderTexture.create(sessionId);
|
|
||||||
|
|
||||||
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
|
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
|
||||||
bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
|
bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
|
||||||
@ -207,7 +211,9 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
// https://github.com/flutter/flutter/issues/64935
|
// https://github.com/flutter/flutter/issues/64935
|
||||||
super.dispose();
|
super.dispose();
|
||||||
debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}");
|
debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}");
|
||||||
await _renderTexture.destroy(closeSession);
|
for (final texture in _renderTextures.values) {
|
||||||
|
await texture.destroy(closeSession);
|
||||||
|
}
|
||||||
// ensure we leave this session, this is a double check
|
// ensure we leave this session, this is a double check
|
||||||
_ffi.inputModel.enterOrLeave(false);
|
_ffi.inputModel.enterOrLeave(false);
|
||||||
DesktopMultiWindow.removeListener(this);
|
DesktopMultiWindow.removeListener(this);
|
||||||
@ -245,6 +251,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
onEnterOrLeaveImageSetter: (func) =>
|
onEnterOrLeaveImageSetter: (func) =>
|
||||||
_onEnterOrLeaveImage4Toolbar = func,
|
_onEnterOrLeaveImage4Toolbar = func,
|
||||||
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Toolbar = null,
|
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Toolbar = null,
|
||||||
|
setRemoteState: setState,
|
||||||
);
|
);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
@ -392,6 +399,38 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<int, RenderTexture> _updateGetRenderTextures(int curDisplay) {
|
||||||
|
tryCreateTexture(int idx) {
|
||||||
|
if (!_renderTextures.containsKey(idx)) {
|
||||||
|
final renderTexture = RenderTexture();
|
||||||
|
_renderTextures[idx] = renderTexture;
|
||||||
|
renderTexture.create(idx, sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tryRemoveTexture(int idx) {
|
||||||
|
if (_renderTextures.containsKey(idx)) {
|
||||||
|
_renderTextures[idx]!.destroy(true);
|
||||||
|
_renderTextures.remove(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curDisplay == kAllDisplayValue) {
|
||||||
|
final displays = _ffi.ffiModel.pi.getCurDisplays();
|
||||||
|
for (var i = 0; i < displays.length; i++) {
|
||||||
|
tryCreateTexture(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tryCreateTexture(curDisplay);
|
||||||
|
for (var i = 0; i < _ffi.ffiModel.pi.displays.length; i++) {
|
||||||
|
if (i != curDisplay) {
|
||||||
|
tryRemoveTexture(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _renderTextures;
|
||||||
|
}
|
||||||
|
|
||||||
Widget getBodyForDesktop(BuildContext context) {
|
Widget getBodyForDesktop(BuildContext context) {
|
||||||
var paints = <Widget>[
|
var paints = <Widget>[
|
||||||
MouseRegion(onEnter: (evt) {
|
MouseRegion(onEnter: (evt) {
|
||||||
@ -402,16 +441,20 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
Provider.of<CanvasModel>(context, listen: false).updateViewStyle();
|
Provider.of<CanvasModel>(context, listen: false).updateViewStyle();
|
||||||
});
|
});
|
||||||
return ImagePaint(
|
final peerDisplay = CurrentDisplayState.find(widget.id);
|
||||||
id: widget.id,
|
return Obx(
|
||||||
zoomCursor: _zoomCursor,
|
() => _ffi.ffiModel.pi.isSet.isFalse
|
||||||
cursorOverImage: _cursorOverImage,
|
? Container(color: Colors.transparent)
|
||||||
keyboardEnabled: _keyboardEnabled,
|
: Obx(() => ImagePaint(
|
||||||
remoteCursorMoved: _remoteCursorMoved,
|
id: widget.id,
|
||||||
textureId: _renderTexture.textureId,
|
zoomCursor: _zoomCursor,
|
||||||
useTextureRender: RenderTexture.useTextureRender,
|
cursorOverImage: _cursorOverImage,
|
||||||
listenerBuilder: (child) =>
|
keyboardEnabled: _keyboardEnabled,
|
||||||
_buildRawTouchAndPointerRegion(child, enterView, leaveView),
|
remoteCursorMoved: _remoteCursorMoved,
|
||||||
|
renderTextures: _updateGetRenderTextures(peerDisplay.value),
|
||||||
|
listenerBuilder: (child) => _buildRawTouchAndPointerRegion(
|
||||||
|
child, enterView, leaveView),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
@ -447,8 +490,7 @@ class ImagePaint extends StatefulWidget {
|
|||||||
final RxBool cursorOverImage;
|
final RxBool cursorOverImage;
|
||||||
final RxBool keyboardEnabled;
|
final RxBool keyboardEnabled;
|
||||||
final RxBool remoteCursorMoved;
|
final RxBool remoteCursorMoved;
|
||||||
final RxInt textureId;
|
final Map<int, RenderTexture> renderTextures;
|
||||||
final bool useTextureRender;
|
|
||||||
final Widget Function(Widget)? listenerBuilder;
|
final Widget Function(Widget)? listenerBuilder;
|
||||||
|
|
||||||
ImagePaint(
|
ImagePaint(
|
||||||
@ -458,8 +500,7 @@ class ImagePaint extends StatefulWidget {
|
|||||||
required this.cursorOverImage,
|
required this.cursorOverImage,
|
||||||
required this.keyboardEnabled,
|
required this.keyboardEnabled,
|
||||||
required this.remoteCursorMoved,
|
required this.remoteCursorMoved,
|
||||||
required this.textureId,
|
required this.renderTextures,
|
||||||
required this.useTextureRender,
|
|
||||||
this.listenerBuilder})
|
this.listenerBuilder})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@ -530,27 +571,13 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) {
|
if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) {
|
||||||
final imageWidth = c.getDisplayWidth() * s;
|
final paintWidth = c.getDisplayWidth() * s;
|
||||||
final imageHeight = c.getDisplayHeight() * s;
|
final paintHeight = c.getDisplayHeight() * s;
|
||||||
final imageSize = Size(imageWidth, imageHeight);
|
final paintSize = Size(paintWidth, paintHeight);
|
||||||
late final Widget imageWidget;
|
final paintWidget = useTextureRender
|
||||||
if (widget.useTextureRender) {
|
? _BuildPaintTextureRender(
|
||||||
imageWidget = SizedBox(
|
c, s, Offset.zero, paintSize, isViewOriginal())
|
||||||
width: imageWidth,
|
: _buildScrollbarNonTextureRender(m, paintSize, s);
|
||||||
height: imageHeight,
|
|
||||||
child: Obx(() => Texture(
|
|
||||||
textureId: widget.textureId.value,
|
|
||||||
filterQuality:
|
|
||||||
isViewOriginal() ? FilterQuality.none : FilterQuality.low,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
imageWidget = CustomPaint(
|
|
||||||
size: imageSize,
|
|
||||||
painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
final percentX = _horizontal.hasClients
|
final percentX = _horizontal.hasClients
|
||||||
@ -570,43 +597,79 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||||||
},
|
},
|
||||||
child: mouseRegion(
|
child: mouseRegion(
|
||||||
child: Obx(() => _buildCrossScrollbarFromLayout(
|
child: Obx(() => _buildCrossScrollbarFromLayout(
|
||||||
context, _buildListener(imageWidget), c.size, imageSize)),
|
context, _buildListener(paintWidget), c.size, paintSize)),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
late final Widget imageWidget;
|
|
||||||
if (c.size.width > 0 && c.size.height > 0) {
|
if (c.size.width > 0 && c.size.height > 0) {
|
||||||
if (widget.useTextureRender) {
|
final paintWidget = useTextureRender
|
||||||
final x = Platform.isLinux ? c.x.toInt().toDouble() : c.x;
|
? _BuildPaintTextureRender(
|
||||||
final y = Platform.isLinux ? c.y.toInt().toDouble() : c.y;
|
c,
|
||||||
imageWidget = Stack(
|
s,
|
||||||
children: [
|
Offset(
|
||||||
Positioned(
|
Platform.isLinux ? c.x.toInt().toDouble() : c.x,
|
||||||
left: x,
|
Platform.isLinux ? c.y.toInt().toDouble() : c.y,
|
||||||
top: y,
|
|
||||||
width: c.getDisplayWidth() * s,
|
|
||||||
height: c.getDisplayHeight() * s,
|
|
||||||
child: Texture(
|
|
||||||
textureId: widget.textureId.value,
|
|
||||||
filterQuality:
|
|
||||||
isViewOriginal() ? FilterQuality.none : FilterQuality.low,
|
|
||||||
),
|
),
|
||||||
)
|
c.size,
|
||||||
],
|
isViewOriginal())
|
||||||
);
|
: _buildScrollAuthNonTextureRender(m, c, s);
|
||||||
} else {
|
return mouseRegion(child: _buildListener(paintWidget));
|
||||||
imageWidget = CustomPaint(
|
|
||||||
size: Size(c.size.width, c.size.height),
|
|
||||||
painter:
|
|
||||||
ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return mouseRegion(child: _buildListener(imageWidget));
|
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildScrollbarNonTextureRender(
|
||||||
|
ImageModel m, Size imageSize, double s) {
|
||||||
|
return CustomPaint(
|
||||||
|
size: imageSize,
|
||||||
|
painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildScrollAuthNonTextureRender(
|
||||||
|
ImageModel m, CanvasModel c, double s) {
|
||||||
|
return CustomPaint(
|
||||||
|
size: Size(c.size.width, c.size.height),
|
||||||
|
painter: ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _BuildPaintTextureRender(
|
||||||
|
CanvasModel c, double s, Offset offset, Size size, bool isViewOriginal) {
|
||||||
|
final ffiModel = c.parent.target!.ffiModel;
|
||||||
|
final displays = ffiModel.pi.getCurDisplays();
|
||||||
|
final children = <Widget>[];
|
||||||
|
final rect = ffiModel.rect;
|
||||||
|
if (rect == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
final curDisplay = ffiModel.pi.currentDisplay;
|
||||||
|
for (var i = 0; i < displays.length; i++) {
|
||||||
|
final textureId = widget
|
||||||
|
.renderTextures[curDisplay == kAllDisplayValue ? i : curDisplay]
|
||||||
|
?.textureId;
|
||||||
|
if (textureId != null) {
|
||||||
|
children.add(Positioned(
|
||||||
|
left: (displays[i].x - rect.left) * s + offset.dx,
|
||||||
|
top: (displays[i].y - rect.top) * s + offset.dy,
|
||||||
|
width: displays[i].width * s,
|
||||||
|
height: displays[i].height * s,
|
||||||
|
child: Obx(() => Texture(
|
||||||
|
textureId: textureId.value,
|
||||||
|
filterQuality:
|
||||||
|
isViewOriginal ? FilterQuality.none : FilterQuality.low,
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SizedBox(
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
child: Stack(children: children),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
MouseCursor _buildCursorOfCache(
|
MouseCursor _buildCursorOfCache(
|
||||||
CursorModel cursor, double scale, CursorData? cache) {
|
CursorModel cursor, double scale, CursorData? cache) {
|
||||||
if (cache == null) {
|
if (cache == null) {
|
||||||
@ -731,7 +794,11 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return widget;
|
return Container(
|
||||||
|
child: widget,
|
||||||
|
width: layoutSize.width,
|
||||||
|
height: layoutSize.height,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildListener(Widget child) {
|
Widget _buildListener(Widget child) {
|
||||||
@ -770,9 +837,14 @@ class CursorPaint extends StatelessWidget {
|
|||||||
double cy = c.y;
|
double cy = c.y;
|
||||||
if (c.viewStyle.style == kRemoteViewStyleOriginal &&
|
if (c.viewStyle.style == kRemoteViewStyleOriginal &&
|
||||||
c.scrollStyle == ScrollStyle.scrollbar) {
|
c.scrollStyle == ScrollStyle.scrollbar) {
|
||||||
final d = c.parent.target!.ffiModel.display;
|
final rect = c.parent.target!.ffiModel.rect;
|
||||||
final imageWidth = d.width * c.scale;
|
if (rect == null) {
|
||||||
final imageHeight = d.height * c.scale;
|
// unreachable!
|
||||||
|
debugPrint('unreachable! The displays rect is null.');
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
final imageWidth = rect.width * c.scale;
|
||||||
|
final imageHeight = rect.height * c.scale;
|
||||||
cx = -imageWidth * c.scrollX;
|
cx = -imageWidth * c.scrollX;
|
||||||
cy = -imageHeight * c.scrollY;
|
cy = -imageHeight * c.scrollY;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
peerId = params['id'];
|
peerId = params['id'];
|
||||||
final sessionId = params['session_id'];
|
final sessionId = params['session_id'];
|
||||||
final tabWindowId = params['tab_window_id'];
|
final tabWindowId = params['tab_window_id'];
|
||||||
|
final display = params['display'];
|
||||||
|
final displays = params['displays'];
|
||||||
if (peerId != null) {
|
if (peerId != null) {
|
||||||
ConnectionTypeState.init(peerId!);
|
ConnectionTypeState.init(peerId!);
|
||||||
tabController.onSelected = (id) {
|
tabController.onSelected = (id) {
|
||||||
@ -80,6 +82,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
id: peerId!,
|
id: peerId!,
|
||||||
sessionId: sessionId == null ? null : SessionID(sessionId),
|
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||||
tabWindowId: tabWindowId,
|
tabWindowId: tabWindowId,
|
||||||
|
display: display,
|
||||||
|
displays: displays?.cast<int>(),
|
||||||
password: params['password'],
|
password: params['password'],
|
||||||
toolbarState: _toolbarState,
|
toolbarState: _toolbarState,
|
||||||
tabController: tabController,
|
tabController: tabController,
|
||||||
@ -109,6 +113,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
final switchUuid = args['switch_uuid'];
|
final switchUuid = args['switch_uuid'];
|
||||||
final sessionId = args['session_id'];
|
final sessionId = args['session_id'];
|
||||||
final tabWindowId = args['tab_window_id'];
|
final tabWindowId = args['tab_window_id'];
|
||||||
|
final display = args['display'];
|
||||||
|
final displays = args['displays'];
|
||||||
windowOnTop(windowId());
|
windowOnTop(windowId());
|
||||||
if (tabController.length == 0) {
|
if (tabController.length == 0) {
|
||||||
if (Platform.isMacOS && stateGlobal.closeOnFullscreen) {
|
if (Platform.isMacOS && stateGlobal.closeOnFullscreen) {
|
||||||
@ -129,6 +135,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
id: id,
|
id: id,
|
||||||
sessionId: sessionId == null ? null : SessionID(sessionId),
|
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||||
tabWindowId: tabWindowId,
|
tabWindowId: tabWindowId,
|
||||||
|
display: display,
|
||||||
|
displays: displays?.cast<int>(),
|
||||||
password: args['password'],
|
password: args['password'],
|
||||||
toolbarState: _toolbarState,
|
toolbarState: _toolbarState,
|
||||||
tabController: tabController,
|
tabController: tabController,
|
||||||
@ -148,6 +156,15 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
windowOnTop(windowId());
|
windowOnTop(windowId());
|
||||||
}
|
}
|
||||||
return jumpOk;
|
return jumpOk;
|
||||||
|
} else if (call.method == kWindowEventActiveDisplaySession) {
|
||||||
|
final args = jsonDecode(call.arguments);
|
||||||
|
final id = args['id'];
|
||||||
|
final display = args['display'];
|
||||||
|
final jumpOk = tabController.jumpToByKeyAndDisplay(id, display);
|
||||||
|
if (jumpOk) {
|
||||||
|
windowOnTop(windowId());
|
||||||
|
}
|
||||||
|
return jumpOk;
|
||||||
} else if (call.method == kWindowEventGetRemoteList) {
|
} else if (call.method == kWindowEventGetRemoteList) {
|
||||||
return tabController.state.value.tabs
|
return tabController.state.value.tabs
|
||||||
.map((e) => e.key)
|
.map((e) => e.key)
|
||||||
@ -160,18 +177,20 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
.join(';');
|
.join(';');
|
||||||
} else if (call.method == kWindowEventGetCachedSessionData) {
|
} else if (call.method == kWindowEventGetCachedSessionData) {
|
||||||
// Ready to show new window and close old tab.
|
// Ready to show new window and close old tab.
|
||||||
final peerId = call.arguments;
|
final args = jsonDecode(call.arguments);
|
||||||
|
final id = args['id'];
|
||||||
|
final close = args['close'];
|
||||||
try {
|
try {
|
||||||
final remotePage = tabController.state.value.tabs
|
final remotePage = tabController.state.value.tabs
|
||||||
.firstWhere((tab) => tab.key == peerId)
|
.firstWhere((tab) => tab.key == id)
|
||||||
.page as RemotePage;
|
.page as RemotePage;
|
||||||
returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString();
|
returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Failed to get cached session data: $e');
|
debugPrint('Failed to get cached session data: $e');
|
||||||
}
|
}
|
||||||
if (returnValue != null) {
|
if (close && returnValue != null) {
|
||||||
closeSessionOnDispose[peerId] = false;
|
closeSessionOnDispose[id] = false;
|
||||||
tabController.closeBy(peerId);
|
tabController.closeBy(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_update_remote_count();
|
_update_remote_count();
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
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/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
@ -325,7 +327,8 @@ class RemoteToolbar extends StatefulWidget {
|
|||||||
final FFI ffi;
|
final FFI ffi;
|
||||||
final ToolbarState state;
|
final ToolbarState state;
|
||||||
final Function(Function(bool)) onEnterOrLeaveImageSetter;
|
final Function(Function(bool)) onEnterOrLeaveImageSetter;
|
||||||
final Function() onEnterOrLeaveImageCleaner;
|
final VoidCallback onEnterOrLeaveImageCleaner;
|
||||||
|
final Function(VoidCallback) setRemoteState;
|
||||||
|
|
||||||
RemoteToolbar({
|
RemoteToolbar({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -334,6 +337,7 @@ class RemoteToolbar extends StatefulWidget {
|
|||||||
required this.state,
|
required this.state,
|
||||||
required this.onEnterOrLeaveImageSetter,
|
required this.onEnterOrLeaveImageSetter,
|
||||||
required this.onEnterOrLeaveImageCleaner,
|
required this.onEnterOrLeaveImageCleaner,
|
||||||
|
required this.setRemoteState,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -450,13 +454,17 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
|||||||
toolbarItems.add(_MobileActionMenu(ffi: widget.ffi));
|
toolbarItems.add(_MobileActionMenu(ffi: widget.ffi));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PrivacyModeState.find(widget.id).isFalse && pi.displays.length > 1) {
|
toolbarItems.add(Obx(() {
|
||||||
toolbarItems.add(
|
if (PrivacyModeState.find(widget.id).isFalse &&
|
||||||
bind.mainGetUserDefaultOption(key: 'show_monitors_toolbar') == 'Y'
|
pi.displaysCount.value > 1) {
|
||||||
? _MultiMonitorMenu(id: widget.id, ffi: widget.ffi)
|
return _MonitorMenu(
|
||||||
: _MonitorMenu(id: widget.id, ffi: widget.ffi),
|
id: widget.id,
|
||||||
);
|
ffi: widget.ffi,
|
||||||
}
|
setRemoteState: widget.setRemoteState);
|
||||||
|
} else {
|
||||||
|
return Offstage();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
toolbarItems
|
toolbarItems
|
||||||
.add(_ControlMenu(id: widget.id, ffi: widget.ffi, state: widget.state));
|
.add(_ControlMenu(id: widget.id, ffi: widget.ffi, state: widget.state));
|
||||||
@ -581,11 +589,22 @@ class _MobileActionMenu extends StatelessWidget {
|
|||||||
class _MonitorMenu extends StatelessWidget {
|
class _MonitorMenu extends StatelessWidget {
|
||||||
final String id;
|
final String id;
|
||||||
final FFI ffi;
|
final FFI ffi;
|
||||||
const _MonitorMenu({Key? key, required this.id, required this.ffi})
|
final Function(VoidCallback) setRemoteState;
|
||||||
: super(key: key);
|
const _MonitorMenu({
|
||||||
|
Key? key,
|
||||||
|
required this.id,
|
||||||
|
required this.ffi,
|
||||||
|
required this.setRemoteState,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
bool get showMonitorsToolbar =>
|
||||||
|
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) =>
|
||||||
|
showMonitorsToolbar ? buildMultiMonitorMenu() : buildMonitorMenu();
|
||||||
|
|
||||||
|
Widget buildMonitorMenu() {
|
||||||
return _IconSubmenuButton(
|
return _IconSubmenuButton(
|
||||||
tooltip: 'Select Monitor',
|
tooltip: 'Select Monitor',
|
||||||
icon: icon(),
|
icon: icon(),
|
||||||
@ -595,7 +614,107 @@ class _MonitorMenu extends StatelessWidget {
|
|||||||
menuStyle: MenuStyle(
|
menuStyle: MenuStyle(
|
||||||
padding:
|
padding:
|
||||||
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
|
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
|
||||||
menuChildren: [Row(children: displays(context))]);
|
menuChildren: [buildMonitorSubmenuWidget()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildMultiMonitorMenu() {
|
||||||
|
return Row(children: buildMonitorList(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildMonitorSubmenuWidget() {
|
||||||
|
final pi = ffi.ffiModel.pi;
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(children: buildMonitorList(false)),
|
||||||
|
pi.isSupportMultiDisplay ? Divider() : Offstage(),
|
||||||
|
pi.isSupportMultiDisplay ? chooseDisplayBehavior() : Offstage(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget chooseDisplayBehavior() {
|
||||||
|
final value =
|
||||||
|
bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) ==
|
||||||
|
'Y';
|
||||||
|
return CkbMenuButton(
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) async {
|
||||||
|
if (value == null) return;
|
||||||
|
await bind.sessionSetDisplaysAsIndividualWindows(
|
||||||
|
sessionId: ffi.sessionId, value: value ? 'Y' : '');
|
||||||
|
},
|
||||||
|
ffi: ffi,
|
||||||
|
child: Text(translate('Show displays as individual windows')));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> buildMonitorList(bool isMulti) {
|
||||||
|
final List<Widget> monitorList = [];
|
||||||
|
final pi = ffi.ffiModel.pi;
|
||||||
|
|
||||||
|
getMonitorText(int i) {
|
||||||
|
if (i == kAllDisplayValue) {
|
||||||
|
if (pi.displays.length == 2) {
|
||||||
|
return '1|2';
|
||||||
|
} else {
|
||||||
|
return 'ALL';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (i + 1).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildMonitorButton(int i) => Obx(() {
|
||||||
|
RxInt display = CurrentDisplayState.find(id);
|
||||||
|
return _IconMenuButton(
|
||||||
|
tooltip: isMulti ? '' : '#${i + 1} monitor',
|
||||||
|
hMargin: isMulti ? null : 6,
|
||||||
|
vMargin: isMulti ? null : 12,
|
||||||
|
topLevel: false,
|
||||||
|
color: i == display.value
|
||||||
|
? _ToolbarTheme.blueColor
|
||||||
|
: _ToolbarTheme.inactiveColor,
|
||||||
|
hoverColor: i == display.value
|
||||||
|
? _ToolbarTheme.hoverBlueColor
|
||||||
|
: _ToolbarTheme.hoverInactiveColor,
|
||||||
|
icon: Container(
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
constraints:
|
||||||
|
const BoxConstraints(minHeight: _ToolbarTheme.height),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
"assets/screen.svg",
|
||||||
|
colorFilter:
|
||||||
|
ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||||
|
),
|
||||||
|
Obx(
|
||||||
|
() => Text(
|
||||||
|
getMonitorText(i),
|
||||||
|
style: TextStyle(
|
||||||
|
color: i == display.value
|
||||||
|
? _ToolbarTheme.blueColor
|
||||||
|
: _ToolbarTheme.inactiveColor,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () => onPressed(i, pi),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < pi.displays.length; i++) {
|
||||||
|
monitorList.add(buildMonitorButton(i));
|
||||||
|
}
|
||||||
|
if (pi.isSupportMultiUiSession && pi.displays.length > 1) {
|
||||||
|
monitorList.add(buildMonitorButton(kAllDisplayValue));
|
||||||
|
}
|
||||||
|
return monitorList;
|
||||||
}
|
}
|
||||||
|
|
||||||
icon() {
|
icon() {
|
||||||
@ -610,7 +729,7 @@ class _MonitorMenu extends StatelessWidget {
|
|||||||
Obx(() {
|
Obx(() {
|
||||||
RxInt display = CurrentDisplayState.find(id);
|
RxInt display = CurrentDisplayState.find(id);
|
||||||
return Text(
|
return Text(
|
||||||
'${display.value + 1}/${pi.displays.length}',
|
'${display.value == kAllDisplayValue ? 'A' : '${display.value + 1}'}/${pi.displays.length}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: _ToolbarTheme.blueColor,
|
color: _ToolbarTheme.blueColor,
|
||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
@ -622,48 +741,44 @@ class _MonitorMenu extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> displays(BuildContext context) {
|
// Open new tab or window to show this monitor.
|
||||||
final List<Widget> rowChildren = [];
|
// For now just open new window.
|
||||||
final pi = ffi.ffiModel.pi;
|
openMonitorInNewTabOrWindow(int i, PeerInfo pi) {
|
||||||
for (int i = 0; i < pi.displays.length; i++) {
|
if (kWindowId == null) {
|
||||||
rowChildren.add(_IconMenuButton(
|
// unreachable
|
||||||
topLevel: false,
|
debugPrint('openMonitorInNewTabOrWindow, unreachable! kWindowId is null');
|
||||||
color: _ToolbarTheme.blueColor,
|
return;
|
||||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
}
|
||||||
tooltip: "#${i + 1} monitor",
|
DesktopMultiWindow.invokeMethod(
|
||||||
hMargin: 6,
|
kMainWindowId,
|
||||||
vMargin: 12,
|
kWindowEventOpenMonitorSession,
|
||||||
icon: Container(
|
jsonEncode({
|
||||||
alignment: AlignmentDirectional.center,
|
'window_id': kWindowId!,
|
||||||
constraints: const BoxConstraints(minHeight: _ToolbarTheme.height),
|
'peer_id': ffi.id,
|
||||||
child: Stack(
|
'display': i,
|
||||||
alignment: Alignment.center,
|
'display_count': pi.displays.length,
|
||||||
children: [
|
}));
|
||||||
SvgPicture.asset(
|
}
|
||||||
"assets/screen.svg",
|
|
||||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
openMonitorInTheSameTab(int i, PeerInfo pi) {
|
||||||
),
|
final displays = i == kAllDisplayValue
|
||||||
Text(
|
? List.generate(pi.displays.length, (index) => index)
|
||||||
(i + 1).toString(),
|
: [i];
|
||||||
style: TextStyle(
|
bind.sessionSwitchDisplay(
|
||||||
color: _ToolbarTheme.blueColor,
|
sessionId: ffi.sessionId, value: Int32List.fromList(displays));
|
||||||
fontSize: 12,
|
ffi.ffiModel.switchToNewDisplay(i, ffi.sessionId, id);
|
||||||
fontWeight: FontWeight.bold,
|
}
|
||||||
),
|
|
||||||
),
|
onPressed(int i, PeerInfo pi) {
|
||||||
],
|
_menuDismissCallback(ffi);
|
||||||
),
|
RxInt display = CurrentDisplayState.find(id);
|
||||||
),
|
if (display.value != i) {
|
||||||
onPressed: () {
|
if (isChooseDisplayToOpenInNewWindow(pi, ffi.sessionId)) {
|
||||||
_menuDismissCallback(ffi);
|
openMonitorInNewTabOrWindow(i, pi);
|
||||||
RxInt display = CurrentDisplayState.find(id);
|
} else {
|
||||||
if (display.value != i) {
|
openMonitorInTheSameTab(i, pi);
|
||||||
bind.sessionSwitchDisplay(sessionId: ffi.sessionId, value: i);
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
return rowChildren;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1044,14 +1159,14 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
Resolution? _localResolution;
|
Resolution? _localResolution;
|
||||||
|
|
||||||
late final TextEditingController _customWidth =
|
late final TextEditingController _customWidth =
|
||||||
TextEditingController(text: display.width.toString());
|
TextEditingController(text: rect?.width.toInt().toString() ?? '');
|
||||||
late final TextEditingController _customHeight =
|
late final TextEditingController _customHeight =
|
||||||
TextEditingController(text: display.height.toString());
|
TextEditingController(text: rect?.height.toInt().toString() ?? '');
|
||||||
|
|
||||||
FFI get ffi => widget.ffi;
|
FFI get ffi => widget.ffi;
|
||||||
PeerInfo get pi => widget.ffi.ffiModel.pi;
|
PeerInfo get pi => widget.ffi.ffiModel.pi;
|
||||||
FfiModel get ffiModel => widget.ffi.ffiModel;
|
FfiModel get ffiModel => widget.ffi.ffiModel;
|
||||||
Display get display => ffiModel.display;
|
Rect? get rect => ffiModel.rect;
|
||||||
List<Resolution> get resolutions => pi.resolutions;
|
List<Resolution> get resolutions => pi.resolutions;
|
||||||
bool get isWayland => bind.mainCurrentIsWayland();
|
bool get isWayland => bind.mainCurrentIsWayland();
|
||||||
|
|
||||||
@ -1063,12 +1178,12 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isVirtualDisplay = display.isVirtualDisplayResolution;
|
final isVirtualDisplay = ffiModel.isVirtualDisplayResolution;
|
||||||
final visible =
|
final visible =
|
||||||
ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1);
|
ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1);
|
||||||
if (!visible) return Offstage();
|
if (!visible) return Offstage();
|
||||||
final showOriginalBtn =
|
final showOriginalBtn =
|
||||||
display.isOriginalResolutionSet && !display.isOriginalResolution;
|
ffiModel.isOriginalResolutionSet && !ffiModel.isOriginalResolution;
|
||||||
final showFitLocalBtn = !_isRemoteResolutionFitLocal();
|
final showFitLocalBtn = !_isRemoteResolutionFitLocal();
|
||||||
_setGroupValue();
|
_setGroupValue();
|
||||||
return _SubmenuButton(
|
return _SubmenuButton(
|
||||||
@ -1085,12 +1200,15 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_setGroupValue() {
|
_setGroupValue() {
|
||||||
|
if (pi.currentDisplay == kAllDisplayValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final lastGroupValue =
|
final lastGroupValue =
|
||||||
stateGlobal.getLastResolutionGroupValue(widget.id, pi.currentDisplay);
|
stateGlobal.getLastResolutionGroupValue(widget.id, pi.currentDisplay);
|
||||||
if (lastGroupValue == _kCustomResolutionValue) {
|
if (lastGroupValue == _kCustomResolutionValue) {
|
||||||
_groupValue = _kCustomResolutionValue;
|
_groupValue = _kCustomResolutionValue;
|
||||||
} else {
|
} else {
|
||||||
_groupValue = '${display.width}x${display.height}';
|
_groupValue = '${rect?.width.toInt()}x${rect?.height.toInt()}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1118,20 +1236,23 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
|
|
||||||
_getLocalResolution() {
|
_getLocalResolution() {
|
||||||
_localResolution = null;
|
_localResolution = null;
|
||||||
final String currentDisplay = bind.mainGetCurrentDisplay();
|
final String mainDisplay = bind.mainGetMainDisplay();
|
||||||
if (currentDisplay.isNotEmpty) {
|
if (mainDisplay.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
final display = json.decode(currentDisplay);
|
final display = json.decode(mainDisplay);
|
||||||
if (display['w'] != null && display['h'] != null) {
|
if (display['w'] != null && display['h'] != null) {
|
||||||
_localResolution = Resolution(display['w'], display['h']);
|
_localResolution = Resolution(display['w'], display['h']);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Failed to decode $currentDisplay, $e');
|
debugPrint('Failed to decode $mainDisplay, $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onChanged(BuildContext context, String? value) async {
|
_onChanged(BuildContext context, String? value) async {
|
||||||
|
if (pi.currentDisplay == kAllDisplayValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
stateGlobal.setLastResolutionGroupValue(
|
stateGlobal.setLastResolutionGroupValue(
|
||||||
widget.id, pi.currentDisplay, value);
|
widget.id, pi.currentDisplay, value);
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
@ -1150,13 +1271,16 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (w != null && h != null) {
|
if (w != null && h != null) {
|
||||||
if (w != display.width || h != display.height) {
|
if (w != rect?.width.toInt() || h != rect?.height.toInt()) {
|
||||||
await _changeResolution(context, w, h);
|
await _changeResolution(context, w, h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_changeResolution(BuildContext context, int w, int h) async {
|
_changeResolution(BuildContext context, int w, int h) async {
|
||||||
|
if (pi.currentDisplay == kAllDisplayValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await bind.sessionChangeResolution(
|
await bind.sessionChangeResolution(
|
||||||
sessionId: ffi.sessionId,
|
sessionId: ffi.sessionId,
|
||||||
display: pi.currentDisplay,
|
display: pi.currentDisplay,
|
||||||
@ -1164,8 +1288,11 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
height: h,
|
height: h,
|
||||||
);
|
);
|
||||||
Future.delayed(Duration(seconds: 3), () async {
|
Future.delayed(Duration(seconds: 3), () async {
|
||||||
final display = ffiModel.display;
|
final rect = ffiModel.rect;
|
||||||
if (w == display.width && h == display.height) {
|
if (rect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (w == rect.width.toInt() && h == rect.height.toInt()) {
|
||||||
if (await widget.screenAdjustor.isWindowCanBeAdjusted()) {
|
if (await widget.screenAdjustor.isWindowCanBeAdjusted()) {
|
||||||
widget.screenAdjustor.doAdjustWindow(context);
|
widget.screenAdjustor.doAdjustWindow(context);
|
||||||
}
|
}
|
||||||
@ -1175,6 +1302,10 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
|
|
||||||
Widget _OriginalResolutionMenuButton(
|
Widget _OriginalResolutionMenuButton(
|
||||||
BuildContext context, bool showOriginalBtn) {
|
BuildContext context, bool showOriginalBtn) {
|
||||||
|
final display = pi.tryGetDisplayIfNotAllDisplay();
|
||||||
|
if (display == null) {
|
||||||
|
return Offstage();
|
||||||
|
}
|
||||||
return Offstage(
|
return Offstage(
|
||||||
offstage: !showOriginalBtn,
|
offstage: !showOriginalBtn,
|
||||||
child: MenuButton(
|
child: MenuButton(
|
||||||
@ -1262,7 +1393,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (display.isVirtualDisplayResolution) {
|
if (ffiModel.isVirtualDisplayResolution) {
|
||||||
return _localResolution!;
|
return _localResolution!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1284,8 +1415,8 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
if (bestFitResolution == null) {
|
if (bestFitResolution == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return bestFitResolution.width == display.width &&
|
return bestFitResolution.width == rect?.width.toInt() &&
|
||||||
bestFitResolution.height == display.height;
|
bestFitResolution.height == rect?.height.toInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1361,7 +1492,7 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pi.is_wayland && mode.key != _kKeyMapMode) {
|
if (pi.isWayland && mode.key != _kKeyMapMode) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1404,7 +1535,7 @@ class _KeyboardMenu extends StatelessWidget {
|
|||||||
|
|
||||||
viewMode() {
|
viewMode() {
|
||||||
final ffiModel = ffi.ffiModel;
|
final ffiModel = ffi.ffiModel;
|
||||||
final enabled = version_cmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard;
|
final enabled = versionCmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard;
|
||||||
return CkbMenuButton(
|
return CkbMenuButton(
|
||||||
value: ffiModel.viewOnly,
|
value: ffiModel.viewOnly,
|
||||||
onChanged: enabled
|
onChanged: enabled
|
||||||
@ -2037,71 +2168,3 @@ Widget _buildPointerTrackWidget(Widget child, FFI ffi) {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MultiMonitorMenu extends StatelessWidget {
|
|
||||||
final String id;
|
|
||||||
final FFI ffi;
|
|
||||||
|
|
||||||
const _MultiMonitorMenu({
|
|
||||||
Key? key,
|
|
||||||
required this.id,
|
|
||||||
required this.ffi,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final List<Widget> rowChildren = [];
|
|
||||||
final pi = ffi.ffiModel.pi;
|
|
||||||
|
|
||||||
for (int i = 0; i < pi.displays.length; i++) {
|
|
||||||
rowChildren.add(
|
|
||||||
Obx(() {
|
|
||||||
RxInt display = CurrentDisplayState.find(id);
|
|
||||||
return _IconMenuButton(
|
|
||||||
tooltip: "",
|
|
||||||
topLevel: false,
|
|
||||||
color: i == display.value
|
|
||||||
? _ToolbarTheme.blueColor
|
|
||||||
: Colors.grey[800]!,
|
|
||||||
hoverColor: i == display.value
|
|
||||||
? _ToolbarTheme.hoverBlueColor
|
|
||||||
: Colors.grey[850]!,
|
|
||||||
icon: Container(
|
|
||||||
alignment: AlignmentDirectional.center,
|
|
||||||
constraints:
|
|
||||||
const BoxConstraints(minHeight: _ToolbarTheme.height),
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
SvgPicture.asset(
|
|
||||||
"assets/screen.svg",
|
|
||||||
colorFilter:
|
|
||||||
ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
|
||||||
),
|
|
||||||
Obx(
|
|
||||||
() => Text(
|
|
||||||
(i + 1).toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: i == display.value
|
|
||||||
? _ToolbarTheme.blueColor
|
|
||||||
: Colors.grey[800]!,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
if (display.value != i) {
|
|
||||||
bind.sessionSwitchDisplay(sessionId: ffi.sessionId, value: i);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Row(children: rowChildren);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart';
|
|||||||
import 'package:flutter/material.dart' hide TabBarTheme;
|
import 'package:flutter/material.dart' hide TabBarTheme;
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
|
||||||
import 'package:flutter_hbb/main.dart';
|
import 'package:flutter_hbb/main.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
@ -176,6 +177,19 @@ class DesktopTabController {
|
|||||||
jumpTo(state.value.tabs.indexWhere((tab) => tab.key == key),
|
jumpTo(state.value.tabs.indexWhere((tab) => tab.key == key),
|
||||||
callOnSelected: callOnSelected);
|
callOnSelected: callOnSelected);
|
||||||
|
|
||||||
|
bool jumpToByKeyAndDisplay(String key, int display) {
|
||||||
|
for (int i = 0; i < state.value.tabs.length; i++) {
|
||||||
|
final tab = state.value.tabs[i];
|
||||||
|
if (tab.key == key) {
|
||||||
|
final ffi = (tab.page as RemotePage).ffi;
|
||||||
|
if (ffi.ffiModel.pi.currentDisplay == display) {
|
||||||
|
return jumpTo(i, callOnSelected: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void closeBy(String? key) {
|
void closeBy(String? key) {
|
||||||
if (!isDesktop) return;
|
if (!isDesktop) return;
|
||||||
assert(onRemoved != null);
|
assert(onRemoved != null);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -755,14 +756,14 @@ void showOptions(
|
|||||||
if (image != null) {
|
if (image != null) {
|
||||||
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
|
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
|
||||||
}
|
}
|
||||||
if (pi.displays.length > 1) {
|
if (pi.displays.length > 1 && pi.currentDisplay != kAllDisplayValue) {
|
||||||
final cur = pi.currentDisplay;
|
final cur = pi.currentDisplay;
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
for (var i = 0; i < pi.displays.length; ++i) {
|
for (var i = 0; i < pi.displays.length; ++i) {
|
||||||
children.add(InkWell(
|
children.add(InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (i == cur) return;
|
if (i == cur) return;
|
||||||
bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: i);
|
bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: Int32List.fromList([i]));
|
||||||
gFFI.dialogManager.dismissAll();
|
gFFI.dialogManager.dismissAll();
|
||||||
},
|
},
|
||||||
child: Ink(
|
child: Ink(
|
||||||
|
@ -4,25 +4,30 @@ import 'package:texture_rgba_renderer/texture_rgba_renderer.dart';
|
|||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import './platform_model.dart';
|
import './platform_model.dart';
|
||||||
|
|
||||||
|
final useTextureRender = bind.mainUseTextureRender();
|
||||||
|
|
||||||
class RenderTexture {
|
class RenderTexture {
|
||||||
final RxInt textureId = RxInt(-1);
|
final RxInt textureId = RxInt(-1);
|
||||||
int _textureKey = -1;
|
int _textureKey = -1;
|
||||||
|
int _display = 0;
|
||||||
SessionID? _sessionId;
|
SessionID? _sessionId;
|
||||||
static final useTextureRender = bind.mainUseTextureRender();
|
|
||||||
|
|
||||||
final textureRenderer = TextureRgbaRenderer();
|
final textureRenderer = TextureRgbaRenderer();
|
||||||
|
|
||||||
RenderTexture();
|
RenderTexture();
|
||||||
|
|
||||||
create(SessionID sessionId) {
|
int get display => _display;
|
||||||
|
|
||||||
|
create(int d, SessionID sessionId) {
|
||||||
if (useTextureRender) {
|
if (useTextureRender) {
|
||||||
|
_display = d;
|
||||||
_textureKey = bind.getNextTextureKey();
|
_textureKey = bind.getNextTextureKey();
|
||||||
_sessionId = sessionId;
|
_sessionId = sessionId;
|
||||||
|
|
||||||
textureRenderer.createTexture(_textureKey).then((id) async {
|
textureRenderer.createTexture(_textureKey).then((id) async {
|
||||||
if (id != -1) {
|
if (id != -1) {
|
||||||
final ptr = await textureRenderer.getTexturePtr(_textureKey);
|
final ptr = await textureRenderer.getTexturePtr(_textureKey);
|
||||||
platformFFI.registerTexture(sessionId, ptr);
|
platformFFI.registerTexture(sessionId, display, ptr);
|
||||||
textureId.value = id;
|
textureId.value = id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -32,7 +37,7 @@ class RenderTexture {
|
|||||||
destroy(bool unregisterTexture) async {
|
destroy(bool unregisterTexture) async {
|
||||||
if (useTextureRender && _textureKey != -1 && _sessionId != null) {
|
if (useTextureRender && _textureKey != -1 && _sessionId != null) {
|
||||||
if (unregisterTexture) {
|
if (unregisterTexture) {
|
||||||
platformFFI.registerTexture(_sessionId!, 0);
|
platformFFI.registerTexture(_sessionId!, display, 0);
|
||||||
}
|
}
|
||||||
await textureRenderer.closeTexture(_textureKey);
|
await textureRenderer.closeTexture(_textureKey);
|
||||||
_textureKey = -1;
|
_textureKey = -1;
|
||||||
|
@ -552,22 +552,22 @@ class InputModel {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
Offset setNearestEdge(double x, double y, Display d) {
|
Offset setNearestEdge(double x, double y, Rect rect) {
|
||||||
double left = x - d.x;
|
double left = x - rect.left;
|
||||||
double right = d.x + d.width - 1 - x;
|
double right = rect.right - 1 - x;
|
||||||
double top = y - d.y;
|
double top = y - rect.top;
|
||||||
double bottom = d.y + d.height - 1 - y;
|
double bottom = rect.bottom - 1 - y;
|
||||||
if (left < right && left < top && left < bottom) {
|
if (left < right && left < top && left < bottom) {
|
||||||
x = d.x;
|
x = rect.left;
|
||||||
}
|
}
|
||||||
if (right < left && right < top && right < bottom) {
|
if (right < left && right < top && right < bottom) {
|
||||||
x = d.x + d.width - 1;
|
x = rect.right - 1;
|
||||||
}
|
}
|
||||||
if (top < left && top < right && top < bottom) {
|
if (top < left && top < right && top < bottom) {
|
||||||
y = d.y;
|
y = rect.top;
|
||||||
}
|
}
|
||||||
if (bottom < left && bottom < right && bottom < top) {
|
if (bottom < left && bottom < right && bottom < top) {
|
||||||
y = d.y + d.height - 1;
|
y = rect.bottom - 1;
|
||||||
}
|
}
|
||||||
return Offset(x, y);
|
return Offset(x, y);
|
||||||
}
|
}
|
||||||
@ -711,9 +711,12 @@ class InputModel {
|
|||||||
final nearThr = 3;
|
final nearThr = 3;
|
||||||
var nearRight = (canvasModel.size.width - x) < nearThr;
|
var nearRight = (canvasModel.size.width - x) < nearThr;
|
||||||
var nearBottom = (canvasModel.size.height - y) < nearThr;
|
var nearBottom = (canvasModel.size.height - y) < nearThr;
|
||||||
final d = ffiModel.display;
|
final rect = ffiModel.rect;
|
||||||
final imageWidth = d.width * canvasModel.scale;
|
if (rect == null) {
|
||||||
final imageHeight = d.height * canvasModel.scale;
|
return null;
|
||||||
|
}
|
||||||
|
final imageWidth = rect.width * canvasModel.scale;
|
||||||
|
final imageHeight = rect.height * canvasModel.scale;
|
||||||
if (canvasModel.scrollStyle == ScrollStyle.scrollbar) {
|
if (canvasModel.scrollStyle == ScrollStyle.scrollbar) {
|
||||||
x += imageWidth * canvasModel.scrollX;
|
x += imageWidth * canvasModel.scrollX;
|
||||||
y += imageHeight * canvasModel.scrollY;
|
y += imageHeight * canvasModel.scrollY;
|
||||||
@ -741,11 +744,11 @@ class InputModel {
|
|||||||
y += step;
|
y += step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x += d.x;
|
x += rect.left;
|
||||||
y += d.y;
|
y += rect.top;
|
||||||
|
|
||||||
if (onExit) {
|
if (onExit) {
|
||||||
final pos = setNearestEdge(x, y, d);
|
final pos = setNearestEdge(x, y, rect);
|
||||||
x = pos.dx;
|
x = pos.dx;
|
||||||
y = pos.dy;
|
y = pos.dy;
|
||||||
}
|
}
|
||||||
@ -761,10 +764,10 @@ class InputModel {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int minX = d.x.toInt();
|
int minX = rect.left.toInt();
|
||||||
int maxX = (d.x + d.width).toInt() - 1;
|
int maxX = (rect.left + rect.width).toInt() - 1;
|
||||||
int minY = d.y.toInt();
|
int minY = rect.top.toInt();
|
||||||
int maxY = (d.y + d.height).toInt() - 1;
|
int maxY = (rect.top + rect.height).toInt() - 1;
|
||||||
evtX = trySetNearestRange(evtX, minX, maxX, 5);
|
evtX = trySetNearestRange(evtX, minX, maxX, 5);
|
||||||
evtY = trySetNearestRange(evtY, minY, maxY, 5);
|
evtY = trySetNearestRange(evtY, minY, maxY, 5);
|
||||||
if (kind == kPointerEventKindMouse) {
|
if (kind == kPointerEventKindMouse) {
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'dart:typed_data';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
@ -86,7 +87,7 @@ class CachedPeerData {
|
|||||||
class FfiModel with ChangeNotifier {
|
class FfiModel with ChangeNotifier {
|
||||||
CachedPeerData cachedPeerData = CachedPeerData();
|
CachedPeerData cachedPeerData = CachedPeerData();
|
||||||
PeerInfo _pi = PeerInfo();
|
PeerInfo _pi = PeerInfo();
|
||||||
Display _display = Display();
|
Rect? _rect;
|
||||||
|
|
||||||
var _inputBlocked = false;
|
var _inputBlocked = false;
|
||||||
final _permissions = <String, bool>{};
|
final _permissions = <String, bool>{};
|
||||||
@ -103,9 +104,15 @@ class FfiModel with ChangeNotifier {
|
|||||||
Timer? waitForImageTimer;
|
Timer? waitForImageTimer;
|
||||||
RxBool waitForFirstImage = true.obs;
|
RxBool waitForFirstImage = true.obs;
|
||||||
|
|
||||||
Map<String, bool> get permissions => _permissions;
|
Rect? get rect => _rect;
|
||||||
|
bool get isOriginalResolutionSet =>
|
||||||
|
_pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolutionSet ?? false;
|
||||||
|
bool get isVirtualDisplayResolution =>
|
||||||
|
_pi.tryGetDisplayIfNotAllDisplay()?.isVirtualDisplayResolution ?? false;
|
||||||
|
bool get isOriginalResolution =>
|
||||||
|
_pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolution ?? false;
|
||||||
|
|
||||||
Display get display => _display;
|
Map<String, bool> get permissions => _permissions;
|
||||||
|
|
||||||
bool? get secure => _secure;
|
bool? get secure => _secure;
|
||||||
|
|
||||||
@ -130,6 +137,24 @@ class FfiModel with ChangeNotifier {
|
|||||||
sessionId = parent.target!.sessionId;
|
sessionId = parent.target!.sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rect? displaysRect() {
|
||||||
|
final displays = _pi.getCurDisplays();
|
||||||
|
if (displays.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
double l = displays[0].x;
|
||||||
|
double t = displays[0].y;
|
||||||
|
double r = displays[0].x + displays[0].width;
|
||||||
|
double b = displays[0].y + displays[0].height;
|
||||||
|
for (var display in displays.sublist(1)) {
|
||||||
|
l = min(l, display.x);
|
||||||
|
t = min(t, display.y);
|
||||||
|
r = max(r, display.x + display.width);
|
||||||
|
b = max(b, display.y + display.height);
|
||||||
|
}
|
||||||
|
return Rect.fromLTRB(l, t, r, b);
|
||||||
|
}
|
||||||
|
|
||||||
toggleTouchMode() {
|
toggleTouchMode() {
|
||||||
if (!isPeerAndroid) {
|
if (!isPeerAndroid) {
|
||||||
_touchMode = !_touchMode;
|
_touchMode = !_touchMode;
|
||||||
@ -154,7 +179,6 @@ class FfiModel with ChangeNotifier {
|
|||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
_pi = PeerInfo();
|
_pi = PeerInfo();
|
||||||
_display = Display();
|
|
||||||
_secure = null;
|
_secure = null;
|
||||||
_direct = null;
|
_direct = null;
|
||||||
_inputBlocked = false;
|
_inputBlocked = false;
|
||||||
@ -207,8 +231,10 @@ class FfiModel with ChangeNotifier {
|
|||||||
updateLastCursorId(element);
|
updateLastCursorId(element);
|
||||||
await handleCursorData(element);
|
await handleCursorData(element);
|
||||||
}
|
}
|
||||||
updateLastCursorId(data.lastCursorId);
|
if (data.lastCursorId.isNotEmpty) {
|
||||||
handleCursorId(data.lastCursorId);
|
updateLastCursorId(data.lastCursorId);
|
||||||
|
handleCursorId(data.lastCursorId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: why called by two position
|
// todo: why called by two position
|
||||||
@ -220,11 +246,12 @@ class FfiModel with ChangeNotifier {
|
|||||||
} else if (name == 'peer_info') {
|
} else if (name == 'peer_info') {
|
||||||
handlePeerInfo(evt, peerId);
|
handlePeerInfo(evt, peerId);
|
||||||
} else if (name == 'sync_peer_info') {
|
} else if (name == 'sync_peer_info') {
|
||||||
handleSyncPeerInfo(evt, sessionId);
|
handleSyncPeerInfo(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');
|
||||||
} else if (name == 'switch_display') {
|
} else if (name == 'switch_display') {
|
||||||
|
// switch display is kept for backward compatibility
|
||||||
handleSwitchDisplay(evt, sessionId, peerId);
|
handleSwitchDisplay(evt, sessionId, peerId);
|
||||||
} else if (name == 'cursor_data') {
|
} else if (name == 'cursor_data') {
|
||||||
updateLastCursorId(evt);
|
updateLastCursorId(evt);
|
||||||
@ -279,7 +306,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
await bind.sessionSwitchSides(sessionId: sessionId);
|
await bind.sessionSwitchSides(sessionId: sessionId);
|
||||||
closeConnection(id: peer_id);
|
closeConnection(id: peer_id);
|
||||||
} else if (name == 'portable_service_running') {
|
} else if (name == 'portable_service_running') {
|
||||||
parent.target?.elevationModel.onPortableServiceRunning(evt);
|
_handlePortableServiceRunning(peerId, evt);
|
||||||
} else if (name == 'on_url_scheme_received') {
|
} else if (name == 'on_url_scheme_received') {
|
||||||
// currently comes from "_url" ipc of mac and dbus of linux
|
// currently comes from "_url" ipc of mac and dbus of linux
|
||||||
onUrlSchemeReceived(evt);
|
onUrlSchemeReceived(evt);
|
||||||
@ -354,20 +381,65 @@ class FfiModel with ChangeNotifier {
|
|||||||
platformFFI.setEventCallback(startEventListener(sessionId, peerId));
|
platformFFI.setEventCallback(startEventListener(sessionId, peerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateCurDisplay(SessionID sessionId, Display newDisplay) {
|
_handlePortableServiceRunning(String peerId, Map<String, dynamic> evt) {
|
||||||
if (newDisplay != _display) {
|
final running = evt['running'] == 'true';
|
||||||
if (newDisplay.x != _display.x || newDisplay.y != _display.y) {
|
parent.target?.elevationModel.onPortableServiceRunning(running);
|
||||||
parent.target?.cursorModel
|
if (running) {
|
||||||
.updateDisplayOrigin(newDisplay.x, newDisplay.y);
|
if (pi.primaryDisplay != kInvalidDisplayIndex) {
|
||||||
|
if (pi.currentDisplay != pi.primaryDisplay) {
|
||||||
|
// Notify to switch display
|
||||||
|
msgBox(sessionId, 'custom-nook-nocancel-hasclose-info', 'Prompt',
|
||||||
|
'elevated_switch_display_msg', '', parent.target!.dialogManager);
|
||||||
|
bind.sessionSwitchDisplay(
|
||||||
|
sessionId: sessionId,
|
||||||
|
value: Int32List.fromList([pi.primaryDisplay]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_display = newDisplay;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAliasChanged(Map<String, dynamic> evt) {
|
||||||
|
if (!isDesktop) return;
|
||||||
|
final String peerId = evt['id'];
|
||||||
|
final String alias = evt['alias'];
|
||||||
|
String label = getDesktopTabLabel(peerId, alias);
|
||||||
|
final rxTabLabel = PeerStringOption.find(evt['id'], 'tabLabel');
|
||||||
|
if (rxTabLabel.value != label) {
|
||||||
|
rxTabLabel.value = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCurDisplay(SessionID sessionId) {
|
||||||
|
final newRect = displaysRect();
|
||||||
|
if (newRect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newRect != _rect) {
|
||||||
|
_rect = newRect;
|
||||||
|
if (newRect.left != _rect?.left || newRect.top != _rect?.top) {
|
||||||
|
parent.target?.cursorModel
|
||||||
|
.updateDisplayOrigin(newRect.left, newRect.top);
|
||||||
|
}
|
||||||
|
parent.target?.canvasModel.updateViewStyle();
|
||||||
_updateSessionWidthHeight(sessionId);
|
_updateSessionWidthHeight(sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwitchDisplay(
|
handleSwitchDisplay(
|
||||||
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
|
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
|
||||||
_pi.currentDisplay = 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) {
|
||||||
|
_pi.currentDisplay = curDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
var newDisplay = Display();
|
var newDisplay = Display();
|
||||||
newDisplay.x = double.tryParse(evt['x']) ?? newDisplay.x;
|
newDisplay.x = double.tryParse(evt['x']) ?? newDisplay.x;
|
||||||
newDisplay.y = double.tryParse(evt['y']) ?? newDisplay.y;
|
newDisplay.y = double.tryParse(evt['y']) ?? newDisplay.y;
|
||||||
@ -378,11 +450,11 @@ class FfiModel with ChangeNotifier {
|
|||||||
int.tryParse(evt['original_width']) ?? kInvalidResolutionValue;
|
int.tryParse(evt['original_width']) ?? kInvalidResolutionValue;
|
||||||
newDisplay.originalHeight =
|
newDisplay.originalHeight =
|
||||||
int.tryParse(evt['original_height']) ?? kInvalidResolutionValue;
|
int.tryParse(evt['original_height']) ?? kInvalidResolutionValue;
|
||||||
|
_pi.displays[curDisplay] = newDisplay;
|
||||||
|
|
||||||
_updateCurDisplay(sessionId, newDisplay);
|
updateCurDisplay(sessionId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
CurrentDisplayState.find(peerId).value = curDisplay;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@ -522,13 +594,30 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_updateSessionWidthHeight(SessionID sessionId) {
|
_updateSessionWidthHeight(SessionID sessionId) {
|
||||||
parent.target?.canvasModel.updateViewStyle();
|
if (_rect == null) return;
|
||||||
if (display.width <= 0 || display.height <= 0) {
|
if (_rect!.width <= 0 || _rect!.height <= 0) {
|
||||||
debugPrintStack(
|
debugPrintStack(
|
||||||
label: 'invalid display size (${display.width},${display.height})');
|
label: 'invalid display size (${_rect!.width},${_rect!.height})');
|
||||||
} else {
|
} else {
|
||||||
bind.sessionSetSize(
|
final displays = _pi.getCurDisplays();
|
||||||
sessionId: sessionId, width: display.width, height: display.height);
|
if (displays.length == 1) {
|
||||||
|
bind.sessionSetSize(
|
||||||
|
sessionId: sessionId,
|
||||||
|
display:
|
||||||
|
pi.currentDisplay == kAllDisplayValue ? 0 : pi.currentDisplay,
|
||||||
|
width: _rect!.width.toInt(),
|
||||||
|
height: _rect!.height.toInt(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < displays.length; ++i) {
|
||||||
|
bind.sessionSetSize(
|
||||||
|
sessionId: sessionId,
|
||||||
|
display: i,
|
||||||
|
width: displays[i].width.toInt(),
|
||||||
|
height: displays[i].height.toInt(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,11 +630,20 @@ class FfiModel with ChangeNotifier {
|
|||||||
|
|
||||||
parent.target?.dialogManager.dismissAll();
|
parent.target?.dialogManager.dismissAll();
|
||||||
_pi.version = evt['version'];
|
_pi.version = evt['version'];
|
||||||
|
_pi.isSupportMultiUiSession =
|
||||||
|
bind.isSupportMultiUiSession(version: _pi.version);
|
||||||
_pi.username = evt['username'];
|
_pi.username = evt['username'];
|
||||||
_pi.hostname = evt['hostname'];
|
_pi.hostname = evt['hostname'];
|
||||||
_pi.platform = evt['platform'];
|
_pi.platform = evt['platform'];
|
||||||
_pi.sasEnabled = evt['sas_enabled'] == 'true';
|
_pi.sasEnabled = evt['sas_enabled'] == 'true';
|
||||||
_pi.currentDisplay = int.parse(evt['current_display']);
|
final currentDisplay = int.parse(evt['current_display']);
|
||||||
|
if (_pi.primaryDisplay == kInvalidDisplayIndex) {
|
||||||
|
_pi.primaryDisplay = currentDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind.peerGetDefaultSessionsCount(id: peerId) <= 1) {
|
||||||
|
_pi.currentDisplay = currentDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
||||||
@ -569,10 +667,10 @@ class FfiModel with ChangeNotifier {
|
|||||||
for (int i = 0; i < displays.length; ++i) {
|
for (int i = 0; i < displays.length; ++i) {
|
||||||
_pi.displays.add(evtToDisplay(displays[i]));
|
_pi.displays.add(evtToDisplay(displays[i]));
|
||||||
}
|
}
|
||||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
_pi.displaysCount.value = _pi.displays.length;
|
||||||
if (_pi.currentDisplay < _pi.displays.length) {
|
if (_pi.currentDisplay < _pi.displays.length) {
|
||||||
_display = _pi.displays[_pi.currentDisplay];
|
// now replaced to _updateCurDisplay
|
||||||
_updateSessionWidthHeight(sessionId);
|
updateCurDisplay(sessionId);
|
||||||
}
|
}
|
||||||
if (displays.isNotEmpty) {
|
if (displays.isNotEmpty) {
|
||||||
_reconnects = 1;
|
_reconnects = 1;
|
||||||
@ -590,12 +688,12 @@ class FfiModel with ChangeNotifier {
|
|||||||
sessionId: sessionId, arg: 'view-only'));
|
sessionId: sessionId, arg: 'view-only'));
|
||||||
}
|
}
|
||||||
if (connType == ConnType.defaultConn) {
|
if (connType == ConnType.defaultConn) {
|
||||||
final platform_additions = evt['platform_additions'];
|
final platformDdditions = evt['platform_additions'];
|
||||||
if (platform_additions != null && platform_additions != '') {
|
if (platformDdditions != null && platformDdditions != '') {
|
||||||
try {
|
try {
|
||||||
_pi.platform_additions = json.decode(platform_additions);
|
_pi.platformDdditions = json.decode(platformDdditions);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Failed to decode platform_additions $e');
|
debugPrint('Failed to decode platformDdditions $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -670,7 +768,8 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handle the peer info synchronization event based on [evt].
|
/// Handle the peer info synchronization event based on [evt].
|
||||||
handleSyncPeerInfo(Map<String, dynamic> evt, SessionID sessionId) async {
|
handleSyncPeerInfo(
|
||||||
|
Map<String, dynamic> evt, SessionID sessionId, String peerId) async {
|
||||||
if (evt['displays'] != null) {
|
if (evt['displays'] != null) {
|
||||||
cachedPeerData.peerInfo['displays'] = evt['displays'];
|
cachedPeerData.peerInfo['displays'] = evt['displays'];
|
||||||
List<dynamic> displays = json.decode(evt['displays']);
|
List<dynamic> displays = json.decode(evt['displays']);
|
||||||
@ -679,14 +778,54 @@ class FfiModel with ChangeNotifier {
|
|||||||
newDisplays.add(evtToDisplay(displays[i]));
|
newDisplays.add(evtToDisplay(displays[i]));
|
||||||
}
|
}
|
||||||
_pi.displays = newDisplays;
|
_pi.displays = newDisplays;
|
||||||
stateGlobal.displaysCount.value = _pi.displays.length;
|
_pi.displaysCount.value = _pi.displays.length;
|
||||||
if (_pi.currentDisplay >= 0 && _pi.currentDisplay < _pi.displays.length) {
|
if (_pi.currentDisplay == kAllDisplayValue) {
|
||||||
_updateCurDisplay(sessionId, _pi.displays[_pi.currentDisplay]);
|
updateCurDisplay(sessionId);
|
||||||
|
// to-do: What if the displays are changed?
|
||||||
|
} else {
|
||||||
|
if (_pi.currentDisplay >= 0 &&
|
||||||
|
_pi.currentDisplay < _pi.displays.length) {
|
||||||
|
updateCurDisplay(sessionId);
|
||||||
|
} else {
|
||||||
|
if (_pi.displays.isNotEmpty) {
|
||||||
|
// Notify to switch display
|
||||||
|
msgBox(sessionId, 'custom-nook-nocancel-hasclose-info', 'Prompt',
|
||||||
|
'display_is_plugged_out_msg', '', parent.target!.dialogManager);
|
||||||
|
final newDisplay = pi.primaryDisplay == kInvalidDisplayIndex
|
||||||
|
? 0
|
||||||
|
: pi.primaryDisplay;
|
||||||
|
final displays = newDisplay;
|
||||||
|
bind.sessionSwitchDisplay(
|
||||||
|
sessionId: sessionId, value: Int32List.fromList([displays]));
|
||||||
|
|
||||||
|
if (_pi.isSupportMultiUiSession) {
|
||||||
|
// If the peer supports multi-ui-session, no switch display message will be send back.
|
||||||
|
// We need to update the display manually.
|
||||||
|
switchToNewDisplay(newDisplay, sessionId, peerId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msgBox(sessionId, 'nocancel-error', 'Prompt', 'No Displays', '',
|
||||||
|
parent.target!.dialogManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Directly switch to the new display without waiting for the response.
|
||||||
|
switchToNewDisplay(int display, SessionID sessionId, String peerId) {
|
||||||
|
// no need to wait for the response
|
||||||
|
pi.currentDisplay = display;
|
||||||
|
updateCurDisplay(sessionId);
|
||||||
|
try {
|
||||||
|
CurrentDisplayState.find(peerId).value = display;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
parent.target?.recordingModel.onSwitchDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
|
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
|
||||||
_inputBlocked = evt['input_state'] == 'on';
|
_inputBlocked = evt['input_state'] == 'on';
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -709,7 +848,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setViewOnly(String id, bool value) {
|
void setViewOnly(String id, bool value) {
|
||||||
if (version_cmp(_pi.version, '1.2.0') < 0) return;
|
if (versionCmp(_pi.version, '1.2.0') < 0) return;
|
||||||
// tmp fix for https://github.com/rustdesk/rustdesk/pull/3706#issuecomment-1481242389
|
// tmp fix for https://github.com/rustdesk/rustdesk/pull/3706#issuecomment-1481242389
|
||||||
// because below rx not used in mobile version, so not initialized, below code will cause crash
|
// because below rx not used in mobile version, so not initialized, below code will cause crash
|
||||||
// current our flutter code quality is fucking shit now. !!!!!!!!!!!!!!!!
|
// current our flutter code quality is fucking shit now. !!!!!!!!!!!!!!!!
|
||||||
@ -749,16 +888,16 @@ class ImageModel with ChangeNotifier {
|
|||||||
|
|
||||||
addCallbackOnFirstImage(Function(String) cb) => callbacksOnFirstImage.add(cb);
|
addCallbackOnFirstImage(Function(String) cb) => callbacksOnFirstImage.add(cb);
|
||||||
|
|
||||||
onRgba(Uint8List rgba) {
|
onRgba(int display, Uint8List rgba) {
|
||||||
final pid = parent.target?.id;
|
final pid = parent.target?.id;
|
||||||
img.decodeImageFromPixels(
|
img.decodeImageFromPixels(
|
||||||
rgba,
|
rgba,
|
||||||
parent.target?.ffiModel.display.width ?? 0,
|
parent.target?.ffiModel.rect?.width.toInt() ?? 0,
|
||||||
parent.target?.ffiModel.display.height ?? 0,
|
parent.target?.ffiModel.rect?.height.toInt() ?? 0,
|
||||||
isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888,
|
isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888,
|
||||||
onPixelsCopied: () {
|
onPixelsCopied: () {
|
||||||
// Unlock the rgba memory from rust codes.
|
// Unlock the rgba memory from rust codes.
|
||||||
platformFFI.nextRgba(sessionId);
|
platformFFI.nextRgba(sessionId, display);
|
||||||
}).then((image) {
|
}).then((image) {
|
||||||
if (parent.target?.id != pid) return;
|
if (parent.target?.id != pid) return;
|
||||||
try {
|
try {
|
||||||
@ -1017,20 +1156,20 @@ class CanvasModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool get cursorEmbedded =>
|
bool get cursorEmbedded =>
|
||||||
parent.target?.ffiModel.display.cursorEmbedded ?? false;
|
parent.target?.ffiModel._pi.cursorEmbedded ?? false;
|
||||||
|
|
||||||
int getDisplayWidth() {
|
int getDisplayWidth() {
|
||||||
final defaultWidth = (isDesktop || isWebDesktop)
|
final defaultWidth = (isDesktop || isWebDesktop)
|
||||||
? kDesktopDefaultDisplayWidth
|
? kDesktopDefaultDisplayWidth
|
||||||
: kMobileDefaultDisplayWidth;
|
: kMobileDefaultDisplayWidth;
|
||||||
return parent.target?.ffiModel.display.width ?? defaultWidth;
|
return parent.target?.ffiModel.rect?.width.toInt() ?? defaultWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getDisplayHeight() {
|
int getDisplayHeight() {
|
||||||
final defaultHeight = (isDesktop || isWebDesktop)
|
final defaultHeight = (isDesktop || isWebDesktop)
|
||||||
? kDesktopDefaultDisplayHeight
|
? kDesktopDefaultDisplayHeight
|
||||||
: kMobileDefaultDisplayHeight;
|
: kMobileDefaultDisplayHeight;
|
||||||
return parent.target?.ffiModel.display.height ?? defaultHeight;
|
return parent.target?.ffiModel.rect?.height.toInt() ?? defaultHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
static double get windowBorderWidth => stateGlobal.windowBorderWidth.value;
|
static double get windowBorderWidth => stateGlobal.windowBorderWidth.value;
|
||||||
@ -1619,7 +1758,27 @@ class QualityMonitorModel with ChangeNotifier {
|
|||||||
updateQualityStatus(Map<String, dynamic> evt) {
|
updateQualityStatus(Map<String, dynamic> evt) {
|
||||||
try {
|
try {
|
||||||
if ((evt['speed'] as String).isNotEmpty) _data.speed = evt['speed'];
|
if ((evt['speed'] as String).isNotEmpty) _data.speed = evt['speed'];
|
||||||
if ((evt['fps'] as String).isNotEmpty) _data.fps = evt['fps'];
|
if ((evt['fps'] as String).isNotEmpty) {
|
||||||
|
final fps = jsonDecode(evt['fps']) as Map<String, dynamic>;
|
||||||
|
final pi = parent.target?.ffiModel.pi;
|
||||||
|
if (pi != null) {
|
||||||
|
final currentDisplay = pi.currentDisplay;
|
||||||
|
if (currentDisplay != kAllDisplayValue) {
|
||||||
|
final fps2 = fps[currentDisplay.toString()];
|
||||||
|
if (fps2 != null) {
|
||||||
|
_data.fps = fps2.toString();
|
||||||
|
}
|
||||||
|
} else if (fps.isNotEmpty) {
|
||||||
|
final fpsList = [];
|
||||||
|
for (var i = 0; i < pi.displays.length; i++) {
|
||||||
|
fpsList.add((fps[i.toString()] ?? 0).toString());
|
||||||
|
}
|
||||||
|
_data.fps = fpsList.join(' ');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_data.fps = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
if ((evt['delay'] as String).isNotEmpty) _data.delay = evt['delay'];
|
if ((evt['delay'] as String).isNotEmpty) _data.delay = evt['delay'];
|
||||||
if ((evt['target_bitrate'] as String).isNotEmpty) {
|
if ((evt['target_bitrate'] as String).isNotEmpty) {
|
||||||
_data.targetBitrate = evt['target_bitrate'];
|
_data.targetBitrate = evt['target_bitrate'];
|
||||||
@ -1646,8 +1805,15 @@ 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;
|
||||||
bind.sessionRecordScreen(
|
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
||||||
sessionId: sessionId, start: true, width: width, height: height);
|
if (currentDisplay != kAllDisplayValue) {
|
||||||
|
bind.sessionRecordScreen(
|
||||||
|
sessionId: sessionId,
|
||||||
|
start: true,
|
||||||
|
display: currentDisplay!,
|
||||||
|
width: width,
|
||||||
|
height: height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() async {
|
toggle() async {
|
||||||
@ -1658,10 +1824,20 @@ class RecordingModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
await bind.sessionRecordStatus(sessionId: sessionId, status: _start);
|
await bind.sessionRecordStatus(sessionId: sessionId, status: _start);
|
||||||
if (_start) {
|
if (_start) {
|
||||||
bind.sessionRefresh(sessionId: sessionId);
|
final pi = parent.target?.ffiModel.pi;
|
||||||
|
if (pi != null) {
|
||||||
|
sessionRefreshVideo(sessionId, pi);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bind.sessionRecordScreen(
|
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
||||||
sessionId: sessionId, start: false, width: 0, height: 0);
|
if (currentDisplay != kAllDisplayValue) {
|
||||||
|
bind.sessionRecordScreen(
|
||||||
|
sessionId: sessionId,
|
||||||
|
start: false,
|
||||||
|
display: currentDisplay!,
|
||||||
|
width: 0,
|
||||||
|
height: 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1670,8 +1846,15 @@ class RecordingModel with ChangeNotifier {
|
|||||||
final sessionId = parent.target?.sessionId;
|
final sessionId = parent.target?.sessionId;
|
||||||
if (sessionId == null) return;
|
if (sessionId == null) return;
|
||||||
_start = false;
|
_start = false;
|
||||||
bind.sessionRecordScreen(
|
final currentDisplay = parent.target?.ffiModel.pi.currentDisplay;
|
||||||
sessionId: sessionId, start: false, width: 0, height: 0);
|
if (currentDisplay != kAllDisplayValue) {
|
||||||
|
bind.sessionRecordScreen(
|
||||||
|
sessionId: sessionId,
|
||||||
|
start: false,
|
||||||
|
display: currentDisplay!,
|
||||||
|
width: 0,
|
||||||
|
height: 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1686,9 +1869,7 @@ class ElevationModel with ChangeNotifier {
|
|||||||
_running = false;
|
_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPortableServiceRunning(Map<String, dynamic> evt) {
|
onPortableServiceRunning(bool running) => _running = running;
|
||||||
_running = evt['running'] == 'true';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ConnType { defaultConn, fileTransfer, portForward, rdp }
|
enum ConnType { defaultConn, fileTransfer, portForward, rdp }
|
||||||
@ -1751,14 +1932,18 @@ class FFI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward].
|
/// Start with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward].
|
||||||
void start(String id,
|
void start(
|
||||||
{bool isFileTransfer = false,
|
String id, {
|
||||||
bool isPortForward = false,
|
bool isFileTransfer = false,
|
||||||
bool isRdp = false,
|
bool isPortForward = false,
|
||||||
String? switchUuid,
|
bool isRdp = false,
|
||||||
String? password,
|
String? switchUuid,
|
||||||
bool? forceRelay,
|
String? password,
|
||||||
int? tabWindowId}) {
|
bool? forceRelay,
|
||||||
|
int? tabWindowId,
|
||||||
|
int? display,
|
||||||
|
List<int>? displays,
|
||||||
|
}) {
|
||||||
closed = false;
|
closed = false;
|
||||||
auditNote = '';
|
auditNote = '';
|
||||||
if (isMobile) mobileReset();
|
if (isMobile) mobileReset();
|
||||||
@ -1788,11 +1973,34 @@ class FFI {
|
|||||||
forceRelay: forceRelay ?? false,
|
forceRelay: forceRelay ?? false,
|
||||||
password: password ?? '',
|
password: password ?? '',
|
||||||
);
|
);
|
||||||
|
} else if (display != null) {
|
||||||
|
if (displays == null) {
|
||||||
|
debugPrint(
|
||||||
|
'Unreachable, failed to add existed session to $id, the displays is null while display is $display');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final addRes = bind.sessionAddExistedSync(id: id, sessionId: sessionId);
|
||||||
|
if (addRes != '') {
|
||||||
|
debugPrint(
|
||||||
|
'Unreachable, failed to add existed session to $id, $addRes');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bind.sessionTryAddDisplay(
|
||||||
|
sessionId: sessionId, displays: Int32List.fromList(displays));
|
||||||
|
ffiModel.pi.currentDisplay = display;
|
||||||
}
|
}
|
||||||
final stream = bind.sessionStart(sessionId: sessionId, id: id);
|
final stream = bind.sessionStart(sessionId: sessionId, id: id);
|
||||||
final cb = ffiModel.startEventListener(sessionId, id);
|
final cb = ffiModel.startEventListener(sessionId, id);
|
||||||
final useTextureRender = bind.mainUseTextureRender();
|
final useTextureRender = bind.mainUseTextureRender();
|
||||||
|
|
||||||
|
// Force refresh displays.
|
||||||
|
// The controlled side may not refresh the image when the (peer,display) is already subscribed.
|
||||||
|
if (displays != null) {
|
||||||
|
for (final display in displays) {
|
||||||
|
bind.sessionRefresh(sessionId: sessionId, display: display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final SimpleWrapper<bool> isToNewWindowNotified = SimpleWrapper(false);
|
final SimpleWrapper<bool> isToNewWindowNotified = SimpleWrapper(false);
|
||||||
// Preserved for the rgba data.
|
// Preserved for the rgba data.
|
||||||
stream.listen((message) {
|
stream.listen((message) {
|
||||||
@ -1801,8 +2009,9 @@ class FFI {
|
|||||||
// Session is read to be moved to a new window.
|
// Session is read to be moved to a new window.
|
||||||
// Get the cached data and handle the cached data.
|
// Get the cached data and handle the cached data.
|
||||||
Future.delayed(Duration.zero, () async {
|
Future.delayed(Duration.zero, () async {
|
||||||
|
final args = jsonEncode({'id': id, 'close': display == null});
|
||||||
final cachedData = await DesktopMultiWindow.invokeMethod(
|
final cachedData = await DesktopMultiWindow.invokeMethod(
|
||||||
tabWindowId, kWindowEventGetCachedSessionData, id);
|
tabWindowId, kWindowEventGetCachedSessionData, args);
|
||||||
if (cachedData == null) {
|
if (cachedData == null) {
|
||||||
// unreachable
|
// unreachable
|
||||||
debugPrint('Unreachable, the cached data is empty.');
|
debugPrint('Unreachable, the cached data is empty.');
|
||||||
@ -1814,7 +2023,7 @@ class FFI {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ffiModel.handleCachedPeerData(data, id);
|
await ffiModel.handleCachedPeerData(data, id);
|
||||||
await bind.sessionRefresh(sessionId: sessionId);
|
await sessionRefreshVideo(sessionId, ffiModel.pi);
|
||||||
});
|
});
|
||||||
isToNewWindowNotified.value = true;
|
isToNewWindowNotified.value = true;
|
||||||
}
|
}
|
||||||
@ -1836,18 +2045,19 @@ class FFI {
|
|||||||
await cb(event);
|
await cb(event);
|
||||||
}
|
}
|
||||||
} else if (message is EventToUI_Rgba) {
|
} else if (message is EventToUI_Rgba) {
|
||||||
|
final display = message.field0;
|
||||||
if (useTextureRender) {
|
if (useTextureRender) {
|
||||||
onEvent2UIRgba();
|
onEvent2UIRgba();
|
||||||
} else {
|
} else {
|
||||||
// Fetch the image buffer from rust codes.
|
// Fetch the image buffer from rust codes.
|
||||||
final sz = platformFFI.getRgbaSize(sessionId);
|
final sz = platformFFI.getRgbaSize(sessionId, display);
|
||||||
if (sz == 0) {
|
if (sz == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final rgba = platformFFI.getRgba(sessionId, sz);
|
final rgba = platformFFI.getRgba(sessionId, display, sz);
|
||||||
if (rgba != null) {
|
if (rgba != null) {
|
||||||
onEvent2UIRgba();
|
onEvent2UIRgba();
|
||||||
imageModel.onRgba(rgba);
|
imageModel.onRgba(display, rgba);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1979,22 +2189,72 @@ class Features {
|
|||||||
bool privacyMode = false;
|
bool privacyMode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const kInvalidDisplayIndex = -1;
|
||||||
|
|
||||||
class PeerInfo with ChangeNotifier {
|
class PeerInfo with ChangeNotifier {
|
||||||
String version = '';
|
String version = '';
|
||||||
String username = '';
|
String username = '';
|
||||||
String hostname = '';
|
String hostname = '';
|
||||||
String platform = '';
|
String platform = '';
|
||||||
bool sasEnabled = false;
|
bool sasEnabled = false;
|
||||||
|
bool isSupportMultiUiSession = false;
|
||||||
int currentDisplay = 0;
|
int currentDisplay = 0;
|
||||||
|
int primaryDisplay = kInvalidDisplayIndex;
|
||||||
List<Display> displays = [];
|
List<Display> displays = [];
|
||||||
Features features = Features();
|
Features features = Features();
|
||||||
List<Resolution> resolutions = [];
|
List<Resolution> resolutions = [];
|
||||||
Map<String, dynamic> platform_additions = {};
|
Map<String, dynamic> platformDdditions = {};
|
||||||
|
|
||||||
|
RxInt displaysCount = 0.obs;
|
||||||
RxBool isSet = false.obs;
|
RxBool isSet = false.obs;
|
||||||
|
|
||||||
bool get is_wayland => platform_additions['is_wayland'] == true;
|
bool get isWayland => platformDdditions['is_wayland'] == true;
|
||||||
bool get is_headless => platform_additions['headless'] == true;
|
bool get isHeadless => platformDdditions['headless'] == true;
|
||||||
|
|
||||||
|
bool get isSupportMultiDisplay => isDesktop && isSupportMultiUiSession;
|
||||||
|
|
||||||
|
bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false;
|
||||||
|
|
||||||
|
Display? tryGetDisplay() {
|
||||||
|
if (displays.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (currentDisplay == kAllDisplayValue) {
|
||||||
|
return displays[0];
|
||||||
|
} else {
|
||||||
|
if (currentDisplay > 0 && currentDisplay < displays.length) {
|
||||||
|
return displays[currentDisplay];
|
||||||
|
} else {
|
||||||
|
return displays[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Display? tryGetDisplayIfNotAllDisplay() {
|
||||||
|
if (displays.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (currentDisplay == kAllDisplayValue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (currentDisplay > 0 && currentDisplay < displays.length) {
|
||||||
|
return displays[currentDisplay];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Display> getCurDisplays() {
|
||||||
|
if (currentDisplay == kAllDisplayValue) {
|
||||||
|
return displays;
|
||||||
|
} else {
|
||||||
|
if (currentDisplay >= 0 && currentDisplay < displays.length) {
|
||||||
|
return [displays[currentDisplay]];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasKey = 'canvas';
|
const canvasKey = 'canvas';
|
||||||
@ -2038,8 +2298,8 @@ Future<void> initializeCursorAndCanvas(FFI ffi) async {
|
|||||||
currentDisplay = p['currentDisplay'];
|
currentDisplay = p['currentDisplay'];
|
||||||
}
|
}
|
||||||
if (p == null || currentDisplay != ffi.ffiModel.pi.currentDisplay) {
|
if (p == null || currentDisplay != ffi.ffiModel.pi.currentDisplay) {
|
||||||
ffi.cursorModel
|
ffi.cursorModel.updateDisplayOrigin(
|
||||||
.updateDisplayOrigin(ffi.ffiModel.display.x, ffi.ffiModel.display.y);
|
ffi.ffiModel.rect?.left ?? 0, ffi.ffiModel.rect?.top ?? 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double xCursor = p['xCursor'];
|
double xCursor = p['xCursor'];
|
||||||
@ -2047,8 +2307,8 @@ Future<void> initializeCursorAndCanvas(FFI ffi) async {
|
|||||||
double xCanvas = p['xCanvas'];
|
double xCanvas = p['xCanvas'];
|
||||||
double yCanvas = p['yCanvas'];
|
double yCanvas = p['yCanvas'];
|
||||||
double scale = p['scale'];
|
double scale = p['scale'];
|
||||||
ffi.cursorModel.updateDisplayOriginWithCursor(
|
ffi.cursorModel.updateDisplayOriginWithCursor(ffi.ffiModel.rect?.left ?? 0,
|
||||||
ffi.ffiModel.display.x, ffi.ffiModel.display.y, xCursor, yCursor);
|
ffi.ffiModel.rect?.top ?? 0, xCursor, yCursor);
|
||||||
ffi.canvasModel.update(xCanvas, yCanvas, scale);
|
ffi.canvasModel.update(xCanvas, yCanvas, scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,8 @@ class RgbaFrame extends Struct {
|
|||||||
external Pointer<Uint8> data;
|
external Pointer<Uint8> data;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>);
|
typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>, int);
|
||||||
|
typedef F3Dart = Pointer<Uint8> Function(Pointer<Utf8>, Int32);
|
||||||
typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt);
|
typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt);
|
||||||
|
|
||||||
/// FFI wrapper around the native Rust core.
|
/// FFI wrapper around the native Rust core.
|
||||||
@ -80,12 +81,12 @@ class PlatformFFI {
|
|||||||
String translate(String name, String locale) =>
|
String translate(String name, String locale) =>
|
||||||
_ffiBind.translate(name: name, locale: locale);
|
_ffiBind.translate(name: name, locale: locale);
|
||||||
|
|
||||||
Uint8List? getRgba(SessionID sessionId, int bufSize) {
|
Uint8List? getRgba(SessionID sessionId, int display, int bufSize) {
|
||||||
if (_session_get_rgba == null) return null;
|
if (_session_get_rgba == null) return null;
|
||||||
final sessionIdStr = sessionId.toString();
|
final sessionIdStr = sessionId.toString();
|
||||||
var a = sessionIdStr.toNativeUtf8();
|
var a = sessionIdStr.toNativeUtf8();
|
||||||
try {
|
try {
|
||||||
final buffer = _session_get_rgba!(a);
|
final buffer = _session_get_rgba!(a, display);
|
||||||
if (buffer == nullptr) {
|
if (buffer == nullptr) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -96,12 +97,11 @@ class PlatformFFI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int getRgbaSize(SessionID sessionId) =>
|
int getRgbaSize(SessionID sessionId, int display) =>
|
||||||
_ffiBind.sessionGetRgbaSize(sessionId: sessionId);
|
_ffiBind.sessionGetRgbaSize(sessionId: sessionId, display: display);
|
||||||
void nextRgba(SessionID sessionId) =>
|
void nextRgba(SessionID sessionId, int display) => _ffiBind.sessionNextRgba(sessionId: sessionId, display: display);
|
||||||
_ffiBind.sessionNextRgba(sessionId: sessionId);
|
void registerTexture(SessionID sessionId, int display, int ptr) =>
|
||||||
void registerTexture(SessionID sessionId, int ptr) =>
|
_ffiBind.sessionRegisterTexture(sessionId: sessionId, display: display, ptr: ptr);
|
||||||
_ffiBind.sessionRegisterTexture(sessionId: sessionId, ptr: ptr);
|
|
||||||
|
|
||||||
/// Init the FFI class, loads the native Rust core library.
|
/// Init the FFI class, loads the native Rust core library.
|
||||||
Future<void> init(String appType) async {
|
Future<void> init(String appType) async {
|
||||||
@ -117,7 +117,7 @@ class PlatformFFI {
|
|||||||
: DynamicLibrary.process();
|
: DynamicLibrary.process();
|
||||||
debugPrint('initializing FFI $_appType');
|
debugPrint('initializing FFI $_appType');
|
||||||
try {
|
try {
|
||||||
_session_get_rgba = dylib.lookupFunction<F3, F3>("session_get_rgba");
|
_session_get_rgba = dylib.lookupFunction<F3Dart, F3>("session_get_rgba");
|
||||||
try {
|
try {
|
||||||
// SYSTEM user failed
|
// SYSTEM user failed
|
||||||
_dir = (await getApplicationDocumentsDirectory()).path;
|
_dir = (await getApplicationDocumentsDirectory()).path;
|
||||||
|
@ -18,7 +18,6 @@ class StateGlobal {
|
|||||||
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
|
final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize);
|
||||||
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
||||||
final RxBool showRemoteToolBar = false.obs;
|
final RxBool showRemoteToolBar = false.obs;
|
||||||
final RxInt displaysCount = 0.obs;
|
|
||||||
final svcStatus = SvcStatus.notReady.obs;
|
final svcStatus = SvcStatus.notReady.obs;
|
||||||
// Only used for macOS
|
// Only used for macOS
|
||||||
bool closeOnFullscreen = false;
|
bool closeOnFullscreen = false;
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -67,6 +68,44 @@ class RustDeskMultiWindowManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function must be called in the main window thread.
|
||||||
|
// Because the _remoteDesktopWindows is managed in that thread.
|
||||||
|
openMonitorSession(
|
||||||
|
int windowId, String peerId, int display, int displayCount) async {
|
||||||
|
if (_remoteDesktopWindows.length > 1) {
|
||||||
|
for (final windowId in _remoteDesktopWindows) {
|
||||||
|
if (await DesktopMultiWindow.invokeMethod(
|
||||||
|
windowId,
|
||||||
|
kWindowEventActiveDisplaySession,
|
||||||
|
jsonEncode({
|
||||||
|
'id': peerId,
|
||||||
|
'display': display,
|
||||||
|
}))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final displays = display == kAllDisplayValue
|
||||||
|
? List.generate(displayCount, (index) => index)
|
||||||
|
: [display];
|
||||||
|
var params = {
|
||||||
|
'type': WindowType.RemoteDesktop.index,
|
||||||
|
'id': peerId,
|
||||||
|
'tab_window_id': windowId,
|
||||||
|
'display': display,
|
||||||
|
'displays': displays,
|
||||||
|
};
|
||||||
|
await _newSession(
|
||||||
|
false,
|
||||||
|
WindowType.RemoteDesktop,
|
||||||
|
kWindowEventNewRemoteDesktop,
|
||||||
|
peerId,
|
||||||
|
_remoteDesktopWindows,
|
||||||
|
jsonEncode(params),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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) async {
|
||||||
final windowController = await DesktopMultiWindow.createWindow(msg);
|
final windowController = await DesktopMultiWindow.createWindow(msg);
|
||||||
@ -148,11 +187,21 @@ class RustDeskMultiWindowManager {
|
|||||||
bool openInTabs = type != WindowType.RemoteDesktop ||
|
bool openInTabs = type != WindowType.RemoteDesktop ||
|
||||||
mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs);
|
mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs);
|
||||||
|
|
||||||
if (windows.length > 1 || !openInTabs) {
|
if (kOpenSamePeerInNewWindow) {
|
||||||
for (final windowId in windows) {
|
// Open in new window if the peer is already connected.
|
||||||
if (await DesktopMultiWindow.invokeMethod(
|
// No need to care about the previous session type.
|
||||||
windowId, kWindowEventActiveSession, remoteId)) {
|
if (type == WindowType.RemoteDesktop &&
|
||||||
return MultiWindowCallResult(windowId, null);
|
await bind.sessionGetFlutterOptionByPeerId(id: remoteId, k: '') !=
|
||||||
|
null) {
|
||||||
|
openInTabs = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (windows.length > 1 || !openInTabs) {
|
||||||
|
for (final windowId in windows) {
|
||||||
|
if (await DesktopMultiWindow.invokeMethod(
|
||||||
|
windowId, kWindowEventActiveSession, remoteId)) {
|
||||||
|
return MultiWindowCallResult(windowId, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use hbb_common::{
|
|||||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||||
Mutex as TokioMutex,
|
Mutex as TokioMutex,
|
||||||
},
|
},
|
||||||
ResultType, SessionID,
|
ResultType,
|
||||||
};
|
};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
@ -61,7 +61,7 @@ pub enum ClipboardFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct MsgChannel {
|
struct MsgChannel {
|
||||||
session_uuid: SessionID,
|
peer_id: String,
|
||||||
conn_id: i32,
|
conn_id: i32,
|
||||||
sender: UnboundedSender<ClipboardFile>,
|
sender: UnboundedSender<ClipboardFile>,
|
||||||
receiver: Arc<TokioMutex<UnboundedReceiver<ClipboardFile>>>,
|
receiver: Arc<TokioMutex<UnboundedReceiver<ClipboardFile>>>,
|
||||||
@ -90,12 +90,12 @@ impl ClipboardFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_client_conn_id(session_uuid: &SessionID) -> Option<i32> {
|
pub fn get_client_conn_id(peer_id: &str) -> Option<i32> {
|
||||||
VEC_MSG_CHANNEL
|
VEC_MSG_CHANNEL
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.session_uuid == session_uuid.to_owned())
|
.find(|x| x.peer_id == peer_id)
|
||||||
.map(|x| x.conn_id)
|
.map(|x| x.conn_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,13 +106,10 @@ fn get_conn_id() -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_rx_cliprdr_client(
|
pub fn get_rx_cliprdr_client(
|
||||||
session_uuid: &SessionID,
|
peer_id: &str,
|
||||||
) -> (i32, Arc<TokioMutex<UnboundedReceiver<ClipboardFile>>>) {
|
) -> (i32, Arc<TokioMutex<UnboundedReceiver<ClipboardFile>>>) {
|
||||||
let mut lock = VEC_MSG_CHANNEL.write().unwrap();
|
let mut lock = VEC_MSG_CHANNEL.write().unwrap();
|
||||||
match lock
|
match lock.iter().find(|x| x.peer_id == peer_id) {
|
||||||
.iter()
|
|
||||||
.find(|x| x.session_uuid == session_uuid.to_owned())
|
|
||||||
{
|
|
||||||
Some(msg_channel) => (msg_channel.conn_id, msg_channel.receiver.clone()),
|
Some(msg_channel) => (msg_channel.conn_id, msg_channel.receiver.clone()),
|
||||||
None => {
|
None => {
|
||||||
let (sender, receiver) = unbounded_channel();
|
let (sender, receiver) = unbounded_channel();
|
||||||
@ -120,7 +117,7 @@ pub fn get_rx_cliprdr_client(
|
|||||||
let receiver2 = receiver.clone();
|
let receiver2 = receiver.clone();
|
||||||
let conn_id = get_conn_id();
|
let conn_id = get_conn_id();
|
||||||
let msg_channel = MsgChannel {
|
let msg_channel = MsgChannel {
|
||||||
session_uuid: session_uuid.to_owned(),
|
peer_id: peer_id.to_owned(),
|
||||||
conn_id,
|
conn_id,
|
||||||
sender,
|
sender,
|
||||||
receiver,
|
receiver,
|
||||||
@ -140,7 +137,7 @@ pub fn get_rx_cliprdr_server(conn_id: i32) -> Arc<TokioMutex<UnboundedReceiver<C
|
|||||||
let receiver = Arc::new(TokioMutex::new(receiver));
|
let receiver = Arc::new(TokioMutex::new(receiver));
|
||||||
let receiver2 = receiver.clone();
|
let receiver2 = receiver.clone();
|
||||||
let msg_channel = MsgChannel {
|
let msg_channel = MsgChannel {
|
||||||
session_uuid: SessionID::nil(),
|
peer_id: "".to_string(),
|
||||||
conn_id,
|
conn_id,
|
||||||
sender,
|
sender,
|
||||||
receiver,
|
receiver,
|
||||||
|
@ -27,6 +27,7 @@ message VideoFrame {
|
|||||||
EncodedVideoFrames vp8s = 12;
|
EncodedVideoFrames vp8s = 12;
|
||||||
EncodedVideoFrames av1s = 13;
|
EncodedVideoFrames av1s = 13;
|
||||||
}
|
}
|
||||||
|
int32 display = 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
message IdPk {
|
message IdPk {
|
||||||
@ -491,6 +492,12 @@ message SwitchDisplay {
|
|||||||
Resolution original_resolution = 8;
|
Resolution original_resolution = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message CaptureDisplays {
|
||||||
|
repeated int32 add = 1;
|
||||||
|
repeated int32 sub = 2;
|
||||||
|
repeated int32 set = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message PermissionInfo {
|
message PermissionInfo {
|
||||||
enum Permission {
|
enum Permission {
|
||||||
Keyboard = 0;
|
Keyboard = 0;
|
||||||
@ -688,6 +695,8 @@ message Misc {
|
|||||||
uint32 full_speed_fps = 27;
|
uint32 full_speed_fps = 27;
|
||||||
uint32 auto_adjust_fps = 28;
|
uint32 auto_adjust_fps = 28;
|
||||||
bool client_record_status = 29;
|
bool client_record_status = 29;
|
||||||
|
CaptureDisplays capture_displays = 30;
|
||||||
|
int32 refresh_video_display = 31;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,6 +284,12 @@ pub struct PeerConfig {
|
|||||||
skip_serializing_if = "String::is_empty"
|
skip_serializing_if = "String::is_empty"
|
||||||
)]
|
)]
|
||||||
pub reverse_mouse_wheel: String,
|
pub reverse_mouse_wheel: String,
|
||||||
|
#[serde(
|
||||||
|
default = "PeerConfig::default_displays_as_individual_windows",
|
||||||
|
deserialize_with = "PeerConfig::deserialize_displays_as_individual_windows",
|
||||||
|
skip_serializing_if = "String::is_empty"
|
||||||
|
)]
|
||||||
|
pub displays_as_individual_windows: String,
|
||||||
|
|
||||||
#[serde(
|
#[serde(
|
||||||
default,
|
default,
|
||||||
@ -328,6 +334,7 @@ impl Default for PeerConfig {
|
|||||||
keyboard_mode: Default::default(),
|
keyboard_mode: Default::default(),
|
||||||
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(),
|
||||||
custom_resolutions: Default::default(),
|
custom_resolutions: Default::default(),
|
||||||
options: Self::default_options(),
|
options: Self::default_options(),
|
||||||
ui_flutter: Default::default(),
|
ui_flutter: Default::default(),
|
||||||
@ -1144,6 +1151,11 @@ impl PeerConfig {
|
|||||||
deserialize_reverse_mouse_wheel,
|
deserialize_reverse_mouse_wheel,
|
||||||
UserDefaultConfig::read().get("reverse_mouse_wheel")
|
UserDefaultConfig::read().get("reverse_mouse_wheel")
|
||||||
);
|
);
|
||||||
|
serde_field_string!(
|
||||||
|
default_displays_as_individual_windows,
|
||||||
|
deserialize_displays_as_individual_windows,
|
||||||
|
UserDefaultConfig::read().get("displays_as_individual_windows")
|
||||||
|
);
|
||||||
|
|
||||||
fn default_custom_image_quality() -> Vec<i32> {
|
fn default_custom_image_quality() -> Vec<i32> {
|
||||||
let f: f64 = UserDefaultConfig::read()
|
let f: f64 = UserDefaultConfig::read()
|
||||||
|
@ -13,7 +13,7 @@ use hbb_common::{
|
|||||||
anyhow::{anyhow, Context},
|
anyhow::{anyhow, Context},
|
||||||
bytes::Bytes,
|
bytes::Bytes,
|
||||||
log,
|
log,
|
||||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
|
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
use std::{ptr, slice};
|
use std::{ptr, slice};
|
||||||
@ -240,7 +240,7 @@ impl EncoderApi for AomEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message> {
|
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<VideoFrame> {
|
||||||
let mut frames = Vec::new();
|
let mut frames = Vec::new();
|
||||||
for ref frame in self
|
for ref frame in self
|
||||||
.encode(ms, frame, STRIDE_ALIGN)
|
.encode(ms, frame, STRIDE_ALIGN)
|
||||||
@ -249,7 +249,7 @@ impl EncoderApi for AomEncoder {
|
|||||||
frames.push(Self::create_frame(frame));
|
frames.push(Self::create_frame(frame));
|
||||||
}
|
}
|
||||||
if frames.len() > 0 {
|
if frames.len() > 0 {
|
||||||
Ok(Self::create_msg(frames))
|
Ok(Self::create_video_frame(frames))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no valid frame"))
|
Err(anyhow!("no valid frame"))
|
||||||
}
|
}
|
||||||
@ -311,16 +311,14 @@ impl AomEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn create_msg(frames: Vec<EncodedVideoFrame>) -> Message {
|
pub fn create_video_frame(frames: Vec<EncodedVideoFrame>) -> VideoFrame {
|
||||||
let mut msg_out = Message::new();
|
|
||||||
let mut vf = VideoFrame::new();
|
let mut vf = VideoFrame::new();
|
||||||
let av1s = EncodedVideoFrames {
|
let av1s = EncodedVideoFrames {
|
||||||
frames: frames.into(),
|
frames: frames.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
vf.set_av1s(av1s);
|
vf.set_av1s(av1s);
|
||||||
msg_out.set_video_frame(vf);
|
vf
|
||||||
msg_out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -23,8 +23,8 @@ use hbb_common::{
|
|||||||
config::PeerConfig,
|
config::PeerConfig,
|
||||||
log,
|
log,
|
||||||
message_proto::{
|
message_proto::{
|
||||||
supported_decoding::PreferCodec, video_frame, EncodedVideoFrames, Message,
|
supported_decoding::PreferCodec, video_frame, EncodedVideoFrames,
|
||||||
SupportedDecoding, SupportedEncoding,
|
SupportedDecoding, SupportedEncoding, VideoFrame,
|
||||||
},
|
},
|
||||||
sysinfo::{System, SystemExt},
|
sysinfo::{System, SystemExt},
|
||||||
tokio::time::Instant,
|
tokio::time::Instant,
|
||||||
@ -60,7 +60,7 @@ pub trait EncoderApi {
|
|||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
|
||||||
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message>;
|
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<VideoFrame>;
|
||||||
|
|
||||||
fn use_yuv(&self) -> bool;
|
fn use_yuv(&self) -> bool;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use hbb_common::{
|
|||||||
bytes::Bytes,
|
bytes::Bytes,
|
||||||
config::HwCodecConfig,
|
config::HwCodecConfig,
|
||||||
log,
|
log,
|
||||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
|
message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
use hwcodec::{
|
use hwcodec::{
|
||||||
@ -92,12 +92,7 @@ impl EncoderApi for HwEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_to_message(
|
fn encode_to_message(&mut self, frame: &[u8], _ms: i64) -> ResultType<VideoFrame> {
|
||||||
&mut self,
|
|
||||||
frame: &[u8],
|
|
||||||
_ms: i64,
|
|
||||||
) -> ResultType<hbb_common::message_proto::Message> {
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
let mut vf = VideoFrame::new();
|
let mut vf = VideoFrame::new();
|
||||||
let mut frames = Vec::new();
|
let mut frames = Vec::new();
|
||||||
for frame in self.encode(frame).with_context(|| "Failed to encode")? {
|
for frame in self.encode(frame).with_context(|| "Failed to encode")? {
|
||||||
@ -117,8 +112,7 @@ impl EncoderApi for HwEncoder {
|
|||||||
DataFormat::H264 => vf.set_h264s(frames),
|
DataFormat::H264 => vf.set_h264s(frames),
|
||||||
DataFormat::H265 => vf.set_h265s(frames),
|
DataFormat::H265 => vf.set_h265s(frames),
|
||||||
}
|
}
|
||||||
msg_out.set_video_frame(vf);
|
Ok(vf)
|
||||||
Ok(msg_out)
|
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no valid frame"))
|
Err(anyhow!("no valid frame"))
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use hbb_common::anyhow::{anyhow, Context};
|
use hbb_common::anyhow::{anyhow, Context};
|
||||||
use hbb_common::log;
|
use hbb_common::log;
|
||||||
use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame};
|
use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame};
|
||||||
use hbb_common::ResultType;
|
use hbb_common::ResultType;
|
||||||
|
|
||||||
use crate::codec::{base_bitrate, codec_thread_num, EncoderApi, Quality};
|
use crate::codec::{base_bitrate, codec_thread_num, EncoderApi, Quality};
|
||||||
@ -172,7 +172,7 @@ impl EncoderApi for VpxEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message> {
|
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<VideoFrame> {
|
||||||
let mut frames = Vec::new();
|
let mut frames = Vec::new();
|
||||||
for ref frame in self
|
for ref frame in self
|
||||||
.encode(ms, frame, STRIDE_ALIGN)
|
.encode(ms, frame, STRIDE_ALIGN)
|
||||||
@ -186,7 +186,7 @@ impl EncoderApi for VpxEncoder {
|
|||||||
|
|
||||||
// to-do: flush periodically, e.g. 1 second
|
// to-do: flush periodically, e.g. 1 second
|
||||||
if frames.len() > 0 {
|
if frames.len() > 0 {
|
||||||
Ok(VpxEncoder::create_msg(self.id, frames))
|
Ok(VpxEncoder::create_video_frame(self.id, frames))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no valid frame"))
|
Err(anyhow!("no valid frame"))
|
||||||
}
|
}
|
||||||
@ -266,8 +266,10 @@ impl VpxEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn create_msg(codec_id: VpxVideoCodecId, frames: Vec<EncodedVideoFrame>) -> Message {
|
pub fn create_video_frame(
|
||||||
let mut msg_out = Message::new();
|
codec_id: VpxVideoCodecId,
|
||||||
|
frames: Vec<EncodedVideoFrame>,
|
||||||
|
) -> VideoFrame {
|
||||||
let mut vf = VideoFrame::new();
|
let mut vf = VideoFrame::new();
|
||||||
let vpxs = EncodedVideoFrames {
|
let vpxs = EncodedVideoFrames {
|
||||||
frames: frames.into(),
|
frames: frames.into(),
|
||||||
@ -277,8 +279,7 @@ impl VpxEncoder {
|
|||||||
VpxVideoCodecId::VP8 => vf.set_vp8s(vpxs),
|
VpxVideoCodecId::VP8 => vf.set_vp8s(vpxs),
|
||||||
VpxVideoCodecId::VP9 => vf.set_vp9s(vpxs),
|
VpxVideoCodecId::VP9 => vf.set_vp9s(vpxs),
|
||||||
}
|
}
|
||||||
msg_out.set_video_frame(vf);
|
vf
|
||||||
msg_out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
201
src/client.rs
201
src/client.rs
@ -3,10 +3,7 @@ use std::{
|
|||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{
|
sync::{mpsc, Arc, Mutex, RwLock},
|
||||||
atomic::{AtomicUsize, Ordering},
|
|
||||||
mpsc, Arc, Mutex, RwLock,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use async_trait::async_trait;
|
pub use async_trait::async_trait;
|
||||||
@ -60,6 +57,7 @@ use scrap::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
common::input::{MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP},
|
common::input::{MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP},
|
||||||
is_keyboard_mode_supported,
|
is_keyboard_mode_supported,
|
||||||
|
ui_session_interface::{InvokeUiSession, Session},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "flutter"))]
|
#[cfg(not(feature = "flutter"))]
|
||||||
@ -675,9 +673,12 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
fn try_stop_clipboard(_self_uuid: &uuid::Uuid) {
|
fn try_stop_clipboard(_self_id: &str) {
|
||||||
#[cfg(feature = "flutter")]
|
#[cfg(feature = "flutter")]
|
||||||
if crate::flutter::sessions::other_sessions_running(_self_uuid) {
|
if crate::flutter::sessions::other_sessions_running(
|
||||||
|
_self_id.to_string(),
|
||||||
|
ConnType::DEFAULT_CONN,
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TEXT_CLIPBOARD_STATE.lock().unwrap().running = false;
|
TEXT_CLIPBOARD_STATE.lock().unwrap().running = false;
|
||||||
@ -1206,6 +1207,17 @@ impl LoginConfigHandler {
|
|||||||
self.save_config(config);
|
self.save_config(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Save reverse mouse wheel ("", "Y") to the current config.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `value` - The "displays_as_individual_windows" value ("", "Y").
|
||||||
|
pub fn save_displays_as_individual_windows(&mut self, value: String) {
|
||||||
|
let mut config = self.load_config();
|
||||||
|
config.displays_as_individual_windows = value;
|
||||||
|
self.save_config(config);
|
||||||
|
}
|
||||||
|
|
||||||
/// Save scroll style to the current config.
|
/// Save scroll style to the current config.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -1523,6 +1535,15 @@ impl LoginConfigHandler {
|
|||||||
msg_out
|
msg_out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a [`Message`] for refreshing video.
|
||||||
|
pub fn refresh_display(display: usize) -> Message {
|
||||||
|
let mut misc = Misc::new();
|
||||||
|
misc.set_refresh_video_display(display as _);
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
msg_out.set_misc(misc);
|
||||||
|
msg_out
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a [`Message`] for saving custom image quality.
|
/// Create a [`Message`] for saving custom image quality.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -1789,89 +1810,158 @@ impl LoginConfigHandler {
|
|||||||
|
|
||||||
/// Media data.
|
/// Media data.
|
||||||
pub enum MediaData {
|
pub enum MediaData {
|
||||||
VideoQueue,
|
VideoQueue(usize),
|
||||||
VideoFrame(Box<VideoFrame>),
|
VideoFrame(Box<VideoFrame>),
|
||||||
AudioFrame(Box<AudioFrame>),
|
AudioFrame(Box<AudioFrame>),
|
||||||
AudioFormat(AudioFormat),
|
AudioFormat(AudioFormat),
|
||||||
Reset,
|
Reset(usize),
|
||||||
RecordScreen(bool, i32, i32, String),
|
RecordScreen(bool, usize, i32, i32, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type MediaSender = mpsc::Sender<MediaData>;
|
pub type MediaSender = mpsc::Sender<MediaData>;
|
||||||
|
|
||||||
|
struct VideoHandlerController {
|
||||||
|
handler: VideoHandler,
|
||||||
|
count: u128,
|
||||||
|
duration: std::time::Duration,
|
||||||
|
skip_beginning: u32,
|
||||||
|
}
|
||||||
|
|
||||||
/// Start video and audio thread.
|
/// Start video and audio thread.
|
||||||
/// Return two [`MediaSender`], they should be given to the media producer.
|
/// Return two [`MediaSender`], they should be given to the media producer.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `video_callback` - The callback for video frame. Being called when a video frame is ready.
|
/// * `video_callback` - The callback for video frame. Being called when a video frame is ready.
|
||||||
pub fn start_video_audio_threads<F>(
|
pub fn start_video_audio_threads<F, T>(
|
||||||
|
session: Session<T>,
|
||||||
video_callback: F,
|
video_callback: F,
|
||||||
) -> (
|
) -> (
|
||||||
MediaSender,
|
MediaSender,
|
||||||
MediaSender,
|
MediaSender,
|
||||||
Arc<ArrayQueue<VideoFrame>>,
|
Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
|
||||||
Arc<AtomicUsize>,
|
Arc<RwLock<HashMap<usize, usize>>>,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(&mut scrap::ImageRgb) + Send,
|
F: 'static + FnMut(usize, &mut scrap::ImageRgb) + Send,
|
||||||
|
T: InvokeUiSession,
|
||||||
{
|
{
|
||||||
let (video_sender, video_receiver) = mpsc::channel::<MediaData>();
|
let (video_sender, video_receiver) = mpsc::channel::<MediaData>();
|
||||||
let video_queue = Arc::new(ArrayQueue::<VideoFrame>::new(VIDEO_QUEUE_SIZE));
|
let video_queue_map: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>> = Default::default();
|
||||||
let video_queue_cloned = video_queue.clone();
|
let video_queue_map_cloned = video_queue_map.clone();
|
||||||
let mut video_callback = video_callback;
|
let mut video_callback = video_callback;
|
||||||
let mut duration = std::time::Duration::ZERO;
|
let fps_map = Arc::new(RwLock::new(HashMap::new()));
|
||||||
let mut count = 0;
|
let decode_fps_map = fps_map.clone();
|
||||||
let fps = Arc::new(AtomicUsize::new(0));
|
|
||||||
let decode_fps = fps.clone();
|
|
||||||
let mut skip_beginning = 0;
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
sync_cpu_usage();
|
sync_cpu_usage();
|
||||||
let mut video_handler = VideoHandler::new();
|
let mut handler_controller_map = Vec::new();
|
||||||
|
// let mut count = Vec::new();
|
||||||
|
// let mut duration = std::time::Duration::ZERO;
|
||||||
|
// let mut skip_beginning = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
if let Ok(data) = video_receiver.recv() {
|
if let Ok(data) = video_receiver.recv() {
|
||||||
match data {
|
match data {
|
||||||
MediaData::VideoFrame(_) | MediaData::VideoQueue => {
|
MediaData::VideoFrame(_) | MediaData::VideoQueue(_) => {
|
||||||
let vf = if let MediaData::VideoFrame(vf) = data {
|
let vf = match data {
|
||||||
*vf
|
MediaData::VideoFrame(vf) => *vf,
|
||||||
} else {
|
MediaData::VideoQueue(display) => {
|
||||||
if let Some(vf) = video_queue.pop() {
|
if let Some(video_queue) =
|
||||||
vf
|
video_queue_map.read().unwrap().get(&display)
|
||||||
} else {
|
{
|
||||||
|
if let Some(vf) = video_queue.pop() {
|
||||||
|
vf
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// unreachable!();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let display = vf.display as usize;
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
if let Ok(true) = video_handler.handle_frame(vf) {
|
if handler_controller_map.len() <= display {
|
||||||
video_callback(&mut video_handler.rgb);
|
for _i in handler_controller_map.len()..=display {
|
||||||
// fps calculation
|
handler_controller_map.push(VideoHandlerController {
|
||||||
// The first frame will be very slow
|
handler: VideoHandler::new(),
|
||||||
if skip_beginning < 5 {
|
count: 0,
|
||||||
skip_beginning += 1;
|
duration: std::time::Duration::ZERO,
|
||||||
continue;
|
skip_beginning: 0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
duration += start.elapsed();
|
}
|
||||||
count += 1;
|
if let Some(handler_controller) = handler_controller_map.get_mut(display) {
|
||||||
if count % 10 == 0 {
|
match handler_controller.handler.handle_frame(vf) {
|
||||||
fps.store(
|
Ok(true) => {
|
||||||
(count * 1000 / duration.as_millis()) as usize,
|
video_callback(display, &mut handler_controller.handler.rgb);
|
||||||
Ordering::Relaxed,
|
|
||||||
);
|
// fps calculation
|
||||||
}
|
// The first frame will be very slow
|
||||||
// Clear to get real-time fps
|
if handler_controller.skip_beginning < 5 {
|
||||||
if count > 150 {
|
handler_controller.skip_beginning += 1;
|
||||||
count = 0;
|
continue;
|
||||||
duration = Duration::ZERO;
|
}
|
||||||
|
|
||||||
|
handler_controller.duration += start.elapsed();
|
||||||
|
handler_controller.count += 1;
|
||||||
|
if handler_controller.count % 10 == 0 {
|
||||||
|
fps_map.write().unwrap().insert(
|
||||||
|
display,
|
||||||
|
(handler_controller.count * 1000
|
||||||
|
/ handler_controller.duration.as_millis())
|
||||||
|
as usize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Clear to get real-time fps
|
||||||
|
if handler_controller.count > 150 {
|
||||||
|
handler_controller.count = 0;
|
||||||
|
handler_controller.duration = Duration::ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// This is a simple workaround.
|
||||||
|
//
|
||||||
|
// I only see the following error:
|
||||||
|
// FailedCall("errcode=1 scrap::common::vpxcodec:libs\\scrap\\src\\common\\vpxcodec.rs:433:9")
|
||||||
|
// When switching from all displays to one display, the error occurs.
|
||||||
|
// eg:
|
||||||
|
// 1. Connect to a device with two displays (A and B).
|
||||||
|
// 2. Switch to display A. The error occurs.
|
||||||
|
// 3. If the error does not occur. Switch from A to display B. The error occurs.
|
||||||
|
//
|
||||||
|
// to-do: fix the error
|
||||||
|
log::error!("handle video frame error, {}", e);
|
||||||
|
session.refresh_video(display as _);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MediaData::Reset => {
|
MediaData::Reset(display) => {
|
||||||
video_handler.reset();
|
if let Some(handler_controler) = handler_controller_map.get_mut(display) {
|
||||||
|
handler_controler.handler.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MediaData::RecordScreen(start, w, h, id) => {
|
MediaData::RecordScreen(start, display, w, h, id) => {
|
||||||
video_handler.record_screen(start, w, h, id)
|
if handler_controller_map.len() == 1 {
|
||||||
|
// Compatible with the sciter version(single ui session).
|
||||||
|
// For the sciter version, there're no multi-ui-sessions for one connection.
|
||||||
|
// The display is always 0, video_handler_controllers.len() is always 1. So we use the first video handler.
|
||||||
|
handler_controller_map[0]
|
||||||
|
.handler
|
||||||
|
.record_screen(start, w, h, id);
|
||||||
|
} else {
|
||||||
|
if let Some(handler_controler) = handler_controller_map.get_mut(display)
|
||||||
|
{
|
||||||
|
handler_controler.handler.record_screen(start, w, h, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -1882,7 +1972,12 @@ where
|
|||||||
log::info!("Video decoder loop exits");
|
log::info!("Video decoder loop exits");
|
||||||
});
|
});
|
||||||
let audio_sender = start_audio_thread();
|
let audio_sender = start_audio_thread();
|
||||||
return (video_sender, audio_sender, video_queue_cloned, decode_fps);
|
return (
|
||||||
|
video_sender,
|
||||||
|
audio_sender,
|
||||||
|
video_queue_map_cloned,
|
||||||
|
decode_fps_map,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start an audio thread
|
/// Start an audio thread
|
||||||
@ -2500,7 +2595,7 @@ pub enum Data {
|
|||||||
SetConfirmOverrideFile((i32, i32, bool, bool, bool)),
|
SetConfirmOverrideFile((i32, i32, bool, bool, bool)),
|
||||||
AddJob((i32, String, String, i32, bool, bool)),
|
AddJob((i32, String, String, i32, bool, bool)),
|
||||||
ResumeJob((i32, bool)),
|
ResumeJob((i32, bool)),
|
||||||
RecordScreen(bool, i32, i32, String),
|
RecordScreen(bool, usize, i32, i32, String),
|
||||||
ElevateDirect,
|
ElevateDirect,
|
||||||
ElevateWithLogon(String, String),
|
ElevateWithLogon(String, String),
|
||||||
NewVoiceCall,
|
NewVoiceCall,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
get_time,
|
get_time,
|
||||||
message_proto::{Message, VoiceCallRequest, VoiceCallResponse},
|
message_proto::{Message, VoiceCallRequest, VoiceCallResponse},
|
||||||
@ -7,7 +8,7 @@ use scrap::CodecFormat;
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct QualityStatus {
|
pub struct QualityStatus {
|
||||||
pub speed: Option<String>,
|
pub speed: Option<String>,
|
||||||
pub fps: Option<i32>,
|
pub fps: HashMap<usize, i32>,
|
||||||
pub delay: Option<i32>,
|
pub delay: Option<i32>,
|
||||||
pub target_bitrate: Option<i32>,
|
pub target_bitrate: Option<i32>,
|
||||||
pub codec_format: Option<CodecFormat>,
|
pub codec_format: Option<CodecFormat>,
|
||||||
|
@ -1,33 +1,41 @@
|
|||||||
use std::collections::HashMap;
|
use std::{
|
||||||
use std::num::NonZeroI64;
|
collections::HashMap,
|
||||||
use std::sync::{
|
num::NonZeroI64,
|
||||||
atomic::{AtomicUsize, Ordering},
|
sync::{
|
||||||
Arc,
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc, RwLock,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, ContextSend};
|
use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, ContextSend};
|
||||||
use crossbeam_queue::ArrayQueue;
|
use crossbeam_queue::ArrayQueue;
|
||||||
use hbb_common::config::{PeerConfig, TransferSerde};
|
|
||||||
use hbb_common::fs::{
|
|
||||||
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
|
|
||||||
RemoveJobMeta,
|
|
||||||
};
|
|
||||||
use hbb_common::message_proto::permission_info::Permission;
|
|
||||||
use hbb_common::protobuf::Message as _;
|
|
||||||
use hbb_common::rendezvous_proto::ConnType;
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
use hbb_common::sleep;
|
use hbb_common::sleep;
|
||||||
#[cfg(not(target_os = "ios"))]
|
#[cfg(not(target_os = "ios"))]
|
||||||
use hbb_common::tokio::sync::mpsc::error::TryRecvError;
|
use hbb_common::tokio::sync::mpsc::error::TryRecvError;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||||
use hbb_common::tokio::{
|
use hbb_common::{
|
||||||
self,
|
allow_err,
|
||||||
sync::mpsc,
|
config::{PeerConfig, TransferSerde},
|
||||||
time::{self, Duration, Instant, Interval},
|
fs,
|
||||||
|
fs::{
|
||||||
|
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
|
||||||
|
RemoveJobMeta,
|
||||||
|
},
|
||||||
|
get_time, log,
|
||||||
|
message_proto::permission_info::Permission,
|
||||||
|
message_proto::*,
|
||||||
|
protobuf::Message as _,
|
||||||
|
rendezvous_proto::ConnType,
|
||||||
|
tokio::{
|
||||||
|
self,
|
||||||
|
sync::mpsc,
|
||||||
|
time::{self, Duration, Instant, Interval},
|
||||||
|
},
|
||||||
|
Stream,
|
||||||
};
|
};
|
||||||
use hbb_common::{allow_err, fs, get_time, log, message_proto::*, Stream};
|
|
||||||
use scrap::CodecFormat;
|
use scrap::CodecFormat;
|
||||||
|
|
||||||
use crate::client::{
|
use crate::client::{
|
||||||
@ -43,7 +51,7 @@ use crate::{client::Data, client::Interface};
|
|||||||
|
|
||||||
pub struct Remote<T: InvokeUiSession> {
|
pub struct Remote<T: InvokeUiSession> {
|
||||||
handler: Session<T>,
|
handler: Session<T>,
|
||||||
video_queue: Arc<ArrayQueue<VideoFrame>>,
|
video_queue_map: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
|
||||||
video_sender: MediaSender,
|
video_sender: MediaSender,
|
||||||
audio_sender: MediaSender,
|
audio_sender: MediaSender,
|
||||||
receiver: mpsc::UnboundedReceiver<Data>,
|
receiver: mpsc::UnboundedReceiver<Data>,
|
||||||
@ -61,27 +69,27 @@ pub struct Remote<T: InvokeUiSession> {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
client_conn_id: i32, // used for file clipboard
|
client_conn_id: i32, // used for file clipboard
|
||||||
data_count: Arc<AtomicUsize>,
|
data_count: Arc<AtomicUsize>,
|
||||||
frame_count: Arc<AtomicUsize>,
|
frame_count_map: Arc<RwLock<HashMap<usize, usize>>>,
|
||||||
video_format: CodecFormat,
|
video_format: CodecFormat,
|
||||||
elevation_requested: bool,
|
elevation_requested: bool,
|
||||||
fps_control: FpsControl,
|
fps_control_map: HashMap<usize, FpsControl>,
|
||||||
decode_fps: Arc<AtomicUsize>,
|
decode_fps_map: Arc<RwLock<HashMap<usize, usize>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InvokeUiSession> Remote<T> {
|
impl<T: InvokeUiSession> Remote<T> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
handler: Session<T>,
|
handler: Session<T>,
|
||||||
video_queue: Arc<ArrayQueue<VideoFrame>>,
|
video_queue: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
|
||||||
video_sender: MediaSender,
|
video_sender: MediaSender,
|
||||||
audio_sender: MediaSender,
|
audio_sender: MediaSender,
|
||||||
receiver: mpsc::UnboundedReceiver<Data>,
|
receiver: mpsc::UnboundedReceiver<Data>,
|
||||||
sender: mpsc::UnboundedSender<Data>,
|
sender: mpsc::UnboundedSender<Data>,
|
||||||
frame_count: Arc<AtomicUsize>,
|
frame_count_map: Arc<RwLock<HashMap<usize, usize>>>,
|
||||||
decode_fps: Arc<AtomicUsize>,
|
decode_fps: Arc<RwLock<HashMap<usize, usize>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
handler,
|
handler,
|
||||||
video_queue,
|
video_queue_map: video_queue,
|
||||||
video_sender,
|
video_sender,
|
||||||
audio_sender,
|
audio_sender,
|
||||||
receiver,
|
receiver,
|
||||||
@ -96,13 +104,13 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
client_conn_id: 0,
|
client_conn_id: 0,
|
||||||
data_count: Arc::new(AtomicUsize::new(0)),
|
data_count: Arc::new(AtomicUsize::new(0)),
|
||||||
frame_count,
|
frame_count_map,
|
||||||
video_format: CodecFormat::Unknown,
|
video_format: CodecFormat::Unknown,
|
||||||
stop_voice_call_sender: None,
|
stop_voice_call_sender: None,
|
||||||
voice_call_request_timestamp: None,
|
voice_call_request_timestamp: None,
|
||||||
elevation_requested: false,
|
elevation_requested: false,
|
||||||
fps_control: Default::default(),
|
fps_control_map: Default::default(),
|
||||||
decode_fps,
|
decode_fps_map: decode_fps,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +160,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
|| self.handler.is_rdp();
|
|| self.handler.is_rdp();
|
||||||
if !is_conn_not_default {
|
if !is_conn_not_default {
|
||||||
(self.client_conn_id, rx_clip_client_lock) =
|
(self.client_conn_id, rx_clip_client_lock) =
|
||||||
clipboard::get_rx_cliprdr_client(&self.handler.session_id);
|
clipboard::get_rx_cliprdr_client(&self.handler.id);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -229,12 +237,18 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
let mut speed = self.data_count.swap(0, Ordering::Relaxed);
|
let mut speed = self.data_count.swap(0, Ordering::Relaxed);
|
||||||
speed = speed * 1000 / elapsed as usize;
|
speed = speed * 1000 / elapsed as usize;
|
||||||
let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
|
let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
|
||||||
let mut fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
|
|
||||||
// Correcting the inaccuracy of status_timer
|
let mut frame_count_map_write = self.frame_count_map.write().unwrap();
|
||||||
fps = fps * 1000 / elapsed as i32;
|
let frame_count_map = frame_count_map_write.clone();
|
||||||
|
frame_count_map_write.values_mut().for_each(|v| *v = 0);
|
||||||
|
drop(frame_count_map_write);
|
||||||
|
let fps = frame_count_map.iter().map(|(k, v)| {
|
||||||
|
// Correcting the inaccuracy of status_timer
|
||||||
|
(k.clone(), (*v as i32) * 1000 / elapsed as i32)
|
||||||
|
}).collect::<HashMap<usize, i32>>();
|
||||||
self.handler.update_quality_status(QualityStatus {
|
self.handler.update_quality_status(QualityStatus {
|
||||||
speed:Some(speed),
|
speed: Some(speed),
|
||||||
fps:Some(fps),
|
fps,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -260,7 +274,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
if _set_disconnected_ok {
|
if _set_disconnected_ok {
|
||||||
Client::try_stop_clipboard(&self.handler.session_id);
|
Client::try_stop_clipboard(&self.handler.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -760,10 +774,10 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Data::RecordScreen(start, w, h, id) => {
|
Data::RecordScreen(start, display, w, h, id) => {
|
||||||
let _ = self
|
let _ = self
|
||||||
.video_sender
|
.video_sender
|
||||||
.send(MediaData::RecordScreen(start, w, h, id));
|
.send(MediaData::RecordScreen(start, display, w, h, id));
|
||||||
}
|
}
|
||||||
Data::ElevateDirect => {
|
Data::ElevateDirect => {
|
||||||
let mut request = ElevationRequest::new();
|
let mut request = ElevationRequest::new();
|
||||||
@ -904,89 +918,100 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fps_control(&mut self, direct: bool) {
|
fn fps_control(&mut self, direct: bool) {
|
||||||
let len = self.video_queue.len();
|
let decode_fps_read = self.decode_fps_map.read().unwrap();
|
||||||
let ctl = &mut self.fps_control;
|
for (display, decode_fps) in decode_fps_read.iter() {
|
||||||
// Current full speed decoding fps
|
let video_queue_map_read = self.video_queue_map.read().unwrap();
|
||||||
let decode_fps = self.decode_fps.load(std::sync::atomic::Ordering::Relaxed);
|
let Some(video_queue) = video_queue_map_read.get(display) else {
|
||||||
if decode_fps == 0 {
|
continue;
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
let limited_fps = if direct {
|
|
||||||
decode_fps * 9 / 10 // 30 got 27
|
|
||||||
} else {
|
|
||||||
decode_fps * 4 / 5 // 30 got 24
|
|
||||||
};
|
|
||||||
// send full speed fps
|
|
||||||
let version = self.handler.lc.read().unwrap().version;
|
|
||||||
let max_encode_speed = 144 * 10 / 9;
|
|
||||||
if version >= hbb_common::get_version_number("1.2.1")
|
|
||||||
&& (ctl.last_full_speed_fps.is_none() // First time
|
|
||||||
|| ((ctl.last_full_speed_fps.unwrap_or_default() - decode_fps as i32).abs() >= 5 // diff 5
|
|
||||||
&& !(decode_fps > max_encode_speed // already exceed max encoding speed
|
|
||||||
&& ctl.last_full_speed_fps.unwrap_or_default() > max_encode_speed as i32)))
|
|
||||||
{
|
|
||||||
let mut misc = Misc::new();
|
|
||||||
misc.set_full_speed_fps(decode_fps as _);
|
|
||||||
let mut msg = Message::new();
|
|
||||||
msg.set_misc(misc);
|
|
||||||
self.sender.send(Data::Message(msg)).ok();
|
|
||||||
ctl.last_full_speed_fps = Some(decode_fps as _);
|
|
||||||
}
|
|
||||||
// decrease judgement
|
|
||||||
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 }; // 500ms
|
|
||||||
let should_decrease = len >= debounce // exceed debounce
|
|
||||||
&& len > ctl.last_queue_size + 5 // still caching
|
|
||||||
&& !ctl.last_custom_fps.unwrap_or(i32::MAX) < limited_fps as i32; // NOT already set a smaller one
|
|
||||||
|
|
||||||
// increase judgement
|
if !self.fps_control_map.contains_key(display) {
|
||||||
if len <= 1 {
|
self.fps_control_map.insert(*display, FpsControl::default());
|
||||||
ctl.idle_counter += 1;
|
|
||||||
} else {
|
|
||||||
ctl.idle_counter = 0;
|
|
||||||
}
|
|
||||||
let mut should_increase = false;
|
|
||||||
if let Some(last_custom_fps) = ctl.last_custom_fps {
|
|
||||||
// ever set
|
|
||||||
if last_custom_fps + 5 < limited_fps as i32 && ctl.idle_counter > 3 {
|
|
||||||
// limited_fps is 5 larger than last set, and idle time is more than 3 seconds
|
|
||||||
should_increase = true;
|
|
||||||
}
|
}
|
||||||
}
|
let Some(ctl) = self.fps_control_map.get_mut(display) else {
|
||||||
if should_decrease || should_increase {
|
return;
|
||||||
// limited_fps to ensure decoding is faster than encoding
|
};
|
||||||
let mut custom_fps = limited_fps as i32;
|
|
||||||
if custom_fps < 1 {
|
let len = video_queue.len();
|
||||||
custom_fps = 1;
|
let decode_fps = *decode_fps;
|
||||||
}
|
let limited_fps = if direct {
|
||||||
// send custom fps
|
decode_fps * 9 / 10 // 30 got 27
|
||||||
let mut misc = Misc::new();
|
|
||||||
if version > hbb_common::get_version_number("1.2.1") {
|
|
||||||
// avoid confusion with custom image quality fps
|
|
||||||
misc.set_auto_adjust_fps(custom_fps as _);
|
|
||||||
} else {
|
} else {
|
||||||
misc.set_option(OptionMessage {
|
decode_fps * 4 / 5 // 30 got 24
|
||||||
custom_fps,
|
};
|
||||||
..Default::default()
|
// send full speed fps
|
||||||
});
|
let version = self.handler.lc.read().unwrap().version;
|
||||||
|
let max_encode_speed = 144 * 10 / 9;
|
||||||
|
if version >= hbb_common::get_version_number("1.2.1")
|
||||||
|
&& (ctl.last_full_speed_fps.is_none() // First time
|
||||||
|
|| ((ctl.last_full_speed_fps.unwrap_or_default() - decode_fps as i32).abs() >= 5 // diff 5
|
||||||
|
&& !(decode_fps > max_encode_speed // already exceed max encoding speed
|
||||||
|
&& ctl.last_full_speed_fps.unwrap_or_default() > max_encode_speed as i32)))
|
||||||
|
{
|
||||||
|
let mut misc = Misc::new();
|
||||||
|
misc.set_full_speed_fps(decode_fps as _);
|
||||||
|
let mut msg = Message::new();
|
||||||
|
msg.set_misc(misc);
|
||||||
|
self.sender.send(Data::Message(msg)).ok();
|
||||||
|
ctl.last_full_speed_fps = Some(decode_fps as _);
|
||||||
|
}
|
||||||
|
// decrease judgement
|
||||||
|
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 }; // 500ms
|
||||||
|
let should_decrease = len >= debounce // exceed debounce
|
||||||
|
&& len > ctl.last_queue_size + 5 // still caching
|
||||||
|
&& !ctl.last_custom_fps.unwrap_or(i32::MAX) < limited_fps as i32; // NOT already set a smaller one
|
||||||
|
|
||||||
|
// increase judgement
|
||||||
|
if len <= 1 {
|
||||||
|
ctl.idle_counter += 1;
|
||||||
|
} else {
|
||||||
|
ctl.idle_counter = 0;
|
||||||
|
}
|
||||||
|
let mut should_increase = false;
|
||||||
|
if let Some(last_custom_fps) = ctl.last_custom_fps {
|
||||||
|
// ever set
|
||||||
|
if last_custom_fps + 5 < limited_fps as i32 && ctl.idle_counter > 3 {
|
||||||
|
// limited_fps is 5 larger than last set, and idle time is more than 3 seconds
|
||||||
|
should_increase = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if should_decrease || should_increase {
|
||||||
|
// limited_fps to ensure decoding is faster than encoding
|
||||||
|
let mut custom_fps = limited_fps as i32;
|
||||||
|
if custom_fps < 1 {
|
||||||
|
custom_fps = 1;
|
||||||
|
}
|
||||||
|
// send custom fps
|
||||||
|
let mut misc = Misc::new();
|
||||||
|
if version > hbb_common::get_version_number("1.2.1") {
|
||||||
|
// avoid confusion with custom image quality fps
|
||||||
|
misc.set_auto_adjust_fps(custom_fps as _);
|
||||||
|
} else {
|
||||||
|
misc.set_option(OptionMessage {
|
||||||
|
custom_fps,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut msg = Message::new();
|
||||||
|
msg.set_misc(misc);
|
||||||
|
self.sender.send(Data::Message(msg)).ok();
|
||||||
|
ctl.last_queue_size = len;
|
||||||
|
ctl.last_custom_fps = Some(custom_fps);
|
||||||
|
}
|
||||||
|
// send refresh
|
||||||
|
if ctl.refresh_times < 10 // enough
|
||||||
|
&& (len > video_queue.capacity() / 2
|
||||||
|
&& (ctl.refresh_times == 0 || ctl.last_refresh_instant.elapsed().as_secs() > 30))
|
||||||
|
{
|
||||||
|
// Refresh causes client set_display, left frames cause flickering.
|
||||||
|
while let Some(_) = video_queue.pop() {}
|
||||||
|
self.handler.refresh_video(*display as _);
|
||||||
|
ctl.refresh_times += 1;
|
||||||
|
ctl.last_refresh_instant = Instant::now();
|
||||||
}
|
}
|
||||||
let mut msg = Message::new();
|
|
||||||
msg.set_misc(misc);
|
|
||||||
self.sender.send(Data::Message(msg)).ok();
|
|
||||||
ctl.last_queue_size = len;
|
|
||||||
ctl.last_custom_fps = Some(custom_fps);
|
|
||||||
}
|
|
||||||
// send refresh
|
|
||||||
if ctl.refresh_times < 10 // enough
|
|
||||||
&& (len > self.video_queue.capacity() / 2
|
|
||||||
&& (ctl.refresh_times == 0 || ctl.last_refresh_instant.elapsed().as_secs() > 30))
|
|
||||||
{
|
|
||||||
// Refresh causes client set_display, left frames cause flickering.
|
|
||||||
while let Some(_) = self.video_queue.pop() {}
|
|
||||||
self.handler.refresh_video();
|
|
||||||
ctl.refresh_times += 1;
|
|
||||||
ctl.last_refresh_instant = Instant::now();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1008,14 +1033,27 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let display = vf.display as usize;
|
||||||
|
let mut video_queue_write = self.video_queue_map.write().unwrap();
|
||||||
|
if !video_queue_write.contains_key(&display) {
|
||||||
|
video_queue_write.insert(
|
||||||
|
display,
|
||||||
|
ArrayQueue::<VideoFrame>::new(crate::client::VIDEO_QUEUE_SIZE),
|
||||||
|
);
|
||||||
|
}
|
||||||
if Self::contains_key_frame(&vf) {
|
if Self::contains_key_frame(&vf) {
|
||||||
while let Some(_) = self.video_queue.pop() {}
|
if let Some(video_queue) = video_queue_write.get_mut(&display) {
|
||||||
|
while let Some(_) = video_queue.pop() {}
|
||||||
|
}
|
||||||
self.video_sender
|
self.video_sender
|
||||||
.send(MediaData::VideoFrame(Box::new(vf)))
|
.send(MediaData::VideoFrame(Box::new(vf)))
|
||||||
.ok();
|
.ok();
|
||||||
} else {
|
} else {
|
||||||
self.video_queue.force_push(vf);
|
if let Some(video_queue) = video_queue_write.get_mut(&display) {
|
||||||
self.video_sender.send(MediaData::VideoQueue).ok();
|
video_queue.force_push(vf);
|
||||||
|
}
|
||||||
|
self.video_sender.send(MediaData::VideoQueue(display)).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(message::Union::Hash(hash)) => {
|
Some(message::Union::Hash(hash)) => {
|
||||||
@ -1297,7 +1335,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
Some(misc::Union::SwitchDisplay(s)) => {
|
Some(misc::Union::SwitchDisplay(s)) => {
|
||||||
self.handler.handle_peer_switch_display(&s);
|
self.handler.handle_peer_switch_display(&s);
|
||||||
self.video_sender.send(MediaData::Reset).ok();
|
self.video_sender
|
||||||
|
.send(MediaData::Reset(s.display as _))
|
||||||
|
.ok();
|
||||||
if s.width > 0 && s.height > 0 {
|
if s.width > 0 && s.height > 0 {
|
||||||
self.handler.set_display(
|
self.handler.set_display(
|
||||||
s.x,
|
s.x,
|
||||||
@ -1674,7 +1714,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
#[cfg(feature = "flutter")]
|
#[cfg(feature = "flutter")]
|
||||||
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
|
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
|
||||||
if self.client_conn_id
|
if self.client_conn_id
|
||||||
!= clipboard::get_client_conn_id(&crate::flutter::get_cur_session_id()).unwrap_or(0)
|
!= clipboard::get_client_conn_id(&crate::flutter::get_cur_peer_id()).unwrap_or(0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,8 @@ pub const PLATFORM_LINUX: &str = "Linux";
|
|||||||
pub const PLATFORM_MACOS: &str = "Mac OS";
|
pub const PLATFORM_MACOS: &str = "Mac OS";
|
||||||
pub const PLATFORM_ANDROID: &str = "Android";
|
pub const PLATFORM_ANDROID: &str = "Android";
|
||||||
|
|
||||||
|
const MIN_VER_MULTI_UI_SESSION: &str = "1.2.4";
|
||||||
|
|
||||||
pub mod input {
|
pub mod input {
|
||||||
pub const MOUSE_TYPE_MOVE: i32 = 0;
|
pub const MOUSE_TYPE_MOVE: i32 = 0;
|
||||||
pub const MOUSE_TYPE_DOWN: i32 = 1;
|
pub const MOUSE_TYPE_DOWN: i32 = 1;
|
||||||
@ -120,6 +122,16 @@ pub fn set_server_running(b: bool) {
|
|||||||
*SERVER_RUNNING.write().unwrap() = b;
|
*SERVER_RUNNING.write().unwrap() = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_support_multi_ui_session(ver: &str) -> bool {
|
||||||
|
is_support_multi_ui_session_num(hbb_common::get_version_number(ver))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_support_multi_ui_session_num(ver: i64) -> bool {
|
||||||
|
ver >= hbb_common::get_version_number(MIN_VER_MULTI_UI_SESSION)
|
||||||
|
}
|
||||||
|
|
||||||
// is server process, with "--server" args
|
// is server process, with "--server" args
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_server() -> bool {
|
pub fn is_server() -> bool {
|
||||||
@ -780,13 +792,17 @@ pub fn get_sysinfo() -> serde_json::Value {
|
|||||||
}
|
}
|
||||||
let hostname = hostname(); // sys.hostname() return localhost on android in my test
|
let hostname = hostname(); // sys.hostname() return localhost on android in my test
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
let mut out = json!({
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
let out;
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
let mut out;
|
||||||
|
out = json!({
|
||||||
"cpu": format!("{cpu}{num_cpus}/{num_pcpus} cores"),
|
"cpu": format!("{cpu}{num_cpus}/{num_pcpus} cores"),
|
||||||
"memory": format!("{memory}GB"),
|
"memory": format!("{memory}GB"),
|
||||||
"os": os,
|
"os": os,
|
||||||
"hostname": hostname,
|
"hostname": hostname,
|
||||||
});
|
});
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
{
|
{
|
||||||
out["username"] = json!(crate::platform::get_active_username());
|
out["username"] = json!(crate::platform::get_active_username());
|
||||||
}
|
}
|
||||||
|
579
src/flutter.rs
579
src/flutter.rs
@ -18,8 +18,6 @@ use hbb_common::{
|
|||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
#[cfg(not(feature = "flutter_texture_render"))]
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
ffi::CString,
|
ffi::CString,
|
||||||
@ -150,25 +148,40 @@ pub unsafe extern "C" fn free_c_args(ptr: *mut *mut c_char, len: c_int) {
|
|||||||
// Afterwards the vector will be dropped and thus freed.
|
// Afterwards the vector will be dropped and thus freed.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct SessionHandler {
|
||||||
|
event_stream: Option<StreamSink<EventToUI>>,
|
||||||
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
notify_rendered: bool,
|
||||||
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
renderer: VideoRenderer,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct FlutterHandler {
|
pub struct FlutterHandler {
|
||||||
pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
|
// ui session id -> display handler data
|
||||||
notify_rendered: Arc<RwLock<bool>>,
|
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
|
||||||
renderer: Arc<RwLock<VideoRenderer>>,
|
|
||||||
peer_info: Arc<RwLock<PeerInfo>>,
|
peer_info: Arc<RwLock<PeerInfo>>,
|
||||||
|
#[cfg(feature = "plugin_framework")]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
|
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "flutter_texture_render"))]
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct FlutterHandler {
|
struct RgbaData {
|
||||||
pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
|
|
||||||
// SAFETY: [rgba] is guarded by [rgba_valid], and it's safe to reach [rgba] with `rgba_valid == true`.
|
// SAFETY: [rgba] is guarded by [rgba_valid], and it's safe to reach [rgba] with `rgba_valid == true`.
|
||||||
// We must check the `rgba_valid` before reading [rgba].
|
// We must check the `rgba_valid` before reading [rgba].
|
||||||
pub rgba: Arc<RwLock<Vec<u8>>>,
|
data: Vec<u8>,
|
||||||
pub rgba_valid: Arc<AtomicBool>,
|
valid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct FlutterHandler {
|
||||||
|
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
|
||||||
|
display_rgbas: Arc<RwLock<HashMap<usize, RgbaData>>>,
|
||||||
peer_info: Arc<RwLock<PeerInfo>>,
|
peer_info: Arc<RwLock<PeerInfo>>,
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
|
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
|
||||||
@ -184,14 +197,22 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
|
|||||||
dst_rgba_stride: c_int,
|
dst_rgba_stride: c_int,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
pub(super) type TextureRgbaPtr = usize;
|
||||||
|
|
||||||
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
struct DisplaySessionInfo {
|
||||||
|
// TextureRgba pointer in flutter native.
|
||||||
|
texture_rgba_ptr: TextureRgbaPtr,
|
||||||
|
size: (usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
// Video Texture Renderer in Flutter
|
// Video Texture Renderer in Flutter
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct VideoRenderer {
|
struct VideoRenderer {
|
||||||
// TextureRgba pointer in flutter native.
|
is_support_multi_ui_session: bool,
|
||||||
ptr: Arc<RwLock<usize>>,
|
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,9 +238,8 @@ impl Default for VideoRenderer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
ptr: Default::default(),
|
map_display_sessions: Default::default(),
|
||||||
width: 0,
|
is_support_multi_ui_session: false,
|
||||||
height: 0,
|
|
||||||
on_rgba_func,
|
on_rgba_func,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,33 +248,74 @@ impl Default for VideoRenderer {
|
|||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
impl VideoRenderer {
|
impl VideoRenderer {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_size(&mut self, width: usize, height: usize) {
|
fn set_size(&mut self, display: usize, width: usize, height: usize) {
|
||||||
self.width = width;
|
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||||
self.height = height;
|
if let Some(info) = sessions_lock.get_mut(&display) {
|
||||||
|
info.size = (width, height);
|
||||||
|
} else {
|
||||||
|
sessions_lock.insert(
|
||||||
|
display,
|
||||||
|
DisplaySessionInfo {
|
||||||
|
texture_rgba_ptr: usize::default(),
|
||||||
|
size: (width, height),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
|
fn register_texture(&self, display: usize, ptr: usize) {
|
||||||
let ptr = self.ptr.read().unwrap();
|
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||||
if *ptr == usize::default() {
|
if ptr == 0 {
|
||||||
|
sessions_lock.remove(&display);
|
||||||
|
} else {
|
||||||
|
if let Some(info) = sessions_lock.get_mut(&display) {
|
||||||
|
if info.texture_rgba_ptr != 0 && info.texture_rgba_ptr != ptr as TextureRgbaPtr {
|
||||||
|
log::error!("unreachable, texture_rgba_ptr is not null and not equal to ptr");
|
||||||
|
}
|
||||||
|
info.texture_rgba_ptr = ptr as _;
|
||||||
|
} else {
|
||||||
|
if ptr != 0 {
|
||||||
|
sessions_lock.insert(
|
||||||
|
display,
|
||||||
|
DisplaySessionInfo {
|
||||||
|
texture_rgba_ptr: ptr as _,
|
||||||
|
size: (0, 0),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_rgba(&self, display: usize, rgba: &scrap::ImageRgb) {
|
||||||
|
let read_lock = self.map_display_sessions.read().unwrap();
|
||||||
|
let opt_info = if !self.is_support_multi_ui_session {
|
||||||
|
read_lock.values().next()
|
||||||
|
} else {
|
||||||
|
read_lock.get(&display)
|
||||||
|
};
|
||||||
|
let Some(info) = opt_info else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if info.texture_rgba_ptr == usize::default() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is also Ok to skip this check.
|
// It is also Ok to skip this check.
|
||||||
if self.width != rgba.w || self.height != rgba.h {
|
if info.size.0 != rgba.w || info.size.1 != rgba.h {
|
||||||
log::error!(
|
log::error!(
|
||||||
"width/height mismatch: ({},{}) != ({},{})",
|
"width/height mismatch: ({},{}) != ({},{})",
|
||||||
self.width,
|
info.size.0,
|
||||||
self.height,
|
info.size.1,
|
||||||
rgba.w,
|
rgba.w,
|
||||||
rgba.h
|
rgba.h
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(func) = &self.on_rgba_func {
|
if let Some(func) = &self.on_rgba_func {
|
||||||
unsafe {
|
unsafe {
|
||||||
func(
|
func(
|
||||||
*ptr as _,
|
info.texture_rgba_ptr as _,
|
||||||
rgba.raw.as_ptr() as _,
|
rgba.raw.as_ptr() as _,
|
||||||
rgba.raw.len() as _,
|
rgba.raw.len() as _,
|
||||||
rgba.w as _,
|
rgba.w as _,
|
||||||
@ -266,34 +327,42 @@ impl VideoRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SessionHandler {
|
||||||
|
pub fn on_waiting_for_image_dialog_show(&mut self) {
|
||||||
|
#[cfg(any(feature = "flutter_texture_render"))]
|
||||||
|
{
|
||||||
|
self.notify_rendered = false;
|
||||||
|
}
|
||||||
|
// rgba array render will notify every frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FlutterHandler {
|
impl FlutterHandler {
|
||||||
/// Push an event to the event queue.
|
/// Push an event to all the event queues.
|
||||||
/// An event is stored as json in the event queue.
|
/// An event is stored as json in the event queues.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `name` - The name of the event.
|
/// * `name` - The name of the event.
|
||||||
/// * `event` - Fields of the event content.
|
/// * `event` - Fields of the event content.
|
||||||
pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) -> Option<bool> {
|
pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) {
|
||||||
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
|
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
|
||||||
debug_assert!(h.get("name").is_none());
|
debug_assert!(h.get("name").is_none());
|
||||||
h.insert("name", name);
|
h.insert("name", name);
|
||||||
let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned());
|
let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned());
|
||||||
Some(
|
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
||||||
self.event_stream
|
if let Some(stream) = &session.event_stream {
|
||||||
.read()
|
stream.add(EventToUI::Event(out.clone()));
|
||||||
.unwrap()
|
}
|
||||||
.as_ref()?
|
}
|
||||||
.add(EventToUI::Event(out)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn close_event_stream(&self) {
|
pub(crate) fn close_event_stream(&self, session_id: SessionID) {
|
||||||
let mut stream_lock = self.event_stream.write().unwrap();
|
// to-do: Make sure the following logic is correct.
|
||||||
if let Some(stream) = &*stream_lock {
|
// No need to remove the display handler, because it will be removed when the connection is closed.
|
||||||
stream.add(EventToUI::Event("close".to_owned()));
|
if let Some(session) = self.session_handlers.write().unwrap().get_mut(&session_id) {
|
||||||
|
try_send_close_event(&session.event_stream);
|
||||||
}
|
}
|
||||||
*stream_lock = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_displays_msg(displays: &Vec<DisplayInfo>) -> String {
|
fn make_displays_msg(displays: &Vec<DisplayInfo>) -> String {
|
||||||
@ -314,6 +383,7 @@ impl FlutterHandler {
|
|||||||
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
|
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin_framework")]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub(crate) fn add_session_hook(&self, key: String, hook: SessionHook) -> bool {
|
pub(crate) fn add_session_hook(&self, key: String, hook: SessionHook) -> bool {
|
||||||
let mut hooks = self.hooks.write().unwrap();
|
let mut hooks = self.hooks.write().unwrap();
|
||||||
@ -325,6 +395,7 @@ impl FlutterHandler {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin_framework")]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub(crate) fn remove_session_hook(&self, key: &String) -> bool {
|
pub(crate) fn remove_session_hook(&self, key: &String) -> bool {
|
||||||
let mut hooks = self.hooks.write().unwrap();
|
let mut hooks = self.hooks.write().unwrap();
|
||||||
@ -335,27 +406,6 @@ impl FlutterHandler {
|
|||||||
let _ = hooks.remove(key);
|
let _ = hooks.remove(key);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
|
||||||
pub fn register_texture(&self, ptr: usize) {
|
|
||||||
*self.renderer.read().unwrap().ptr.write().unwrap() = ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
|
||||||
pub fn set_size(&self, width: usize, height: usize) {
|
|
||||||
*self.notify_rendered.write().unwrap() = false;
|
|
||||||
self.renderer.write().unwrap().set_size(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_waiting_for_image_dialog_show(&self) {
|
|
||||||
#[cfg(any(feature = "flutter_texture_render"))]
|
|
||||||
{
|
|
||||||
*self.notify_rendered.write().unwrap() = false;
|
|
||||||
}
|
|
||||||
// rgba array render will notify every frame
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InvokeUiSession for FlutterHandler {
|
impl InvokeUiSession for FlutterHandler {
|
||||||
@ -408,7 +458,10 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
"update_quality_status",
|
"update_quality_status",
|
||||||
vec![
|
vec![
|
||||||
("speed", &status.speed.map_or(NULL, |it| it)),
|
("speed", &status.speed.map_or(NULL, |it| it)),
|
||||||
("fps", &status.fps.map_or(NULL, |it| it.to_string())),
|
(
|
||||||
|
"fps",
|
||||||
|
&serde_json::ser::to_string(&status.fps).unwrap_or(NULL.to_owned()),
|
||||||
|
),
|
||||||
("delay", &status.delay.map_or(NULL, |it| it.to_string())),
|
("delay", &status.delay.map_or(NULL, |it| it.to_string())),
|
||||||
(
|
(
|
||||||
"target_bitrate",
|
"target_bitrate",
|
||||||
@ -529,7 +582,7 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(not(feature = "flutter_texture_render"))]
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
|
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
|
||||||
// Give a chance for plugins or etc to hook a rgba data.
|
// Give a chance for plugins or etc to hook a rgba data.
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
for (key, hook) in self.hooks.read().unwrap().iter() {
|
for (key, hook) in self.hooks.read().unwrap().iter() {
|
||||||
@ -541,27 +594,51 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
}
|
}
|
||||||
// If the current rgba is not fetched by flutter, i.e., is valid.
|
// If the current rgba is not fetched by flutter, i.e., is valid.
|
||||||
// We give up sending a new event to flutter.
|
// We give up sending a new event to flutter.
|
||||||
if self.rgba_valid.load(Ordering::Relaxed) {
|
let mut rgba_write_lock = self.display_rgbas.write().unwrap();
|
||||||
return;
|
if let Some(rgba_data) = rgba_write_lock.get_mut(&display) {
|
||||||
|
if rgba_data.valid {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
rgba_data.valid = true;
|
||||||
|
}
|
||||||
|
// Return the rgba buffer to the video handler for reusing allocated rgba buffer.
|
||||||
|
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut rgba_data.data);
|
||||||
|
} else {
|
||||||
|
let mut rgba_data = RgbaData::default();
|
||||||
|
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut rgba_data.data);
|
||||||
|
rgba_write_lock.insert(display, rgba_data);
|
||||||
}
|
}
|
||||||
self.rgba_valid.store(true, Ordering::Relaxed);
|
drop(rgba_write_lock);
|
||||||
// Return the rgba buffer to the video handler for reusing allocated rgba buffer.
|
|
||||||
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut *self.rgba.write().unwrap());
|
// Non-texture-render UI does not support multiple displays in the one UI session.
|
||||||
if let Some(stream) = &*self.event_stream.read().unwrap() {
|
// It's Ok to notify each session for now.
|
||||||
stream.add(EventToUI::Rgba);
|
for h in self.session_handlers.read().unwrap().values() {
|
||||||
|
if let Some(stream) = &h.event_stream {
|
||||||
|
stream.add(EventToUI::Rgba(display));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
|
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
|
||||||
self.renderer.read().unwrap().on_rgba(rgba);
|
let mut try_notify_sessions = Vec::new();
|
||||||
if *self.notify_rendered.read().unwrap() {
|
for (id, session) in self.session_handlers.read().unwrap().iter() {
|
||||||
return;
|
session.renderer.on_rgba(display, rgba);
|
||||||
|
if !session.notify_rendered {
|
||||||
|
try_notify_sessions.push(id.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(stream) = &*self.event_stream.read().unwrap() {
|
if try_notify_sessions.len() > 0 {
|
||||||
stream.add(EventToUI::Rgba);
|
let mut write_lock = self.session_handlers.write().unwrap();
|
||||||
*self.notify_rendered.write().unwrap() = true;
|
for id in try_notify_sessions.iter() {
|
||||||
|
if let Some(session) = write_lock.get_mut(id) {
|
||||||
|
if let Some(stream) = &session.event_stream {
|
||||||
|
stream.add(EventToUI::Rgba(display));
|
||||||
|
session.notify_rendered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,6 +655,17 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned());
|
let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned());
|
||||||
let resolutions = serialize_resolutions(&pi.resolutions.resolutions);
|
let resolutions = serialize_resolutions(&pi.resolutions.resolutions);
|
||||||
*self.peer_info.write().unwrap() = pi.clone();
|
*self.peer_info.write().unwrap() = pi.clone();
|
||||||
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
{
|
||||||
|
self.session_handlers
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.values_mut()
|
||||||
|
.for_each(|h| {
|
||||||
|
h.renderer.is_support_multi_ui_session =
|
||||||
|
crate::common::is_support_multi_ui_session(&pi.version);
|
||||||
|
});
|
||||||
|
}
|
||||||
self.push_event(
|
self.push_event(
|
||||||
"peer_info",
|
"peer_info",
|
||||||
vec![
|
vec![
|
||||||
@ -701,21 +789,31 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_rgba(&self) -> *const u8 {
|
fn get_rgba(&self, _display: usize) -> *const u8 {
|
||||||
#[cfg(not(feature = "flutter_texture_render"))]
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
if self.rgba_valid.load(Ordering::Relaxed) {
|
if let Some(rgba_data) = self.display_rgbas.read().unwrap().get(&_display) {
|
||||||
return self.rgba.read().unwrap().as_ptr();
|
if rgba_data.valid {
|
||||||
|
return rgba_data.data.as_ptr();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::ptr::null_mut()
|
std::ptr::null_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn next_rgba(&self) {
|
fn next_rgba(&self, _display: usize) {
|
||||||
#[cfg(not(feature = "flutter_texture_render"))]
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
self.rgba_valid.store(false, Ordering::Relaxed);
|
if let Some(rgba_data) = self.display_rgbas.write().unwrap().get_mut(&_display) {
|
||||||
|
rgba_data.valid = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function is only used for the default connection session.
|
||||||
|
pub fn session_add_existed(peer_id: String, session_id: SessionID) -> ResultType<()> {
|
||||||
|
sessions::insert_peer_session_id(peer_id, ConnType::DEFAULT_CONN, session_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new remote session with the given id.
|
/// Create a new remote session with the given id.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -733,18 +831,6 @@ pub fn session_add(
|
|||||||
force_relay: bool,
|
force_relay: bool,
|
||||||
password: String,
|
password: String,
|
||||||
) -> ResultType<FlutterSession> {
|
) -> ResultType<FlutterSession> {
|
||||||
LocalConfig::set_remote_id(&id);
|
|
||||||
|
|
||||||
let session: Session<FlutterHandler> = Session {
|
|
||||||
session_id: session_id.clone(),
|
|
||||||
id: id.to_owned(),
|
|
||||||
password,
|
|
||||||
server_keyboard_enabled: Arc::new(RwLock::new(true)),
|
|
||||||
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
|
|
||||||
server_clipboard_enabled: Arc::new(RwLock::new(true)),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let conn_type = if is_file_transfer {
|
let conn_type = if is_file_transfer {
|
||||||
ConnType::FILE_TRANSFER
|
ConnType::FILE_TRANSFER
|
||||||
} else if is_port_forward {
|
} else if is_port_forward {
|
||||||
@ -757,6 +843,26 @@ pub fn session_add(
|
|||||||
ConnType::DEFAULT_CONN
|
ConnType::DEFAULT_CONN
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// to-do: check the same id session.
|
||||||
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
|
if session.lc.read().unwrap().conn_type != conn_type {
|
||||||
|
bail!("same session id is found with different conn type?");
|
||||||
|
}
|
||||||
|
// The same session is added before?
|
||||||
|
bail!("same session id is found");
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalConfig::set_remote_id(&id);
|
||||||
|
|
||||||
|
let session: Session<FlutterHandler> = Session {
|
||||||
|
id: id.to_owned(),
|
||||||
|
password,
|
||||||
|
server_keyboard_enabled: Arc::new(RwLock::new(true)),
|
||||||
|
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
|
||||||
|
server_clipboard_enabled: Arc::new(RwLock::new(true)),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let switch_uuid = if switch_uuid.is_empty() {
|
let switch_uuid = if switch_uuid.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -768,12 +874,8 @@ pub fn session_add(
|
|||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.initialize(id.to_owned(), conn_type, switch_uuid, force_relay);
|
.initialize(id.to_owned(), conn_type, switch_uuid, force_relay);
|
||||||
|
|
||||||
let session = Arc::new(session.clone());
|
let session = Arc::new(session.clone());
|
||||||
if let Some(same_id_session) = sessions::add_session(session_id.to_owned(), session.clone()) {
|
sessions::insert_session(session_id.to_owned(), conn_type, session.clone());
|
||||||
log::error!("Should not happen");
|
|
||||||
same_id_session.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(session)
|
Ok(session)
|
||||||
}
|
}
|
||||||
@ -789,18 +891,39 @@ pub fn session_start_(
|
|||||||
id: &str,
|
id: &str,
|
||||||
event_stream: StreamSink<EventToUI>,
|
event_stream: StreamSink<EventToUI>,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
if let Some(session) = sessions::get_session(session_id) {
|
// is_connected is used to indicate whether to start a peer connection. For two cases:
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
// 1. "Move tab to new window"
|
||||||
log::info!(
|
// 2. multi ui session within the same peer connnection.
|
||||||
"Session {} start, render by flutter texture rgba plugin",
|
let mut is_connected = false;
|
||||||
id
|
let mut is_found = false;
|
||||||
|
for s in sessions::get_sessions() {
|
||||||
|
if let Some(h) = s.session_handlers.write().unwrap().get_mut(session_id) {
|
||||||
|
is_connected = h.event_stream.is_some();
|
||||||
|
try_send_close_event(&h.event_stream);
|
||||||
|
h.event_stream = Some(event_stream);
|
||||||
|
is_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !is_found {
|
||||||
|
bail!(
|
||||||
|
"No session with peer id {}, session id: {}",
|
||||||
|
id,
|
||||||
|
session_id.to_string()
|
||||||
);
|
);
|
||||||
#[cfg(not(feature = "flutter_texture_render"))]
|
}
|
||||||
log::info!("Session {} start, render by flutter paint widget", id);
|
|
||||||
let is_pre_added = session.event_stream.read().unwrap().is_some();
|
if let Some(session) = sessions::get_session_by_session_id(session_id) {
|
||||||
session.close_event_stream();
|
let is_first_ui_session = session.session_handlers.read().unwrap().len() == 1;
|
||||||
*session.event_stream.write().unwrap() = Some(event_stream);
|
if !is_connected && is_first_ui_session {
|
||||||
if !is_pre_added {
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
log::info!(
|
||||||
|
"Session {} start, render by flutter texture rgba plugin",
|
||||||
|
id
|
||||||
|
);
|
||||||
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
|
log::info!("Session {} start, render by flutter paint widget", id);
|
||||||
|
|
||||||
let session = (*session).clone();
|
let session = (*session).clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let round = session.connection_round_state.lock().unwrap().new_round();
|
let round = session.connection_round_state.lock().unwrap().new_round();
|
||||||
@ -813,19 +936,26 @@ pub fn session_start_(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_send_close_event(event_stream: &Option<StreamSink<EventToUI>>) {
|
||||||
|
if let Some(stream) = &event_stream {
|
||||||
|
stream.add(EventToUI::Event("close".to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub fn update_text_clipboard_required() {
|
pub fn update_text_clipboard_required() {
|
||||||
let is_required = sessions::get_sessions()
|
let is_required = sessions::get_sessions()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|session| session.is_text_clipboard_required());
|
.any(|s| s.is_text_clipboard_required());
|
||||||
Client::set_is_text_clipboard_required(is_required);
|
Client::set_is_text_clipboard_required(is_required);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub fn send_text_clipboard_msg(msg: Message) {
|
pub fn send_text_clipboard_msg(msg: Message) {
|
||||||
for session in sessions::get_sessions() {
|
for s in sessions::get_sessions() {
|
||||||
if session.is_text_clipboard_required() {
|
if s.is_text_clipboard_required() {
|
||||||
session.send(Data::Message(msg.clone()));
|
s.send(Data::Message(msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -998,6 +1128,11 @@ pub fn get_cur_session_id() -> SessionID {
|
|||||||
CUR_SESSION_ID.read().unwrap().clone()
|
CUR_SESSION_ID.read().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_cur_peer_id() -> String {
|
||||||
|
sessions::get_peer_id_by_session_id(&get_cur_session_id(), ConnType::DEFAULT_CONN)
|
||||||
|
.unwrap_or("".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_cur_session_id(session_id: SessionID) {
|
pub fn set_cur_session_id(session_id: SessionID) {
|
||||||
if get_cur_session_id() != session_id {
|
if get_cur_session_id() != session_id {
|
||||||
*CUR_SESSION_ID.write().unwrap() = session_id;
|
*CUR_SESSION_ID.write().unwrap() = session_id;
|
||||||
@ -1034,47 +1169,76 @@ fn char_to_session_id(c: *const char) -> ResultType<SessionID> {
|
|||||||
SessionID::from_str(str).map_err(|e| anyhow!("{:?}", e))
|
SessionID::from_str(str).map_err(|e| anyhow!("{:?}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_rgba_size(_session_id: SessionID) -> usize {
|
pub fn session_get_rgba_size(_session_id: SessionID, _display: usize) -> usize {
|
||||||
#[cfg(not(feature = "flutter_texture_render"))]
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
if let Some(session) = sessions::get_session(&_session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&_session_id) {
|
||||||
return session.rgba.read().unwrap().len();
|
return session
|
||||||
|
.display_rgbas
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(&_display)
|
||||||
|
.map_or(0, |rgba| rgba.data.len());
|
||||||
}
|
}
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
|
pub extern "C" fn session_get_rgba(session_uuid_str: *const char, display: usize) -> *const u8 {
|
||||||
if let Ok(session_id) = char_to_session_id(session_uuid_str) {
|
if let Ok(session_id) = char_to_session_id(session_uuid_str) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
|
||||||
return session.get_rgba();
|
return s.ui_handler.get_rgba(display);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ptr::null()
|
std::ptr::null()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_next_rgba(session_id: SessionID) {
|
pub fn session_next_rgba(session_id: SessionID, display: usize) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
|
||||||
return session.next_rgba();
|
return s.ui_handler.next_rgba(display);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn session_register_texture(_session_id: SessionID, _ptr: usize) {
|
pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, _height: usize) {
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
if let Some(session) = sessions::get_session(&_session_id) {
|
for s in sessions::get_sessions() {
|
||||||
session.register_texture(_ptr);
|
if let Some(h) = s
|
||||||
return;
|
.ui_handler
|
||||||
|
.session_handlers
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.get_mut(&_session_id)
|
||||||
|
{
|
||||||
|
h.notify_rendered = false;
|
||||||
|
h.renderer.set_size(_display, _width, _height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn push_session_event(
|
pub fn session_register_texture(_session_id: SessionID, _display: usize, _ptr: usize) {
|
||||||
session_id: &SessionID,
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
name: &str,
|
for s in sessions::get_sessions() {
|
||||||
event: Vec<(&str, &str)>,
|
if let Some(h) = s
|
||||||
) -> Option<bool> {
|
.ui_handler
|
||||||
sessions::get_session(session_id)?.push_event(name, event)
|
.session_handlers
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(&_session_id)
|
||||||
|
{
|
||||||
|
h.renderer.register_texture(_display, _ptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn push_session_event(session_id: &SessionID, name: &str, event: Vec<(&str, &str)>) {
|
||||||
|
if let Some(s) = sessions::get_session_by_session_id(session_id) {
|
||||||
|
s.push_event(name, event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -1123,7 +1287,7 @@ fn session_send_touch_scale(
|
|||||||
) {
|
) {
|
||||||
match v.get("v").and_then(|s| s.as_i64()) {
|
match v.get("v").and_then(|s| s.as_i64()) {
|
||||||
Some(scale) => {
|
Some(scale) => {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
|
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1147,7 +1311,7 @@ fn session_send_touch_pan(
|
|||||||
v.get("y").and_then(|y| y.as_i64()),
|
v.get("y").and_then(|y| y.as_i64()),
|
||||||
) {
|
) {
|
||||||
(Some(x), Some(y)) => {
|
(Some(x), Some(y)) => {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session
|
session
|
||||||
.send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command);
|
.send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command);
|
||||||
}
|
}
|
||||||
@ -1191,6 +1355,15 @@ pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
|
||||||
|
for s in sessions::get_sessions() {
|
||||||
|
if let Some(h) = s.session_handlers.write().unwrap().get_mut(&session_id) {
|
||||||
|
h.on_waiting_for_image_dialog_show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Hooks for session.
|
/// Hooks for session.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum SessionHook {
|
pub enum SessionHook {
|
||||||
@ -1199,7 +1372,7 @@ pub enum SessionHook {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_cur_session() -> Option<FlutterSession> {
|
pub fn get_cur_session() -> Option<FlutterSession> {
|
||||||
sessions::get_session(&*CUR_SESSION_ID.read().unwrap())
|
sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessions mod is used to avoid the big lock of sessions' map.
|
// sessions mod is used to avoid the big lock of sessions' map.
|
||||||
@ -1207,22 +1380,118 @@ pub mod sessions {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref SESSIONS: RwLock<HashMap<SessionID, FlutterSession>> = Default::default();
|
// peer -> peer session, peer session -> ui sessions
|
||||||
|
static ref SESSIONS: RwLock<HashMap<(String, ConnType), FlutterSession>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn add_session(session_id: SessionID, session: FlutterSession) -> Option<FlutterSession> {
|
pub fn get_session_count(peer_id: String, conn_type: ConnType) -> usize {
|
||||||
SESSIONS.write().unwrap().insert(session_id, session)
|
SESSIONS
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(&(peer_id, conn_type))
|
||||||
|
.map(|s| s.ui_handler.session_handlers.read().unwrap().len())
|
||||||
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove_session(session_id: &SessionID) -> Option<FlutterSession> {
|
pub fn get_peer_id_by_session_id(id: &SessionID, conn_type: ConnType) -> Option<String> {
|
||||||
SESSIONS.write().unwrap().remove(session_id)
|
SESSIONS
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.find_map(|((peer_id, t), s)| {
|
||||||
|
if *t == conn_type
|
||||||
|
&& s.ui_handler
|
||||||
|
.session_handlers
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.contains_key(id)
|
||||||
|
{
|
||||||
|
Some(peer_id.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_session(session_id: &SessionID) -> Option<FlutterSession> {
|
pub fn get_session_by_session_id(id: &SessionID) -> Option<FlutterSession> {
|
||||||
SESSIONS.read().unwrap().get(session_id).cloned()
|
SESSIONS
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.values()
|
||||||
|
.find(|s| {
|
||||||
|
s.ui_handler
|
||||||
|
.session_handlers
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.contains_key(id)
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_session_by_peer_id(peer_id: String, conn_type: ConnType) -> Option<FlutterSession> {
|
||||||
|
SESSIONS.read().unwrap().get(&(peer_id, conn_type)).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn remove_session_by_session_id(id: &SessionID) -> Option<FlutterSession> {
|
||||||
|
let mut remove_peer_key = None;
|
||||||
|
for (peer_key, s) in SESSIONS.write().unwrap().iter_mut() {
|
||||||
|
let mut write_lock = s.ui_handler.session_handlers.write().unwrap();
|
||||||
|
if write_lock.remove(id).is_some() {
|
||||||
|
if write_lock.is_empty() {
|
||||||
|
remove_peer_key = Some(peer_key.clone());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SESSIONS.write().unwrap().remove(&remove_peer_key?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn insert_session(session_id: SessionID, conn_type: ConnType, session: FlutterSession) {
|
||||||
|
SESSIONS
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.entry((session.id.clone(), conn_type))
|
||||||
|
.or_insert(session)
|
||||||
|
.ui_handler
|
||||||
|
.session_handlers
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.insert(session_id, Default::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn insert_peer_session_id(
|
||||||
|
peer_id: String,
|
||||||
|
conn_type: ConnType,
|
||||||
|
session_id: SessionID,
|
||||||
|
) -> bool {
|
||||||
|
if let Some(s) = SESSIONS.read().unwrap().get(&(peer_id, conn_type)) {
|
||||||
|
#[cfg(not(feature = "flutter_texture_render"))]
|
||||||
|
let h = SessionHandler::default();
|
||||||
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
let mut h = SessionHandler::default();
|
||||||
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
|
{
|
||||||
|
h.renderer.is_support_multi_ui_session = crate::common::is_support_multi_ui_session(
|
||||||
|
&s.ui_handler.peer_info.read().unwrap().version,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let _ = s
|
||||||
|
.ui_handler
|
||||||
|
.session_handlers
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.insert(session_id, h);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -1230,26 +1499,14 @@ pub mod sessions {
|
|||||||
SESSIONS.read().unwrap().values().cloned().collect()
|
SESSIONS.read().unwrap().values().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_session_by_peer_id(peer_id: &str) -> Option<FlutterSession> {
|
|
||||||
SESSIONS
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.values()
|
|
||||||
.find(|session| session.id == peer_id)
|
|
||||||
.map(|s| s.clone())
|
|
||||||
.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub fn other_sessions_running(session_id: &SessionID) -> bool {
|
pub fn other_sessions_running(peer_id: String, conn_type: ConnType) -> bool {
|
||||||
SESSIONS
|
SESSIONS
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.keys()
|
.get(&(peer_id, conn_type))
|
||||||
.filter(|k| *k != session_id)
|
.map(|s| s.session_handlers.read().unwrap().len() != 0)
|
||||||
.count()
|
.unwrap_or(false)
|
||||||
!= 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
client::file_trait::FileManager,
|
client::file_trait::FileManager,
|
||||||
common::is_keyboard_mode_supported,
|
common::is_keyboard_mode_supported,
|
||||||
common::make_fd_to_json,
|
common::make_fd_to_json,
|
||||||
flutter::{self, session_add, session_start_, sessions},
|
flutter::{self, session_add, session_add_existed, session_start_, sessions},
|
||||||
input::*,
|
input::*,
|
||||||
ui_interface::{self, *},
|
ui_interface::{self, *},
|
||||||
};
|
};
|
||||||
@ -16,6 +16,7 @@ use hbb_common::{
|
|||||||
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
|
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
|
||||||
fs, lazy_static, log,
|
fs, lazy_static, log,
|
||||||
message_proto::KeyboardMode,
|
message_proto::KeyboardMode,
|
||||||
|
rendezvous_proto::ConnType,
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
@ -67,7 +68,7 @@ pub fn stop_global_event_stream(app_type: String) {
|
|||||||
}
|
}
|
||||||
pub enum EventToUI {
|
pub enum EventToUI {
|
||||||
Event(String),
|
Event(String),
|
||||||
Rgba,
|
Rgba(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn host_stop_system_key_propagate(_stopped: bool) {
|
pub fn host_stop_system_key_propagate(_stopped: bool) {
|
||||||
@ -75,8 +76,26 @@ pub fn host_stop_system_key_propagate(_stopped: bool) {
|
|||||||
crate::platform::windows::stop_system_key_propagate(_stopped);
|
crate::platform::windows::stop_system_key_propagate(_stopped);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: -> ResultType<()> cannot be parsed by frb_codegen
|
// This function is only used to count the number of control sessions.
|
||||||
// thread 'main' panicked at 'Failed to parse function output type `ResultType<()>`', $HOME\.cargo\git\checkouts\flutter_rust_bridge-ddba876d3ebb2a1e\e5adce5\frb_codegen\src\parser\mod.rs:151:25
|
pub fn peer_get_default_sessions_count(id: String) -> SyncReturn<usize> {
|
||||||
|
SyncReturn(sessions::get_session_count(id, ConnType::DEFAULT_CONN))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_add_existed_sync(id: String, session_id: SessionID) -> SyncReturn<String> {
|
||||||
|
if let Err(e) = session_add_existed(id.clone(), session_id) {
|
||||||
|
SyncReturn(format!("Failed to add session with id {}, {}", &id, e))
|
||||||
|
} else {
|
||||||
|
SyncReturn("".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_try_add_display(session_id: SessionID, displays: Vec<i32>) -> SyncReturn<()> {
|
||||||
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
|
session.capture_displays(displays, vec![], vec![]);
|
||||||
|
}
|
||||||
|
SyncReturn(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_add_sync(
|
pub fn session_add_sync(
|
||||||
session_id: SessionID,
|
session_id: SessionID,
|
||||||
id: String,
|
id: String,
|
||||||
@ -112,7 +131,7 @@ pub fn session_start(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_remember(session_id: SessionID) -> Option<bool> {
|
pub fn session_get_remember(session_id: SessionID) -> Option<bool> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_remember())
|
Some(session.get_remember())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -120,7 +139,7 @@ pub fn session_get_remember(session_id: SessionID) -> Option<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_toggle_option(session_id: SessionID, arg: String) -> Option<bool> {
|
pub fn session_get_toggle_option(session_id: SessionID, arg: String) -> Option<bool> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_toggle_option(arg))
|
Some(session.get_toggle_option(arg))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -133,7 +152,7 @@ pub fn session_get_toggle_option_sync(session_id: SessionID, arg: String) -> Syn
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_option(session_id: SessionID, arg: String) -> Option<String> {
|
pub fn session_get_option(session_id: SessionID, arg: String) -> Option<String> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_option(arg))
|
Some(session.get_option(arg))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -147,55 +166,61 @@ pub fn session_login(
|
|||||||
password: String,
|
password: String,
|
||||||
remember: bool,
|
remember: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.login(os_username, os_password, password, remember);
|
session.login(os_username, os_password, password, remember);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_close(session_id: SessionID) {
|
pub fn session_close(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::remove_session(&session_id) {
|
if let Some(session) = sessions::remove_session_by_session_id(&session_id) {
|
||||||
session.close_event_stream();
|
session.close_event_stream(session_id);
|
||||||
session.close();
|
session.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_refresh(session_id: SessionID) {
|
pub fn session_refresh(session_id: SessionID, display: usize) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.refresh_video();
|
session.refresh_video(display as _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_record_screen(session_id: SessionID, start: bool, width: usize, height: usize) {
|
pub fn session_record_screen(
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
session_id: SessionID,
|
||||||
session.record_screen(start, width as _, height as _);
|
start: bool,
|
||||||
|
display: usize,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
) {
|
||||||
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
|
session.record_screen(start, display as _, width as _, height as _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_record_status(session_id: SessionID, status: bool) {
|
pub fn session_record_status(session_id: SessionID, status: bool) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.record_status(status);
|
session.record_status(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_reconnect(session_id: SessionID, force_relay: bool) {
|
pub fn session_reconnect(session_id: SessionID, force_relay: bool) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.reconnect(force_relay);
|
session.reconnect(force_relay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_toggle_option(session_id: SessionID, value: String) {
|
pub fn session_toggle_option(session_id: SessionID, value: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
log::warn!("toggle option {}", &value);
|
log::warn!("toggle option {}", &value);
|
||||||
session.toggle_option(value.clone());
|
session.toggle_option(value.clone());
|
||||||
}
|
}
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
if sessions::get_session(&session_id).is_some() && value == "disable-clipboard" {
|
if sessions::get_session_by_session_id(&session_id).is_some() && value == "disable-clipboard" {
|
||||||
crate::flutter::update_text_clipboard_required();
|
crate::flutter::update_text_clipboard_required();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<String> {
|
pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<String> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_flutter_option(k))
|
Some(session.get_flutter_option(k))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -203,13 +228,14 @@ pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<St
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_set_flutter_option(session_id: SessionID, k: String, v: String) {
|
pub fn session_set_flutter_option(session_id: SessionID, k: String, v: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.save_flutter_option(k, v);
|
session.save_flutter_option(k, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function is only used for the default connection session.
|
||||||
pub fn session_get_flutter_option_by_peer_id(id: String, k: String) -> Option<String> {
|
pub fn session_get_flutter_option_by_peer_id(id: String, k: String) -> Option<String> {
|
||||||
if let Some(session) = sessions::get_session_by_peer_id(&id) {
|
if let Some(session) = sessions::get_session_by_peer_id(id, ConnType::DEFAULT_CONN) {
|
||||||
Some(session.get_flutter_option(k))
|
Some(session.get_flutter_option(k))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -238,7 +264,7 @@ pub fn set_local_kb_layout_type(kb_layout_type: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_view_style(session_id: SessionID) -> Option<String> {
|
pub fn session_get_view_style(session_id: SessionID) -> Option<String> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_view_style())
|
Some(session.get_view_style())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -246,13 +272,13 @@ pub fn session_get_view_style(session_id: SessionID) -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_set_view_style(session_id: SessionID, value: String) {
|
pub fn session_set_view_style(session_id: SessionID, value: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.save_view_style(value);
|
session.save_view_style(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_scroll_style(session_id: SessionID) -> Option<String> {
|
pub fn session_get_scroll_style(session_id: SessionID) -> Option<String> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_scroll_style())
|
Some(session.get_scroll_style())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -260,13 +286,13 @@ pub fn session_get_scroll_style(session_id: SessionID) -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_set_scroll_style(session_id: SessionID, value: String) {
|
pub fn session_set_scroll_style(session_id: SessionID, value: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.save_scroll_style(value);
|
session.save_scroll_style(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_image_quality(session_id: SessionID) -> Option<String> {
|
pub fn session_get_image_quality(session_id: SessionID) -> Option<String> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_image_quality())
|
Some(session.get_image_quality())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -274,13 +300,13 @@ pub fn session_get_image_quality(session_id: SessionID) -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_set_image_quality(session_id: SessionID, value: String) {
|
pub fn session_set_image_quality(session_id: SessionID, value: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.save_image_quality(value);
|
session.save_image_quality(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_keyboard_mode(session_id: SessionID) -> Option<String> {
|
pub fn session_get_keyboard_mode(session_id: SessionID) -> Option<String> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_keyboard_mode())
|
Some(session.get_keyboard_mode())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -289,7 +315,7 @@ pub fn session_get_keyboard_mode(session_id: SessionID) -> Option<String> {
|
|||||||
|
|
||||||
pub fn session_set_keyboard_mode(session_id: SessionID, value: String) {
|
pub fn session_set_keyboard_mode(session_id: SessionID, value: String) {
|
||||||
let mut _mode_updated = false;
|
let mut _mode_updated = false;
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.save_keyboard_mode(value.clone());
|
session.save_keyboard_mode(value.clone());
|
||||||
_mode_updated = true;
|
_mode_updated = true;
|
||||||
}
|
}
|
||||||
@ -300,7 +326,7 @@ pub fn session_set_keyboard_mode(session_id: SessionID, value: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String> {
|
pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
Some(session.get_reverse_mouse_wheel())
|
Some(session.get_reverse_mouse_wheel())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -308,13 +334,27 @@ pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) {
|
pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.save_reverse_mouse_wheel(value);
|
session.save_reverse_mouse_wheel(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
SyncReturn(Some(session.get_displays_as_individual_windows()))
|
||||||
|
} else {
|
||||||
|
SyncReturn(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_set_displays_as_individual_windows(session_id: SessionID, value: String) {
|
||||||
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
|
session.save_displays_as_individual_windows(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(&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())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -322,7 +362,7 @@ pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) -> SyncReturn<bool> {
|
pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) -> SyncReturn<bool> {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
if let Ok(mode) = KeyboardMode::from_str(&mode[..]) {
|
if let Ok(mode) = KeyboardMode::from_str(&mode[..]) {
|
||||||
SyncReturn(is_keyboard_mode_supported(
|
SyncReturn(is_keyboard_mode_supported(
|
||||||
&mode,
|
&mode,
|
||||||
@ -337,32 +377,36 @@ pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_set_custom_image_quality(session_id: SessionID, value: i32) {
|
pub fn session_set_custom_image_quality(session_id: SessionID, value: i32) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.save_custom_image_quality(value);
|
session.save_custom_image_quality(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_set_custom_fps(session_id: SessionID, fps: i32) {
|
pub fn session_set_custom_fps(session_id: SessionID, fps: i32) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.set_custom_fps(fps);
|
session.set_custom_fps(fps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_lock_screen(session_id: SessionID) {
|
pub fn session_lock_screen(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.lock_screen();
|
session.lock_screen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_ctrl_alt_del(session_id: SessionID) {
|
pub fn session_ctrl_alt_del(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.ctrl_alt_del();
|
session.ctrl_alt_del();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_switch_display(session_id: SessionID, value: i32) {
|
pub fn session_switch_display(session_id: SessionID, value: Vec<i32>) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.switch_display(value);
|
if value.len() == 1 {
|
||||||
|
session.switch_display(value[0]);
|
||||||
|
} else {
|
||||||
|
session.capture_displays(vec![], vec![], value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +418,7 @@ pub fn session_handle_flutter_key_event(
|
|||||||
lock_modes: i32,
|
lock_modes: i32,
|
||||||
down_or_up: bool,
|
down_or_up: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
let keyboard_mode = session.get_keyboard_mode();
|
let keyboard_mode = session.get_keyboard_mode();
|
||||||
session.handle_flutter_key_event(
|
session.handle_flutter_key_event(
|
||||||
&keyboard_mode,
|
&keyboard_mode,
|
||||||
@ -395,7 +439,7 @@ pub fn session_handle_flutter_key_event(
|
|||||||
// This will cause the keyboard input to take no effect.
|
// This will cause the keyboard input to take no effect.
|
||||||
pub fn session_enter_or_leave(_session_id: SessionID, _enter: bool) -> SyncReturn<()> {
|
pub fn session_enter_or_leave(_session_id: SessionID, _enter: bool) -> SyncReturn<()> {
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
if let Some(session) = sessions::get_session(&_session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&_session_id) {
|
||||||
let keyboard_mode = session.get_keyboard_mode();
|
let keyboard_mode = session.get_keyboard_mode();
|
||||||
if _enter {
|
if _enter {
|
||||||
set_cur_session_id_(_session_id, &keyboard_mode);
|
set_cur_session_id_(_session_id, &keyboard_mode);
|
||||||
@ -417,14 +461,14 @@ pub fn session_input_key(
|
|||||||
shift: bool,
|
shift: bool,
|
||||||
command: bool,
|
command: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
session.input_key(&name, down, press, alt, ctrl, shift, command);
|
session.input_key(&name, down, press, alt, ctrl, shift, command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_input_string(session_id: SessionID, value: String) {
|
pub fn session_input_string(session_id: SessionID, value: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
session.input_string(&value);
|
session.input_string(&value);
|
||||||
}
|
}
|
||||||
@ -432,33 +476,33 @@ pub fn session_input_string(session_id: SessionID, value: String) {
|
|||||||
|
|
||||||
// chat_client_mode
|
// chat_client_mode
|
||||||
pub fn session_send_chat(session_id: SessionID, text: String) {
|
pub fn session_send_chat(session_id: SessionID, text: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.send_chat(text);
|
session.send_chat(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_peer_option(session_id: SessionID, name: String, value: String) {
|
pub fn session_peer_option(session_id: SessionID, name: String, value: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.set_option(name, value);
|
session.set_option(name, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_peer_option(session_id: SessionID, name: String) -> String {
|
pub fn session_get_peer_option(session_id: SessionID, name: String) -> String {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
return session.get_option(name);
|
return session.get_option(name);
|
||||||
}
|
}
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_input_os_password(session_id: SessionID, value: String) {
|
pub fn session_input_os_password(session_id: SessionID, value: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.input_os_password(value, true);
|
session.input_os_password(value, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// File Action
|
// File Action
|
||||||
pub fn session_read_remote_dir(session_id: SessionID, path: String, include_hidden: bool) {
|
pub fn session_read_remote_dir(session_id: SessionID, path: String, include_hidden: bool) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.read_remote_dir(path, include_hidden);
|
session.read_remote_dir(path, include_hidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -472,7 +516,7 @@ pub fn session_send_files(
|
|||||||
include_hidden: bool,
|
include_hidden: bool,
|
||||||
is_remote: bool,
|
is_remote: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
|
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -485,7 +529,7 @@ pub fn session_set_confirm_override_file(
|
|||||||
remember: bool,
|
remember: bool,
|
||||||
is_upload: bool,
|
is_upload: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.set_confirm_override_file(act_id, file_num, need_override, remember, is_upload);
|
session.set_confirm_override_file(act_id, file_num, need_override, remember, is_upload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -497,7 +541,7 @@ pub fn session_remove_file(
|
|||||||
file_num: i32,
|
file_num: i32,
|
||||||
is_remote: bool,
|
is_remote: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.remove_file(act_id, path, file_num, is_remote);
|
session.remove_file(act_id, path, file_num, is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,7 +553,7 @@ pub fn session_read_dir_recursive(
|
|||||||
is_remote: bool,
|
is_remote: bool,
|
||||||
show_hidden: bool,
|
show_hidden: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.remove_dir_all(act_id, path, is_remote, show_hidden);
|
session.remove_dir_all(act_id, path, is_remote, show_hidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -520,19 +564,19 @@ pub fn session_remove_all_empty_dirs(
|
|||||||
path: String,
|
path: String,
|
||||||
is_remote: bool,
|
is_remote: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.remove_dir(act_id, path, is_remote);
|
session.remove_dir(act_id, path, is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_cancel_job(session_id: SessionID, act_id: i32) {
|
pub fn session_cancel_job(session_id: SessionID, act_id: i32) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.cancel_job(act_id);
|
session.cancel_job(act_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_create_dir(session_id: SessionID, act_id: i32, path: String, is_remote: bool) {
|
pub fn session_create_dir(session_id: SessionID, act_id: i32, path: String, is_remote: bool) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.create_dir(act_id, path, is_remote);
|
session.create_dir(act_id, path, is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -549,14 +593,14 @@ pub fn session_read_local_dir_sync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_platform(session_id: SessionID, is_remote: bool) -> String {
|
pub fn session_get_platform(session_id: SessionID, is_remote: bool) -> String {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
return session.get_platform(is_remote);
|
return session.get_platform(is_remote);
|
||||||
}
|
}
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_load_last_transfer_jobs(session_id: SessionID) {
|
pub fn session_load_last_transfer_jobs(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
return session.load_last_jobs();
|
return session.load_last_jobs();
|
||||||
} else {
|
} else {
|
||||||
// a tip for flutter dev
|
// a tip for flutter dev
|
||||||
@ -576,46 +620,44 @@ pub fn session_add_job(
|
|||||||
include_hidden: bool,
|
include_hidden: bool,
|
||||||
is_remote: bool,
|
is_remote: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.add_job(act_id, path, to, file_num, include_hidden, is_remote);
|
session.add_job(act_id, path, to, file_num, include_hidden, is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_resume_job(session_id: SessionID, act_id: i32, is_remote: bool) {
|
pub fn session_resume_job(session_id: SessionID, act_id: i32, is_remote: bool) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.resume_job(act_id, is_remote);
|
session.resume_job(act_id, is_remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_elevate_direct(session_id: SessionID) {
|
pub fn session_elevate_direct(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.elevate_direct();
|
session.elevate_direct();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_elevate_with_logon(session_id: SessionID, username: String, password: String) {
|
pub fn session_elevate_with_logon(session_id: SessionID, username: String, password: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.elevate_with_logon(username, password);
|
session.elevate_with_logon(username, password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_switch_sides(session_id: SessionID) {
|
pub fn session_switch_sides(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.switch_sides();
|
session.switch_sides();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32, height: i32) {
|
pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32, height: i32) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.change_resolution(display, width, height);
|
session.change_resolution(display, width, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) {
|
pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, _height: usize) {
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
if let Some(session) = sessions::get_session(&_session_id) {
|
super::flutter::session_set_size(_session_id, _display, _width, _height)
|
||||||
session.set_size(_width, _height);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_get_sound_inputs() -> Vec<String> {
|
pub fn main_get_sound_inputs() -> Vec<String> {
|
||||||
@ -1008,18 +1050,22 @@ pub fn main_handle_relay_id(id: String) -> String {
|
|||||||
handle_relay_id(id)
|
handle_relay_id(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_get_current_display() -> SyncReturn<String> {
|
pub fn main_get_main_display() -> SyncReturn<String> {
|
||||||
#[cfg(not(target_os = "ios"))]
|
|
||||||
let display_info = match crate::video_service::get_current_display() {
|
|
||||||
Ok((_, _, display)) => serde_json::to_string(&HashMap::from([
|
|
||||||
("w", display.width()),
|
|
||||||
("h", display.height()),
|
|
||||||
]))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
Err(..) => "".to_string(),
|
|
||||||
};
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
let display_info = "".to_owned();
|
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() {
|
||||||
|
// to-do: Need to detect current display index.
|
||||||
|
if let Some(display) = displays.iter().next() {
|
||||||
|
display_info = serde_json::to_string(&HashMap::from([
|
||||||
|
("w", display.width()),
|
||||||
|
("h", display.height()),
|
||||||
|
]))
|
||||||
|
.unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
SyncReturn(display_info)
|
SyncReturn(display_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1029,31 +1075,31 @@ pub fn session_add_port_forward(
|
|||||||
remote_host: String,
|
remote_host: String,
|
||||||
remote_port: i32,
|
remote_port: i32,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.add_port_forward(local_port, remote_host, remote_port);
|
session.add_port_forward(local_port, remote_host, remote_port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_remove_port_forward(session_id: SessionID, local_port: i32) {
|
pub fn session_remove_port_forward(session_id: SessionID, local_port: i32) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.remove_port_forward(local_port);
|
session.remove_port_forward(local_port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_new_rdp(session_id: SessionID) {
|
pub fn session_new_rdp(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.new_rdp();
|
session.new_rdp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_request_voice_call(session_id: SessionID) {
|
pub fn session_request_voice_call(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.request_voice_call();
|
session.request_voice_call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_close_voice_call(session_id: SessionID) {
|
pub fn session_close_voice_call(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.close_voice_call();
|
session.close_voice_call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1238,20 +1284,20 @@ pub fn session_send_mouse(session_id: SessionID, msg: String) {
|
|||||||
_ => 0,
|
_ => 0,
|
||||||
} << 3;
|
} << 3;
|
||||||
}
|
}
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.send_mouse(mask, x, y, alt, ctrl, shift, command);
|
session.send_mouse(mask, x, y, alt, ctrl, shift, command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_restart_remote_device(session_id: SessionID) {
|
pub fn session_restart_remote_device(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.restart_remote_device();
|
session.restart_remote_device();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> SyncReturn<String> {
|
pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> SyncReturn<String> {
|
||||||
let res = if let Some(session) = sessions::get_session(&session_id) {
|
let res = if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.get_audit_server(typ)
|
session.get_audit_server(typ)
|
||||||
} else {
|
} else {
|
||||||
"".to_owned()
|
"".to_owned()
|
||||||
@ -1260,13 +1306,13 @@ pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> Sync
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_send_note(session_id: SessionID, note: String) {
|
pub fn session_send_note(session_id: SessionID, note: String) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.send_note(note)
|
session.send_note(note)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_alternative_codecs(session_id: SessionID) -> String {
|
pub fn session_alternative_codecs(session_id: SessionID) -> String {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
let (vp8, av1, h264, h265) = session.alternative_codecs();
|
let (vp8, av1, h264, h265) = session.alternative_codecs();
|
||||||
let msg = HashMap::from([("vp8", vp8), ("av1", av1), ("h264", h264), ("h265", h265)]);
|
let msg = HashMap::from([("vp8", vp8), ("av1", av1), ("h264", h264), ("h265", h265)]);
|
||||||
serde_json::ser::to_string(&msg).unwrap_or("".to_owned())
|
serde_json::ser::to_string(&msg).unwrap_or("".to_owned())
|
||||||
@ -1276,15 +1322,13 @@ pub fn session_alternative_codecs(session_id: SessionID) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_change_prefer_codec(session_id: SessionID) {
|
pub fn session_change_prefer_codec(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.change_prefer_codec();
|
session.change_prefer_codec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
|
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
super::flutter::session_on_waiting_for_image_dialog_show(session_id);
|
||||||
session.ui_handler.on_waiting_for_image_dialog_show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_set_home_dir(_home: String) {
|
pub fn main_set_home_dir(_home: String) {
|
||||||
@ -1434,16 +1478,22 @@ pub fn translate(name: String, locale: String) -> SyncReturn<String> {
|
|||||||
SyncReturn(crate::client::translate_locale(name, &locale))
|
SyncReturn(crate::client::translate_locale(name, &locale))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_get_rgba_size(session_id: SessionID) -> SyncReturn<usize> {
|
pub fn session_get_rgba_size(session_id: SessionID, display: usize) -> SyncReturn<usize> {
|
||||||
SyncReturn(super::flutter::session_get_rgba_size(session_id))
|
SyncReturn(super::flutter::session_get_rgba_size(session_id, display))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_next_rgba(session_id: SessionID) -> SyncReturn<()> {
|
pub fn session_next_rgba(session_id: SessionID, display: usize) -> SyncReturn<()> {
|
||||||
SyncReturn(super::flutter::session_next_rgba(session_id))
|
SyncReturn(super::flutter::session_next_rgba(session_id, display))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_register_texture(session_id: SessionID, ptr: usize) -> SyncReturn<()> {
|
pub fn session_register_texture(
|
||||||
SyncReturn(super::flutter::session_register_texture(session_id, ptr))
|
session_id: SessionID,
|
||||||
|
display: usize,
|
||||||
|
ptr: usize,
|
||||||
|
) -> SyncReturn<()> {
|
||||||
|
SyncReturn(super::flutter::session_register_texture(
|
||||||
|
session_id, display, ptr,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_onlines(ids: Vec<String>) {
|
pub fn query_onlines(ids: Vec<String>) {
|
||||||
@ -1522,15 +1572,15 @@ pub fn main_update_me() -> SyncReturn<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cur_session_id(session_id: SessionID) {
|
pub fn set_cur_session_id(session_id: SessionID) {
|
||||||
if let Some(session) = sessions::get_session(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
set_cur_session_id_(session_id, &session.get_keyboard_mode())
|
set_cur_session_id_(session_id, &session.get_keyboard_mode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_cur_session_id_(session_id: SessionID, keyboard_mode: &str) {
|
fn set_cur_session_id_(session_id: SessionID, _keyboard_mode: &str) {
|
||||||
super::flutter::set_cur_session_id(session_id);
|
super::flutter::set_cur_session_id(session_id);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
crate::keyboard::update_grab_get_key_name(keyboard_mode);
|
crate::keyboard::update_grab_get_key_name(_keyboard_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install_show_run_without_install() -> SyncReturn<bool> {
|
pub fn install_show_run_without_install() -> SyncReturn<bool> {
|
||||||
@ -1625,6 +1675,10 @@ pub fn main_test_wallpaper(_second: u64) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main_support_remove_wallpaper() -> bool {
|
||||||
|
support_remove_wallpaper()
|
||||||
|
}
|
||||||
|
|
||||||
/// Send a url scheme throught the ipc.
|
/// Send a url scheme throught the ipc.
|
||||||
///
|
///
|
||||||
/// * macOS only
|
/// * macOS only
|
||||||
@ -1808,6 +1862,10 @@ pub fn plugin_install(_id: String, _b: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_support_multi_ui_session(version: String) -> SyncReturn<bool> {
|
||||||
|
SyncReturn(crate::common::is_support_multi_ui_session(&version))
|
||||||
|
}
|
||||||
|
|
||||||
#[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};
|
||||||
|
@ -214,6 +214,7 @@ static mut IS_0X021D_DOWN: bool = false;
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
static mut IS_LEFT_OPTION_DOWN: bool = false;
|
static mut IS_LEFT_OPTION_DOWN: bool = false;
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
fn get_keyboard_mode() -> String {
|
fn get_keyboard_mode() -> String {
|
||||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||||
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
|
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", "按交集过滤"),
|
("Filter by intersection", "按交集过滤"),
|
||||||
("Remove wallpaper during incoming sessions", "接受会话时移除桌面壁纸"),
|
("Remove wallpaper during incoming sessions", "接受会话时移除桌面壁纸"),
|
||||||
("Test", "测试"),
|
("Test", "测试"),
|
||||||
|
("switch_display_elevated_connections_tip", "提权后,被控有多个连接,不能切换到非主显示器。若要控制多显示器,请安装后再试。"),
|
||||||
|
("display_is_plugged_out_msg", "显示器被拔出,切换到第一个显示器。"),
|
||||||
|
("No displays", "没有显示器。"),
|
||||||
|
("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"),
|
||||||
|
("Open in new window", "在新的窗口中打开"),
|
||||||
|
("Show displays as individual windows", "在单个窗口中打开显示器"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -555,8 +555,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Check for software update on startup", "Beim Start auf Softwareaktualisierung prüfen"),
|
("Check for software update on startup", "Beim Start auf Softwareaktualisierung prüfen"),
|
||||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Bitte aktualisieren Sie RustDesk Server Pro auf die Version {} oder neuer!"),
|
("upgrade_rustdesk_server_pro_to_{}_tip", "Bitte aktualisieren Sie RustDesk Server Pro auf die Version {} oder neuer!"),
|
||||||
("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"),
|
("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Filter by intersection", "Nach Schnittmenge filtern"),
|
||||||
("Test", ""),
|
("Remove wallpaper during incoming sessions", "Hintergrundbild während eingehender Sitzungen entfernen"),
|
||||||
("Filter by intersection", "Nach Schnittpunkt filtern")
|
("Test", "Test"),
|
||||||
|
("Filter by intersection", "Nach Schnittpunkt filtern"),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -222,5 +222,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Please upgrade RustDesk Server Pro to version {} or newer!"),
|
("upgrade_rustdesk_server_pro_to_{}_tip", "Please upgrade RustDesk Server Pro to version {} or newer!"),
|
||||||
("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."),
|
||||||
|
("display_is_plugged_out_msg", "The diplay 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."),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", "Filter berdasarkan interseksi"),
|
("Filter by intersection", "Filter berdasarkan interseksi"),
|
||||||
("Remove wallpaper during incoming sessions", "Hilangkan latar dinding ketika ada sesi yang masuk"),
|
("Remove wallpaper during incoming sessions", "Hilangkan latar dinding ketika ada sesi yang masuk"),
|
||||||
("Test", "Tes"),
|
("Test", "Tes"),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -559,5 +559,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", "Filtra per incrocio"),
|
("Filter by intersection", "Filtra per incrocio"),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", "Filtrēt pēc krustpunkta"),
|
("Filter by intersection", "Filtrēt pēc krustpunkta"),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -556,7 +556,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("upgrade_rustdesk_server_pro_to_{}_tip", "Обновите RustDesk Server Pro до версии {} или новее!"),
|
("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", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Filter by intersection", ""),
|
("Filter by intersection", ""),
|
||||||
("Remove wallpaper during incoming sessions", ""),
|
("Remove wallpaper during incoming sessions", ""),
|
||||||
("Test", ""),
|
("Test", ""),
|
||||||
|
("switch_display_elevated_connections_tip", ""),
|
||||||
|
("display_is_plugged_out_msg", ""),
|
||||||
|
("No displays", ""),
|
||||||
|
("elevated_switch_display_msg", ""),
|
||||||
|
("Open in new window", ""),
|
||||||
|
("Show displays as individual windows", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -1341,6 +1341,14 @@ impl WallPaperRemover {
|
|||||||
old_path_dark,
|
old_path_dark,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn support() -> bool {
|
||||||
|
let desktop = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default();
|
||||||
|
if wallpaper::gnome::is_compliant(&desktop) || desktop.as_str() == "XFCE" {
|
||||||
|
return wallpaper::get().is_ok();
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for WallPaperRemover {
|
impl Drop for WallPaperRemover {
|
||||||
|
@ -83,6 +83,7 @@ pub const PA_SAMPLE_RATE: u32 = 48000;
|
|||||||
pub(crate) struct InstallingService; // please use new
|
pub(crate) struct InstallingService; // please use new
|
||||||
|
|
||||||
impl InstallingService {
|
impl InstallingService {
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
*INSTALLING_SERVICE.lock().unwrap() = true;
|
*INSTALLING_SERVICE.lock().unwrap() = true;
|
||||||
Self
|
Self
|
||||||
|
@ -2392,6 +2392,10 @@ impl WallPaperRemover {
|
|||||||
Ok(Self { old_path })
|
Ok(Self { old_path })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn support() -> bool {
|
||||||
|
wallpaper::get().is_ok() || !Self::get_recent_wallpaper().unwrap_or_default().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_recent_wallpaper() -> ResultType<String> {
|
fn get_recent_wallpaper() -> ResultType<String> {
|
||||||
// SystemParametersInfoW may return %appdata%\Microsoft\Windows\Themes\TranscodedWallpaper, not real path and may not real cache
|
// SystemParametersInfoW may return %appdata%\Microsoft\Windows\Themes\TranscodedWallpaper, not real path and may not real cache
|
||||||
// https://www.makeuseof.com/find-desktop-wallpapers-file-location-windows-11/
|
// https://www.makeuseof.com/find-desktop-wallpapers-file-location-windows-11/
|
||||||
|
@ -26,7 +26,7 @@ use hbb_common::{
|
|||||||
};
|
};
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
use service::ServiceTmpl;
|
use service::ServiceTmpl;
|
||||||
use service::{GenericService, Service, Subscriber};
|
use service::{EmptyExtraFieldService, GenericService, Service, Subscriber};
|
||||||
|
|
||||||
use crate::ipc::Data;
|
use crate::ipc::Data;
|
||||||
|
|
||||||
@ -53,6 +53,7 @@ pub const NAME_POS: &'static str = "";
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod connection;
|
mod connection;
|
||||||
|
pub mod display_service;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub mod portable_service;
|
pub mod portable_service;
|
||||||
mod service;
|
mod service;
|
||||||
@ -80,7 +81,7 @@ lazy_static::lazy_static! {
|
|||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
connections: ConnMap,
|
connections: ConnMap,
|
||||||
services: HashMap<&'static str, Box<dyn Service>>,
|
services: HashMap<String, Box<dyn Service>>,
|
||||||
id_count: i32,
|
id_count: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,11 +95,15 @@ pub fn new() -> ServerPtr {
|
|||||||
id_count: hbb_common::rand::random::<i32>() % 1000 + 1000, // ensure positive
|
id_count: hbb_common::rand::random::<i32>() % 1000 + 1000, // ensure positive
|
||||||
};
|
};
|
||||||
server.add_service(Box::new(audio_service::new()));
|
server.add_service(Box::new(audio_service::new()));
|
||||||
server.add_service(Box::new(video_service::new()));
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
server.add_service(Box::new(display_service::new()));
|
||||||
|
server.add_service(Box::new(video_service::new(
|
||||||
|
*display_service::PRIMARY_DISPLAY_IDX,
|
||||||
|
)));
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
{
|
{
|
||||||
server.add_service(Box::new(clipboard_service::new()));
|
server.add_service(Box::new(clipboard_service::new()));
|
||||||
if !video_service::capture_cursor_embedded() {
|
if !display_service::capture_cursor_embedded() {
|
||||||
server.add_service(Box::new(input_service::new_cursor()));
|
server.add_service(Box::new(input_service::new_cursor()));
|
||||||
server.add_service(Box::new(input_service::new_pos()));
|
server.add_service(Box::new(input_service::new_pos()));
|
||||||
}
|
}
|
||||||
@ -253,9 +258,19 @@ async fn create_relay_connection_(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
|
fn is_video_service_name(name: &str) -> bool {
|
||||||
|
name.starts_with(video_service::NAME)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_connection(&mut self, conn: ConnInner, noperms: &Vec<&'static str>) {
|
pub fn add_connection(&mut self, conn: ConnInner, noperms: &Vec<&'static str>) {
|
||||||
|
let primary_video_service_name =
|
||||||
|
video_service::get_service_name(*display_service::PRIMARY_DISPLAY_IDX);
|
||||||
for s in self.services.values() {
|
for s in self.services.values() {
|
||||||
if !noperms.contains(&s.name()) {
|
let name = s.name();
|
||||||
|
if Self::is_video_service_name(&name) && name != primary_video_service_name {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !noperms.contains(&(&name as _)) {
|
||||||
s.on_subscribe(conn.clone());
|
s.on_subscribe(conn.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,8 +302,12 @@ impl Server {
|
|||||||
self.services.insert(name, service);
|
self.services.insert(name, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, name: &str) -> bool {
|
||||||
|
self.services.contains_key(name)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn subscribe(&mut self, name: &str, conn: ConnInner, sub: bool) {
|
pub fn subscribe(&mut self, name: &str, conn: ConnInner, sub: bool) {
|
||||||
if let Some(s) = self.services.get(&name) {
|
if let Some(s) = self.services.get(name) {
|
||||||
if s.is_subed(conn.id()) == sub {
|
if s.is_subed(conn.id()) == sub {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -305,6 +324,47 @@ impl Server {
|
|||||||
self.id_count += 1;
|
self.id_count += 1;
|
||||||
self.id_count
|
self.id_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_video_service_opt(&self, display: Option<usize>, opt: &str, value: &str) {
|
||||||
|
for (k, v) in self.services.iter() {
|
||||||
|
if let Some(display) = display {
|
||||||
|
if k != &video_service::get_service_name(display) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if Self::is_video_service_name(k) {
|
||||||
|
v.set_option(opt, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capture_displays(
|
||||||
|
&mut self,
|
||||||
|
conn: ConnInner,
|
||||||
|
displays: &[usize],
|
||||||
|
include: bool,
|
||||||
|
exclude: bool,
|
||||||
|
) {
|
||||||
|
let displays = displays
|
||||||
|
.iter()
|
||||||
|
.map(|d| video_service::get_service_name(*d))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let keys = self.services.keys().cloned().collect::<Vec<_>>();
|
||||||
|
for name in keys.iter() {
|
||||||
|
if Self::is_video_service_name(&name) {
|
||||||
|
if displays.contains(&name) {
|
||||||
|
if include {
|
||||||
|
self.subscribe(&name, conn.clone(), true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if exclude {
|
||||||
|
self.subscribe(&name, conn.clone(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Server {
|
impl Drop for Server {
|
||||||
|
@ -24,16 +24,16 @@ static RESTARTING: AtomicBool = AtomicBool::new(false);
|
|||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||||
pub fn new() -> GenericService {
|
pub fn new() -> GenericService {
|
||||||
let sp = GenericService::new(NAME, true);
|
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
|
||||||
sp.repeat::<cpal_impl::State, _>(33, cpal_impl::run);
|
GenericService::repeat::<cpal_impl::State, _, _>(&svc.clone(), 33, cpal_impl::run);
|
||||||
sp
|
svc.sp
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
pub fn new() -> GenericService {
|
pub fn new() -> GenericService {
|
||||||
let sp = GenericService::new(NAME, true);
|
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
|
||||||
sp.run(pa_impl::run);
|
GenericService::run(&svc.clone(), pa_impl::run);
|
||||||
sp
|
svc.sp
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restart() {
|
pub fn restart() {
|
||||||
@ -48,7 +48,7 @@ pub fn restart() {
|
|||||||
mod pa_impl {
|
mod pa_impl {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
pub async fn run(sp: GenericService) -> ResultType<()> {
|
pub async fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||||
hbb_common::sleep(0.1).await; // one moment to wait for _pa ipc
|
hbb_common::sleep(0.1).await; // one moment to wait for _pa ipc
|
||||||
RESTARTING.store(false, Ordering::SeqCst);
|
RESTARTING.store(false, Ordering::SeqCst);
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@ -125,7 +125,7 @@ mod cpal_impl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(sp: GenericService, state: &mut State) -> ResultType<()> {
|
pub fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> {
|
||||||
sp.snapshot(|sps| {
|
sp.snapshot(|sps| {
|
||||||
match &state.stream {
|
match &state.stream {
|
||||||
None => {
|
None => {
|
||||||
|
@ -28,12 +28,12 @@ impl super::service::Reset for State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> GenericService {
|
pub fn new() -> GenericService {
|
||||||
let sp = GenericService::new(NAME, true);
|
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
|
||||||
sp.repeat::<State, _>(INTERVAL, run);
|
GenericService::repeat::<State, _, _>(&svc.clone(), INTERVAL, run);
|
||||||
sp
|
svc.sp
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(sp: GenericService, state: &mut State) -> ResultType<()> {
|
fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> {
|
||||||
if let Some(ctx) = state.ctx.as_mut() {
|
if let Some(ctx) = state.ctx.as_mut() {
|
||||||
if let Some(msg) = check_clipboard(ctx, None) {
|
if let Some(msg) = check_clipboard(ctx, None) {
|
||||||
sp.send(msg);
|
sp.send(msg);
|
||||||
|
@ -15,7 +15,7 @@ use crate::{
|
|||||||
new_voice_call_request, new_voice_call_response, start_audio_thread, MediaData, MediaSender,
|
new_voice_call_request, new_voice_call_response, start_audio_thread, MediaData, MediaSender,
|
||||||
},
|
},
|
||||||
common::{get_default_sound_input, set_sound_input},
|
common::{get_default_sound_input, set_sound_input},
|
||||||
video_service,
|
display_service, video_service,
|
||||||
};
|
};
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
|
||||||
@ -160,6 +160,7 @@ pub enum AuthConnType {
|
|||||||
|
|
||||||
pub struct Connection {
|
pub struct Connection {
|
||||||
inner: ConnInner,
|
inner: ConnInner,
|
||||||
|
display_idx: usize,
|
||||||
stream: super::Stream,
|
stream: super::Stream,
|
||||||
server: super::ServerPtrWeak,
|
server: super::ServerPtrWeak,
|
||||||
hash: Hash,
|
hash: Hash,
|
||||||
@ -303,6 +304,7 @@ impl Connection {
|
|||||||
tx: Some(tx),
|
tx: Some(tx),
|
||||||
tx_video: Some(tx_video),
|
tx_video: Some(tx_video),
|
||||||
},
|
},
|
||||||
|
display_idx: *display_service::PRIMARY_DISPLAY_IDX,
|
||||||
stream,
|
stream,
|
||||||
server,
|
server,
|
||||||
hash,
|
hash,
|
||||||
@ -609,6 +611,9 @@ impl Connection {
|
|||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(message::Union::PeerInfo(_)) => {
|
||||||
|
conn.refresh_video_display(None);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
if let Err(err) = conn.stream.send(msg).await {
|
if let Err(err) = conn.stream.send(msg).await {
|
||||||
@ -1098,19 +1103,20 @@ impl Connection {
|
|||||||
pi.username = username;
|
pi.username = username;
|
||||||
pi.sas_enabled = sas_enabled;
|
pi.sas_enabled = sas_enabled;
|
||||||
pi.features = Some(Features {
|
pi.features = Some(Features {
|
||||||
privacy_mode: video_service::is_privacy_mode_supported(),
|
privacy_mode: display_service::is_privacy_mode_supported(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into();
|
.into();
|
||||||
// `try_reset_current_display` is needed because `get_displays` may change the current display,
|
|
||||||
// which may cause the mismatch of current display and the current display name.
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
video_service::try_reset_current_display();
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
{
|
{
|
||||||
pi.resolutions = Some(SupportedResolutions {
|
pi.resolutions = Some(SupportedResolutions {
|
||||||
resolutions: video_service::get_current_display()
|
resolutions: display_service::try_get_displays()
|
||||||
.map(|(_, _, d)| crate::platform::resolutions(&d.name()))
|
.map(|displays| {
|
||||||
|
displays
|
||||||
|
.get(self.display_idx)
|
||||||
|
.map(|d| crate::platform::resolutions(&d.name()))
|
||||||
|
.unwrap_or(vec![])
|
||||||
|
})
|
||||||
.unwrap_or(vec![]),
|
.unwrap_or(vec![]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
@ -1126,16 +1132,15 @@ impl Connection {
|
|||||||
self.send(msg_out).await;
|
self.send(msg_out).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
match super::video_service::get_displays().await {
|
match super::display_service::get_displays().await {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
res.set_error(format!("{}", err));
|
res.set_error(format!("{}", err));
|
||||||
}
|
}
|
||||||
Ok((current, displays)) => {
|
Ok(displays) => {
|
||||||
pi.displays = displays.clone();
|
pi.displays = displays.clone();
|
||||||
pi.current_display = current as _;
|
pi.current_display = self.display_idx as _;
|
||||||
res.set_peer_info(pi);
|
res.set_peer_info(pi);
|
||||||
sub_service = true;
|
sub_service = true;
|
||||||
*super::video_service::LAST_SYNC_DISPLAYS.write().unwrap() = displays;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::on_remote_authorized();
|
Self::on_remote_authorized();
|
||||||
@ -1289,6 +1294,8 @@ impl Connection {
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
fn input_key(&self, msg: KeyEvent, press: bool) {
|
fn input_key(&self, msg: KeyEvent, press: bool) {
|
||||||
|
// to-do: if is the legacy mode, and the key is function key "LockScreen".
|
||||||
|
// Switch to the primary display.
|
||||||
self.tx_input.send(MessageInput::Key((msg, press))).ok();
|
self.tx_input.send(MessageInput::Key((msg, press))).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1945,15 +1952,13 @@ impl Connection {
|
|||||||
},
|
},
|
||||||
Some(message::Union::Misc(misc)) => match misc.union {
|
Some(message::Union::Misc(misc)) => match misc.union {
|
||||||
Some(misc::Union::SwitchDisplay(s)) => {
|
Some(misc::Union::SwitchDisplay(s)) => {
|
||||||
video_service::switch_display(s.display).await;
|
self.handle_switch_display(s).await;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
}
|
||||||
if s.width != 0 && s.height != 0 {
|
Some(misc::Union::CaptureDisplays(displays)) => {
|
||||||
self.change_resolution(&Resolution {
|
let add = displays.add.iter().map(|d| *d as usize).collect::<Vec<_>>();
|
||||||
width: s.width,
|
let sub = displays.sub.iter().map(|d| *d as usize).collect::<Vec<_>>();
|
||||||
height: s.height,
|
let set = displays.set.iter().map(|d| *d as usize).collect::<Vec<_>>();
|
||||||
..Default::default()
|
self.capture_displays(&add, &sub, &set).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 });
|
||||||
@ -1965,10 +1970,16 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
Some(misc::Union::RefreshVideo(r)) => {
|
Some(misc::Union::RefreshVideo(r)) => {
|
||||||
if r {
|
if r {
|
||||||
super::video_service::refresh();
|
// Refresh all videos.
|
||||||
|
// Compatibility with old versions and sciter(remote).
|
||||||
|
self.refresh_video_display(None);
|
||||||
}
|
}
|
||||||
self.update_auto_disconnect_timer();
|
self.update_auto_disconnect_timer();
|
||||||
}
|
}
|
||||||
|
Some(misc::Union::RefreshVideoDisplay(display)) => {
|
||||||
|
self.refresh_video_display(Some(display as usize));
|
||||||
|
self.update_auto_disconnect_timer();
|
||||||
|
}
|
||||||
Some(misc::Union::VideoReceived(_)) => {
|
Some(misc::Union::VideoReceived(_)) => {
|
||||||
video_service::notify_video_frame_fetched(
|
video_service::notify_video_frame_fetched(
|
||||||
self.inner.id,
|
self.inner.id,
|
||||||
@ -2084,6 +2095,75 @@ impl Connection {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn refresh_video_display(&self, display: Option<usize>) {
|
||||||
|
video_service::refresh();
|
||||||
|
self.server.upgrade().map(|s| {
|
||||||
|
s.read().unwrap().set_video_service_opt(
|
||||||
|
display,
|
||||||
|
video_service::OPTION_REFRESH,
|
||||||
|
super::service::SERVICE_OPTION_VALUE_TRUE,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_switch_display(&mut self, s: SwitchDisplay) {
|
||||||
|
#[cfg(windows)]
|
||||||
|
if portable_client::running()
|
||||||
|
&& *CONN_COUNT.lock().unwrap() > 1
|
||||||
|
&& s.display != (*display_service::PRIMARY_DISPLAY_IDX as i32)
|
||||||
|
{
|
||||||
|
log::info!("Switch to non-primary display is not supported in the elevated mode when there are multiple connections.");
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
let res = MessageBox {
|
||||||
|
msgtype: "nook-nocancel-hasclose".to_owned(),
|
||||||
|
title: "Prompt".to_owned(),
|
||||||
|
text: "switch_display_elevated_connections_tip".to_owned(),
|
||||||
|
link: "".to_owned(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
msg_out.set_message_box(res);
|
||||||
|
self.send(msg_out).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let display_idx = s.display as usize;
|
||||||
|
if self.display_idx != display_idx {
|
||||||
|
if let Some(server) = self.server.upgrade() {
|
||||||
|
self.switch_display_to(display_idx, server.clone());
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
if s.width != 0 && s.height != 0 {
|
||||||
|
self.change_resolution(&Resolution {
|
||||||
|
width: s.width,
|
||||||
|
height: s.height,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// send display changed message
|
||||||
|
if let Some(msg_out) =
|
||||||
|
video_service::make_display_changed_msg(self.display_idx, None)
|
||||||
|
{
|
||||||
|
self.send(msg_out).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_display_to(&mut self, display_idx: usize, server: Arc<RwLock<Server>>) {
|
||||||
|
let new_service_name = video_service::get_service_name(display_idx);
|
||||||
|
let old_service_name = video_service::get_service_name(self.display_idx);
|
||||||
|
let mut lock = server.write().unwrap();
|
||||||
|
if display_idx != *display_service::PRIMARY_DISPLAY_IDX {
|
||||||
|
if !lock.contains(&new_service_name) {
|
||||||
|
lock.add_service(Box::new(video_service::new(display_idx)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lock.subscribe(&old_service_name, self.inner.clone(), false);
|
||||||
|
lock.subscribe(&new_service_name, self.inner.clone(), true);
|
||||||
|
self.display_idx = display_idx;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
async fn handle_elevation_request(&mut self, para: portable_client::StartPara) {
|
async fn handle_elevation_request(&mut self, para: portable_client::StartPara) {
|
||||||
let mut err;
|
let mut err;
|
||||||
@ -2106,36 +2186,64 @@ impl Connection {
|
|||||||
self.update_auto_disconnect_timer();
|
self.update_auto_disconnect_timer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn capture_displays(&mut self, add: &[usize], sub: &[usize], set: &[usize]) {
|
||||||
|
if let Some(sever) = self.server.upgrade() {
|
||||||
|
let mut lock = sever.write().unwrap();
|
||||||
|
for display in add.iter() {
|
||||||
|
let service_name = video_service::get_service_name(*display);
|
||||||
|
if !lock.contains(&service_name) {
|
||||||
|
lock.add_service(Box::new(video_service::new(*display)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for display in set.iter() {
|
||||||
|
let service_name = video_service::get_service_name(*display);
|
||||||
|
if !lock.contains(&service_name) {
|
||||||
|
lock.add_service(Box::new(video_service::new(*display)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !add.is_empty() {
|
||||||
|
lock.capture_displays(self.inner.clone(), add, true, false);
|
||||||
|
} else if !sub.is_empty() {
|
||||||
|
lock.capture_displays(self.inner.clone(), sub, false, true);
|
||||||
|
} else {
|
||||||
|
lock.capture_displays(self.inner.clone(), set, true, true);
|
||||||
|
}
|
||||||
|
drop(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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 {
|
||||||
if let Ok((_, _, display)) = video_service::get_current_display() {
|
if let Ok(displays) = display_service::try_get_displays() {
|
||||||
let name = display.name();
|
if let Some(display) = displays.get(self.display_idx) {
|
||||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
let name = display.name();
|
||||||
if let Some(_ok) =
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
crate::virtual_display_manager::change_resolution_if_is_virtual_display(
|
if let Some(_ok) =
|
||||||
|
crate::virtual_display_manager::change_resolution_if_is_virtual_display(
|
||||||
|
&name,
|
||||||
|
r.width as _,
|
||||||
|
r.height as _,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
display_service::set_last_changed_resolution(
|
||||||
&name,
|
&name,
|
||||||
r.width as _,
|
(display.width() as _, display.height() as _),
|
||||||
r.height as _,
|
(r.width, r.height),
|
||||||
)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
video_service::set_last_changed_resolution(
|
|
||||||
&name,
|
|
||||||
(display.width() as _, display.height() as _),
|
|
||||||
(r.width, r.height),
|
|
||||||
);
|
|
||||||
if let Err(e) =
|
|
||||||
crate::platform::change_resolution(&name, r.width as _, r.height as _)
|
|
||||||
{
|
|
||||||
log::error!(
|
|
||||||
"Failed to change resolution '{}' to ({},{}):{:?}",
|
|
||||||
&name,
|
|
||||||
r.width,
|
|
||||||
r.height,
|
|
||||||
e
|
|
||||||
);
|
);
|
||||||
|
if let Err(e) =
|
||||||
|
crate::platform::change_resolution(&name, r.width as _, r.height as _)
|
||||||
|
{
|
||||||
|
log::error!(
|
||||||
|
"Failed to change resolution '{}' to ({},{}):{:?}",
|
||||||
|
&name,
|
||||||
|
r.width,
|
||||||
|
r.height,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2281,7 +2389,7 @@ impl Connection {
|
|||||||
if self.keyboard {
|
if self.keyboard {
|
||||||
match q {
|
match q {
|
||||||
BoolOption::Yes => {
|
BoolOption::Yes => {
|
||||||
let msg_out = if !video_service::is_privacy_mode_supported() {
|
let msg_out = if !display_service::is_privacy_mode_supported() {
|
||||||
crate::common::make_privacy_mode_msg_with_details(
|
crate::common::make_privacy_mode_msg_with_details(
|
||||||
back_notification::PrivacyModeState::PrvNotSupported,
|
back_notification::PrivacyModeState::PrvNotSupported,
|
||||||
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
|
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
|
||||||
@ -2289,8 +2397,11 @@ impl Connection {
|
|||||||
} else {
|
} else {
|
||||||
match privacy_mode::turn_on_privacy(self.inner.id) {
|
match privacy_mode::turn_on_privacy(self.inner.id) {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
let err_msg =
|
let err_msg = video_service::test_create_capturer(
|
||||||
video_service::test_create_capturer(self.inner.id, 5_000);
|
self.inner.id,
|
||||||
|
self.display_idx,
|
||||||
|
5_000,
|
||||||
|
);
|
||||||
if err_msg.is_empty() {
|
if err_msg.is_empty() {
|
||||||
video_service::set_privacy_mode_conn_id(self.inner.id);
|
video_service::set_privacy_mode_conn_id(self.inner.id);
|
||||||
crate::common::make_privacy_mode_msg(
|
crate::common::make_privacy_mode_msg(
|
||||||
@ -2326,7 +2437,7 @@ impl Connection {
|
|||||||
self.send(msg_out).await;
|
self.send(msg_out).await;
|
||||||
}
|
}
|
||||||
BoolOption::No => {
|
BoolOption::No => {
|
||||||
let msg_out = if !video_service::is_privacy_mode_supported() {
|
let msg_out = if !display_service::is_privacy_mode_supported() {
|
||||||
crate::common::make_privacy_mode_msg_with_details(
|
crate::common::make_privacy_mode_msg_with_details(
|
||||||
back_notification::PrivacyModeState::PrvNotSupported,
|
back_notification::PrivacyModeState::PrvNotSupported,
|
||||||
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
|
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
|
||||||
@ -2801,11 +2912,11 @@ mod raii {
|
|||||||
active_conns_lock.retain(|&c| c != self.0);
|
active_conns_lock.retain(|&c| c != self.0);
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
if active_conns_lock.is_empty() {
|
if active_conns_lock.is_empty() {
|
||||||
video_service::reset_resolutions();
|
display_service::reset_resolutions();
|
||||||
}
|
}
|
||||||
#[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() {
|
||||||
video_service::try_plug_out_virtual_display();
|
display_service::try_plug_out_virtual_display();
|
||||||
}
|
}
|
||||||
#[cfg(all(windows))]
|
#[cfg(all(windows))]
|
||||||
if active_conns_lock.is_empty() {
|
if active_conns_lock.is_empty() {
|
||||||
|
262
src/server/display_service.rs
Normal file
262
src/server/display_service.rs
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
use super::*;
|
||||||
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
|
use crate::virtual_display_manager;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use hbb_common::get_version_number;
|
||||||
|
use hbb_common::protobuf::MessageField;
|
||||||
|
use scrap::Display;
|
||||||
|
|
||||||
|
pub const NAME: &'static str = "display";
|
||||||
|
|
||||||
|
struct ChangedResolution {
|
||||||
|
original: (i32, i32),
|
||||||
|
changed: (i32, i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported();
|
||||||
|
static ref CHANGED_RESOLUTIONS: Arc<RwLock<HashMap<String, ChangedResolution>>> = Default::default();
|
||||||
|
// Initial primary display index.
|
||||||
|
// It should only be updated when the rustdesk server is started, and should not be updated when displays changed.
|
||||||
|
pub static ref PRIMARY_DISPLAY_IDX: usize = get_primary();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_last_changed_resolution(display_name: &str, original: (i32, i32), changed: (i32, i32)) {
|
||||||
|
let mut lock = CHANGED_RESOLUTIONS.write().unwrap();
|
||||||
|
match lock.get_mut(display_name) {
|
||||||
|
Some(res) => res.changed = changed,
|
||||||
|
None => {
|
||||||
|
lock.insert(
|
||||||
|
display_name.to_owned(),
|
||||||
|
ChangedResolution { original, changed },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
pub fn reset_resolutions() {
|
||||||
|
for (name, res) in CHANGED_RESOLUTIONS.read().unwrap().iter() {
|
||||||
|
let (w, h) = res.original;
|
||||||
|
if let Err(e) = crate::platform::change_resolution(name, w as _, h as _) {
|
||||||
|
log::error!(
|
||||||
|
"Failed to reset resolution of display '{}' to ({},{}): {}",
|
||||||
|
name,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_capturer_mag_supported() -> bool {
|
||||||
|
#[cfg(windows)]
|
||||||
|
return scrap::CapturerMag::is_supported();
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn capture_cursor_embedded() -> bool {
|
||||||
|
scrap::is_cursor_embedded()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_privacy_mode_supported() -> bool {
|
||||||
|
#[cfg(windows)]
|
||||||
|
return *IS_CAPTURER_MAGNIFIER_SUPPORTED
|
||||||
|
&& get_version_number(&crate::VERSION) > get_version_number("1.1.9");
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct StateDisplay {
|
||||||
|
synced_displays: Vec<DisplayInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::service::Reset for StateDisplay {
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.synced_displays.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> GenericService {
|
||||||
|
let svc = EmptyExtraFieldService::new(NAME.to_owned(), false);
|
||||||
|
GenericService::repeat::<StateDisplay, _, _>(&svc.clone(), 300, run);
|
||||||
|
svc.sp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_get_displays_changed_msg(last_synced_displays: &mut Vec<DisplayInfo>) -> Option<Message> {
|
||||||
|
let displays = try_get_displays().ok()?;
|
||||||
|
if displays.len() == last_synced_displays.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display to DisplayInfo
|
||||||
|
let displays = to_display_info(&displays);
|
||||||
|
if last_synced_displays.len() == 0 {
|
||||||
|
*last_synced_displays = displays;
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut pi = PeerInfo {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
pi.displays = displays.clone();
|
||||||
|
pi.current_display = 0;
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
msg_out.set_peer_info(pi);
|
||||||
|
*last_synced_displays = displays;
|
||||||
|
Some(msg_out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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, state: &mut StateDisplay) -> ResultType<()> {
|
||||||
|
if let Some(msg_out) = check_get_displays_changed_msg(&mut state.synced_displays) {
|
||||||
|
sp.send(msg_out);
|
||||||
|
log::info!("Displays changed");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn get_original_resolution(
|
||||||
|
display_name: &str,
|
||||||
|
w: usize,
|
||||||
|
h: usize,
|
||||||
|
) -> MessageField<Resolution> {
|
||||||
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
|
let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name);
|
||||||
|
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
|
||||||
|
let is_virtual_display = false;
|
||||||
|
Some(if is_virtual_display {
|
||||||
|
Resolution {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut changed_resolutions = CHANGED_RESOLUTIONS.write().unwrap();
|
||||||
|
let (width, height) = match changed_resolutions.get(display_name) {
|
||||||
|
Some(res) => {
|
||||||
|
if res.changed.0 != w as i32 || res.changed.1 != h as i32 {
|
||||||
|
// If the resolution is changed by third process, remove the record in changed_resolutions.
|
||||||
|
changed_resolutions.remove(display_name);
|
||||||
|
(w as _, h as _)
|
||||||
|
} else {
|
||||||
|
res.original
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => (w as _, h as _),
|
||||||
|
};
|
||||||
|
Resolution {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_display_info(all: &Vec<Display>) -> Vec<DisplayInfo> {
|
||||||
|
all.iter()
|
||||||
|
.map(|d| {
|
||||||
|
let display_name = d.name();
|
||||||
|
let original_resolution = get_original_resolution(&display_name, d.width(), d.height());
|
||||||
|
DisplayInfo {
|
||||||
|
x: d.origin().0 as _,
|
||||||
|
y: d.origin().1 as _,
|
||||||
|
width: d.width() as _,
|
||||||
|
height: d.height() as _,
|
||||||
|
name: display_name,
|
||||||
|
online: d.is_online(),
|
||||||
|
cursor_embedded: false,
|
||||||
|
original_resolution,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<DisplayInfo>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_inited_msg() -> Option<Message> {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if !scrap::is_x11() {
|
||||||
|
return super::wayland::is_inited();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_displays() -> ResultType<Vec<DisplayInfo>> {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
if !scrap::is_x11() {
|
||||||
|
return super::wayland::get_displays().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(to_display_info(&try_get_displays()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_primary() -> usize {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
if !scrap::is_x11() {
|
||||||
|
return match super::wayland::get_primary() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_) => 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try_get_displays().map(|d| get_primary_2(&d)).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_primary_2(all: &Vec<Display>) -> usize {
|
||||||
|
all.iter().position(|d| d.is_primary()).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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;
|
||||||
|
display.width() <= dummy_display_side_max_size
|
||||||
|
&& display.height() <= dummy_display_side_max_size
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
|
||||||
|
pub fn try_get_displays() -> ResultType<Vec<Display>> {
|
||||||
|
Ok(Display::all()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
||||||
|
pub 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(displays)
|
||||||
|
}
|
@ -17,7 +17,7 @@ use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey};
|
|||||||
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};
|
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
ops::Sub,
|
ops::{Deref, DerefMut, Sub},
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
thread,
|
thread,
|
||||||
time::{self, Duration, Instant},
|
time::{self, Duration, Instant},
|
||||||
@ -236,18 +236,43 @@ fn should_disable_numlock(evt: &KeyEvent) -> bool {
|
|||||||
|
|
||||||
pub const NAME_CURSOR: &'static str = "mouse_cursor";
|
pub const NAME_CURSOR: &'static str = "mouse_cursor";
|
||||||
pub const NAME_POS: &'static str = "mouse_pos";
|
pub const NAME_POS: &'static str = "mouse_pos";
|
||||||
pub type MouseCursorService = ServiceTmpl<MouseCursorSub>;
|
#[derive(Clone)]
|
||||||
|
pub struct MouseCursorService {
|
||||||
|
pub sp: ServiceTmpl<MouseCursorSub>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_cursor() -> MouseCursorService {
|
impl Deref for MouseCursorService {
|
||||||
let sp = MouseCursorService::new(NAME_CURSOR, true);
|
type Target = ServiceTmpl<MouseCursorSub>;
|
||||||
sp.repeat::<StateCursor, _>(33, run_cursor);
|
|
||||||
sp
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.sp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for MouseCursorService {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.sp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MouseCursorService {
|
||||||
|
pub fn new(name: String, need_snapshot: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
sp: ServiceTmpl::<MouseCursorSub>::new(name, need_snapshot),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_cursor() -> ServiceTmpl<MouseCursorSub> {
|
||||||
|
let svc = MouseCursorService::new(NAME_CURSOR.to_owned(), true);
|
||||||
|
ServiceTmpl::<MouseCursorSub>::repeat::<StateCursor, _, _>(&svc.clone(), 33, run_cursor);
|
||||||
|
svc.sp
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_pos() -> GenericService {
|
pub fn new_pos() -> GenericService {
|
||||||
let sp = GenericService::new(NAME_POS, false);
|
let svc = EmptyExtraFieldService::new(NAME_POS.to_owned(), false);
|
||||||
sp.repeat::<StatePos, _>(33, run_pos);
|
GenericService::repeat::<StatePos, _, _>(&svc.clone(), 33, run_pos);
|
||||||
sp
|
svc.sp
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -258,7 +283,7 @@ fn update_last_cursor_pos(x: i32, y: i32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> {
|
fn run_pos(sp: EmptyExtraFieldService, state: &mut StatePos) -> ResultType<()> {
|
||||||
let (_, (x, y)) = *LATEST_SYS_CURSOR_POS.lock().unwrap();
|
let (_, (x, y)) = *LATEST_SYS_CURSOR_POS.lock().unwrap();
|
||||||
if x == INVALID_CURSOR_POS || y == INVALID_CURSOR_POS {
|
if x == INVALID_CURSOR_POS || y == INVALID_CURSOR_POS {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -988,7 +1013,6 @@ pub async fn lock_screen() {
|
|||||||
crate::platform::lock_screen();
|
crate::platform::lock_screen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super::video_service::switch_to_primary().await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_key(evt: &KeyEvent) {
|
pub fn handle_key(evt: &KeyEvent) {
|
||||||
|
@ -25,7 +25,6 @@ use winapi::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
ipc::{self, new_listener, Connection, Data, DataPortableService},
|
ipc::{self, new_listener, Connection, Data, DataPortableService},
|
||||||
platform::set_path_permission,
|
platform::set_path_permission,
|
||||||
video_service::get_current_display,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::video_qos;
|
use super::video_qos;
|
||||||
@ -224,6 +223,8 @@ mod utils {
|
|||||||
pub mod server {
|
pub mod server {
|
||||||
use hbb_common::message_proto::PointerDeviceEvent;
|
use hbb_common::message_proto::PointerDeviceEvent;
|
||||||
|
|
||||||
|
use crate::display_service;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
@ -324,12 +325,17 @@ pub mod server {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if c.is_none() {
|
if c.is_none() {
|
||||||
*crate::video_service::CURRENT_DISPLAY.lock().unwrap() = current_display;
|
let Ok(mut displays) = display_service::try_get_displays() else {
|
||||||
let Ok((_, _current, display)) = get_current_display() else {
|
log::error!("Failed to get displays");
|
||||||
log::error!("Failed to get current display");
|
|
||||||
*EXIT.lock().unwrap() = true;
|
*EXIT.lock().unwrap() = true;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
if displays.len() <= current_display {
|
||||||
|
log::error!("Invalid display index:{}", current_display);
|
||||||
|
*EXIT.lock().unwrap() = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let display = displays.remove(current_display);
|
||||||
display_width = display.width();
|
display_width = display.width();
|
||||||
display_height = display.height();
|
display_height = display.height();
|
||||||
match Capturer::new(display, use_yuv) {
|
match Capturer::new(display, use_yuv) {
|
||||||
@ -522,6 +528,8 @@ pub mod server {
|
|||||||
pub mod client {
|
pub mod client {
|
||||||
use hbb_common::{anyhow::Context, message_proto::PointerDeviceEvent};
|
use hbb_common::{anyhow::Context, message_proto::PointerDeviceEvent};
|
||||||
|
|
||||||
|
use crate::display_service;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
@ -665,8 +673,8 @@ pub mod client {
|
|||||||
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
|
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
|
||||||
}
|
}
|
||||||
let (mut width, mut height) = (0, 0);
|
let (mut width, mut height) = (0, 0);
|
||||||
if let Ok((_, current, display)) = get_current_display() {
|
if let Ok(displays) = display_service::try_get_displays() {
|
||||||
if current_display == current {
|
if let Some(display) = displays.get(current_display) {
|
||||||
width = display.width();
|
width = display.width();
|
||||||
height = display.height();
|
height = display.height();
|
||||||
}
|
}
|
||||||
@ -910,7 +918,15 @@ pub mod client {
|
|||||||
}
|
}
|
||||||
if portable_service_running {
|
if portable_service_running {
|
||||||
log::info!("Create shared memory capturer");
|
log::info!("Create shared memory capturer");
|
||||||
return Ok(Box::new(CapturerPortable::new(current_display, use_yuv)));
|
if current_display == *display_service::PRIMARY_DISPLAY_IDX {
|
||||||
|
return Ok(Box::new(CapturerPortable::new(current_display, use_yuv)));
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"Ignore capture display index: {}, the primary display index is: {}",
|
||||||
|
current_display,
|
||||||
|
*display_service::PRIMARY_DISPLAY_IDX
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log::debug!("Create capturer dxgi|gdi");
|
log::debug!("Create capturer dxgi|gdi");
|
||||||
return Ok(Box::new(
|
return Ok(Box::new(
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
thread::{self, JoinHandle},
|
thread::{self, JoinHandle},
|
||||||
time,
|
time,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait Service: Send + Sync {
|
pub trait Service: Send + Sync {
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> String;
|
||||||
fn on_subscribe(&self, sub: ConnInner);
|
fn on_subscribe(&self, sub: ConnInner);
|
||||||
fn on_unsubscribe(&self, id: i32);
|
fn on_unsubscribe(&self, id: i32);
|
||||||
fn is_subed(&self, id: i32) -> bool;
|
fn is_subed(&self, id: i32) -> bool;
|
||||||
fn join(&self);
|
fn join(&self);
|
||||||
|
fn get_option(&self, opt: &str) -> Option<String>;
|
||||||
|
fn set_option(&self, opt: &str, val: &str) -> Option<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Subscriber: Default + Send + Sync + 'static {
|
pub trait Subscriber: Default + Send + Sync + 'static {
|
||||||
@ -20,12 +23,13 @@ pub trait Subscriber: Default + Send + Sync + 'static {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ServiceInner<T: Subscriber + From<ConnInner>> {
|
pub struct ServiceInner<T: Subscriber + From<ConnInner>> {
|
||||||
name: &'static str,
|
name: String,
|
||||||
handle: Option<JoinHandle<()>>,
|
handle: Option<JoinHandle<()>>,
|
||||||
subscribes: HashMap<i32, T>,
|
subscribes: HashMap<i32, T>,
|
||||||
new_subscribes: HashMap<i32, T>,
|
new_subscribes: HashMap<i32, T>,
|
||||||
active: bool,
|
active: bool,
|
||||||
need_snapshot: bool,
|
need_snapshot: bool,
|
||||||
|
options: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Reset {
|
pub trait Reset {
|
||||||
@ -37,6 +41,35 @@ pub struct ServiceSwap<T: Subscriber + From<ConnInner>>(ServiceTmpl<T>);
|
|||||||
pub type GenericService = ServiceTmpl<ConnInner>;
|
pub type GenericService = ServiceTmpl<ConnInner>;
|
||||||
pub const HIBERNATE_TIMEOUT: u64 = 30;
|
pub const HIBERNATE_TIMEOUT: u64 = 30;
|
||||||
pub const MAX_ERROR_TIMEOUT: u64 = 1_000;
|
pub const MAX_ERROR_TIMEOUT: u64 = 1_000;
|
||||||
|
pub const SERVICE_OPTION_VALUE_TRUE: &str = "1";
|
||||||
|
pub const SERVICE_OPTION_VALUE_FALSE: &str = "0";
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EmptyExtraFieldService {
|
||||||
|
pub sp: GenericService,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for EmptyExtraFieldService {
|
||||||
|
type Target = ServiceTmpl<ConnInner>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.sp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for EmptyExtraFieldService {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.sp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmptyExtraFieldService {
|
||||||
|
pub fn new(name: String, need_snapshot: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
sp: GenericService::new(name, need_snapshot),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Subscriber + From<ConnInner>> ServiceInner<T> {
|
impl<T: Subscriber + From<ConnInner>> ServiceInner<T> {
|
||||||
fn send_new_subscribes(&mut self, msg: Arc<Message>) {
|
fn send_new_subscribes(&mut self, msg: Arc<Message>) {
|
||||||
@ -60,8 +93,8 @@ impl<T: Subscriber + From<ConnInner>> ServiceInner<T> {
|
|||||||
|
|
||||||
impl<T: Subscriber + From<ConnInner>> Service for ServiceTmpl<T> {
|
impl<T: Subscriber + From<ConnInner>> Service for ServiceTmpl<T> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> String {
|
||||||
self.0.read().unwrap().name
|
self.0.read().unwrap().name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_subed(&self, id: i32) -> bool {
|
fn is_subed(&self, id: i32) -> bool {
|
||||||
@ -96,6 +129,18 @@ impl<T: Subscriber + From<ConnInner>> Service for ServiceTmpl<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_option(&self, opt: &str) -> Option<String> {
|
||||||
|
self.0.read().unwrap().options.get(opt).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_option(&self, opt: &str, val: &str) -> Option<String> {
|
||||||
|
self.0
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.options
|
||||||
|
.insert(opt.to_string(), val.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Subscriber + From<ConnInner>> Clone for ServiceTmpl<T> {
|
impl<T: Subscriber + From<ConnInner>> Clone for ServiceTmpl<T> {
|
||||||
@ -105,7 +150,7 @@ impl<T: Subscriber + From<ConnInner>> Clone for ServiceTmpl<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
||||||
pub fn new(name: &'static str, need_snapshot: bool) -> Self {
|
pub fn new(name: String, need_snapshot: bool) -> Self {
|
||||||
Self(Arc::new(RwLock::new(ServiceInner::<T> {
|
Self(Arc::new(RwLock::new(ServiceInner::<T> {
|
||||||
name,
|
name,
|
||||||
active: true,
|
active: true,
|
||||||
@ -114,6 +159,21 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_option_true(&self, opt: &str) -> bool {
|
||||||
|
self.get_option(opt)
|
||||||
|
.map_or(false, |v| v == SERVICE_OPTION_VALUE_TRUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_option_bool(&self, opt: &str, val: bool) {
|
||||||
|
if val {
|
||||||
|
self.set_option(opt, SERVICE_OPTION_VALUE_TRUE);
|
||||||
|
} else {
|
||||||
|
self.set_option(opt, SERVICE_OPTION_VALUE_FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn has_subscribes(&self) -> bool {
|
pub fn has_subscribes(&self) -> bool {
|
||||||
self.0.read().unwrap().has_subscribes()
|
self.0.read().unwrap().has_subscribes()
|
||||||
@ -189,14 +249,15 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn repeat<S, F>(&self, interval_ms: u64, callback: F)
|
pub fn repeat<S, F, Svc>(svc: &Svc, interval_ms: u64, callback: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(Self, &mut S) -> ResultType<()> + Send,
|
F: 'static + FnMut(Svc, &mut S) -> ResultType<()> + Send,
|
||||||
S: 'static + Default + Reset,
|
S: 'static + Default + Reset,
|
||||||
|
Svc: 'static + Clone + Send + DerefMut<Target = ServiceTmpl<T>>,
|
||||||
{
|
{
|
||||||
let interval = time::Duration::from_millis(interval_ms);
|
let interval = time::Duration::from_millis(interval_ms);
|
||||||
let mut callback = callback;
|
let mut callback = callback;
|
||||||
let sp = self.clone();
|
let sp = svc.clone();
|
||||||
let thread = thread::spawn(move || {
|
let thread = thread::spawn(move || {
|
||||||
let mut state = S::default();
|
let mut state = S::default();
|
||||||
let mut may_reset = false;
|
let mut may_reset = false;
|
||||||
@ -223,14 +284,15 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
|||||||
}
|
}
|
||||||
log::info!("Service {} exit", sp.name());
|
log::info!("Service {} exit", sp.name());
|
||||||
});
|
});
|
||||||
self.0.write().unwrap().handle = Some(thread);
|
svc.0.write().unwrap().handle = Some(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run<F>(&self, callback: F)
|
pub fn run<F, Svc>(svc: &Svc, callback: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(Self) -> ResultType<()> + Send,
|
F: 'static + FnMut(Svc) -> ResultType<()> + Send,
|
||||||
|
Svc: 'static + Clone + Send + DerefMut<Target = ServiceTmpl<T>>,
|
||||||
{
|
{
|
||||||
let sp = self.clone();
|
let sp = svc.clone();
|
||||||
let mut callback = callback;
|
let mut callback = callback;
|
||||||
let thread = thread::spawn(move || {
|
let thread = thread::spawn(move || {
|
||||||
let mut error_timeout = HIBERNATE_TIMEOUT;
|
let mut error_timeout = HIBERNATE_TIMEOUT;
|
||||||
@ -259,7 +321,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
|
|||||||
}
|
}
|
||||||
log::info!("Service {} exit", sp.name());
|
log::info!("Service {} exit", sp.name());
|
||||||
});
|
});
|
||||||
self.0.write().unwrap().handle = Some(thread);
|
svc.0.write().unwrap().handle = Some(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -18,15 +18,11 @@
|
|||||||
// to-do:
|
// to-do:
|
||||||
// https://slhck.info/video/2017/03/01/rate-control.html
|
// https://slhck.info/video/2017/03/01/rate-control.html
|
||||||
|
|
||||||
use super::{video_qos::VideoQoS, *};
|
use super::{service::ServiceTmpl, video_qos::VideoQoS, *};
|
||||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
|
||||||
use crate::virtual_display_manager;
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::{platform::windows::is_process_consent_running, privacy_win_mag};
|
use crate::{platform::windows::is_process_consent_running, privacy_win_mag};
|
||||||
#[cfg(windows)]
|
|
||||||
use hbb_common::get_version_number;
|
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
protobuf::MessageField,
|
anyhow::anyhow,
|
||||||
tokio::sync::{
|
tokio::sync::{
|
||||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||||
Mutex as TokioMutex,
|
Mutex as TokioMutex,
|
||||||
@ -51,71 +47,18 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const NAME: &'static str = "video";
|
pub const NAME: &'static str = "video";
|
||||||
|
pub const OPTION_DISPLAY_CHANGED: &'static str = "changed";
|
||||||
struct ChangedResolution {
|
pub const OPTION_REFRESH: &'static str = "refresh";
|
||||||
original: (i32, i32),
|
|
||||||
changed: (i32, i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref CURRENT_DISPLAY: Arc<Mutex<usize>> = Arc::new(Mutex::new(usize::MAX));
|
|
||||||
static ref LAST_ACTIVE: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
|
|
||||||
static ref SWITCH: Arc<Mutex<bool>> = Default::default();
|
|
||||||
static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option<Instant>)>, Arc<TokioMutex<UnboundedReceiver<(i32, Option<Instant>)>>>) = {
|
static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option<Instant>)>, Arc<TokioMutex<UnboundedReceiver<(i32, Option<Instant>)>>>) = {
|
||||||
let (tx, rx) = unbounded_channel();
|
let (tx, rx) = unbounded_channel();
|
||||||
(tx, Arc::new(TokioMutex::new(rx)))
|
(tx, Arc::new(TokioMutex::new(rx)))
|
||||||
};
|
};
|
||||||
static ref PRIVACY_MODE_CONN_ID: Mutex<i32> = Mutex::new(0);
|
static ref PRIVACY_MODE_CONN_ID: Mutex<i32> = Mutex::new(0);
|
||||||
static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported();
|
|
||||||
pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
|
pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
|
||||||
pub static ref IS_UAC_RUNNING: Arc<Mutex<bool>> = Default::default();
|
pub static ref IS_UAC_RUNNING: Arc<Mutex<bool>> = Default::default();
|
||||||
pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default();
|
pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default();
|
||||||
pub static ref LAST_SYNC_DISPLAYS: Arc<RwLock<Vec<DisplayInfo>>> = Default::default();
|
|
||||||
static ref CHANGED_RESOLUTIONS: Arc<RwLock<HashMap<String, ChangedResolution>>> = Default::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_last_changed_resolution(display_name: &str, original: (i32, i32), changed: (i32, i32)) {
|
|
||||||
let mut lock = CHANGED_RESOLUTIONS.write().unwrap();
|
|
||||||
match lock.get_mut(display_name) {
|
|
||||||
Some(res) => res.changed = changed,
|
|
||||||
None => {
|
|
||||||
lock.insert(
|
|
||||||
display_name.to_owned(),
|
|
||||||
ChangedResolution { original, changed },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
pub fn reset_resolutions() {
|
|
||||||
for (name, res) in CHANGED_RESOLUTIONS.read().unwrap().iter() {
|
|
||||||
let (w, h) = res.original;
|
|
||||||
if let Err(e) = crate::platform::change_resolution(name, w as _, h as _) {
|
|
||||||
log::error!(
|
|
||||||
"Failed to reset resolution of display '{}' to ({},{}): {}",
|
|
||||||
name,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_capturer_mag_supported() -> bool {
|
|
||||||
#[cfg(windows)]
|
|
||||||
return scrap::CapturerMag::is_supported();
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn capture_cursor_embedded() -> bool {
|
|
||||||
scrap::is_cursor_embedded()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -133,15 +76,6 @@ pub fn get_privacy_mode_conn_id() -> i32 {
|
|||||||
*PRIVACY_MODE_CONN_ID.lock().unwrap()
|
*PRIVACY_MODE_CONN_ID.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_privacy_mode_supported() -> bool {
|
|
||||||
#[cfg(windows)]
|
|
||||||
return *IS_CAPTURER_MAGNIFIER_SUPPORTED
|
|
||||||
&& get_version_number(&crate::VERSION) > get_version_number("1.1.9");
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VideoFrameController {
|
struct VideoFrameController {
|
||||||
cur: Instant,
|
cur: Instant,
|
||||||
send_conn_ids: HashSet<i32>,
|
send_conn_ids: HashSet<i32>,
|
||||||
@ -192,10 +126,37 @@ impl VideoFrameController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> GenericService {
|
#[derive(Clone)]
|
||||||
let sp = GenericService::new(NAME, true);
|
pub struct VideoService {
|
||||||
sp.run(run);
|
sp: GenericService,
|
||||||
sp
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for VideoService {
|
||||||
|
type Target = ServiceTmpl<ConnInner>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.sp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for VideoService {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.sp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_service_name(idx: usize) -> String {
|
||||||
|
format!("{}{}", NAME, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(idx: usize) -> GenericService {
|
||||||
|
let vs = VideoService {
|
||||||
|
sp: GenericService::new(get_service_name(idx), true),
|
||||||
|
idx,
|
||||||
|
};
|
||||||
|
GenericService::run(&vs, run);
|
||||||
|
vs.sp
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_display_changed(
|
fn check_display_changed(
|
||||||
@ -221,19 +182,10 @@ fn check_display_changed(
|
|||||||
if n != last_n {
|
if n != last_n {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
match displays.get(last_current) {
|
||||||
for (i, d) in displays.iter().enumerate() {
|
Some(d) => d.width() != last_width || d.height() != last_height,
|
||||||
if d.is_primary() {
|
None => true,
|
||||||
if i != last_current {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
if d.width() != last_width || d.height() != last_height {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capturer object is expensive, avoiding to create it frequently.
|
// Capturer object is expensive, avoiding to create it frequently.
|
||||||
@ -317,14 +269,27 @@ fn create_capturer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This function works on privacy mode. Windows only for now.
|
// This function works on privacy mode. Windows only for now.
|
||||||
pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> String {
|
pub fn test_create_capturer(
|
||||||
|
privacy_mode_id: i32,
|
||||||
|
display_idx: usize,
|
||||||
|
timeout_millis: u64,
|
||||||
|
) -> String {
|
||||||
let test_begin = Instant::now();
|
let test_begin = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
let err = match get_current_display() {
|
let err = match try_get_displays() {
|
||||||
Ok((_, current, display)) => {
|
Ok(mut displays) => {
|
||||||
match create_capturer(privacy_mode_id, display, true, current, false) {
|
if displays.len() <= display_idx {
|
||||||
Ok(_) => return "".to_owned(),
|
anyhow!(
|
||||||
Err(e) => e,
|
"Failed to get display {}, the displays' count is {}",
|
||||||
|
display_idx,
|
||||||
|
displays.len()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let display = displays.remove(display_idx);
|
||||||
|
match create_capturer(privacy_mode_id, display, true, display_idx, false) {
|
||||||
|
Ok(_) => return "".to_owned(),
|
||||||
|
Err(e) => e,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
@ -352,6 +317,7 @@ fn check_uac_switch(privacy_mode_id: i32, capturer_privacy_mode_id: i32) -> Resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct CapturerInfo {
|
pub(super) struct CapturerInfo {
|
||||||
|
pub name: String,
|
||||||
pub origin: (i32, i32),
|
pub origin: (i32, i32),
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
@ -376,7 +342,11 @@ impl DerefMut for CapturerInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<CapturerInfo> {
|
fn get_capturer(
|
||||||
|
current: usize,
|
||||||
|
use_yuv: bool,
|
||||||
|
portable_service_running: bool,
|
||||||
|
) -> ResultType<CapturerInfo> {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
if !scrap::is_x11() {
|
if !scrap::is_x11() {
|
||||||
@ -384,8 +354,18 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (ndisplay, current, display) = get_current_display()?;
|
let mut displays = try_get_displays()?;
|
||||||
|
let ndisplay = displays.len();
|
||||||
|
if ndisplay <= current {
|
||||||
|
bail!(
|
||||||
|
"Failed to get display {}, displays len: {}",
|
||||||
|
current,
|
||||||
|
ndisplay
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let display = displays.remove(current);
|
||||||
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
||||||
|
let name = display.name();
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}, name:{}",
|
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}, name:{}",
|
||||||
ndisplay,
|
ndisplay,
|
||||||
@ -395,7 +375,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
|
|||||||
height,
|
height,
|
||||||
num_cpus::get_physical(),
|
num_cpus::get_physical(),
|
||||||
num_cpus::get(),
|
num_cpus::get(),
|
||||||
display.name(),
|
&name,
|
||||||
);
|
);
|
||||||
|
|
||||||
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||||
@ -429,6 +409,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
|
|||||||
portable_service_running,
|
portable_service_running,
|
||||||
)?;
|
)?;
|
||||||
Ok(CapturerInfo {
|
Ok(CapturerInfo {
|
||||||
|
name,
|
||||||
origin,
|
origin,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -440,42 +421,13 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_displays_new() -> Option<Vec<Display>> {
|
fn run(vs: VideoService) -> ResultType<()> {
|
||||||
let displays = try_get_displays().ok()?;
|
|
||||||
let last_sync_displays = &*LAST_SYNC_DISPLAYS.read().unwrap();
|
|
||||||
if displays.len() != last_sync_displays.len() {
|
|
||||||
// No need to check if the resolutions are changed by third process.
|
|
||||||
Some(displays)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_get_displays_changed_msg() -> Option<Message> {
|
|
||||||
let displays = check_displays_new()?;
|
|
||||||
// Display to DisplayInfo
|
|
||||||
let (current, displays) = get_displays_2(&displays);
|
|
||||||
let mut pi = PeerInfo {
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
pi.displays = displays.clone();
|
|
||||||
pi.current_display = current as _;
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_peer_info(pi);
|
|
||||||
*LAST_SYNC_DISPLAYS.write().unwrap() = displays;
|
|
||||||
Some(msg_out)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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: GenericService) -> ResultType<()> {
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
let _wake_lock = get_wake_lock();
|
let _wake_lock = get_wake_lock();
|
||||||
|
|
||||||
// ensure_inited() is needed because clear() may be called.
|
// ensure_inited() is needed because clear() may be called.
|
||||||
|
// to-do: wayland ensure_inited should pass current display index.
|
||||||
|
// But for now, we do not support multi-screen capture on wayland.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
super::wayland::ensure_inited()?;
|
super::wayland::ensure_inited()?;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -483,7 +435,9 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let last_portable_service_running = false;
|
let last_portable_service_running = false;
|
||||||
|
|
||||||
let mut c = get_capturer(true, last_portable_service_running)?;
|
let display_idx = vs.idx;
|
||||||
|
let sp = vs.sp;
|
||||||
|
let mut c = get_capturer(display_idx, true, last_portable_service_running)?;
|
||||||
|
|
||||||
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
let mut video_qos = VIDEO_QOS.lock().unwrap();
|
||||||
video_qos.refresh(None);
|
video_qos.refresh(None);
|
||||||
@ -506,37 +460,18 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
c.set_use_yuv(encoder.use_yuv());
|
c.set_use_yuv(encoder.use_yuv());
|
||||||
VIDEO_QOS.lock().unwrap().store_bitrate(encoder.bitrate());
|
VIDEO_QOS.lock().unwrap().store_bitrate(encoder.bitrate());
|
||||||
|
|
||||||
if *SWITCH.lock().unwrap() {
|
if sp.is_option_true(OPTION_DISPLAY_CHANGED) {
|
||||||
log::debug!("Broadcasting display switch");
|
log::debug!("Broadcasting display changed");
|
||||||
let mut misc = Misc::new();
|
broadcast_display_changed(
|
||||||
let display_name = get_current_display()
|
display_idx,
|
||||||
.map(|(_, _, d)| d.name())
|
&sp,
|
||||||
.unwrap_or_default();
|
Some((c.name.clone(), c.origin.clone(), c.width, c.height)),
|
||||||
let original_resolution = get_original_resolution(&display_name, c.width, c.height);
|
);
|
||||||
misc.set_switch_display(SwitchDisplay {
|
sp.set_option_bool(OPTION_DISPLAY_CHANGED, false);
|
||||||
display: c.current as _,
|
}
|
||||||
x: c.origin.0 as _,
|
|
||||||
y: c.origin.1 as _,
|
if sp.is_option_true(OPTION_REFRESH) {
|
||||||
width: c.width as _,
|
sp.set_option_bool(OPTION_REFRESH, false);
|
||||||
height: c.height as _,
|
|
||||||
cursor_embedded: capture_cursor_embedded(),
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
resolutions: Some(SupportedResolutions {
|
|
||||||
resolutions: if display_name.is_empty() {
|
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
crate::platform::resolutions(&display_name)
|
|
||||||
},
|
|
||||||
..SupportedResolutions::default()
|
|
||||||
})
|
|
||||||
.into(),
|
|
||||||
original_resolution,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_misc(misc);
|
|
||||||
*SWITCH.lock().unwrap() = false;
|
|
||||||
sp.send(msg_out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut frame_controller = VideoFrameController::new();
|
let mut frame_controller = VideoFrameController::new();
|
||||||
@ -572,13 +507,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
}
|
}
|
||||||
drop(video_qos);
|
drop(video_qos);
|
||||||
|
|
||||||
if *SWITCH.lock().unwrap() {
|
if sp.is_option_true(OPTION_DISPLAY_CHANGED) || sp.is_option_true(OPTION_REFRESH) {
|
||||||
bail!("SWITCH");
|
|
||||||
}
|
|
||||||
if c.current != *CURRENT_DISPLAY.lock().unwrap() {
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
super::wayland::clear();
|
|
||||||
*SWITCH.lock().unwrap() = true;
|
|
||||||
bail!("SWITCH");
|
bail!("SWITCH");
|
||||||
}
|
}
|
||||||
if codec_name != Encoder::negotiated_codec() {
|
if codec_name != Encoder::negotiated_codec() {
|
||||||
@ -604,23 +533,12 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
// Capturer on macos does not return Err event the solution is changed.
|
// Capturer on macos does not return Err event the solution is changed.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
|
if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
|
||||||
|
sp.set_option_bool(OPTION_DISPLAY_CHANGED, true);
|
||||||
log::info!("Displays changed");
|
log::info!("Displays changed");
|
||||||
*SWITCH.lock().unwrap() = true;
|
|
||||||
bail!("SWITCH");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(msg_out) = check_get_displays_changed_msg() {
|
|
||||||
sp.send(msg_out);
|
|
||||||
log::info!("Displays changed");
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
super::wayland::clear();
|
|
||||||
*SWITCH.lock().unwrap() = true;
|
|
||||||
bail!("SWITCH");
|
bail!("SWITCH");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*LAST_ACTIVE.lock().unwrap() = now;
|
|
||||||
|
|
||||||
frame_controller.reset();
|
frame_controller.reset();
|
||||||
|
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
@ -631,8 +549,14 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
match frame {
|
match frame {
|
||||||
scrap::Frame::RAW(data) => {
|
scrap::Frame::RAW(data) => {
|
||||||
if data.len() != 0 {
|
if data.len() != 0 {
|
||||||
let send_conn_ids =
|
let send_conn_ids = handle_one_frame(
|
||||||
handle_one_frame(&sp, data, ms, &mut encoder, recorder.clone())?;
|
display_idx,
|
||||||
|
&sp,
|
||||||
|
data,
|
||||||
|
ms,
|
||||||
|
&mut encoder,
|
||||||
|
recorder.clone(),
|
||||||
|
)?;
|
||||||
frame_controller.set_send(now, send_conn_ids);
|
frame_controller.set_send(now, send_conn_ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -649,7 +573,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
let time = now - start;
|
let time = now - start;
|
||||||
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
||||||
let send_conn_ids =
|
let send_conn_ids =
|
||||||
handle_one_frame(&sp, &frame, ms, &mut encoder, recorder.clone())?;
|
handle_one_frame(display_idx, &sp, &frame, ms, &mut encoder, recorder.clone())?;
|
||||||
frame_controller.set_send(now, send_conn_ids);
|
frame_controller.set_send(now, send_conn_ids);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
@ -693,7 +617,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
log::info!("Displays changed");
|
log::info!("Displays changed");
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
super::wayland::clear();
|
super::wayland::clear();
|
||||||
*SWITCH.lock().unwrap() = true;
|
sp.set_option_bool(OPTION_DISPLAY_CHANGED, true);
|
||||||
bail!("SWITCH");
|
bail!("SWITCH");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -829,6 +753,7 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn handle_one_frame(
|
fn handle_one_frame(
|
||||||
|
display: usize,
|
||||||
sp: &GenericService,
|
sp: &GenericService,
|
||||||
frame: &[u8],
|
frame: &[u8],
|
||||||
ms: i64,
|
ms: i64,
|
||||||
@ -844,7 +769,10 @@ fn handle_one_frame(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut send_conn_ids: HashSet<i32> = Default::default();
|
let mut send_conn_ids: HashSet<i32> = Default::default();
|
||||||
if let Ok(msg) = encoder.encode_to_message(frame, ms) {
|
if let Ok(mut vf) = encoder.encode_to_message(frame, ms) {
|
||||||
|
vf.display = display as _;
|
||||||
|
let mut msg = Message::new();
|
||||||
|
msg.set_video_frame(vf);
|
||||||
#[cfg(not(target_os = "ios"))]
|
#[cfg(not(target_os = "ios"))]
|
||||||
recorder
|
recorder
|
||||||
.lock()
|
.lock()
|
||||||
@ -856,69 +784,6 @@ fn handle_one_frame(
|
|||||||
Ok(send_conn_ids)
|
Ok(send_conn_ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageField<Resolution> {
|
|
||||||
#[cfg(all(windows, feature = "virtual_display_driver"))]
|
|
||||||
let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name);
|
|
||||||
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
|
|
||||||
let is_virtual_display = false;
|
|
||||||
Some(if is_virtual_display {
|
|
||||||
Resolution {
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut changed_resolutions = CHANGED_RESOLUTIONS.write().unwrap();
|
|
||||||
let (width, height) = match changed_resolutions.get(display_name) {
|
|
||||||
Some(res) => {
|
|
||||||
if res.changed.0 != w as i32 || res.changed.1 != h as i32 {
|
|
||||||
// If the resolution is changed by third process, remove the record in changed_resolutions.
|
|
||||||
changed_resolutions.remove(display_name);
|
|
||||||
(w as _, h as _)
|
|
||||||
} else {
|
|
||||||
res.original
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => (w as _, h as _),
|
|
||||||
};
|
|
||||||
Resolution {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
|
|
||||||
let mut displays = Vec::new();
|
|
||||||
let mut primary = 0;
|
|
||||||
for (i, d) in all.iter().enumerate() {
|
|
||||||
if d.is_primary() {
|
|
||||||
primary = i;
|
|
||||||
}
|
|
||||||
let display_name = d.name();
|
|
||||||
let original_resolution = get_original_resolution(&display_name, d.width(), d.height());
|
|
||||||
displays.push(DisplayInfo {
|
|
||||||
x: d.origin().0 as _,
|
|
||||||
y: d.origin().1 as _,
|
|
||||||
width: d.width() as _,
|
|
||||||
height: d.height() as _,
|
|
||||||
name: display_name,
|
|
||||||
online: d.is_online(),
|
|
||||||
cursor_embedded: false,
|
|
||||||
original_resolution,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let mut lock = CURRENT_DISPLAY.lock().unwrap();
|
|
||||||
if *lock >= displays.len() {
|
|
||||||
*lock = primary
|
|
||||||
}
|
|
||||||
(*lock, displays)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_inited_msg() -> Option<Message> {
|
pub fn is_inited_msg() -> Option<Message> {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
if !scrap::is_x11() {
|
if !scrap::is_x11() {
|
||||||
@ -927,65 +792,10 @@ pub fn is_inited_msg() -> Option<Message> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// switch to primary display if long time (30 seconds) no users
|
|
||||||
#[inline]
|
|
||||||
pub fn try_reset_current_display() {
|
|
||||||
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
|
|
||||||
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
|
|
||||||
}
|
|
||||||
*LAST_ACTIVE.lock().unwrap() = time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
if !scrap::is_x11() {
|
|
||||||
return super::wayland::get_displays().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(get_displays_2(&try_get_displays()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn switch_display(i: i32) {
|
|
||||||
let i = i as usize;
|
|
||||||
if let Ok((_, displays)) = get_displays().await {
|
|
||||||
if i < displays.len() {
|
|
||||||
*CURRENT_DISPLAY.lock().unwrap() = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn refresh() {
|
pub fn refresh() {
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
Display::refresh_size();
|
Display::refresh_size();
|
||||||
*SWITCH.lock().unwrap() = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_primary() -> usize {
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
if !scrap::is_x11() {
|
|
||||||
return match super::wayland::get_primary() {
|
|
||||||
Ok(n) => n,
|
|
||||||
Err(_) => 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(all) = try_get_displays() {
|
|
||||||
for (i, d) in all.iter().enumerate() {
|
|
||||||
if d.is_primary() {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn switch_to_primary() {
|
|
||||||
switch_display(get_primary() as _).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -1034,30 +844,6 @@ fn try_get_displays() -> ResultType<Vec<Display>> {
|
|||||||
Ok(Display::all()?)
|
Ok(Display::all()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_current_display_2(mut all: Vec<Display>) -> ResultType<(usize, usize, Display)> {
|
|
||||||
let mut current = *CURRENT_DISPLAY.lock().unwrap() as usize;
|
|
||||||
if all.len() == 0 {
|
|
||||||
bail!("No displays");
|
|
||||||
}
|
|
||||||
let n = all.len();
|
|
||||||
if current >= n {
|
|
||||||
current = 0;
|
|
||||||
for (i, d) in all.iter().enumerate() {
|
|
||||||
if d.is_primary() {
|
|
||||||
current = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*CURRENT_DISPLAY.lock().unwrap() = current;
|
|
||||||
}
|
|
||||||
return Ok((n, current, all.remove(current)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_current_display() -> ResultType<(usize, usize, Display)> {
|
|
||||||
get_current_display_2(try_get_displays()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn start_uac_elevation_check() {
|
fn start_uac_elevation_check() {
|
||||||
static START: Once = Once::new();
|
static START: Once = Once::new();
|
||||||
@ -1090,3 +876,63 @@ fn get_wake_lock() -> crate::platform::WakeLock {
|
|||||||
};
|
};
|
||||||
crate::platform::WakeLock::new(display, idle, sleep)
|
crate::platform::WakeLock::new(display, idle, sleep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn broadcast_display_changed(
|
||||||
|
display_idx: usize,
|
||||||
|
sp: &GenericService,
|
||||||
|
display_meta: Option<(String, (i32, i32), usize, usize)>,
|
||||||
|
) {
|
||||||
|
if let Some(msg_out) = make_display_changed_msg(display_idx, display_meta) {
|
||||||
|
sp.send(msg_out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_display_info_simple_meta(display_idx: usize) -> Option<(String, (i32, i32), usize, usize)> {
|
||||||
|
let displays = display_service::try_get_displays().ok()?;
|
||||||
|
if let Some(display) = displays.get(display_idx) {
|
||||||
|
Some((
|
||||||
|
display.name(),
|
||||||
|
display.origin(),
|
||||||
|
display.width(),
|
||||||
|
display.height(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_display_changed_msg(
|
||||||
|
display_idx: usize,
|
||||||
|
display_meta: Option<(String, (i32, i32), usize, usize)>,
|
||||||
|
) -> Option<Message> {
|
||||||
|
let mut misc = Misc::new();
|
||||||
|
let (name, origin, width, height) = match display_meta {
|
||||||
|
Some(d) => d,
|
||||||
|
None => get_display_info_simple_meta(display_idx)?,
|
||||||
|
};
|
||||||
|
let original_resolution = display_service::get_original_resolution(&name, width, height);
|
||||||
|
misc.set_switch_display(SwitchDisplay {
|
||||||
|
display: display_idx as _,
|
||||||
|
x: origin.0,
|
||||||
|
y: origin.1,
|
||||||
|
width: width as _,
|
||||||
|
height: height as _,
|
||||||
|
cursor_embedded: display_service::capture_cursor_embedded(),
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
resolutions: Some(SupportedResolutions {
|
||||||
|
resolutions: if name.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
crate::platform::resolutions(&name)
|
||||||
|
},
|
||||||
|
..SupportedResolutions::default()
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
original_resolution,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
msg_out.set_misc(misc);
|
||||||
|
Some(msg_out)
|
||||||
|
}
|
||||||
|
@ -142,9 +142,11 @@ pub(super) async fn check_init() -> ResultType<()> {
|
|||||||
if *CAP_DISPLAY_INFO.read().unwrap() == 0 {
|
if *CAP_DISPLAY_INFO.read().unwrap() == 0 {
|
||||||
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
|
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
|
||||||
if *lock == 0 {
|
if *lock == 0 {
|
||||||
let all = Display::all()?;
|
let mut all = Display::all()?;
|
||||||
let num = all.len();
|
let num = all.len();
|
||||||
let (primary, mut displays) = super::video_service::get_displays_2(&all);
|
let primary = super::display_service::get_primary_2(&all);
|
||||||
|
let current = primary;
|
||||||
|
let mut displays = super::display_service::to_display_info(&all);
|
||||||
for display in displays.iter_mut() {
|
for display in displays.iter_mut() {
|
||||||
display.cursor_embedded = is_cursor_embedded();
|
display.cursor_embedded = is_cursor_embedded();
|
||||||
}
|
}
|
||||||
@ -154,12 +156,11 @@ pub(super) async fn check_init() -> ResultType<()> {
|
|||||||
rects.push((d.origin(), d.width(), d.height()));
|
rects.push((d.origin(), d.width(), d.height()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (ndisplay, current, display) =
|
let display = all.remove(current);
|
||||||
super::video_service::get_current_display_2(all)?;
|
|
||||||
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}",
|
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}",
|
||||||
ndisplay,
|
num,
|
||||||
current,
|
current,
|
||||||
&origin,
|
&origin,
|
||||||
width,
|
width,
|
||||||
@ -213,16 +214,14 @@ pub(super) async fn check_init() -> ResultType<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
|
pub(super) async fn get_displays() -> ResultType<Vec<DisplayInfo>> {
|
||||||
check_init().await?;
|
check_init().await?;
|
||||||
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
||||||
if addr != 0 {
|
if addr != 0 {
|
||||||
let cap_display_info: *const CapDisplayInfo = addr as _;
|
let cap_display_info: *const CapDisplayInfo = addr as _;
|
||||||
unsafe {
|
unsafe {
|
||||||
let cap_display_info = &*cap_display_info;
|
let cap_display_info = &*cap_display_info;
|
||||||
let primary = cap_display_info.primary;
|
Ok(cap_display_info.displays.clone())
|
||||||
let displays = cap_display_info.displays.clone();
|
|
||||||
Ok((primary, displays))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bail!("Failed to get capturer display info");
|
bail!("Failed to get capturer display info");
|
||||||
@ -268,6 +267,9 @@ pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> {
|
|||||||
let cap_display_info = &*cap_display_info;
|
let cap_display_info = &*cap_display_info;
|
||||||
let rect = cap_display_info.rects[cap_display_info.current];
|
let rect = cap_display_info.rects[cap_display_info.current];
|
||||||
Ok(super::video_service::CapturerInfo {
|
Ok(super::video_service::CapturerInfo {
|
||||||
|
name: cap_display_info.displays[cap_display_info.current]
|
||||||
|
.name
|
||||||
|
.clone(),
|
||||||
origin: rect.0,
|
origin: rect.0,
|
||||||
width: rect.1,
|
width: rect.1,
|
||||||
height: rect.2,
|
height: rect.2,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
iter::FromIterator,
|
iter::FromIterator,
|
||||||
process::Child,
|
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,7 +21,6 @@ mod cm;
|
|||||||
pub mod inline;
|
pub mod inline;
|
||||||
pub mod remote;
|
pub mod remote;
|
||||||
|
|
||||||
pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
type Status = (i32, bool, i64, String);
|
type Status = (i32, bool, i64, String);
|
||||||
|
|
||||||
@ -34,7 +32,6 @@ lazy_static::lazy_static! {
|
|||||||
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
#[cfg(not(any(feature = "flutter", feature = "cli")))]
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref CUR_SESSION: Arc<Mutex<Option<Session<remote::SciterHandler>>>> = Default::default();
|
pub static ref CUR_SESSION: Arc<Mutex<Option<Session<remote::SciterHandler>>>> = Default::default();
|
||||||
static ref CHILDREN : Children = Default::default();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UIHostHandler;
|
struct UIHostHandler;
|
||||||
@ -598,6 +595,10 @@ impl UI {
|
|||||||
fn get_login_device_info(&self) -> String {
|
fn get_login_device_info(&self) -> String {
|
||||||
get_login_device_info_json()
|
get_login_device_info_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn support_remove_wallpaper(&self) -> bool {
|
||||||
|
support_remove_wallpaper()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sciter::EventHandler for UI {
|
impl sciter::EventHandler for UI {
|
||||||
@ -683,6 +684,7 @@ impl sciter::EventHandler for UI {
|
|||||||
fn default_video_save_directory();
|
fn default_video_save_directory();
|
||||||
fn handle_relay_id(String);
|
fn handle_relay_id(String);
|
||||||
fn get_login_device_info();
|
fn get_login_device_info();
|
||||||
|
fn support_remove_wallpaper();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,10 +298,11 @@ class Header: Reactor.Component {
|
|||||||
recording = !recording;
|
recording = !recording;
|
||||||
header.update();
|
header.update();
|
||||||
handler.record_status(recording);
|
handler.record_status(recording);
|
||||||
|
// 0 is just a dummy value. It will be ignored by the handler.
|
||||||
if (recording)
|
if (recording)
|
||||||
handler.refresh_video();
|
handler.refresh_video(0);
|
||||||
else
|
else
|
||||||
handler.record_screen(false, display_width, display_height);
|
handler.record_screen(false, 0, display_width, display_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(#screen) (_, me) {
|
event click $(#screen) (_, me) {
|
||||||
@ -370,7 +371,8 @@ class Header: Reactor.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
event click $(#refresh) {
|
event click $(#refresh) {
|
||||||
handler.refresh_video();
|
// 0 is just a dummy value. It will be ignored by the handler.
|
||||||
|
handler.refresh_video(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
event click $(#block-input) {
|
event click $(#block-input) {
|
||||||
|
@ -210,6 +210,7 @@ class Enhancements: Reactor.Component {
|
|||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
var has_hwcodec = handler.has_hwcodec();
|
var has_hwcodec = handler.has_hwcodec();
|
||||||
|
var support_remove_wallpaper = handler.support_remove_wallpaper();
|
||||||
var me = this;
|
var me = this;
|
||||||
self.timer(1ms, function() { me.toggleMenuState() });
|
self.timer(1ms, function() { me.toggleMenuState() });
|
||||||
return <li>{translate('Enhancements')}
|
return <li>{translate('Enhancements')}
|
||||||
@ -217,7 +218,7 @@ class Enhancements: Reactor.Component {
|
|||||||
{has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")} (beta)</li> : ""}
|
{has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")} (beta)</li> : ""}
|
||||||
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive bitrate")} (beta)</li>
|
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive bitrate")} (beta)</li>
|
||||||
<li #screen-recording>{translate("Recording")}</li>
|
<li #screen-recording>{translate("Recording")}</li>
|
||||||
{is_osx ? "" : <li #allow-remove-wallpaper><span>{svg_checkmark}</span>{translate("Remove wallpaper during incoming sessions")}</li>}
|
{support_remove_wallpaper ? <li #allow-remove-wallpaper><span>{svg_checkmark}</span>{translate("Remove wallpaper during incoming sessions")}</li> : ""}
|
||||||
</menu>
|
</menu>
|
||||||
</li>;
|
</li>;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,11 @@ impl InvokeUiSession for SciterHandler {
|
|||||||
"updateQualityStatus",
|
"updateQualityStatus",
|
||||||
&make_args!(
|
&make_args!(
|
||||||
status.speed.map_or(Value::null(), |it| it.into()),
|
status.speed.map_or(Value::null(), |it| it.into()),
|
||||||
status.fps.map_or(Value::null(), |it| it.into()),
|
status
|
||||||
|
.fps
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map_or(Value::null(), |(_, v)| (*v).into()),
|
||||||
status.delay.map_or(Value::null(), |it| it.into()),
|
status.delay.map_or(Value::null(), |it| it.into()),
|
||||||
status.target_bitrate.map_or(Value::null(), |it| it.into()),
|
status.target_bitrate.map_or(Value::null(), |it| it.into()),
|
||||||
status
|
status
|
||||||
@ -223,7 +227,7 @@ impl InvokeUiSession for SciterHandler {
|
|||||||
self.call("adaptSize", &make_args!());
|
self.call("adaptSize", &make_args!());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
|
fn on_rgba(&self, _display: usize, rgba: &mut scrap::ImageRgb) {
|
||||||
VIDEO
|
VIDEO
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -304,11 +308,11 @@ impl InvokeUiSession for SciterHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// RGBA is directly rendered by [on_rgba]. No need to store the rgba for the sciter ui.
|
/// RGBA is directly rendered by [on_rgba]. No need to store the rgba for the sciter ui.
|
||||||
fn get_rgba(&self) -> *const u8 {
|
fn get_rgba(&self, _display: usize) -> *const u8 {
|
||||||
std::ptr::null()
|
std::ptr::null()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_rgba(&self) {}
|
fn next_rgba(&self, _display: usize) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SciterSession(Session<SciterHandler>);
|
pub struct SciterSession(Session<SciterHandler>);
|
||||||
@ -449,8 +453,8 @@ impl sciter::EventHandler for SciterSession {
|
|||||||
fn save_view_style(String);
|
fn save_view_style(String);
|
||||||
fn save_image_quality(String);
|
fn save_image_quality(String);
|
||||||
fn save_custom_image_quality(i32);
|
fn save_custom_image_quality(i32);
|
||||||
fn refresh_video();
|
fn refresh_video(i32);
|
||||||
fn record_screen(bool, i32, i32);
|
fn record_screen(bool, i32, i32, i32);
|
||||||
fn record_status(bool);
|
fn record_status(bool);
|
||||||
fn get_toggle_option(String);
|
fn get_toggle_option(String);
|
||||||
fn is_privacy_mode_supported();
|
fn is_privacy_mode_supported();
|
||||||
|
@ -23,7 +23,7 @@ handler.setDisplay = function(x, y, w, h, cursor_embedded) {
|
|||||||
display_origin_y = y;
|
display_origin_y = y;
|
||||||
display_cursor_embedded = cursor_embedded;
|
display_cursor_embedded = cursor_embedded;
|
||||||
adaptDisplay();
|
adaptDisplay();
|
||||||
if (recording) handler.record_screen(true, w, h);
|
if (recording) handler.record_screen(true, 0, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case toolbar not shown correctly
|
// in case toolbar not shown correctly
|
||||||
@ -478,7 +478,7 @@ function self.closing() {
|
|||||||
var (x, y, w, h) = view.box(#rectw, #border, #screen);
|
var (x, y, w, h) = view.box(#rectw, #border, #screen);
|
||||||
if (is_file_transfer) save_file_transfer_close_state();
|
if (is_file_transfer) save_file_transfer_close_state();
|
||||||
if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h);
|
if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h);
|
||||||
if (recording) handler.record_screen(false, display_width, display_height);
|
if (recording) handler.record_screen(false, 0, display_width, display_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
var qualityMonitor;
|
var qualityMonitor;
|
||||||
|
@ -164,6 +164,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[cfg(windows)]
|
||||||
fn is_authorized(&self, id: i32) -> bool {
|
fn is_authorized(&self, id: i32) -> bool {
|
||||||
CLIENTS
|
CLIENTS
|
||||||
.read()
|
.read()
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
use hbb_common::password_security;
|
use hbb_common::password_security;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err,
|
||||||
config::{self, Config, LocalConfig, PeerConfig},
|
|
||||||
directories_next, log, tokio,
|
|
||||||
};
|
|
||||||
use hbb_common::{
|
|
||||||
bytes::Bytes,
|
bytes::Bytes,
|
||||||
|
config::{self, Config, LocalConfig, PeerConfig},
|
||||||
config::{CONNECT_TIMEOUT, RENDEZVOUS_PORT},
|
config::{CONNECT_TIMEOUT, RENDEZVOUS_PORT},
|
||||||
|
directories_next,
|
||||||
futures::future::join_all,
|
futures::future::join_all,
|
||||||
|
log,
|
||||||
rendezvous_proto::*,
|
rendezvous_proto::*,
|
||||||
|
tokio,
|
||||||
};
|
};
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
@ -17,9 +17,10 @@ use hbb_common::{
|
|||||||
tokio::{sync::mpsc, time},
|
tokio::{sync::mpsc, time},
|
||||||
};
|
};
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
use std::process::Child;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
process::Child,
|
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ use crate::ipc;
|
|||||||
|
|
||||||
type Message = RendezvousMessage;
|
type Message = RendezvousMessage;
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
|
pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
@ -594,7 +596,13 @@ pub fn current_is_wayland() -> bool {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_new_version() -> String {
|
pub fn get_new_version() -> String {
|
||||||
(*SOFTWARE_UPDATE_URL.lock().unwrap().rsplit('/').next().unwrap_or("")).to_string()
|
(*SOFTWARE_UPDATE_URL
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.rsplit('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or(""))
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -1248,3 +1256,10 @@ pub fn handle_relay_id(id: String) -> String {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn support_remove_wallpaper() -> bool {
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
|
return crate::platform::WallPaperRemover::support();
|
||||||
|
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -3,14 +3,12 @@ use async_trait::async_trait;
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use rdev::{Event, EventType::*, KeyCode};
|
use rdev::{Event, EventType::*, KeyCode};
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
use std::{collections::HashMap, sync::atomic::AtomicBool};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{
|
sync::{Arc, Mutex, RwLock},
|
||||||
atomic::{AtomicUsize, Ordering},
|
|
||||||
Arc, Mutex, RwLock,
|
|
||||||
},
|
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -28,7 +26,7 @@ use hbb_common::{
|
|||||||
sync::mpsc,
|
sync::mpsc,
|
||||||
time::{Duration as TokioDuration, Instant},
|
time::{Duration as TokioDuration, Instant},
|
||||||
},
|
},
|
||||||
SessionID, Stream,
|
Stream,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::client::io_loop::Remote;
|
use crate::client::io_loop::Remote;
|
||||||
@ -49,8 +47,7 @@ const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15;
|
|||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Session<T: InvokeUiSession> {
|
pub struct Session<T: InvokeUiSession> {
|
||||||
pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass
|
pub id: String, // peer id
|
||||||
pub id: String, // peer id
|
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
pub lc: Arc<RwLock<LoginConfigHandler>>,
|
pub lc: Arc<RwLock<LoginConfigHandler>>,
|
||||||
@ -239,10 +236,18 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
self.lc.read().unwrap().reverse_mouse_wheel.clone()
|
self.lc.read().unwrap().reverse_mouse_wheel.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_displays_as_individual_windows(&self) -> String {
|
||||||
|
self.lc.read().unwrap().displays_as_individual_windows.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_reverse_mouse_wheel(&self, value: String) {
|
pub fn save_reverse_mouse_wheel(&self, value: String) {
|
||||||
self.lc.write().unwrap().save_reverse_mouse_wheel(value);
|
self.lc.write().unwrap().save_reverse_mouse_wheel(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_displays_as_individual_windows(&self, value: String) {
|
||||||
|
self.lc.write().unwrap().save_displays_as_individual_windows(value);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_view_style(&self, value: String) {
|
pub fn save_view_style(&self, value: String) {
|
||||||
self.lc.write().unwrap().save_view_style(value);
|
self.lc.write().unwrap().save_view_style(value);
|
||||||
}
|
}
|
||||||
@ -286,12 +291,30 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
&& !self.lc.read().unwrap().disable_clipboard.v
|
&& !self.lc.read().unwrap().disable_clipboard.v
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_video(&self) {
|
#[cfg(feature = "flutter")]
|
||||||
|
pub fn refresh_video(&self, display: i32) {
|
||||||
|
if crate::common::is_support_multi_ui_session_num(self.lc.read().unwrap().version) {
|
||||||
|
self.send(Data::Message(LoginConfigHandler::refresh_display(
|
||||||
|
display as _,
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
self.send(Data::Message(LoginConfigHandler::refresh()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "flutter"))]
|
||||||
|
pub fn refresh_video(&self, _display: i32) {
|
||||||
self.send(Data::Message(LoginConfigHandler::refresh()));
|
self.send(Data::Message(LoginConfigHandler::refresh()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn record_screen(&self, start: bool, w: i32, h: i32) {
|
pub fn record_screen(&self, start: bool, display: i32, w: i32, h: i32) {
|
||||||
self.send(Data::RecordScreen(start, w, h, self.id.clone()));
|
self.send(Data::RecordScreen(
|
||||||
|
start,
|
||||||
|
display as usize,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
self.id.clone(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn record_status(&self, status: bool) {
|
pub fn record_status(&self, status: bool) {
|
||||||
@ -603,6 +626,19 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
self.send(Data::Message(msg_out));
|
self.send(Data::Message(msg_out));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn capture_displays(&self, add: Vec<i32>, sub: Vec<i32>, set: Vec<i32>) {
|
||||||
|
let mut misc = Misc::new();
|
||||||
|
misc.set_capture_displays(CaptureDisplays {
|
||||||
|
add,
|
||||||
|
sub,
|
||||||
|
set,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
msg_out.set_misc(misc);
|
||||||
|
self.send(Data::Message(msg_out));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn switch_display(&self, display: i32) {
|
pub fn switch_display(&self, display: i32) {
|
||||||
let (w, h) = match self.lc.read().unwrap().get_custom_resolution(display) {
|
let (w, h) = match self.lc.read().unwrap().get_custom_resolution(display) {
|
||||||
Some((w, h)) => (w, h),
|
Some((w, h)) => (w, h),
|
||||||
@ -1164,7 +1200,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
|
|||||||
fn update_block_input_state(&self, on: bool);
|
fn update_block_input_state(&self, on: bool);
|
||||||
fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64);
|
fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64);
|
||||||
fn adapt_size(&self);
|
fn adapt_size(&self);
|
||||||
fn on_rgba(&self, rgba: &mut scrap::ImageRgb);
|
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb);
|
||||||
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);
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
fn clipboard(&self, content: String);
|
fn clipboard(&self, content: String);
|
||||||
@ -1175,8 +1211,8 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
|
|||||||
fn on_voice_call_closed(&self, reason: &str);
|
fn on_voice_call_closed(&self, reason: &str);
|
||||||
fn on_voice_call_waiting(&self);
|
fn on_voice_call_waiting(&self);
|
||||||
fn on_voice_call_incoming(&self);
|
fn on_voice_call_incoming(&self);
|
||||||
fn get_rgba(&self) -> *const u8;
|
fn get_rgba(&self, display: usize) -> *const u8;
|
||||||
fn next_rgba(&self);
|
fn next_rgba(&self, display: usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InvokeUiSession> Deref for Session<T> {
|
impl<T: InvokeUiSession> Deref for Session<T> {
|
||||||
@ -1237,7 +1273,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
|||||||
if pi.displays.is_empty() {
|
if pi.displays.is_empty() {
|
||||||
self.lc.write().unwrap().handle_peer_info(&pi);
|
self.lc.write().unwrap().handle_peer_info(&pi);
|
||||||
self.update_privacy_mode();
|
self.update_privacy_mode();
|
||||||
self.msgbox("error", "Remote Error", "No Display", "");
|
self.msgbox("error", "Remote Error", "No Displays", "");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.try_change_init_resolution(pi.current_display);
|
self.try_change_init_resolution(pi.current_display);
|
||||||
@ -1447,24 +1483,29 @@ pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>, round: u32) {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let frame_count = Arc::new(AtomicUsize::new(0));
|
let frame_count_map: Arc<RwLock<HashMap<usize, usize>>> = Default::default();
|
||||||
let frame_count_cl = frame_count.clone();
|
let frame_count_map_cl = frame_count_map.clone();
|
||||||
let ui_handler = handler.ui_handler.clone();
|
let ui_handler = handler.ui_handler.clone();
|
||||||
let (video_sender, audio_sender, video_queue, decode_fps) =
|
let (video_sender, audio_sender, video_queue_map, decode_fps_map) = start_video_audio_threads(
|
||||||
start_video_audio_threads(move |data: &mut scrap::ImageRgb| {
|
handler.clone(),
|
||||||
frame_count_cl.fetch_add(1, Ordering::Relaxed);
|
move |display: usize, data: &mut scrap::ImageRgb| {
|
||||||
ui_handler.on_rgba(data);
|
let mut write_lock = frame_count_map_cl.write().unwrap();
|
||||||
});
|
let count = write_lock.get(&display).unwrap_or(&0) + 1;
|
||||||
|
write_lock.insert(display, count);
|
||||||
|
drop(write_lock);
|
||||||
|
ui_handler.on_rgba(display, data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let mut remote = Remote::new(
|
let mut remote = Remote::new(
|
||||||
handler,
|
handler,
|
||||||
video_queue,
|
video_queue_map,
|
||||||
video_sender,
|
video_sender,
|
||||||
audio_sender,
|
audio_sender,
|
||||||
receiver,
|
receiver,
|
||||||
sender,
|
sender,
|
||||||
frame_count,
|
frame_count_map,
|
||||||
decode_fps,
|
decode_fps_map,
|
||||||
);
|
);
|
||||||
remote.io_loop(&key, &token, round).await;
|
remote.io_loop(&key, &token, round).await;
|
||||||
remote.sync_jobs_status_to_local().await;
|
remote.sync_jobs_status_to_local().await;
|
||||||
|
Loading…
Reference in New Issue
Block a user