From 6a5122bdae14bebd4698f79554a7c34259845297 Mon Sep 17 00:00:00 2001 From: ShootingStarDragons Date: Sat, 26 Aug 2023 17:39:52 +0800 Subject: [PATCH] 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) {