wayland cursor embeded

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2022-11-29 16:36:35 +08:00
parent e13e0ab18f
commit 4d044ca57a
18 changed files with 152 additions and 82 deletions

View File

@ -235,12 +235,14 @@ class _RemotePageState extends State<RemotePage>
})) }))
]; ];
paints.add(Obx(() => Visibility( if (!_ffi.canvasModel.cursorEmbeded) {
visible: _showRemoteCursor.isTrue && _remoteCursorMoved.isTrue, paints.add(Obx(() => Visibility(
child: CursorPaint( visible: _showRemoteCursor.isTrue && _remoteCursorMoved.isTrue,
id: widget.id, child: CursorPaint(
zoomCursor: _zoomCursor, id: widget.id,
)))); zoomCursor: _zoomCursor,
))));
}
paints.add(QualityMonitor(_ffi.qualityMonitorModel)); paints.add(QualityMonitor(_ffi.qualityMonitorModel));
paints.add(RemoteMenubar( paints.add(RemoteMenubar(
id: widget.id, id: widget.id,
@ -300,20 +302,22 @@ class _ImagePaintState extends State<ImagePaint> {
mouseRegion({child}) => Obx(() => MouseRegion( mouseRegion({child}) => Obx(() => MouseRegion(
cursor: cursorOverImage.isTrue cursor: cursorOverImage.isTrue
? keyboardEnabled.isTrue ? c.cursorEmbeded
? (() { ? SystemMouseCursors.none
if (remoteCursorMoved.isTrue) { : keyboardEnabled.isTrue
_lastRemoteCursorMoved = true; ? (() {
return SystemMouseCursors.none; if (remoteCursorMoved.isTrue) {
} else { _lastRemoteCursorMoved = true;
if (_lastRemoteCursorMoved) { return SystemMouseCursors.none;
_lastRemoteCursorMoved = false; } else {
_firstEnterImage.value = true; if (_lastRemoteCursorMoved) {
} _lastRemoteCursorMoved = false;
return _buildCustomCursor(context, s); _firstEnterImage.value = true;
} }
}()) return _buildCustomCursor(context, s);
: _buildDisabledCursor(context, s) }
}())
: _buildDisabledCursor(context, s)
: MouseCursor.defer, : MouseCursor.defer,
onHover: (evt) {}, onHover: (evt) {},
child: child)); child: child));

View File

@ -255,8 +255,11 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
}, },
padding: padding, padding: padding,
), ),
MenuEntryDivider<String>(), ]);
() {
if (!ffi.canvasModel.cursorEmbeded) {
menu.add(MenuEntryDivider<String>());
menu.add(() {
final state = ShowRemoteCursorState.find(key); final state = ShowRemoteCursorState.find(key);
return MenuEntrySwitch2<String>( return MenuEntrySwitch2<String>(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
@ -272,8 +275,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
}, },
padding: padding, padding: padding,
); );
}() }());
]); }
if (perms['keyboard'] != false) { if (perms['keyboard'] != false) {
if (perms['clipboard'] != false) { if (perms['clipboard'] != false) {

View File

@ -1088,23 +1088,25 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
} }
/// Show remote cursor /// Show remote cursor
displayMenu.add(() { if (!widget.ffi.canvasModel.cursorEmbeded) {
final state = ShowRemoteCursorState.find(widget.id); displayMenu.add(() {
return MenuEntrySwitch2<String>( final state = ShowRemoteCursorState.find(widget.id);
switchType: SwitchType.scheckbox, return MenuEntrySwitch2<String>(
text: translate('Show remote cursor'), switchType: SwitchType.scheckbox,
getter: () { text: translate('Show remote cursor'),
return state; getter: () {
}, return state;
setter: (bool v) async { },
state.value = v; setter: (bool v) async {
await bind.sessionToggleOption( state.value = v;
id: widget.id, value: 'show-remote-cursor'); await bind.sessionToggleOption(
}, id: widget.id, value: 'show-remote-cursor');
padding: padding, },
dismissOnClicked: true, padding: padding,
); dismissOnClicked: true,
}()); );
}());
}
/// Show remote cursor scaling with image /// Show remote cursor scaling with image
if (widget.state.viewStyle.value != kRemoteViewStyleOriginal) { if (widget.state.viewStyle.value != kRemoteViewStyleOriginal) {

View File

@ -494,38 +494,45 @@ class _RemotePageState extends State<RemotePage> {
Widget getBodyForMobile() { Widget getBodyForMobile() {
return Container( return Container(
color: MyTheme.canvasColor, color: MyTheme.canvasColor,
child: Stack(children: [ child: Stack(children: () {
ImagePaint(), final paints = [
CursorPaint(), ImagePaint(),
QualityMonitor(gFFI.qualityMonitorModel), QualityMonitor(gFFI.qualityMonitorModel),
getHelpTools(), getHelpTools(),
SizedBox( SizedBox(
width: 0, width: 0,
height: 0, height: 0,
child: !_showEdit child: !_showEdit
? Container() ? Container()
: TextFormField( : TextFormField(
textInputAction: TextInputAction.newline, textInputAction: TextInputAction.newline,
autocorrect: false, autocorrect: false,
enableSuggestions: false, enableSuggestions: false,
autofocus: true, autofocus: true,
focusNode: _mobileFocusNode, focusNode: _mobileFocusNode,
maxLines: null, maxLines: null,
initialValue: _value, initialValue: _value,
// trick way to make backspace work always // trick way to make backspace work always
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
onChanged: handleSoftKeyboardInput, onChanged: handleSoftKeyboardInput,
), ),
), ),
])); ];
if (!gFFI.canvasModel.cursorEmbeded) {
paints.add(CursorPaint());
}
return paints;
}()));
} }
Widget getBodyForDesktopWithListener(bool keyboard) { Widget getBodyForDesktopWithListener(bool keyboard) {
var paints = <Widget>[ImagePaint()]; var paints = <Widget>[ImagePaint()];
final cursor = bind.sessionGetToggleOptionSync( if (!gFFI.canvasModel.cursorEmbeded) {
id: widget.id, arg: 'show-remote-cursor'); final cursor = bind.sessionGetToggleOptionSync(
if (keyboard || cursor) { id: widget.id, arg: 'show-remote-cursor');
paints.add(CursorPaint()); if (keyboard || cursor) {
paints.add(CursorPaint());
}
} }
return Container( return Container(
color: MyTheme.canvasColor, child: Stack(children: paints)); color: MyTheme.canvasColor, child: Stack(children: paints));
@ -1046,9 +1053,12 @@ void showOptions(
} }
final toggles = [ final toggles = [
getToggle(id, setState, 'show-remote-cursor', 'Show remote cursor'),
getToggle(id, setState, 'show-quality-monitor', 'Show quality monitor'), 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( return CustomAlertDialog(
content: Column( content: Column(

View File

@ -221,6 +221,7 @@ class FfiModel with ChangeNotifier {
_display.y = double.parse(evt['y']); _display.y = double.parse(evt['y']);
_display.width = int.parse(evt['width']); _display.width = int.parse(evt['width']);
_display.height = int.parse(evt['height']); _display.height = int.parse(evt['height']);
_display.cursorEmbeded = int.parse(evt['cursor_embeded']) == 1;
if (old != _pi.currentDisplay) { if (old != _pi.currentDisplay) {
parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y); parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y);
} }
@ -330,6 +331,7 @@ class FfiModel with ChangeNotifier {
d.y = d0['y'].toDouble(); d.y = d0['y'].toDouble();
d.width = d0['width']; d.width = d0['width'];
d.height = d0['height']; d.height = d0['height'];
d.cursorEmbeded = d0['cursor_embeded'] == 1;
_pi.displays.add(d); _pi.displays.add(d);
} }
if (_pi.currentDisplay < _pi.displays.length) { if (_pi.currentDisplay < _pi.displays.length) {
@ -582,6 +584,9 @@ class CanvasModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
bool get cursorEmbeded =>
parent.target?.ffiModel.display.cursorEmbeded ?? false;
int getDisplayWidth() { int getDisplayWidth() {
final defaultWidth = (isDesktop || isWebDesktop) final defaultWidth = (isDesktop || isWebDesktop)
? kDesktopDefaultDisplayWidth ? kDesktopDefaultDisplayWidth
@ -1311,6 +1316,7 @@ class Display {
double y = 0; double y = 0;
int width = 0; int width = 0;
int height = 0; int height = 0;
bool cursorEmbeded = false;
Display() { Display() {
width = (isDesktop || isWebDesktop) width = (isDesktop || isWebDesktop)

View File

@ -40,6 +40,7 @@ message DisplayInfo {
int32 height = 4; int32 height = 4;
string name = 5; string name = 5;
bool online = 6; bool online = 6;
bool cursor_embeded = 7;
} }
message PortForward { message PortForward {
@ -419,6 +420,7 @@ message SwitchDisplay {
sint32 y = 3; sint32 y = 3;
int32 width = 4; int32 width = 4;
int32 height = 5; int32 height = 5;
bool cursor_embeded = 6;
} }
message PermissionInfo { message PermissionInfo {

View File

@ -69,3 +69,19 @@ pub trait TraitCapturer {
pub fn is_x11() -> bool { pub fn is_x11() -> bool {
"x11" == hbb_common::platform::linux::get_display_server() "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
}

View File

@ -4,6 +4,8 @@ use std::{io, sync::RwLock, time::Duration};
pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>); pub struct Capturer(Display, Box<dyn Recorder>, bool, Vec<u8>);
pub const IS_CURSOR_EMBEDED: bool = true;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref MAP_ERR: RwLock<Option<fn(err: String)-> io::Error>> = Default::default(); static ref MAP_ERR: RwLock<Option<fn(err: String)-> io::Error>> = Default::default();
} }
@ -66,7 +68,7 @@ impl Display {
} }
pub fn all() -> io::Result<Vec<Display>> { pub fn all() -> io::Result<Vec<Display>> {
Ok(pipewire::get_capturables(false) Ok(pipewire::get_capturables(true)
.map_err(map_err)? .map_err(map_err)?
.drain(..) .drain(..)
.map(|x| Display(x)) .map(|x| Display(x))

View File

@ -3,6 +3,8 @@ use std::{io, ops, time::Duration};
pub struct Capturer(x11::Capturer); pub struct Capturer(x11::Capturer);
pub const IS_CURSOR_EMBEDED: bool = true;
impl Capturer { impl Capturer {
pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> { pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> {
x11::Capturer::new(display.0, yuv).map(Capturer) x11::Capturer::new(display.0, yuv).map(Capturer)

View File

@ -976,7 +976,7 @@ impl<T: InvokeUiSession> Remote<T> {
self.handler.ui_handler.switch_display(&s); self.handler.ui_handler.switch_display(&s);
self.video_sender.send(MediaData::Reset).ok(); self.video_sender.send(MediaData::Reset).ok();
if s.width > 0 && s.height > 0 { 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)) => { Some(misc::Union::CloseReason(c)) => {

View File

@ -155,7 +155,7 @@ impl InvokeUiSession for FlutterHandler {
} }
/// unused in flutter, use switch_display or set_peer_info /// 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) { fn update_privacy_mode(&self) {
self.push_event("update_privacy_mode", [].into()); self.push_event("update_privacy_mode", [].into());
@ -295,6 +295,7 @@ impl InvokeUiSession for FlutterHandler {
h.insert("y", d.y); h.insert("y", d.y);
h.insert("width", d.width); h.insert("width", d.width);
h.insert("height", d.height); h.insert("height", d.height);
h.insert("cursor_embeded", if d.cursor_embeded { 1 } else { 0 });
displays.push(h); displays.push(h);
} }
let displays = serde_json::ser::to_string(&displays).unwrap_or("".to_owned()); 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()), ("y", &display.y.to_string()),
("width", &display.width.to_string()), ("width", &display.width.to_string()),
("height", &display.height.to_string()), ("height", &display.height.to_string()),
("cursor_embeded", &{if display.cursor_embeded {1} else {0}}.to_string()),
], ],
); );
} }

View File

@ -110,7 +110,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"fa" => fa::T.deref(), "fa" => fa::T.deref(),
"ca" => ca::T.deref(), "ca" => ca::T.deref(),
"gr" => gr::T.deref(), "gr" => gr::T.deref(),
"gr" => sv::T.deref(), "sv" => sv::T.deref(),
_ => en::T.deref(), _ => en::T.deref(),
}; };
if let Some(v) = m.get(&name as &str) { if let Some(v) = m.get(&name as &str) {

View File

@ -85,8 +85,10 @@ pub fn new() -> ServerPtr {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ {
server.add_service(Box::new(clipboard_service::new())); server.add_service(Box::new(clipboard_service::new()));
server.add_service(Box::new(input_service::new_cursor())); if !video_service::capture_cursor_embeded() {
server.add_service(Box::new(input_service::new_pos())); server.add_service(Box::new(input_service::new_cursor()));
server.add_service(Box::new(input_service::new_pos()));
}
} }
Arc::new(RwLock::new(server)) Arc::new(RwLock::new(server))
} }

View File

@ -74,6 +74,10 @@ fn is_capturer_mag_supported() -> bool {
false false
} }
pub fn capture_cursor_embeded() -> bool {
scrap::is_cursor_embeded()
}
pub fn notify_video_frame_feched(conn_id: i32, frame_tm: Option<Instant>) { pub fn notify_video_frame_feched(conn_id: i32, frame_tm: Option<Instant>) {
FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).unwrap() FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).unwrap()
} }
@ -455,6 +459,7 @@ fn run(sp: GenericService) -> ResultType<()> {
y: c.origin.1 as _, y: c.origin.1 as _,
width: c.width as _, width: c.width as _,
height: c.height as _, height: c.height as _,
cursor_embeded: capture_cursor_embeded(),
..Default::default() ..Default::default()
}); });
let mut msg_out = Message::new(); let mut msg_out = Message::new();
@ -783,6 +788,7 @@ pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
height: d.height() as _, height: d.height() as _,
name: d.name(), name: d.name(),
online: d.is_online(), online: d.is_online(),
cursor_embeded: false,
..Default::default() ..Default::default()
}); });
} }

View File

@ -127,7 +127,10 @@ pub(super) async fn check_init() -> ResultType<()> {
if *lock == 0 { if *lock == 0 {
let all = Display::all()?; let all = Display::all()?;
let num = all.len(); 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(); let mut rects: Vec<((i32, i32), usize, usize)> = Vec::new();
for d in &all { for d in &all {

View File

@ -79,8 +79,8 @@ impl InvokeUiSession for SciterHandler {
} }
} }
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) {
self.call("setDisplay", &make_args!(x, y, w, h)); self.call("setDisplay", &make_args!(x, y, w, h, cursor_embeded));
// https://sciter.com/forums/topic/color_spaceiyuv-crash // https://sciter.com/forums/topic/color_spaceiyuv-crash
// Nothing spectacular in decoder done on CPU side. // Nothing spectacular in decoder done on CPU side.
// So if you can do BGRA translation on your side the better. // 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("y", d.y);
display.set_item("width", d.width); display.set_item("width", d.width);
display.set_item("height", d.height); display.set_item("height", d.height);
display.set_item("cursor_embeded", d.cursor_embeded);
displays.push(display); displays.push(display);
} }
pi_sciter.set_item("displays", displays); pi_sciter.set_item("displays", displays);

View File

@ -6,6 +6,7 @@ var display_width = 0;
var display_height = 0; var display_height = 0;
var display_origin_x = 0; var display_origin_x = 0;
var display_origin_y = 0; var display_origin_y = 0;
var display_cursor_embeded = false;
var display_scale = 1; var display_scale = 1;
var keyboard_enabled = true; // server side var keyboard_enabled = true; // server side
var clipboard_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 recording_enabled = true; // server side
var scroll_body = $(body); var scroll_body = $(body);
handler.setDisplay = function(x, y, w, h) { handler.setDisplay = function(x, y, w, h, cursor_embeded) {
display_width = w; display_width = w;
display_height = h; display_height = h;
display_origin_x = x; display_origin_x = x;
display_origin_y = y; display_origin_y = y;
display_cursor_embeded = cursor_embeded;
adaptDisplay(); adaptDisplay();
if (recording) handler.record_screen(true, w, h); if (recording) handler.record_screen(true, w, h);
} }
@ -195,6 +197,9 @@ function handler.onMouse(evt)
dragging = false; dragging = false;
break; break;
case Event.MOUSE_MOVE: case Event.MOUSE_MOVE:
if (display_cursor_embeded) {
break;
}
if (cursor_img.style#display != "none" && keyboard_enabled) { if (cursor_img.style#display != "none" && keyboard_enabled) {
cursor_img.style#display = "none"; cursor_img.style#display = "none";
} }
@ -360,6 +365,10 @@ function updateCursor(system=false) {
} }
function refreshCursor() { function refreshCursor() {
if (display_cursor_embeded) {
cursor_img.style#display = "none";
return;
}
if (cur_id != -1) { if (cur_id != -1) {
handler.setCursorId(cur_id); handler.setCursorId(cur_id);
} }

View File

@ -1098,7 +1098,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn set_cursor_data(&self, cd: CursorData); fn set_cursor_data(&self, cd: CursorData);
fn set_cursor_id(&self, id: String); fn set_cursor_id(&self, id: String);
fn set_cursor_position(&self, cp: CursorPosition); 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 switch_display(&self, display: &SwitchDisplay);
fn set_peer_info(&self, peer_info: &PeerInfo); // flutter fn set_peer_info(&self, peer_info: &PeerInfo); // flutter
fn update_privacy_mode(&self); fn update_privacy_mode(&self);
@ -1211,7 +1211,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
input_os_password(p, true, self.clone()); input_os_password(p, true, self.clone());
} }
let current = &pi.displays[pi.current_display as usize]; 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(); self.update_privacy_mode();
// Save recent peers, then push event to flutter. So flutter can refresh peer page. // Save recent peers, then push event to flutter. So flutter can refresh peer page.