From 4d044ca57ae99d9878ef44f3c7dd527ecfcc9b90 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 29 Nov 2022 16:36:35 +0800 Subject: [PATCH] wayland cursor embeded Signed-off-by: fufesou --- flutter/lib/desktop/pages/remote_page.dart | 44 ++++++------ .../lib/desktop/pages/remote_tab_page.dart | 11 +-- .../lib/desktop/widgets/remote_menubar.dart | 36 +++++----- flutter/lib/mobile/pages/remote_page.dart | 68 +++++++++++-------- flutter/lib/models/model.dart | 6 ++ libs/hbb_common/protos/message.proto | 2 + libs/scrap/src/common/mod.rs | 16 +++++ libs/scrap/src/common/wayland.rs | 4 +- libs/scrap/src/common/x11.rs | 2 + src/client/io_loop.rs | 2 +- src/flutter.rs | 4 +- src/lang.rs | 2 +- src/server.rs | 6 +- src/server/video_service.rs | 6 ++ src/server/wayland.rs | 5 +- src/ui/remote.rs | 5 +- src/ui/remote.tis | 11 ++- src/ui_session_interface.rs | 4 +- 18 files changed, 152 insertions(+), 82 deletions(-) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 6d0d9a047..bac80e7ac 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -235,12 +235,14 @@ class _RemotePageState extends State })) ]; - paints.add(Obx(() => Visibility( - visible: _showRemoteCursor.isTrue && _remoteCursorMoved.isTrue, - child: CursorPaint( - id: widget.id, - zoomCursor: _zoomCursor, - )))); + if (!_ffi.canvasModel.cursorEmbeded) { + paints.add(Obx(() => Visibility( + visible: _showRemoteCursor.isTrue && _remoteCursorMoved.isTrue, + child: CursorPaint( + id: widget.id, + zoomCursor: _zoomCursor, + )))); + } paints.add(QualityMonitor(_ffi.qualityMonitorModel)); paints.add(RemoteMenubar( id: widget.id, @@ -300,20 +302,22 @@ class _ImagePaintState extends State { mouseRegion({child}) => Obx(() => MouseRegion( cursor: cursorOverImage.isTrue - ? keyboardEnabled.isTrue - ? (() { - if (remoteCursorMoved.isTrue) { - _lastRemoteCursorMoved = true; - return SystemMouseCursors.none; - } else { - if (_lastRemoteCursorMoved) { - _lastRemoteCursorMoved = false; - _firstEnterImage.value = true; - } - return _buildCustomCursor(context, s); - } - }()) - : _buildDisabledCursor(context, s) + ? c.cursorEmbeded + ? SystemMouseCursors.none + : keyboardEnabled.isTrue + ? (() { + if (remoteCursorMoved.isTrue) { + _lastRemoteCursorMoved = true; + return SystemMouseCursors.none; + } else { + if (_lastRemoteCursorMoved) { + _lastRemoteCursorMoved = false; + _firstEnterImage.value = true; + } + return _buildCustomCursor(context, s); + } + }()) + : _buildDisabledCursor(context, s) : MouseCursor.defer, onHover: (evt) {}, child: child)); diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 85df25477..713c3d13c 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -255,8 +255,11 @@ class _ConnectionTabPageState extends State { }, padding: padding, ), - MenuEntryDivider(), - () { + ]); + + if (!ffi.canvasModel.cursorEmbeded) { + menu.add(MenuEntryDivider()); + menu.add(() { final state = ShowRemoteCursorState.find(key); return MenuEntrySwitch2( switchType: SwitchType.scheckbox, @@ -272,8 +275,8 @@ class _ConnectionTabPageState extends State { }, padding: padding, ); - }() - ]); + }()); + } if (perms['keyboard'] != false) { if (perms['clipboard'] != false) { diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 39c37dd8b..d40a21e5d 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -1088,23 +1088,25 @@ class _RemoteMenubarState extends State { } /// Show remote cursor - displayMenu.add(() { - final state = ShowRemoteCursorState.find(widget.id); - return MenuEntrySwitch2( - switchType: SwitchType.scheckbox, - text: translate('Show remote cursor'), - getter: () { - return state; - }, - setter: (bool v) async { - state.value = v; - await bind.sessionToggleOption( - id: widget.id, value: 'show-remote-cursor'); - }, - padding: padding, - dismissOnClicked: true, - ); - }()); + if (!widget.ffi.canvasModel.cursorEmbeded) { + displayMenu.add(() { + final state = ShowRemoteCursorState.find(widget.id); + return MenuEntrySwitch2( + switchType: SwitchType.scheckbox, + text: translate('Show remote cursor'), + getter: () { + return state; + }, + setter: (bool v) async { + state.value = v; + await bind.sessionToggleOption( + id: widget.id, value: 'show-remote-cursor'); + }, + padding: padding, + dismissOnClicked: true, + ); + }()); + } /// Show remote cursor scaling with image if (widget.state.viewStyle.value != kRemoteViewStyleOriginal) { diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index e6ebfcb73..fcfb8ad60 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -494,38 +494,45 @@ class _RemotePageState extends State { Widget getBodyForMobile() { return Container( color: MyTheme.canvasColor, - child: Stack(children: [ - ImagePaint(), - CursorPaint(), - QualityMonitor(gFFI.qualityMonitorModel), - getHelpTools(), - SizedBox( - width: 0, - height: 0, - child: !_showEdit - ? Container() - : TextFormField( - textInputAction: TextInputAction.newline, - autocorrect: false, - enableSuggestions: false, - autofocus: true, - focusNode: _mobileFocusNode, - maxLines: null, - initialValue: _value, - // trick way to make backspace work always - keyboardType: TextInputType.multiline, - onChanged: handleSoftKeyboardInput, - ), - ), - ])); + child: Stack(children: () { + final paints = [ + ImagePaint(), + QualityMonitor(gFFI.qualityMonitorModel), + getHelpTools(), + SizedBox( + width: 0, + height: 0, + child: !_showEdit + ? Container() + : TextFormField( + textInputAction: TextInputAction.newline, + autocorrect: false, + enableSuggestions: false, + autofocus: true, + focusNode: _mobileFocusNode, + maxLines: null, + initialValue: _value, + // trick way to make backspace work always + keyboardType: TextInputType.multiline, + onChanged: handleSoftKeyboardInput, + ), + ), + ]; + if (!gFFI.canvasModel.cursorEmbeded) { + paints.add(CursorPaint()); + } + return paints; + }())); } Widget getBodyForDesktopWithListener(bool keyboard) { var paints = [ImagePaint()]; - final cursor = bind.sessionGetToggleOptionSync( - id: widget.id, arg: 'show-remote-cursor'); - if (keyboard || cursor) { - paints.add(CursorPaint()); + if (!gFFI.canvasModel.cursorEmbeded) { + final cursor = bind.sessionGetToggleOptionSync( + id: widget.id, arg: 'show-remote-cursor'); + if (keyboard || cursor) { + paints.add(CursorPaint()); + } } return Container( color: MyTheme.canvasColor, child: Stack(children: paints)); @@ -1046,9 +1053,12 @@ void showOptions( } final toggles = [ - getToggle(id, setState, 'show-remote-cursor', 'Show remote cursor'), getToggle(id, setState, 'show-quality-monitor', 'Show quality monitor'), ]; + if (!gFFI.canvasModel.cursorEmbeded) { + toggles.insert(0, + getToggle(id, setState, 'show-remote-cursor', 'Show remote cursor')); + } return CustomAlertDialog( content: Column( diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index a7a51b9a0..f6bfde941 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -221,6 +221,7 @@ class FfiModel with ChangeNotifier { _display.y = double.parse(evt['y']); _display.width = int.parse(evt['width']); _display.height = int.parse(evt['height']); + _display.cursorEmbeded = int.parse(evt['cursor_embeded']) == 1; if (old != _pi.currentDisplay) { parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y); } @@ -330,6 +331,7 @@ class FfiModel with ChangeNotifier { d.y = d0['y'].toDouble(); d.width = d0['width']; d.height = d0['height']; + d.cursorEmbeded = d0['cursor_embeded'] == 1; _pi.displays.add(d); } if (_pi.currentDisplay < _pi.displays.length) { @@ -582,6 +584,9 @@ class CanvasModel with ChangeNotifier { notifyListeners(); } + bool get cursorEmbeded => + parent.target?.ffiModel.display.cursorEmbeded ?? false; + int getDisplayWidth() { final defaultWidth = (isDesktop || isWebDesktop) ? kDesktopDefaultDisplayWidth @@ -1311,6 +1316,7 @@ class Display { double y = 0; int width = 0; int height = 0; + bool cursorEmbeded = false; Display() { width = (isDesktop || isWebDesktop) diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 34983599a..9217388aa 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -40,6 +40,7 @@ message DisplayInfo { int32 height = 4; string name = 5; bool online = 6; + bool cursor_embeded = 7; } message PortForward { @@ -419,6 +420,7 @@ message SwitchDisplay { sint32 y = 3; int32 width = 4; int32 height = 5; + bool cursor_embeded = 6; } message PermissionInfo { diff --git a/libs/scrap/src/common/mod.rs b/libs/scrap/src/common/mod.rs index 468efb88e..82f65537b 100644 --- a/libs/scrap/src/common/mod.rs +++ b/libs/scrap/src/common/mod.rs @@ -69,3 +69,19 @@ pub trait TraitCapturer { pub fn is_x11() -> bool { "x11" == hbb_common::platform::linux::get_display_server() } + +#[cfg(x11)] +#[inline] +pub fn is_cursor_embeded() -> bool { + if is_x11() { + x11::IS_CURSOR_EMBEDED + } else { + wayland::IS_CURSOR_EMBEDED + } +} + +#[cfg(not(x11))] +#[inline] +pub fn is_cursor_embeded() -> bool { + false +} diff --git a/libs/scrap/src/common/wayland.rs b/libs/scrap/src/common/wayland.rs index 6ad2d84cb..2593e56fe 100644 --- a/libs/scrap/src/common/wayland.rs +++ b/libs/scrap/src/common/wayland.rs @@ -4,6 +4,8 @@ use std::{io, sync::RwLock, time::Duration}; pub struct Capturer(Display, Box, bool, Vec); +pub const IS_CURSOR_EMBEDED: bool = true; + lazy_static::lazy_static! { static ref MAP_ERR: RwLock io::Error>> = Default::default(); } @@ -66,7 +68,7 @@ impl Display { } pub fn all() -> io::Result> { - Ok(pipewire::get_capturables(false) + Ok(pipewire::get_capturables(true) .map_err(map_err)? .drain(..) .map(|x| Display(x)) diff --git a/libs/scrap/src/common/x11.rs b/libs/scrap/src/common/x11.rs index 791514deb..a7122adcb 100644 --- a/libs/scrap/src/common/x11.rs +++ b/libs/scrap/src/common/x11.rs @@ -3,6 +3,8 @@ use std::{io, ops, time::Duration}; pub struct Capturer(x11::Capturer); +pub const IS_CURSOR_EMBEDED: bool = true; + impl Capturer { pub fn new(display: Display, yuv: bool) -> io::Result { x11::Capturer::new(display.0, yuv).map(Capturer) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 6ad4f96d6..16f91d89d 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -976,7 +976,7 @@ impl Remote { self.handler.ui_handler.switch_display(&s); self.video_sender.send(MediaData::Reset).ok(); if s.width > 0 && s.height > 0 { - self.handler.set_display(s.x, s.y, s.width, s.height); + self.handler.set_display(s.x, s.y, s.width, s.height, s.cursor_embeded); } } Some(misc::Union::CloseReason(c)) => { diff --git a/src/flutter.rs b/src/flutter.rs index 9c4208625..9649c9b46 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -155,7 +155,7 @@ impl InvokeUiSession for FlutterHandler { } /// unused in flutter, use switch_display or set_peer_info - fn set_display(&self, _x: i32, _y: i32, _w: i32, _h: i32) {} + fn set_display(&self, _x: i32, _y: i32, _w: i32, _h: i32, _cursor_embeded: bool) {} fn update_privacy_mode(&self) { self.push_event("update_privacy_mode", [].into()); @@ -295,6 +295,7 @@ impl InvokeUiSession for FlutterHandler { h.insert("y", d.y); h.insert("width", d.width); h.insert("height", d.height); + h.insert("cursor_embeded", if d.cursor_embeded { 1 } else { 0 }); displays.push(h); } let displays = serde_json::ser::to_string(&displays).unwrap_or("".to_owned()); @@ -343,6 +344,7 @@ impl InvokeUiSession for FlutterHandler { ("y", &display.y.to_string()), ("width", &display.width.to_string()), ("height", &display.height.to_string()), + ("cursor_embeded", &{if display.cursor_embeded {1} else {0}}.to_string()), ], ); } diff --git a/src/lang.rs b/src/lang.rs index de0fed0b8..6254e988a 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -110,7 +110,7 @@ pub fn translate_locale(name: String, locale: &str) -> String { "fa" => fa::T.deref(), "ca" => ca::T.deref(), "gr" => gr::T.deref(), - "gr" => sv::T.deref(), + "sv" => sv::T.deref(), _ => en::T.deref(), }; if let Some(v) = m.get(&name as &str) { diff --git a/src/server.rs b/src/server.rs index f5326288a..a12a31dbd 100644 --- a/src/server.rs +++ b/src/server.rs @@ -85,8 +85,10 @@ pub fn new() -> ServerPtr { #[cfg(not(any(target_os = "android", target_os = "ios")))] { server.add_service(Box::new(clipboard_service::new())); - server.add_service(Box::new(input_service::new_cursor())); - server.add_service(Box::new(input_service::new_pos())); + if !video_service::capture_cursor_embeded() { + server.add_service(Box::new(input_service::new_cursor())); + server.add_service(Box::new(input_service::new_pos())); + } } Arc::new(RwLock::new(server)) } diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 04df87bef..a169b3835 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -74,6 +74,10 @@ fn is_capturer_mag_supported() -> bool { false } +pub fn capture_cursor_embeded() -> bool { + scrap::is_cursor_embeded() +} + pub fn notify_video_frame_feched(conn_id: i32, frame_tm: Option) { FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).unwrap() } @@ -455,6 +459,7 @@ fn run(sp: GenericService) -> ResultType<()> { y: c.origin.1 as _, width: c.width as _, height: c.height as _, + cursor_embeded: capture_cursor_embeded(), ..Default::default() }); let mut msg_out = Message::new(); @@ -783,6 +788,7 @@ pub(super) fn get_displays_2(all: &Vec) -> (usize, Vec) { height: d.height() as _, name: d.name(), online: d.is_online(), + cursor_embeded: false, ..Default::default() }); } diff --git a/src/server/wayland.rs b/src/server/wayland.rs index 4ffb9e225..071077c84 100644 --- a/src/server/wayland.rs +++ b/src/server/wayland.rs @@ -127,7 +127,10 @@ pub(super) async fn check_init() -> ResultType<()> { if *lock == 0 { let all = Display::all()?; let num = all.len(); - let (primary, displays) = super::video_service::get_displays_2(&all); + let (primary, mut displays) = super::video_service::get_displays_2(&all); + for display in displays.iter_mut() { + display.cursor_embeded = true; + } let mut rects: Vec<((i32, i32), usize, usize)> = Vec::new(); for d in &all { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 66b46cf85..3d209a71c 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -79,8 +79,8 @@ impl InvokeUiSession for SciterHandler { } } - fn set_display(&self, x: i32, y: i32, w: i32, h: i32) { - self.call("setDisplay", &make_args!(x, y, w, h)); + fn set_display(&self, x: i32, y: i32, w: i32, h: i32, cursor_embeded: bool) { + self.call("setDisplay", &make_args!(x, y, w, h, cursor_embeded)); // https://sciter.com/forums/topic/color_spaceiyuv-crash // Nothing spectacular in decoder – done on CPU side. // So if you can do BGRA translation on your side – the better. @@ -223,6 +223,7 @@ impl InvokeUiSession for SciterHandler { display.set_item("y", d.y); display.set_item("width", d.width); display.set_item("height", d.height); + display.set_item("cursor_embeded", d.cursor_embeded); displays.push(display); } pi_sciter.set_item("displays", displays); diff --git a/src/ui/remote.tis b/src/ui/remote.tis index 02f0de270..012205abc 100644 --- a/src/ui/remote.tis +++ b/src/ui/remote.tis @@ -6,6 +6,7 @@ var display_width = 0; var display_height = 0; var display_origin_x = 0; var display_origin_y = 0; +var display_cursor_embeded = false; var display_scale = 1; var keyboard_enabled = true; // server side var clipboard_enabled = true; // server side @@ -15,11 +16,12 @@ var restart_enabled = true; // server side var recording_enabled = true; // server side var scroll_body = $(body); -handler.setDisplay = function(x, y, w, h) { +handler.setDisplay = function(x, y, w, h, cursor_embeded) { display_width = w; display_height = h; display_origin_x = x; display_origin_y = y; + display_cursor_embeded = cursor_embeded; adaptDisplay(); if (recording) handler.record_screen(true, w, h); } @@ -195,6 +197,9 @@ function handler.onMouse(evt) dragging = false; break; case Event.MOUSE_MOVE: + if (display_cursor_embeded) { + break; + } if (cursor_img.style#display != "none" && keyboard_enabled) { cursor_img.style#display = "none"; } @@ -360,6 +365,10 @@ function updateCursor(system=false) { } function refreshCursor() { + if (display_cursor_embeded) { + cursor_img.style#display = "none"; + return; + } if (cur_id != -1) { handler.setCursorId(cur_id); } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index efc82cbc1..6f8820e87 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -1098,7 +1098,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { fn set_cursor_data(&self, cd: CursorData); fn set_cursor_id(&self, id: String); fn set_cursor_position(&self, cp: CursorPosition); - fn set_display(&self, x: i32, y: i32, w: i32, h: i32); + fn set_display(&self, x: i32, y: i32, w: i32, h: i32, cursor_embeded: bool); fn switch_display(&self, display: &SwitchDisplay); fn set_peer_info(&self, peer_info: &PeerInfo); // flutter fn update_privacy_mode(&self); @@ -1211,7 +1211,7 @@ impl Interface for Session { input_os_password(p, true, self.clone()); } let current = &pi.displays[pi.current_display as usize]; - self.set_display(current.x, current.y, current.width, current.height); + self.set_display(current.x, current.y, current.width, current.height, current.cursor_embeded); } self.update_privacy_mode(); // Save recent peers, then push event to flutter. So flutter can refresh peer page.