From b7df95d4a7f48396731aa36e74cdcb27efd84811 Mon Sep 17 00:00:00 2001 From: ShootingStarDragons Date: Thu, 24 Aug 2023 22:23:15 +0800 Subject: [PATCH 1/6] chore: remote wip --- src/main.rs | 3 + src/remote.rs | 216 ++++++++++++++++++++++++++++++++++++++++++++++ src/screencast.rs | 25 +++++- src/session.rs | 39 ++++++++- 4 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 src/remote.rs diff --git a/src/main.rs b/src/main.rs index 5b7ec48..49929c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ +mod remote; mod request; mod screencast; mod screenshot; mod session; mod slintbackend; +use remote::RemoteBackend; use screencast::ScreenCastBackend; use screenshot::ScreenShotBackend; @@ -53,6 +55,7 @@ async fn main() -> anyhow::Result<()> { .name("org.freedesktop.impl.portal.desktop.luminous")? .serve_at("/org/freedesktop/portal/desktop", ScreenShotBackend)? .serve_at("/org/freedesktop/portal/desktop", ScreenCastBackend)? + .serve_at("/org/freedesktop/portal/desktop", RemoteBackend)? .build() .await?; diff --git a/src/remote.rs b/src/remote.rs new file mode 100644 index 0000000..93bea5d --- /dev/null +++ b/src/remote.rs @@ -0,0 +1,216 @@ +use std::collections::HashMap; + +use enumflags2::BitFlags; +use zbus::dbus_interface; + +use zbus::zvariant::{DeserializeDict, ObjectPath, OwnedValue, SerializeDict, Type, Value}; + +use serde::{Deserialize, Serialize}; + +use once_cell::sync::Lazy; +use std::sync::Arc; +use tokio::sync::Mutex; + +use crate::pipewirethread::ScreencastThread; +use crate::request::RequestInterface; +use crate::session::{append_session, DeviceTypes, Session, SessionType, SourceType, SESSIONS}; +use crate::PortalResponse; + +use crate::screencast::SelectSourcesOptions; + +#[derive(SerializeDict, DeserializeDict, Type, Debug, Default)] +/// Specified options for a [`Screencast::create_session`] request. +#[zvariant(signature = "dict")] +struct SessionCreateResult { + handle_token: String, +} + +#[derive(Clone, Serialize, Deserialize, Type, Default, Debug)] +/// A PipeWire stream. +pub struct Stream(u32, StreamProperties); + +#[derive(Clone, SerializeDict, DeserializeDict, Default, Type, Debug)] +/// The stream properties. +#[zvariant(signature = "dict")] +struct StreamProperties { + id: Option, + position: Option<(i32, i32)>, + size: Option<(i32, i32)>, + source_type: Option, +} + +// TODO: this is copy from ashpd, but the dict is a little different from xdg_desktop_portal +#[derive(Clone, SerializeDict, DeserializeDict, Default, Debug, Type)] +#[zvariant(signature = "dict")] +struct RemoteStartReturnValue { + streams: Vec, + devices: BitFlags, + clipboard_enabled: bool, +} + +pub type RemoteSessionData = (String, ScreencastThread); +pub static CAST_SESSIONS: Lazy>>> = + Lazy::new(|| Arc::new(Mutex::new(Vec::new()))); + +pub async fn append_remote_session(session: RemoteSessionData) { + let mut sessions = CAST_SESSIONS.lock().await; + sessions.push(session) +} + +pub async fn remove_remote_session(path: &str) { + let mut sessions = CAST_SESSIONS.lock().await; + let Some(index) = sessions + .iter() + .position(|the_session| the_session.0 == path) else { + return; + }; + sessions[index].1.stop(); + tracing::info!("session {} is stopped", sessions[index].0); + sessions.remove(index); +} + +pub struct RemoteBackend; + +#[dbus_interface(name = "org.freedesktop.impl.portal.RemoteDesktop")] +impl RemoteBackend { + #[dbus_interface(property, name = "version")] + fn version(&self) -> u32 { + 2 + } + + #[dbus_interface(property)] + fn available_device_types(&self) -> u32 { + (DeviceTypes::KEYBOARD | DeviceTypes::POINTER).bits() + } + + async fn create_session( + &self, + request_handle: ObjectPath<'_>, + session_handle: ObjectPath<'_>, + app_id: String, + _options: HashMap>, + #[zbus(object_server)] server: &zbus::ObjectServer, + ) -> zbus::fdo::Result> { + tracing::info!( + "Start shot: path :{}, appid: {}", + request_handle.as_str(), + app_id + ); + server + .at( + request_handle.clone(), + RequestInterface { + handle_path: request_handle.clone().into(), + }, + ) + .await?; + let current_session = Session::new(session_handle.clone(), SessionType::Remote); + append_session(current_session.clone()).await; + server.at(session_handle.clone(), current_session).await?; + Ok(PortalResponse::Success(SessionCreateResult { + handle_token: session_handle.to_string(), + })) + } + + async fn select_devices( + &self, + _request_handle: ObjectPath<'_>, + session_handle: ObjectPath<'_>, + _app_id: String, + options: SelectSourcesOptions, + ) -> zbus::fdo::Result>> { + let mut locked_sessions = SESSIONS.lock().await; + let Some(index) = locked_sessions.iter().position(|this_session| this_session.handle_path == session_handle.clone().into()) else { + tracing::warn!("No session is created or it is removed"); + return Ok(PortalResponse::Other); + }; + if locked_sessions[index].session_type != SessionType::Remote { + return Ok(PortalResponse::Other); + } + locked_sessions[index].set_options(options); + locked_sessions[index].set_device_type(DeviceTypes::KEYBOARD | DeviceTypes::POINTER); + Ok(PortalResponse::Success(HashMap::new())) + } + + async fn start( + &self, + _request_handle: ObjectPath<'_>, + session_handle: ObjectPath<'_>, + _app_id: String, + _parent_window: String, + _options: HashMap>, + ) -> zbus::fdo::Result> { + let cast_sessions = CAST_SESSIONS.lock().await; + if let Some(session) = cast_sessions + .iter() + .find(|session| session.0 == session_handle.to_string()) + { + return Ok(PortalResponse::Success(RemoteStartReturnValue { + streams: vec![Stream(session.1.node_id(), StreamProperties::default())], + ..Default::default() + })); + } + drop(cast_sessions); + + let locked_sessions = SESSIONS.lock().await; + let Some(index) = locked_sessions.iter().position(|this_session| this_session.handle_path == session_handle.clone().into()) else { + tracing::warn!("No session is created or it is removed"); + return Ok(PortalResponse::Other); + }; + + let current_session = locked_sessions[index].clone(); + if current_session.session_type != SessionType::Remote { + return Ok(PortalResponse::Other); + } + drop(locked_sessions); + + // TODO: use slurp now + let show_cursor = current_session.cursor_mode.show_cursor(); + let connection = libwayshot::WayshotConnection::new().unwrap(); + let outputs = connection.get_all_outputs(); + let slurp = std::process::Command::new("slurp") + .arg("-o") + .output() + .map_err(|_| zbus::Error::Failure("Cannot find slurp".to_string()))? + .stdout; + let output = String::from_utf8_lossy(&slurp); + let output = output + .split(' ') + .next() + .ok_or(zbus::Error::Failure("Not get slurp area".to_string()))?; + + let point: Vec<&str> = output.split(',').collect(); + let x: i32 = point[0] + .parse() + .map_err(|_| zbus::Error::Failure("X is not correct".to_string()))?; + let y: i32 = point[1] + .parse() + .map_err(|_| zbus::Error::Failure("Y is not correct".to_string()))?; + + let Some(output) = outputs + .iter() + .find(|output| output.dimensions.x == x && output.dimensions.y == y) + else { + return Ok(PortalResponse::Other); + }; + + let cast_thread = ScreencastThread::start_cast( + show_cursor, + output.mode.width as u32, + output.mode.height as u32, + None, + output.wl_output.clone(), + ) + .await + .map_err(|e| zbus::Error::Failure(format!("cannot start pipewire stream, error: {e}")))?; + + let node_id = cast_thread.node_id(); + + append_remote_session((session_handle.to_string(), cast_thread)).await; + + Ok(PortalResponse::Success(RemoteStartReturnValue { + streams: vec![Stream(node_id, StreamProperties::default())], + ..Default::default() + })) + } +} diff --git a/src/screencast.rs b/src/screencast.rs index f56e04f..a7e233c 100644 --- a/src/screencast.rs +++ b/src/screencast.rs @@ -14,7 +14,9 @@ use tokio::sync::Mutex; use crate::pipewirethread::ScreencastThread; use crate::request::RequestInterface; -use crate::session::{append_session, CursorMode, PersistMode, Session, SourceType, SESSIONS}; +use crate::session::{ + append_session, CursorMode, PersistMode, Session, SessionType, SourceType, SESSIONS, +}; use crate::PortalResponse; #[derive(SerializeDict, DeserializeDict, Type, Debug, Default)] @@ -124,7 +126,7 @@ impl ScreenCastBackend { }, ) .await?; - let current_session = Session::new(session_handle.clone()); + let current_session = Session::new(session_handle.clone(), SessionType::ScreenCast); append_session(current_session.clone()).await; server.at(session_handle.clone(), current_session).await?; Ok(PortalResponse::Success(SessionCreateResult { @@ -147,6 +149,9 @@ impl ScreenCastBackend { tracing::warn!("No session is created or it is removed"); return Ok(PortalResponse::Other); }; + if locked_sessions[index].session_type != SessionType::ScreenCast { + return Ok(PortalResponse::Other); + } locked_sessions[index].set_options(options); Ok(PortalResponse::Success(HashMap::new())) } @@ -159,6 +164,18 @@ impl ScreenCastBackend { _parent_window: String, _options: HashMap>, ) -> zbus::fdo::Result> { + let cast_sessions = CAST_SESSIONS.lock().await; + if let Some(session) = cast_sessions + .iter() + .find(|session| session.0 == session_handle.to_string()) + { + return Ok(PortalResponse::Success(StartReturnValue { + streams: vec![Stream(session.1.node_id(), StreamProperties::default())], + ..Default::default() + })); + } + drop(cast_sessions); + let locked_sessions = SESSIONS.lock().await; let Some(index) = locked_sessions .iter() @@ -167,7 +184,11 @@ impl ScreenCastBackend { tracing::warn!("No session is created or it is removed"); return Ok(PortalResponse::Other); }; + let current_session = locked_sessions[index].clone(); + if current_session.session_type != SessionType::ScreenCast { + return Ok(PortalResponse::Other); + } drop(locked_sessions); // TODO: use slurp now diff --git a/src/session.rs b/src/session.rs index 29ff242..d3623ac 100644 --- a/src/session.rs +++ b/src/session.rs @@ -10,7 +10,10 @@ use once_cell::sync::Lazy; use std::sync::Arc; use tokio::sync::Mutex; -use crate::screencast::{remove_cast_session, SelectSourcesOptions}; +use crate::{ + remote::remove_remote_session, + screencast::{remove_cast_session, SelectSourcesOptions}, +}; pub static SESSIONS: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(Vec::new()))); @@ -29,6 +32,7 @@ pub async fn remove_session(session: &Session) { return; }; remove_cast_session(&session.handle_path.to_string()).await; + remove_remote_session(&session.handle_path.to_string()).await; sessions.remove(index); } @@ -61,12 +65,34 @@ pub enum CursorMode { Metadata = 4, } +// Remote +#[bitflags] +#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Copy, Clone, Type, Default)] +#[repr(u32)] +/// A bit flag for the possible cursor modes. +pub enum DeviceTypes { + #[default] + /// The cursor is not part of the screen cast stream. + KEYBOARD = 1, + /// The cursor is embedded as part of the stream buffers. + POINTER = 2, + /// The cursor is not part of the screen cast stream, but sent as PipeWire + /// stream metadata. + TOUCHSCREEN = 4, +} + impl CursorMode { pub fn show_cursor(&self) -> bool { !matches!(self, CursorMode::Hidden) } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SessionType { + ScreenCast, + Remote, +} + #[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone, Type)] #[repr(u32)] /// Persistence mode for a screencast session. @@ -83,21 +109,26 @@ pub enum PersistMode { #[derive(Debug, Clone)] // TODO: when is remote? pub struct Session { + pub session_type: SessionType, pub handle_path: OwnedObjectPath, pub source_type: BitFlags, pub multiple: bool, pub cursor_mode: CursorMode, pub persist_mode: PersistMode, + + pub device_type: BitFlags, } impl Session { - pub fn new>(path: P) -> Self { + pub fn new>(path: P, session_type: SessionType) -> Self { Self { + session_type, handle_path: path.into(), source_type: SourceType::Monitor.into(), multiple: false, cursor_mode: CursorMode::Hidden, persist_mode: PersistMode::DoNot, + device_type: DeviceTypes::KEYBOARD.into(), } } pub fn set_options(&mut self, options: SelectSourcesOptions) { @@ -112,6 +143,10 @@ impl Session { self.persist_mode = persist_mode; } } + + pub fn set_device_type(&mut self, device_type: BitFlags) { + self.device_type = device_type; + } } #[dbus_interface(name = "org.freedesktop.impl.portal.Session")] From 168c67a6af340927902246be5638fbe9a8fbe9fd Mon Sep 17 00:00:00 2001 From: ShootingStarDragons Date: Fri, 25 Aug 2023 09:43:33 +0800 Subject: [PATCH 2/6] chore: format --- src/remote.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/remote.rs b/src/remote.rs index 93bea5d..749f8ea 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -61,7 +61,8 @@ pub async fn remove_remote_session(path: &str) { let mut sessions = CAST_SESSIONS.lock().await; let Some(index) = sessions .iter() - .position(|the_session| the_session.0 == path) else { + .position(|the_session| the_session.0 == path) + else { return; }; sessions[index].1.stop(); @@ -120,7 +121,10 @@ impl RemoteBackend { options: SelectSourcesOptions, ) -> zbus::fdo::Result>> { let mut locked_sessions = SESSIONS.lock().await; - let Some(index) = locked_sessions.iter().position(|this_session| this_session.handle_path == session_handle.clone().into()) else { + let Some(index) = locked_sessions + .iter() + .position(|this_session| this_session.handle_path == session_handle.clone().into()) + else { tracing::warn!("No session is created or it is removed"); return Ok(PortalResponse::Other); }; @@ -153,7 +157,10 @@ impl RemoteBackend { drop(cast_sessions); let locked_sessions = SESSIONS.lock().await; - let Some(index) = locked_sessions.iter().position(|this_session| this_session.handle_path == session_handle.clone().into()) else { + let Some(index) = locked_sessions + .iter() + .position(|this_session| this_session.handle_path == session_handle.clone().into()) + else { tracing::warn!("No session is created or it is removed"); return Ok(PortalResponse::Other); }; @@ -190,7 +197,7 @@ impl RemoteBackend { let Some(output) = outputs .iter() .find(|output| output.dimensions.x == x && output.dimensions.y == y) - else { + else { return Ok(PortalResponse::Other); }; From 6a5122bdae14bebd4698f79554a7c34259845297 Mon Sep 17 00:00:00 2001 From: ShootingStarDragons Date: Sat, 26 Aug 2023 17:39:52 +0800 Subject: [PATCH 3/6] chore: base remote --- Cargo.lock | 30 ++++++ Cargo.toml | 19 ++++ src/remote.rs | 192 ++++++++++++++++++++++++++++++++++-- src/remote/dispatch.rs | 165 +++++++++++++++++++++++++++++++ src/remote/remote_thread.rs | 115 +++++++++++++++++++++ src/remote/state.rs | 114 +++++++++++++++++++++ src/session.rs | 8 +- 7 files changed, 629 insertions(+), 14 deletions(-) create mode 100644 src/remote/dispatch.rs create mode 100644 src/remote/remote_thread.rs create mode 100644 src/remote/state.rs diff --git a/Cargo.lock b/Cargo.lock index 1c9e83c..ad9f911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4092,6 +4092,19 @@ dependencies = [ "wayland-scanner 0.30.1", ] +[[package]] +name = "wayland-protocols-misc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897d4e99645e1ed9245e9e6b5efa78828d2b23b661016d63d55251243d812f8b" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend", + "wayland-client 0.30.2", + "wayland-protocols 0.30.1", + "wayland-scanner 0.30.1", +] + [[package]] name = "wayland-protocols-wlr" version = "0.1.0" @@ -4521,10 +4534,17 @@ dependencies = [ "serde_repr", "slint", "slint-build", + "tempfile", + "thiserror", "tokio", "tracing", "tracing-subscriber", "url", + "wayland-client 0.30.2", + "wayland-protocols 0.30.1", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "xkbcommon", "zbus", ] @@ -4538,6 +4558,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "xkbcommon" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52db25b599e92bf6e3904134618728eeb7b49a5a4f38f107f92399bb9c496b88" +dependencies = [ + "libc", + "memmap2 0.7.1", +] + [[package]] name = "xml-rs" version = "0.8.16" diff --git a/Cargo.toml b/Cargo.toml index 3b0c90a..ccce008 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,24 @@ libwayshot = { git = "https://github.com/waycrate/wayshot.git" } libspa_sys = { package = "libspa-sys", git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs" } rustix = { version = "0.38.8", features = ["fs", "use-libc"] } +# REMOTE +wayland-protocols = { version = "0.30.0", default-features = false, features = [ + "unstable", + "client", +] } +#wayland-protocols = { version = "=0.30.0-beta.13", features = ["client", "unstable"] } + + +wayland-protocols-wlr = { version = "0.1.0", default-features = false, features = [ + "client", +] } +wayland-client = { version = "0.30.2" } + +wayland-protocols-misc = { version = "0.1.0", features = ["client"] } +xkbcommon = "0.5.0" +tempfile = "3.5.0" +thiserror = "1.0.47" + + [build-dependencies] slint-build = "1.1.0" diff --git a/src/remote.rs b/src/remote.rs index 749f8ea..d9f1671 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -1,3 +1,9 @@ +mod dispatch; +mod remote_thread; +mod state; + +use remote_thread::RemoteControl; + use std::collections::HashMap; use enumflags2::BitFlags; @@ -18,6 +24,8 @@ use crate::PortalResponse; use crate::screencast::SelectSourcesOptions; +use self::remote_thread::KeyOrPointerRequest; + #[derive(SerializeDict, DeserializeDict, Type, Debug, Default)] /// Specified options for a [`Screencast::create_session`] request. #[zvariant(signature = "dict")] @@ -48,17 +56,17 @@ struct RemoteStartReturnValue { clipboard_enabled: bool, } -pub type RemoteSessionData = (String, ScreencastThread); -pub static CAST_SESSIONS: Lazy>>> = +pub type RemoteSessionData = (String, ScreencastThread, RemoteControl); +pub static REMOTE_SESSIONS: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(Vec::new()))); pub async fn append_remote_session(session: RemoteSessionData) { - let mut sessions = CAST_SESSIONS.lock().await; + let mut sessions = REMOTE_SESSIONS.lock().await; sessions.push(session) } pub async fn remove_remote_session(path: &str) { - let mut sessions = CAST_SESSIONS.lock().await; + let mut sessions = REMOTE_SESSIONS.lock().await; let Some(index) = sessions .iter() .position(|the_session| the_session.0 == path) @@ -66,6 +74,7 @@ pub async fn remove_remote_session(path: &str) { return; }; sessions[index].1.stop(); + sessions[index].2.stop(); tracing::info!("session {} is stopped", sessions[index].0); sessions.remove(index); } @@ -81,7 +90,7 @@ impl RemoteBackend { #[dbus_interface(property)] fn available_device_types(&self) -> u32 { - (DeviceTypes::KEYBOARD | DeviceTypes::POINTER).bits() + (DeviceTypes::Keyboard | DeviceTypes::Pointer).bits() } async fn create_session( @@ -132,7 +141,7 @@ impl RemoteBackend { return Ok(PortalResponse::Other); } locked_sessions[index].set_options(options); - locked_sessions[index].set_device_type(DeviceTypes::KEYBOARD | DeviceTypes::POINTER); + locked_sessions[index].set_device_type(DeviceTypes::Keyboard | DeviceTypes::Pointer); Ok(PortalResponse::Success(HashMap::new())) } @@ -144,8 +153,8 @@ impl RemoteBackend { _parent_window: String, _options: HashMap>, ) -> zbus::fdo::Result> { - let cast_sessions = CAST_SESSIONS.lock().await; - if let Some(session) = cast_sessions + let remote_sessions = REMOTE_SESSIONS.lock().await; + if let Some(session) = remote_sessions .iter() .find(|session| session.0 == session_handle.to_string()) { @@ -154,7 +163,7 @@ impl RemoteBackend { ..Default::default() })); } - drop(cast_sessions); + drop(remote_sessions); let locked_sessions = SESSIONS.lock().await; let Some(index) = locked_sessions @@ -211,13 +220,176 @@ impl RemoteBackend { .await .map_err(|e| zbus::Error::Failure(format!("cannot start pipewire stream, error: {e}")))?; + let remote_control = RemoteControl::init(); let node_id = cast_thread.node_id(); - append_remote_session((session_handle.to_string(), cast_thread)).await; + append_remote_session((session_handle.to_string(), cast_thread, remote_control)).await; Ok(PortalResponse::Success(RemoteStartReturnValue { streams: vec![Stream(node_id, StreamProperties::default())], ..Default::default() })) } + + // keyboard and else + + async fn notify_pointer_motion( + &self, + session_handle: ObjectPath<'_>, + _options: HashMap>, + dx: f64, + dy: f64, + ) -> zbus::fdo::Result<()> { + let remote_sessions = REMOTE_SESSIONS.lock().await; + let Some(session) = remote_sessions + .iter() + .find(|session| session.0 == session_handle.to_string()) + else { + return Ok(()); + }; + let remote_control = &session.2; + remote_control + .sender + .send(KeyOrPointerRequest::PointerMotion { dx, dy }) + .map_err(|_| zbus::Error::Failure("Send failed".to_string()))?; + Ok(()) + } + + async fn notify_pointer_motion_absolute( + &self, + session_handle: ObjectPath<'_>, + _options: HashMap>, + _steam: u32, + x: f64, + y: f64, + ) -> zbus::fdo::Result<()> { + let remote_sessions = REMOTE_SESSIONS.lock().await; + let Some(session) = remote_sessions + .iter() + .find(|session| session.0 == session_handle.to_string()) + else { + return Ok(()); + }; + let remote_control = &session.2; + remote_control + .sender + .send(KeyOrPointerRequest::PointerMotionAbsolute { + x, + y, + x_extent: 2000, + y_extent: 2000, + }) + .map_err(|_| zbus::Error::Failure("Send failed".to_string()))?; + Ok(()) + } + + async fn notify_pointer_button( + &self, + session_handle: ObjectPath<'_>, + _options: HashMap>, + button: i32, + state: u32, + ) -> zbus::fdo::Result<()> { + let remote_sessions = REMOTE_SESSIONS.lock().await; + let Some(session) = remote_sessions + .iter() + .find(|session| session.0 == session_handle.to_string()) + else { + return Ok(()); + }; + let remote_control = &session.2; + remote_control + .sender + .send(KeyOrPointerRequest::PointerButton { button, state }) + .map_err(|_| zbus::Error::Failure("Send failed".to_string()))?; + Ok(()) + } + + async fn notify_pointer_axis( + &self, + session_handle: ObjectPath<'_>, + _options: HashMap>, + dx: f64, + dy: f64, + ) -> zbus::fdo::Result<()> { + let remote_sessions = REMOTE_SESSIONS.lock().await; + let Some(session) = remote_sessions + .iter() + .find(|session| session.0 == session_handle.to_string()) + else { + return Ok(()); + }; + let remote_control = &session.2; + remote_control + .sender + .send(KeyOrPointerRequest::PointerAxis { dx, dy }) + .map_err(|_| zbus::Error::Failure("Send failed".to_string()))?; + Ok(()) + } + + async fn notify_pointer_axix_discrate( + &self, + session_handle: ObjectPath<'_>, + _options: HashMap>, + axis: u32, + steps: i32, + ) -> zbus::fdo::Result<()> { + let remote_sessions = REMOTE_SESSIONS.lock().await; + let Some(session) = remote_sessions + .iter() + .find(|session| session.0 == session_handle.to_string()) + else { + return Ok(()); + }; + let remote_control = &session.2; + remote_control + .sender + .send(KeyOrPointerRequest::PointerAxisDiscrate { axis, steps }) + .map_err(|_| zbus::Error::Failure("Send failed".to_string()))?; + Ok(()) + } + + async fn notify_keyboard_keycode( + &self, + session_handle: ObjectPath<'_>, + _options: HashMap>, + keycode: i32, + state: u32, + ) -> zbus::fdo::Result<()> { + let remote_sessions = REMOTE_SESSIONS.lock().await; + let Some(session) = remote_sessions + .iter() + .find(|session| session.0 == session_handle.to_string()) + else { + return Ok(()); + }; + let remote_control = &session.2; + remote_control + .sender + .send(KeyOrPointerRequest::KeyboardKeycode { keycode, state }) + .map_err(|_| zbus::Error::Failure("Send failed".to_string()))?; + Ok(()) + } + + async fn notify_keyboard_keysym( + &self, + session_handle: ObjectPath<'_>, + _options: HashMap>, + keysym: i32, + state: u32, + ) -> zbus::fdo::Result<()> { + let remote_sessions = REMOTE_SESSIONS.lock().await; + let Some(session) = remote_sessions + .iter() + .find(|session| session.0 == session_handle.to_string()) + else { + return Ok(()); + }; + let remote_control = &session.2; + remote_control + .sender + .send(KeyOrPointerRequest::KeyboardKeysym { keysym, state }) + .map_err(|_| zbus::Error::Failure("Send failed".to_string()))?; + Ok(()) + } } diff --git a/src/remote/dispatch.rs b/src/remote/dispatch.rs new file mode 100644 index 0000000..0a9a93c --- /dev/null +++ b/src/remote/dispatch.rs @@ -0,0 +1,165 @@ +use super::state::AppData; +use std::os::unix::prelude::AsRawFd; +use wayland_client::{ + protocol::{wl_keyboard, wl_registry, wl_seat::WlSeat, wl_shm::WlShm}, + Connection, Dispatch, Proxy, QueueHandle, +}; +use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::{ + zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1, + zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1, +}; + +use std::{ffi::CString, fs::File, io::Write, path::PathBuf}; +use wayland_protocols_wlr::virtual_pointer::v1::client::{ + zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1, + zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1, +}; +use xkbcommon::xkb; +pub fn get_keymap_as_file() -> (File, u32) { + let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + + let keymap = xkb::Keymap::new_from_names( + &context, + "", + "", + "us", + "", + None, + xkb::KEYMAP_COMPILE_NO_FLAGS, + ) + .expect("xkbcommon keymap panicked!"); + let xkb_state = xkb::State::new(&keymap); + let keymap = xkb_state + .get_keymap() + .get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1); + let keymap = CString::new(keymap).expect("Keymap should not contain interior nul bytes"); + let keymap = keymap.as_bytes_with_nul(); + let dir = std::env::var_os("XDG_RUNTIME_DIR") + .map(PathBuf::from) + .unwrap_or_else(std::env::temp_dir); + let mut file = tempfile::tempfile_in(dir).expect("File could not be created!"); + file.write_all(keymap).unwrap(); + file.flush().unwrap(); + (file, keymap.len() as u32) +} +impl Dispatch for AppData { + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + // When receiving events from the wl_registry, we are only interested in the + // `global` event, which signals a new available global. + // When receiving this event, we just print its characteristics in this example. + if let wl_registry::Event::Global { + name, + interface, + version, + } = event + { + if interface == WlShm::interface().name { + registry.bind::(name, version, qh, ()); + } else if interface == WlSeat::interface().name { + registry.bind::(name, version, qh, ()); + } else if interface == ZwpVirtualKeyboardManagerV1::interface().name { + let virtual_keyboard_manager = + registry.bind::(name, version, qh, ()); + state.virtual_keyboard_manager = Some(virtual_keyboard_manager); + } else if interface == ZwlrVirtualPointerManagerV1::interface().name { + let virtual_pointer_manager = + registry.bind::(name, version, qh, ()); + state.virtual_pointer_manager = Some(virtual_pointer_manager); + } + } + } +} + +impl Dispatch for AppData { + fn event( + _state: &mut Self, + _proxy: &WlShm, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for AppData { + fn event( + state: &mut Self, + seat: &WlSeat, + _event: ::Event, + _data: &(), + _conn: &Connection, + qh: &QueueHandle, + ) { + if let Some(virtual_keyboard_manager) = state.virtual_keyboard_manager.as_ref() { + let virtual_keyboard = virtual_keyboard_manager.create_virtual_keyboard(seat, qh, ()); + let (file, size) = get_keymap_as_file(); + virtual_keyboard.keymap( + wl_keyboard::KeymapFormat::XkbV1.into(), + file.as_raw_fd(), + size, + ); + state.virtual_keyboard = Some(virtual_keyboard); + } + if let Some(virtual_pointer_manager) = state.virtual_pointer_manager.as_ref() { + let virtual_pointer = + virtual_pointer_manager.create_virtual_pointer(Some(seat), qh, ()); + state.virtual_pointer = Some(virtual_pointer); + } + } +} + +impl Dispatch for AppData { + fn event( + _state: &mut Self, + _proxy: &ZwlrVirtualPointerV1, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for AppData { + fn event( + _state: &mut Self, + _proxy: &ZwpVirtualKeyboardManagerV1, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for AppData { + fn event( + _state: &mut Self, + _proxy: &ZwlrVirtualPointerManagerV1, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for AppData { + fn event( + _state: &mut Self, + _proxy: &ZwpVirtualKeyboardV1, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} diff --git a/src/remote/remote_thread.rs b/src/remote/remote_thread.rs new file mode 100644 index 0000000..7ffbe98 --- /dev/null +++ b/src/remote/remote_thread.rs @@ -0,0 +1,115 @@ +use wayland_client::Connection; + +use super::state::AppData; +use super::state::KeyPointerError; + +use std::sync::mpsc::{self, Receiver, Sender}; + +#[derive(Debug)] +pub enum KeyOrPointerRequest { + PointerMotion { + dx: f64, + dy: f64, + }, + PointerMotionAbsolute { + x: f64, + y: f64, + x_extent: u32, + y_extent: u32, + }, + PointerButton { + button: i32, + state: u32, + }, + PointerAxis { + dx: f64, + dy: f64, + }, + PointerAxisDiscrate { + axis: u32, + steps: i32, + }, + KeyboardKeycode { + keycode: i32, + state: u32, + }, + KeyboardKeysym { + keysym: i32, + state: u32, + }, + Exit, +} + +#[derive(Debug)] +pub struct RemoteControl { + pub sender: Sender, +} + +impl RemoteControl { + pub fn init() -> Self { + let (sender, receiver) = mpsc::channel(); + std::thread::spawn(move || { + let _ = remote_loop(receiver); + }); + Self { sender } + } + + pub fn stop(&self) { + let _ = self.sender.send(KeyOrPointerRequest::Exit); + } +} + +pub fn remote_loop(receiver: Receiver) -> Result<(), KeyPointerError> { + // Create a Wayland connection by connecting to the server through the + // environment-provided configuration. + let conn = Connection::connect_to_env() + .map_err(|_| KeyPointerError::ConnectionError("Cannot create connection".to_string()))?; + + // Retrieve the WlDisplay Wayland object from the connection. This object is + // the starting point of any Wayland program, from which all other objects will + // be created. + let display = conn.display(); + + // Create an event queue for our event processing + let mut event_queue = conn.new_event_queue(); + // An get its handle to associated new objects to it + let qh = event_queue.handle(); + + // Create a wl_registry object by sending the wl_display.get_registry request + // This method takes two arguments: a handle to the queue the newly created + // wl_registry will be assigned to, and the user-data that should be associated + // with this registry (here it is () as we don't need user-data). + let _registry = display.get_registry(&qh, ()); + + // At this point everything is ready, and we just need to wait to receive the events + // from the wl_registry, our callback will print the advertized globals. + let data = AppData::init(&mut event_queue)?; + + while let Ok(message) = receiver.recv() { + match message { + KeyOrPointerRequest::PointerMotion { dx, dy } => data.notify_pointer_motion(dx, dy), + KeyOrPointerRequest::PointerMotionAbsolute { + x, + y, + x_extent, + y_extent, + } => data.notify_pointer_motion_absolute(x, y, x_extent, y_extent), + KeyOrPointerRequest::PointerButton { button, state } => { + data.notify_pointer_button(button, state) + } + KeyOrPointerRequest::PointerAxis { dx, dy } => data.notify_pointer_axis(dx, dy), + KeyOrPointerRequest::PointerAxisDiscrate { axis, steps } => { + data.notify_pointer_axis_discrete(axis, steps) + } + KeyOrPointerRequest::KeyboardKeycode { keycode, state } => { + data.notify_keyboard_keycode(keycode, state) + } + KeyOrPointerRequest::KeyboardKeysym { keysym, state } => { + data.notify_keyboard_keysym(keysym, state) + } + KeyOrPointerRequest::Exit => break, + } + } + + Ok(()) +} diff --git a/src/remote/state.rs b/src/remote/state.rs new file mode 100644 index 0000000..97631a0 --- /dev/null +++ b/src/remote/state.rs @@ -0,0 +1,114 @@ +use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::{ + zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1, + zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1, +}; + +use wayland_client::{protocol::wl_pointer, EventQueue}; +use wayland_protocols_wlr::virtual_pointer::v1::client::{ + zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1, + zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1, +}; + +use thiserror::Error; +// This struct represents the state of our app. This simple app does not +// need any state, by this type still supports the `Dispatch` implementations. +#[derive(Debug)] +pub struct AppData { + pub(crate) virtual_keyboard_manager: Option, + pub(crate) virtual_keyboard: Option, + + pub(crate) virtual_pointer_manager: Option, + pub(crate) virtual_pointer: Option, +} + +impl AppData { + fn new() -> Self { + Self { + virtual_keyboard_manager: None, + virtual_keyboard: None, + virtual_pointer_manager: None, + virtual_pointer: None, + } + } +} + +#[derive(Error, Debug)] +pub enum KeyPointerError { + #[error("Connection create Error")] + ConnectionError(String), + #[error("Error during queue")] + QueueError, +} + +impl AppData { + pub fn init(queue: &mut EventQueue) -> Result { + let mut data = AppData::new(); + while data.virtual_keyboard.is_none() || data.virtual_pointer.is_none() { + queue + .blocking_dispatch(&mut data) + .map_err(|_| KeyPointerError::QueueError)?; + } + Ok(data) + } + + pub fn notify_pointer_motion(&self, dx: f64, dy: f64) { + self.virtual_pointer.as_ref().unwrap().motion(10, dx, dy); + } + + pub fn notify_pointer_motion_absolute(&self, x: f64, y: f64, x_extent: u32, y_extent: u32) { + self.virtual_pointer + .as_ref() + .unwrap() + .motion_absolute(10, x as u32, y as u32, x_extent, y_extent); + } + + pub fn notify_pointer_button(&self, button: i32, state: u32) { + self.virtual_pointer.as_ref().unwrap().button( + 100, + button as u32, + if state == 0 { + wl_pointer::ButtonState::Pressed + } else { + wl_pointer::ButtonState::Released + }, + ); + } + + pub fn notify_pointer_axis(&self, dx: f64, dy: f64) { + self.virtual_pointer + .as_ref() + .unwrap() + .axis(100, wl_pointer::Axis::HorizontalScroll, dx); + self.virtual_pointer + .as_ref() + .unwrap() + .axis(100, wl_pointer::Axis::VerticalScroll, dy); + } + + pub fn notify_pointer_axis_discrete(&self, axis: u32, steps: i32) { + self.virtual_pointer.as_ref().unwrap().axis_discrete( + 100, + if axis == 0 { + wl_pointer::Axis::VerticalScroll + } else { + wl_pointer::Axis::HorizontalScroll + }, + 10.0, + steps, + ); + } + + pub fn notify_keyboard_keycode(&self, keycode: i32, state: u32) { + self.virtual_keyboard + .as_ref() + .unwrap() + .key(100, keycode as u32, state); + } + + pub fn notify_keyboard_keysym(&self, keysym: i32, state: u32) { + self.virtual_keyboard + .as_ref() + .unwrap() + .key(100, keysym as u32, state); + } +} diff --git a/src/session.rs b/src/session.rs index d3623ac..88e7325 100644 --- a/src/session.rs +++ b/src/session.rs @@ -73,12 +73,12 @@ pub enum CursorMode { pub enum DeviceTypes { #[default] /// The cursor is not part of the screen cast stream. - KEYBOARD = 1, + Keyboard = 1, /// The cursor is embedded as part of the stream buffers. - POINTER = 2, + Pointer = 2, /// The cursor is not part of the screen cast stream, but sent as PipeWire /// stream metadata. - TOUCHSCREEN = 4, + TouchScreen = 4, } impl CursorMode { @@ -128,7 +128,7 @@ impl Session { multiple: false, cursor_mode: CursorMode::Hidden, persist_mode: PersistMode::DoNot, - device_type: DeviceTypes::KEYBOARD.into(), + device_type: DeviceTypes::Keyboard.into(), } } pub fn set_options(&mut self, options: SelectSourcesOptions) { From 7dd2e6f0a771a8758b423a982c30f2469c2b8440 Mon Sep 17 00:00:00 2001 From: ShootingStarDragons Date: Sun, 27 Aug 2023 10:32:50 +0800 Subject: [PATCH 4/6] chore: finished --- src/remote.rs | 29 +++++++++++++++++++++-------- src/screencast.rs | 5 +---- src/session.rs | 19 ++++++++++++------- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/remote.rs b/src/remote.rs index d9f1671..f4e54ff 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -19,10 +19,11 @@ use tokio::sync::Mutex; use crate::pipewirethread::ScreencastThread; use crate::request::RequestInterface; -use crate::session::{append_session, DeviceTypes, Session, SessionType, SourceType, SESSIONS}; -use crate::PortalResponse; +use crate::session::{ + append_session, DeviceType, PersistMode, Session, SessionType, SourceType, SESSIONS, +}; -use crate::screencast::SelectSourcesOptions; +use crate::PortalResponse; use self::remote_thread::KeyOrPointerRequest; @@ -52,10 +53,21 @@ struct StreamProperties { #[zvariant(signature = "dict")] struct RemoteStartReturnValue { streams: Vec, - devices: BitFlags, + devices: BitFlags, clipboard_enabled: bool, } +#[derive(SerializeDict, DeserializeDict, Type, Debug, Default)] +/// Specified options for a [`RemoteDesktop::select_devices`] request. +#[zvariant(signature = "dict")] +pub struct SelectDevicesOptions { + /// A string that will be used as the last element of the handle. + /// The device types to request remote controlling of. Default is all. + pub types: Option>, + pub restore_token: Option, + pub persist_mode: Option, +} + pub type RemoteSessionData = (String, ScreencastThread, RemoteControl); pub static REMOTE_SESSIONS: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(Vec::new()))); @@ -90,7 +102,7 @@ impl RemoteBackend { #[dbus_interface(property)] fn available_device_types(&self) -> u32 { - (DeviceTypes::Keyboard | DeviceTypes::Pointer).bits() + (DeviceType::Keyboard | DeviceType::Pointer).bits() } async fn create_session( @@ -127,7 +139,7 @@ impl RemoteBackend { _request_handle: ObjectPath<'_>, session_handle: ObjectPath<'_>, _app_id: String, - options: SelectSourcesOptions, + options: SelectDevicesOptions, ) -> zbus::fdo::Result>> { let mut locked_sessions = SESSIONS.lock().await; let Some(index) = locked_sessions @@ -140,8 +152,7 @@ impl RemoteBackend { if locked_sessions[index].session_type != SessionType::Remote { return Ok(PortalResponse::Other); } - locked_sessions[index].set_options(options); - locked_sessions[index].set_device_type(DeviceTypes::Keyboard | DeviceTypes::Pointer); + locked_sessions[index].set_remote_options(options); Ok(PortalResponse::Success(HashMap::new())) } @@ -178,6 +189,7 @@ impl RemoteBackend { if current_session.session_type != SessionType::Remote { return Ok(PortalResponse::Other); } + let device_type = current_session.device_type; drop(locked_sessions); // TODO: use slurp now @@ -227,6 +239,7 @@ impl RemoteBackend { Ok(PortalResponse::Success(RemoteStartReturnValue { streams: vec![Stream(node_id, StreamProperties::default())], + devices: device_type, ..Default::default() })) } diff --git a/src/screencast.rs b/src/screencast.rs index a7e233c..96a01fd 100644 --- a/src/screencast.rs +++ b/src/screencast.rs @@ -149,10 +149,7 @@ impl ScreenCastBackend { tracing::warn!("No session is created or it is removed"); return Ok(PortalResponse::Other); }; - if locked_sessions[index].session_type != SessionType::ScreenCast { - return Ok(PortalResponse::Other); - } - locked_sessions[index].set_options(options); + locked_sessions[index].set_screencast_options(options); Ok(PortalResponse::Success(HashMap::new())) } diff --git a/src/session.rs b/src/session.rs index 88e7325..8bba811 100644 --- a/src/session.rs +++ b/src/session.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use tokio::sync::Mutex; use crate::{ - remote::remove_remote_session, + remote::{remove_remote_session, SelectDevicesOptions}, screencast::{remove_cast_session, SelectSourcesOptions}, }; @@ -70,7 +70,7 @@ pub enum CursorMode { #[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Copy, Clone, Type, Default)] #[repr(u32)] /// A bit flag for the possible cursor modes. -pub enum DeviceTypes { +pub enum DeviceType { #[default] /// The cursor is not part of the screen cast stream. Keyboard = 1, @@ -116,7 +116,7 @@ pub struct Session { pub cursor_mode: CursorMode, pub persist_mode: PersistMode, - pub device_type: BitFlags, + pub device_type: BitFlags, } impl Session { @@ -128,10 +128,10 @@ impl Session { multiple: false, cursor_mode: CursorMode::Hidden, persist_mode: PersistMode::DoNot, - device_type: DeviceTypes::Keyboard.into(), + device_type: DeviceType::Keyboard.into(), } } - pub fn set_options(&mut self, options: SelectSourcesOptions) { + pub fn set_screencast_options(&mut self, options: SelectSourcesOptions) { if let Some(types) = options.types { self.source_type = types; } @@ -144,8 +144,13 @@ impl Session { } } - pub fn set_device_type(&mut self, device_type: BitFlags) { - self.device_type = device_type; + pub fn set_remote_options(&mut self, options: SelectDevicesOptions) { + if let Some(types) = options.types { + self.device_type = types; + } + if let Some(persist_mode) = options.persist_mode { + self.persist_mode = persist_mode; + } } } From 7b8bb03346ea8da5da508a242ac1259ceb1a7517 Mon Sep 17 00:00:00 2001 From: ShootingStarDragons Date: Sun, 27 Aug 2023 10:33:29 +0800 Subject: [PATCH 5/6] chore: finished --- misc/luminous.portal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/luminous.portal b/misc/luminous.portal index 396b7d9..58c918b 100644 --- a/misc/luminous.portal +++ b/misc/luminous.portal @@ -1,4 +1,4 @@ [portal] DBusName=org.freedesktop.impl.portal.desktop.luminous -Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast; +Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop; UseIn=wlroots;sway;Wayfire;river;phosh;Hyprland,nextwm; From 880cf3f77b887e36d9ff31bbec9682867b81a9dd Mon Sep 17 00:00:00 2001 From: ShootingStarDragons Date: Sun, 27 Aug 2023 10:43:02 +0800 Subject: [PATCH 6/6] chore: add libxkbcommon as dep --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index 2a72fc4..70829ac 100644 --- a/flake.nix +++ b/flake.nix @@ -28,6 +28,7 @@ # Libs pipewire wayland + libxkbcommon stdenv # Tools