Feat: Follow remote cursor and window focus | Auto display switch (#7717)
* feat: auto switch display on follow remote cursor Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * feat: auto switch display on follow remote window focus Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build and remove unused imports Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix linux get_focused_window_id Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * lock show remote cursor when follow remote cursor is enabled Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix config Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * prevent auto display switch on show all display and displays as individual windows Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove unused function Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove unwraps and improve iterations Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * set updateCursorPos to false to avoid interrupting remote cursor Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * update lang Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix web build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * update checks for options and enable in view mode Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use focused display index for window focus service Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use window center for windows display focused Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove unused imports Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use libxdo instead of xdotool Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix multi monitor check Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * enable show cursor when follow cursor is default Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove show_all_displays,use runtime state instead Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix show cursor lock state on default Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove view mode with follow options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use separate message for follow current display Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix options Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * sciter support for follow remote cursor and window Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add check for ui session handlers count Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use cached displays and remove peer info write Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * No follow options when show all displays Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * No follow options when multi ui session Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * turn off follow options when not used|prevent msgs Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use window center for switch in linux Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * use subbed display count to prevent switch msgs Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix web build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * move subbed displays count Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * fix build Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add noperms for window focus Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add subscribe for window focus Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * remove window_focus message and unsub on multi ui Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> * add multi ui session field Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> --------- Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com> Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
parent
43a0a4f8e0
commit
3811f41076
@ -168,6 +168,29 @@ class ShowRemoteCursorState {
|
||||
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
|
||||
}
|
||||
|
||||
class ShowRemoteCursorLockState {
|
||||
static String tag(String id) => 'show_remote_cursor_lock_$id';
|
||||
|
||||
static void init(String id) {
|
||||
final key = tag(id);
|
||||
if (!Get.isRegistered(tag: key)) {
|
||||
final RxBool state = false.obs;
|
||||
Get.put(state, tag: key);
|
||||
} else {
|
||||
Get.find<RxBool>(tag: key).value = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void delete(String id) {
|
||||
final key = tag(id);
|
||||
if (Get.isRegistered(tag: key)) {
|
||||
Get.delete(tag: key);
|
||||
}
|
||||
}
|
||||
|
||||
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
|
||||
}
|
||||
|
||||
class KeyboardEnabledState {
|
||||
static String tag(String id) => 'keyboard_enabled_$id';
|
||||
|
||||
@ -315,6 +338,7 @@ initSharedStates(String id) {
|
||||
CurrentDisplayState.init(id);
|
||||
KeyboardEnabledState.init(id);
|
||||
ShowRemoteCursorState.init(id);
|
||||
ShowRemoteCursorLockState.init(id);
|
||||
RemoteCursorMovedState.init(id);
|
||||
FingerprintState.init(id);
|
||||
PeerBoolOption.init(id, 'zoom-cursor', () => false);
|
||||
@ -327,6 +351,7 @@ removeSharedStates(String id) {
|
||||
BlockInputState.delete(id);
|
||||
CurrentDisplayState.delete(id);
|
||||
ShowRemoteCursorState.delete(id);
|
||||
ShowRemoteCursorLockState.delete(id);
|
||||
KeyboardEnabledState.delete(id);
|
||||
RemoteCursorMovedState.delete(id);
|
||||
FingerprintState.delete(id);
|
||||
|
@ -212,6 +212,8 @@ List<(String, String)> otherDefaultSettings() {
|
||||
if ((isDesktop || isWebDesktop)) ('show_monitors_tip', kKeyShowMonitorsToolbar),
|
||||
if ((isDesktop || isWebDesktop)) ('Collapse toolbar', 'collapse_toolbar'),
|
||||
('Show remote cursor', 'show_remote_cursor'),
|
||||
('Follow remote cursor', 'follow_remote_cursor'),
|
||||
('Follow remote window focus', 'follow_remote_window'),
|
||||
if ((isDesktop || isWebDesktop)) ('Zoom cursor', 'zoom-cursor'),
|
||||
('Show quality monitor', 'show_quality_monitor'),
|
||||
('Mute', 'disable_audio'),
|
||||
|
@ -371,7 +371,7 @@ Future<List<TRadioMenu<String>>> toolbarCodec(
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
Future<List<TToggleMenu>> toolbarCursor(
|
||||
BuildContext context, String id, FFI ffi) async {
|
||||
List<TToggleMenu> v = [];
|
||||
final ffiModel = ffi.ffiModel;
|
||||
@ -384,12 +384,17 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
!ffi.canvasModel.cursorEmbedded &&
|
||||
!pi.isWayland) {
|
||||
final state = ShowRemoteCursorState.find(id);
|
||||
final lockState = ShowRemoteCursorLockState.find(id);
|
||||
final enabled = !ffiModel.viewOnly;
|
||||
final option = 'show-remote-cursor';
|
||||
if (pi.currentDisplay == kAllDisplayValue ||
|
||||
bind.sessionIsMultiUiSession(sessionId: sessionId)) {
|
||||
lockState.value = false;
|
||||
}
|
||||
v.add(TToggleMenu(
|
||||
child: Text(translate('Show remote cursor')),
|
||||
value: state.value,
|
||||
onChanged: enabled
|
||||
onChanged: enabled && !lockState.value
|
||||
? (value) async {
|
||||
if (value == null) return;
|
||||
await bind.sessionToggleOption(
|
||||
@ -399,6 +404,67 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
}
|
||||
: null));
|
||||
}
|
||||
// follow remote cursor
|
||||
if (pi.platform != kPeerPlatformAndroid &&
|
||||
!ffi.canvasModel.cursorEmbedded &&
|
||||
!pi.isWayland &&
|
||||
versionCmp(pi.version, "1.2.4") >= 0 &&
|
||||
pi.displays.length > 1 &&
|
||||
pi.currentDisplay != kAllDisplayValue &&
|
||||
!bind.sessionIsMultiUiSession(sessionId: sessionId)) {
|
||||
final option = 'follow-remote-cursor';
|
||||
final value =
|
||||
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||
final showCursorOption = 'show-remote-cursor';
|
||||
final showCursorState = ShowRemoteCursorState.find(id);
|
||||
final showCursorLockState = ShowRemoteCursorLockState.find(id);
|
||||
final showCursorEnabled = bind.sessionGetToggleOptionSync(
|
||||
sessionId: sessionId, arg: showCursorOption);
|
||||
showCursorLockState.value = value;
|
||||
if (value && !showCursorEnabled) {
|
||||
await bind.sessionToggleOption(
|
||||
sessionId: sessionId, value: showCursorOption);
|
||||
showCursorState.value = bind.sessionGetToggleOptionSync(
|
||||
sessionId: sessionId, arg: showCursorOption);
|
||||
}
|
||||
v.add(TToggleMenu(
|
||||
child: Text(translate('Follow remote cursor')),
|
||||
value: value,
|
||||
onChanged: (value) async {
|
||||
if (value == null) return;
|
||||
await bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
value = bind.sessionGetToggleOptionSync(
|
||||
sessionId: sessionId, arg: option);
|
||||
showCursorLockState.value = value;
|
||||
if (!showCursorEnabled) {
|
||||
await bind.sessionToggleOption(
|
||||
sessionId: sessionId, value: showCursorOption);
|
||||
showCursorState.value = bind.sessionGetToggleOptionSync(
|
||||
sessionId: sessionId, arg: showCursorOption);
|
||||
}
|
||||
}));
|
||||
}
|
||||
// follow remote window focus
|
||||
if (pi.platform != kPeerPlatformAndroid &&
|
||||
!ffi.canvasModel.cursorEmbedded &&
|
||||
!pi.isWayland &&
|
||||
versionCmp(pi.version, "1.2.4") >= 0 &&
|
||||
pi.displays.length > 1 &&
|
||||
pi.currentDisplay != kAllDisplayValue &&
|
||||
!bind.sessionIsMultiUiSession(sessionId: sessionId)) {
|
||||
final option = 'follow-remote-window';
|
||||
final value =
|
||||
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||
v.add(TToggleMenu(
|
||||
child: Text(translate('Follow remote window focus')),
|
||||
value: value,
|
||||
onChanged: (value) async {
|
||||
if (value == null) return;
|
||||
await bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||
value = bind.sessionGetToggleOptionSync(
|
||||
sessionId: sessionId, arg: option);
|
||||
}));
|
||||
}
|
||||
// zoom cursor
|
||||
final viewStyle = await bind.sessionGetViewStyle(sessionId: sessionId) ?? '';
|
||||
if (!isMobile &&
|
||||
@ -417,6 +483,17 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
},
|
||||
));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
BuildContext context, String id, FFI ffi) async {
|
||||
List<TToggleMenu> v = [];
|
||||
final ffiModel = ffi.ffiModel;
|
||||
final pi = ffiModel.pi;
|
||||
final perms = ffiModel.permissions;
|
||||
final sessionId = ffi.sessionId;
|
||||
|
||||
// show quality monitor
|
||||
final option = 'show-quality-monitor';
|
||||
v.add(TToggleMenu(
|
||||
|
@ -1045,7 +1045,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_screenAdjustor.updateScreen();
|
||||
|
||||
menuChildrenGetter() {
|
||||
final menuChildren = <Widget>[
|
||||
_screenAdjustor.adjustWindow(context),
|
||||
@ -1069,6 +1068,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
ffi: widget.ffi,
|
||||
),
|
||||
Divider(),
|
||||
cursorToggles(),
|
||||
Divider(),
|
||||
toggles(),
|
||||
];
|
||||
// privacy mode
|
||||
@ -1212,6 +1213,23 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
});
|
||||
}
|
||||
|
||||
cursorToggles() {
|
||||
return futureBuilder(
|
||||
future: toolbarCursor(context, id, ffi),
|
||||
hasData: (data) {
|
||||
final v = data as List<TToggleMenu>;
|
||||
if (v.isEmpty) return Offstage();
|
||||
return Column(
|
||||
children: v
|
||||
.map((e) => CkbMenuButton(
|
||||
value: e.value,
|
||||
onChanged: e.onChanged,
|
||||
child: e.child,
|
||||
ffi: ffi))
|
||||
.toList());
|
||||
});
|
||||
}
|
||||
|
||||
toggles() {
|
||||
return futureBuilder(
|
||||
future: toolbarDisplayToggle(context, id, ffi),
|
||||
|
@ -836,6 +836,7 @@ void showOptions(
|
||||
List<TRadioMenu<String>> imageQualityRadios =
|
||||
await toolbarImageQuality(context, id, gFFI);
|
||||
List<TRadioMenu<String>> codecRadios = await toolbarCodec(context, id, gFFI);
|
||||
List<TToggleMenu> cursorToggles = await toolbarCursor(context, id, gFFI);
|
||||
List<TToggleMenu> displayToggles =
|
||||
await toolbarDisplayToggle(context, id, gFFI);
|
||||
|
||||
@ -876,8 +877,23 @@ void showOptions(
|
||||
})),
|
||||
if (codecRadios.isNotEmpty) const Divider(color: MyTheme.border),
|
||||
];
|
||||
final rxCursorToggleValues = cursorToggles.map((e) => e.value.obs).toList();
|
||||
final cursorTogglesList = cursorToggles
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) => Obx(() => CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
value: rxCursorToggleValues[e.key].value,
|
||||
onChanged: (v) {
|
||||
e.value.onChanged?.call(v);
|
||||
if (v != null) rxCursorToggleValues[e.key].value = v;
|
||||
},
|
||||
title: e.value.child)))
|
||||
.toList();
|
||||
|
||||
final rxToggleValues = displayToggles.map((e) => e.value.obs).toList();
|
||||
final toggles = displayToggles
|
||||
final displayTogglesList = displayToggles
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) => Obx(() => CheckboxListTile(
|
||||
@ -890,6 +906,11 @@ void showOptions(
|
||||
},
|
||||
title: e.value.child)))
|
||||
.toList();
|
||||
final toggles = [
|
||||
...cursorTogglesList,
|
||||
if (cursorToggles.isNotEmpty) const Divider(color: MyTheme.border),
|
||||
...displayTogglesList,
|
||||
];
|
||||
|
||||
Widget privacyModeWidget = Offstage();
|
||||
if (privacyModeList.length > 1) {
|
||||
|
@ -367,6 +367,8 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
} else if (name == 'sync_peer_option') {
|
||||
_handleSyncPeerOption(evt, peerId);
|
||||
} else if (name == 'follow_current_display') {
|
||||
handleFollowCurrentDisplay(evt, sessionId, peerId);
|
||||
} else {
|
||||
debugPrint('Unknown event name: $name');
|
||||
}
|
||||
@ -440,7 +442,7 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
updateCurDisplay(SessionID sessionId, {updateCursorPos = true}) {
|
||||
updateCurDisplay(SessionID sessionId, {updateCursorPos = false}) {
|
||||
final newRect = displaysRect();
|
||||
if (newRect == null) {
|
||||
return;
|
||||
@ -1040,9 +1042,30 @@ class FfiModel with ChangeNotifier {
|
||||
json.encode(_pi.platformAdditions);
|
||||
}
|
||||
|
||||
handleFollowCurrentDisplay(
|
||||
Map<String, dynamic> evt, SessionID sessionId, String peerId) async {
|
||||
if (evt['display_idx'] != null) {
|
||||
if (pi.currentDisplay == kAllDisplayValue) {
|
||||
return;
|
||||
}
|
||||
_pi.currentDisplay = int.parse(evt['display_idx']);
|
||||
try {
|
||||
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
bind.sessionSwitchDisplay(
|
||||
isDesktop: isDesktop,
|
||||
sessionId: sessionId,
|
||||
value: Int32List.fromList([_pi.currentDisplay]),
|
||||
);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Directly switch to the new display without waiting for the response.
|
||||
switchToNewDisplay(int display, SessionID sessionId, String peerId,
|
||||
{bool updateCursorPos = true}) {
|
||||
{bool updateCursorPos = false}) {
|
||||
// VideoHandler creation is upon when video frames are received, so either caching commands(don't know next width/height) or stopping recording when switching displays.
|
||||
parent.target?.recordingModel.onClose();
|
||||
// no need to wait for the response
|
||||
|
@ -346,6 +346,10 @@ class RustdeskImpl {
|
||||
return mode == kKeyLegacyMode;
|
||||
}
|
||||
|
||||
bool sessionIsMultiUiSession({required UuidValue sessionId, dynamic hint}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> sessionSetCustomImageQuality(
|
||||
{required UuidValue sessionId, required int value, dynamic hint}) {
|
||||
return Future(() => js.context.callMethod('setByName', [
|
||||
|
@ -603,6 +603,8 @@ message OptionMessage {
|
||||
// Resolution custom_resolution = 13;
|
||||
// BoolOption support_windows_specific_session = 14;
|
||||
// starting from 15 please, do not use removed fields
|
||||
BoolOption follow_remote_cursor = 15;
|
||||
BoolOption follow_remote_window = 16;
|
||||
}
|
||||
|
||||
message TestDelay {
|
||||
@ -765,6 +767,7 @@ message Misc {
|
||||
uint32 selected_sid = 35;
|
||||
DisplayResolution change_display_resolution = 36;
|
||||
MessageQuery message_query = 37;
|
||||
int32 follow_current_display = 38;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,6 +281,10 @@ pub struct PeerConfig {
|
||||
pub enable_file_transfer: EnableFileTransfer,
|
||||
#[serde(flatten)]
|
||||
pub show_quality_monitor: ShowQualityMonitor,
|
||||
#[serde(flatten)]
|
||||
pub follow_remote_cursor: FollowRemoteCursor,
|
||||
#[serde(flatten)]
|
||||
pub follow_remote_window: FollowRemoteWindow,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_string",
|
||||
@ -353,6 +357,8 @@ impl Default for PeerConfig {
|
||||
disable_clipboard: Default::default(),
|
||||
enable_file_transfer: Default::default(),
|
||||
show_quality_monitor: Default::default(),
|
||||
follow_remote_cursor: Default::default(),
|
||||
follow_remote_window: Default::default(),
|
||||
keyboard_mode: Default::default(),
|
||||
view_only: Default::default(),
|
||||
reverse_mouse_wheel: Self::default_reverse_mouse_wheel(),
|
||||
@ -1258,6 +1264,19 @@ serde_field_bool!(
|
||||
default_show_remote_cursor,
|
||||
"ShowRemoteCursor::default_show_remote_cursor"
|
||||
);
|
||||
serde_field_bool!(
|
||||
FollowRemoteCursor,
|
||||
"follow_remote_cursor",
|
||||
default_follow_remote_cursor,
|
||||
"FollowRemoteCursor::default_follow_remote_cursor"
|
||||
);
|
||||
|
||||
serde_field_bool!(
|
||||
FollowRemoteWindow,
|
||||
"follow_remote_window",
|
||||
default_follow_remote_window,
|
||||
"FollowRemoteWindow::default_follow_remote_window"
|
||||
);
|
||||
serde_field_bool!(
|
||||
ShowQualityMonitor,
|
||||
"show_quality_monitor",
|
||||
|
@ -1456,6 +1456,22 @@ impl LoginConfigHandler {
|
||||
BoolOption::No
|
||||
})
|
||||
.into();
|
||||
} else if name == "follow-remote-cursor" {
|
||||
config.follow_remote_cursor.v = !config.follow_remote_cursor.v;
|
||||
option.follow_remote_cursor = (if config.follow_remote_cursor.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
})
|
||||
.into();
|
||||
} else if name == "follow-remote-window" {
|
||||
config.follow_remote_window.v = !config.follow_remote_window.v;
|
||||
option.follow_remote_window = (if config.follow_remote_window.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
})
|
||||
.into();
|
||||
} else if name == "disable-audio" {
|
||||
config.disable_audio.v = !config.disable_audio.v;
|
||||
option.disable_audio = (if config.disable_audio.v {
|
||||
@ -1601,6 +1617,12 @@ impl LoginConfigHandler {
|
||||
if view_only || self.get_toggle_option("show-remote-cursor") {
|
||||
msg.show_remote_cursor = BoolOption::Yes.into();
|
||||
}
|
||||
if self.get_toggle_option("follow-remote-cursor") {
|
||||
msg.follow_remote_cursor = BoolOption::Yes.into();
|
||||
}
|
||||
if self.get_toggle_option("follow-remote-window") {
|
||||
msg.follow_remote_window = BoolOption::Yes.into();
|
||||
}
|
||||
if !view_only && self.get_toggle_option("lock-after-session-end") {
|
||||
msg.lock_after_session_end = BoolOption::Yes.into();
|
||||
}
|
||||
@ -1692,6 +1714,10 @@ impl LoginConfigHandler {
|
||||
self.config.allow_swap_key.v
|
||||
} else if name == "view-only" {
|
||||
self.config.view_only.v
|
||||
} else if name == "follow-remote-cursor" {
|
||||
self.config.follow_remote_cursor.v
|
||||
} else if name == "follow-remote-window" {
|
||||
self.config.follow_remote_window.v
|
||||
} else {
|
||||
!self.get_option(name).is_empty()
|
||||
}
|
||||
|
@ -1504,7 +1504,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
log::info!("update supported encoding:{:?}", e);
|
||||
self.handler.lc.write().unwrap().supported_encoding = e;
|
||||
}
|
||||
|
||||
Some(misc::Union::FollowCurrentDisplay(d_idx)) => {
|
||||
self.handler.set_current_display(d_idx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(message::Union::TestDelay(t)) => {
|
||||
|
@ -882,6 +882,21 @@ impl InvokeUiSession for FlutterHandler {
|
||||
);
|
||||
}
|
||||
|
||||
fn is_multi_ui_session(&self) -> bool {
|
||||
self.session_handlers.read().unwrap().len() > 1
|
||||
}
|
||||
|
||||
fn set_current_display(&self, disp_idx: i32) {
|
||||
if self.is_multi_ui_session() {
|
||||
return;
|
||||
}
|
||||
self.push_event(
|
||||
"follow_current_display",
|
||||
&[("display_idx", &disp_idx.to_string())],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
fn on_connected(&self, _conn_type: ConnType) {}
|
||||
|
||||
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
|
||||
|
@ -213,6 +213,14 @@ pub fn session_refresh(session_id: SessionID, display: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_is_multi_ui_session(session_id: SessionID) -> SyncReturn<bool> {
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
SyncReturn(session.is_multi_ui_session())
|
||||
} else {
|
||||
SyncReturn(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_record_screen(
|
||||
session_id: SessionID,
|
||||
start: bool,
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -604,5 +604,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "打开 Web 控制台以执行更多操作"),
|
||||
("allow-only-conn-window-open-tip", "仅当 RustDesk 窗口打开时允许连接"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "没有物理显示器,没必要使用隐私模式。"),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Více na webové konzoli"),
|
||||
("allow-only-conn-window-open-tip", "Povolit připojení pouze v případě, že je otevřené okno RustDesk"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Žádné fyzické displeje, není třeba používat režim soukromí."),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Mehr über Webkonsole"),
|
||||
("allow-only-conn-window-open-tip", "Verbindung nur zulassen, wenn das RustDesk-Fenster geöffnet ist"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Keine physischen Bildschirme; keine Notwendigkeit, den Datenschutzmodus zu verwenden."),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -222,5 +222,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "More on web console"),
|
||||
("allow-only-conn-window-open-tip", "Only allow connection if RustDesk window is open"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "No physical displays, no need to use the privacy mode."),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Más en consola web"),
|
||||
("allow-only-conn-window-open-tip", "Permitir la conexión solo si la ventana RusDesk está abierta"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "No hay pantallas físicas, no es necesario usar el modo privado."),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "اطلاعات بیشتر در کنسول وب"),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -602,5 +602,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Više na web konzoli"),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Altre info sulla console web"),
|
||||
("allow-only-conn-window-open-tip", "Consenti la connessione solo se la finestra RustDesk è aperta"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Nessun display fisico, nessuna necessità di usare la modalità privacy."),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Vairāk par tīmekļa konsoli"),
|
||||
("allow-only-conn-window-open-tip", "Atļaut savienojumu tikai tad, ja ir atvērts RustDesk logs"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Nav fizisku displeju, nav jāizmanto privātuma režīms."),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Meer over de webconsole"),
|
||||
("allow-only-conn-window-open-tip", "Alleen verbindingen toestaan als het RustDesk-venster geopend is"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Więcej w konsoli web"),
|
||||
("allow-only-conn-window-open-tip", "Zezwalaj na połączenie tylko wtedy, gdy okno RustDesk jest otwarte"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Больше в веб-консоли"),
|
||||
("allow-only-conn-window-open-tip", "Разрешать подключение только при открытом окне RustDesk"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Физические дисплеи отсутствуют, нет необходимости использовать режим конфиденциальности."),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Viac na webovej konzole"),
|
||||
("allow-only-conn-window-open-tip", "Povoliť pripojenie iba vtedy, ak je otvorené okno aplikácie RustDesk"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", "Žiadne fyzické displeje, nie je potrebné používať režim ochrany osobných údajov."),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "打開 Web 控制台以進行更多操作"),
|
||||
("allow-only-conn-window-open-tip", "只在 RustDesk 視窗開啟時允許連接"),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", "Детальніше про веб-консоль"),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -603,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("ab_web_console_tip", ""),
|
||||
("allow-only-conn-window-open-tip", ""),
|
||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||
("Follow remote cursor", ""),
|
||||
("Follow remote window focus", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ mod keyboard;
|
||||
/// cbindgen:ignore
|
||||
pub mod platform;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use platform::{get_cursor, get_cursor_data, get_cursor_pos, start_os_service};
|
||||
pub use platform::{
|
||||
get_cursor, get_cursor_data, get_cursor_pos, get_focused_display, start_os_service,
|
||||
};
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
/// cbindgen:ignore
|
||||
mod server;
|
||||
@ -36,15 +38,15 @@ pub mod flutter;
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
pub mod flutter_ffi;
|
||||
use common::*;
|
||||
mod auth_2fa;
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod cli;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
||||
pub mod core_main;
|
||||
mod lang;
|
||||
mod custom_server;
|
||||
mod lang;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod port_forward;
|
||||
mod auth_2fa;
|
||||
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
|
@ -11,7 +11,7 @@ use hbb_common::{
|
||||
config::Config,
|
||||
libc::{c_char, c_int, c_long, c_void},
|
||||
log,
|
||||
message_proto::Resolution,
|
||||
message_proto::{DisplayInfo, Resolution},
|
||||
regex::{Captures, Regex},
|
||||
};
|
||||
use std::{
|
||||
@ -53,6 +53,20 @@ extern "C" {
|
||||
screen_num: *mut c_int,
|
||||
) -> c_int;
|
||||
fn xdo_new(display: *const c_char) -> Xdo;
|
||||
fn xdo_get_active_window(xdo: Xdo, window: *mut *mut c_void) -> c_int;
|
||||
fn xdo_get_window_location(
|
||||
xdo: Xdo,
|
||||
window: *mut c_void,
|
||||
x: *mut c_int,
|
||||
y: *mut c_int,
|
||||
screen_num: *mut c_int,
|
||||
) -> c_int;
|
||||
fn xdo_get_window_size(
|
||||
xdo: Xdo,
|
||||
window: *mut c_void,
|
||||
width: *mut c_int,
|
||||
height: *mut c_int,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
#[link(name = "X11")]
|
||||
@ -119,6 +133,50 @@ pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
|
||||
pub fn reset_input_cache() {}
|
||||
|
||||
pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
||||
let mut res = None;
|
||||
XDO.with(|xdo| {
|
||||
if let Ok(xdo) = xdo.try_borrow_mut() {
|
||||
if xdo.is_null() {
|
||||
return;
|
||||
}
|
||||
let mut x: c_int = 0;
|
||||
let mut y: c_int = 0;
|
||||
let mut width: c_int = 0;
|
||||
let mut height: c_int = 0;
|
||||
let mut window: *mut c_void = std::ptr::null_mut();
|
||||
|
||||
unsafe {
|
||||
if xdo_get_active_window(*xdo, &mut window) != 0 {
|
||||
return;
|
||||
}
|
||||
if xdo_get_window_location(
|
||||
*xdo,
|
||||
window,
|
||||
&mut x as _,
|
||||
&mut y as _,
|
||||
std::ptr::null_mut(),
|
||||
) != 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
if xdo_get_window_size(*xdo, window, &mut width as _, &mut height as _) != 0 {
|
||||
return;
|
||||
}
|
||||
let center_x = x + width / 2;
|
||||
let center_y = y + height / 2;
|
||||
res = displays.iter().position(|d| {
|
||||
center_x >= d.x
|
||||
&& center_x < d.x + d.width
|
||||
&& center_y >= d.y
|
||||
&& center_y < d.y + d.height
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get_cursor() -> ResultType<Option<u64>> {
|
||||
let mut res = None;
|
||||
DISPLAY.with(|conn| {
|
||||
@ -1228,7 +1286,7 @@ mod desktop {
|
||||
if !home.is_empty() {
|
||||
assert_eq!(d.home, home);
|
||||
} else {
|
||||
//
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,13 @@ use core_graphics::{
|
||||
display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo},
|
||||
window::{kCGWindowName, kCGWindowOwnerPID},
|
||||
};
|
||||
use hbb_common::sysinfo::{Pid, Process, ProcessRefreshKind, System};
|
||||
use hbb_common::{anyhow::anyhow, bail, log, message_proto::Resolution};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::anyhow,
|
||||
bail, log,
|
||||
message_proto::{DisplayInfo, Resolution},
|
||||
sysinfo::{Pid, Process, ProcessRefreshKind, System},
|
||||
};
|
||||
use include_dir::{include_dir, Dir};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use scrap::{libc::c_void, quartz::ffi::*};
|
||||
@ -302,6 +307,20 @@ pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
*/
|
||||
}
|
||||
|
||||
pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
||||
unsafe {
|
||||
let main_screen: id = msg_send![class!(NSScreen), mainScreen];
|
||||
let screen: id = msg_send![main_screen, deviceDescription];
|
||||
let id: id =
|
||||
msg_send![screen, objectForKey: NSString::alloc(nil).init_str("NSScreenNumber")];
|
||||
let display_name: u32 = msg_send![id, unsignedIntValue];
|
||||
|
||||
displays
|
||||
.iter()
|
||||
.position(|d| d.name == display_name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cursor() -> ResultType<Option<u64>> {
|
||||
unsafe {
|
||||
let seed = CGSCurrentCursorSeed();
|
||||
|
@ -12,7 +12,7 @@ use hbb_common::{
|
||||
bail,
|
||||
config::{self, Config},
|
||||
log,
|
||||
message_proto::{Resolution, WindowsSession},
|
||||
message_proto::{DisplayInfo, Resolution, WindowsSession},
|
||||
sleep, timeout, tokio,
|
||||
};
|
||||
use std::process::{Command, Stdio};
|
||||
@ -65,6 +65,24 @@ use windows_service::{
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
|
||||
pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
||||
unsafe {
|
||||
let hWnd = GetForegroundWindow();
|
||||
let mut rect: RECT = mem::zeroed();
|
||||
if GetWindowRect(hWnd, &mut rect as *mut RECT) == 0 {
|
||||
return None;
|
||||
}
|
||||
displays.iter().position(|display| {
|
||||
let center_x = rect.left + (rect.right - rect.left) / 2;
|
||||
let center_y = rect.top + (rect.bottom - rect.top) / 2;
|
||||
center_x >= display.x
|
||||
&& center_x <= display.x + display.width
|
||||
&& center_y >= display.y
|
||||
&& center_y <= display.y + display.height
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
unsafe {
|
||||
#[allow(invalid_value)]
|
||||
|
@ -50,6 +50,7 @@ pub const NAME: &'static str = "";
|
||||
pub mod input_service {
|
||||
pub const NAME_CURSOR: &'static str = "";
|
||||
pub const NAME_POS: &'static str = "";
|
||||
pub const NAME_WINDOW_FOCUS: &'static str = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,6 +106,7 @@ pub fn new() -> ServerPtr {
|
||||
if !display_service::capture_cursor_embedded() {
|
||||
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_window_focus()));
|
||||
}
|
||||
}
|
||||
Arc::new(RwLock::new(server))
|
||||
@ -354,6 +356,15 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_subbed_displays_count(&self, conn_id: i32) -> usize {
|
||||
self.services
|
||||
.keys()
|
||||
.filter(|k| {
|
||||
Self::is_video_service_name(k) && self.services.get(*k).unwrap().is_subed(conn_id)
|
||||
})
|
||||
.count()
|
||||
}
|
||||
|
||||
fn capture_displays(
|
||||
&mut self,
|
||||
conn: ConnInner,
|
||||
|
@ -31,8 +31,7 @@ use hbb_common::platform::linux::run_cmds;
|
||||
use hbb_common::protobuf::EnumOrUnknown;
|
||||
use hbb_common::{
|
||||
config::Config,
|
||||
fs,
|
||||
fs::can_enable_overwrite_detection,
|
||||
fs::{self, can_enable_overwrite_detection},
|
||||
futures::{SinkExt, StreamExt},
|
||||
get_time, get_version_number,
|
||||
message_proto::{option_message::BoolOption, permission_info::Permission},
|
||||
@ -241,6 +240,9 @@ pub struct Connection {
|
||||
delayed_read_dir: Option<(String, bool)>,
|
||||
#[cfg(target_os = "macos")]
|
||||
retina: Retina,
|
||||
follow_remote_cursor: bool,
|
||||
follow_remote_window: bool,
|
||||
multi_ui_session: bool,
|
||||
}
|
||||
|
||||
impl ConnInner {
|
||||
@ -348,6 +350,9 @@ impl Connection {
|
||||
network_delay: 0,
|
||||
lock_after_session_end: false,
|
||||
show_remote_cursor: false,
|
||||
follow_remote_cursor: false,
|
||||
follow_remote_window: false,
|
||||
multi_ui_session: false,
|
||||
ip: "".to_owned(),
|
||||
disable_audio: false,
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
@ -666,8 +671,14 @@ impl Connection {
|
||||
#[cfg(target_os = "macos")]
|
||||
conn.retina.set_displays(&_pi.displays);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
Some(message::Union::CursorPosition(pos)) => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
if conn.follow_remote_cursor {
|
||||
conn.handle_cursor_switch_display(pos.clone()).await;
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(new_msg) = conn.retina.on_cursor_pos(&pos, conn.display_idx) {
|
||||
msg = Arc::new(new_msg);
|
||||
}
|
||||
@ -1308,6 +1319,9 @@ impl Connection {
|
||||
if !self.show_remote_cursor {
|
||||
noperms.push(NAME_POS);
|
||||
}
|
||||
if !self.follow_remote_window {
|
||||
noperms.push(NAME_WINDOW_FOCUS);
|
||||
}
|
||||
if !self.clipboard_enabled() || !self.peer_keyboard_enabled() {
|
||||
noperms.push(super::clipboard_service::NAME);
|
||||
}
|
||||
@ -2581,6 +2595,14 @@ impl Connection {
|
||||
} else {
|
||||
lock.capture_displays(self.inner.clone(), set, true, true);
|
||||
}
|
||||
self.multi_ui_session = lock.get_subbed_displays_count(self.inner.id()) > 1;
|
||||
if self.follow_remote_window {
|
||||
lock.subscribe(
|
||||
NAME_WINDOW_FOCUS,
|
||||
self.inner.clone(),
|
||||
!self.multi_ui_session,
|
||||
);
|
||||
}
|
||||
drop(lock);
|
||||
}
|
||||
}
|
||||
@ -2766,6 +2788,24 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Ok(q) = o.follow_remote_cursor.enum_value() {
|
||||
if q != BoolOption::NotSet {
|
||||
self.follow_remote_cursor = q == BoolOption::Yes;
|
||||
}
|
||||
}
|
||||
if let Ok(q) = o.follow_remote_window.enum_value() {
|
||||
if q != BoolOption::NotSet {
|
||||
self.follow_remote_window = q == BoolOption::Yes;
|
||||
if let Some(s) = self.server.upgrade() {
|
||||
s.write().unwrap().subscribe(
|
||||
NAME_WINDOW_FOCUS,
|
||||
self.inner.clone(),
|
||||
self.follow_remote_window,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(q) = o.disable_audio.enum_value() {
|
||||
if q != BoolOption::NotSet {
|
||||
self.disable_audio = q == BoolOption::Yes;
|
||||
@ -3126,6 +3166,30 @@ impl Connection {
|
||||
self.inner.send(msg.into());
|
||||
self.supported_encoding_flag = (true, not_use);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
async fn handle_cursor_switch_display(&mut self, pos: CursorPosition) {
|
||||
if self.multi_ui_session {
|
||||
return;
|
||||
}
|
||||
let displays = super::display_service::get_sync_displays();
|
||||
let d_index = displays.iter().position(|d| {
|
||||
let scale = d.scale;
|
||||
pos.x >= d.x
|
||||
&& pos.y >= d.y
|
||||
&& (pos.x - d.x) as f64 * scale < d.width as f64
|
||||
&& (pos.y - d.y) as f64 * scale < d.height as f64
|
||||
});
|
||||
if let Some(d_index) = d_index {
|
||||
if self.display_idx != d_index {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_follow_current_display(d_index as i32);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
self.send(msg_out).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
|
||||
|
@ -258,7 +258,6 @@ pub(super) fn get_original_resolution(
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(super) fn get_sync_displays() -> Vec<DisplayInfo> {
|
||||
SYNC_DISPLAYS.lock().unwrap().displays.clone()
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ use std::{
|
||||
use winapi::um::winuser::WHEEL_DELTA;
|
||||
|
||||
const INVALID_CURSOR_POS: i32 = i32::MIN;
|
||||
const INVALID_DISPLAY_IDX: i32 = -1;
|
||||
|
||||
#[derive(Default)]
|
||||
struct StateCursor {
|
||||
@ -74,6 +75,29 @@ impl StatePos {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StateWindowFocus {
|
||||
display_idx: i32,
|
||||
}
|
||||
|
||||
impl super::service::Reset for StateWindowFocus {
|
||||
fn reset(&mut self) {
|
||||
self.display_idx = INVALID_DISPLAY_IDX;
|
||||
}
|
||||
}
|
||||
|
||||
impl StateWindowFocus {
|
||||
#[inline]
|
||||
fn is_valid(&self) -> bool {
|
||||
self.display_idx != INVALID_DISPLAY_IDX
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_changed(&self, disp_idx: i32) -> bool {
|
||||
self.is_valid() && self.display_idx != disp_idx
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
struct Input {
|
||||
conn: i32,
|
||||
@ -238,6 +262,7 @@ fn should_disable_numlock(evt: &KeyEvent) -> bool {
|
||||
|
||||
pub const NAME_CURSOR: &'static str = "mouse_cursor";
|
||||
pub const NAME_POS: &'static str = "mouse_pos";
|
||||
pub const NAME_WINDOW_FOCUS: &'static str = "window_focus";
|
||||
#[derive(Clone)]
|
||||
pub struct MouseCursorService {
|
||||
pub sp: ServiceTmpl<MouseCursorSub>,
|
||||
@ -277,6 +302,12 @@ pub fn new_pos() -> GenericService {
|
||||
svc.sp
|
||||
}
|
||||
|
||||
pub fn new_window_focus() -> GenericService {
|
||||
let svc = EmptyExtraFieldService::new(NAME_WINDOW_FOCUS.to_owned(), false);
|
||||
GenericService::repeat::<StateWindowFocus, _, _>(&svc.clone(), 33, run_window_focus);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_last_cursor_pos(x: i32, y: i32) {
|
||||
let mut lock = LATEST_SYS_CURSOR_POS.lock().unwrap();
|
||||
@ -352,6 +383,22 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_window_focus(sp: EmptyExtraFieldService, state: &mut StateWindowFocus) -> ResultType<()> {
|
||||
let displays = super::display_service::get_sync_displays();
|
||||
let disp_idx = crate::get_focused_display(displays);
|
||||
if let Some(disp_idx) = disp_idx.map(|id| id as i32) {
|
||||
if state.is_changed(disp_idx) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_follow_current_display(disp_idx as i32);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
sp.send(msg_out);
|
||||
}
|
||||
state.display_idx = disp_idx;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
enum KeysDown {
|
||||
RdevKey(RawKey),
|
||||
@ -424,12 +471,15 @@ struct VirtualInputState {
|
||||
#[cfg(target_os = "macos")]
|
||||
impl VirtualInputState {
|
||||
fn new() -> Option<Self> {
|
||||
VirtualInput::new(CGEventSourceStateID::CombinedSessionState, CGEventTapLocation::Session)
|
||||
.map(|virtual_input| Self {
|
||||
virtual_input,
|
||||
capslock_down: false,
|
||||
})
|
||||
.ok()
|
||||
VirtualInput::new(
|
||||
CGEventSourceStateID::CombinedSessionState,
|
||||
CGEventTapLocation::Session,
|
||||
)
|
||||
.map(|virtual_input| Self {
|
||||
virtual_input,
|
||||
capslock_down: false,
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -194,6 +194,8 @@ class Header: Reactor.Component {
|
||||
</div> : ""}
|
||||
<div .separator />
|
||||
{!cursor_embedded && <li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>}
|
||||
{<li #follow-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Follow remote cursor')}</li>}
|
||||
{<li #follow-remote-window .toggle-option><span>{svg_checkmark}</span>{translate('Follow remote window focus')}</li>}
|
||||
<li #show-quality-monitor .toggle-option><span>{svg_checkmark}</span>{translate('Show quality monitor')}</li>
|
||||
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>{translate('Mute')}</li> : ""}
|
||||
{(is_win && pi.platform == "Windows") && file_enabled ? <li #enable-file-transfer .toggle-option><span>{svg_checkmark}</span>{translate('Enable file copy and paste')}</li> : ""}
|
||||
@ -479,7 +481,7 @@ function toggleMenuState() {
|
||||
for (var el in $$(menu#keyboard-options>li)) {
|
||||
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
|
||||
}
|
||||
for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end", "allow_swap_key", "i444"]) {
|
||||
for (var id in ["show-remote-cursor", "follow-remote-cursor", "follow-remote-window", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end", "allow_swap_key", "i444"]) {
|
||||
var el = self.select('#' + id);
|
||||
if (el) {
|
||||
var value = handler.get_toggle_option(id);
|
||||
@ -538,6 +540,15 @@ handler.setMultipleWindowsSession = function(sessions) {
|
||||
});
|
||||
}
|
||||
|
||||
handler.setCurrentDisplay = function(v) {
|
||||
pi.current_display = v;
|
||||
handler.switch_display(v);
|
||||
header.update();
|
||||
if (is_port_forward) {
|
||||
view.windowState = View.WINDOW_MINIMIZED;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePrivacyMode() {
|
||||
var el = $(li#privacy-mode);
|
||||
if (el) {
|
||||
|
@ -259,6 +259,10 @@ impl InvokeUiSession for SciterHandler {
|
||||
// Ignore for sciter version.
|
||||
}
|
||||
|
||||
fn set_current_display(&self, _disp_idx: i32) {
|
||||
self.call("setCurrentDisplay", &make_args!(_disp_idx));
|
||||
}
|
||||
|
||||
fn set_multiple_windows_session(&self, sessions: Vec<WindowsSession>) {
|
||||
let mut v = Value::array(0);
|
||||
let mut sessions = sessions;
|
||||
|
@ -192,6 +192,11 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.lc.read().unwrap().conn_type.eq(&ConnType::RDP)
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
pub fn is_multi_ui_session(&self) -> bool {
|
||||
self.ui_handler.is_multi_ui_session()
|
||||
}
|
||||
|
||||
pub fn get_view_style(&self) -> String {
|
||||
self.lc.read().unwrap().view_style.clone()
|
||||
}
|
||||
@ -1378,6 +1383,9 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
|
||||
#[cfg(all(feature = "vram", feature = "flutter"))]
|
||||
fn on_texture(&self, display: usize, texture: *mut c_void);
|
||||
fn set_multiple_windows_session(&self, sessions: Vec<WindowsSession>);
|
||||
fn set_current_display(&self, disp_idx: i32);
|
||||
#[cfg(feature = "flutter")]
|
||||
fn is_multi_ui_session(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T: InvokeUiSession> Deref for Session<T> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user