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:
Sahil Yeole 2024-04-25 10:56:02 +05:30 committed by GitHub
parent 43a0a4f8e0
commit 3811f41076
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 597 additions and 26 deletions

View File

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

View File

@ -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'),

View File

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

View File

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

View File

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

View File

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

View File

@ -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', [

View File

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

View File

@ -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",

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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")))]

View File

@ -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 {
//
//
}
}
}

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

@ -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]

View File

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

View File

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

View File

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