Compare commits
13 Commits
ac188788cf
...
c863a57e4d
Author | SHA1 | Date |
---|---|---|
Marc-André Lureau | c863a57e4d | |
Marc-André Lureau | 9cd1267c55 | |
Marc-André Lureau | 1e0aa2b772 | |
Marc-André Lureau | 192ed89593 | |
Marc-André Lureau | 0a68e1e08d | |
Marc-André Lureau | 187f680599 | |
Marc-André Lureau | 726c893298 | |
Marc-André Lureau | 4395b41fd7 | |
Marc-André Lureau | fd67aa0277 | |
Marc-André Lureau | 79fa942b7a | |
Bilal Elmoussaoui | 504611a853 | |
Marc-André Lureau | 912d39dc20 | |
Marc-André Lureau | e03f2d80a6 |
|
@ -1,5 +1,4 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,9 +12,11 @@ default-members = ["qemu-rdw"]
|
|||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
tracing = "0.1"
|
||||
zbus = { version = "4", features = ["p2p"] }
|
||||
pixman-sys = "0.1"
|
||||
qemu-display = { path = "qemu-display", version = "0.1" }
|
||||
keycodemap = { path = "keycodemap", version = "0.1" }
|
||||
zbus = { version = "4", features = ["p2p"] }
|
||||
|
||||
[patch.crates-io]
|
||||
vnc = { git = "https://github.com/elmarco/rust-vnc", branch = "server" }
|
||||
|
|
|
@ -10,10 +10,10 @@ edition = "2018"
|
|||
qmp = ["dep:qapi", "dep:base64"]
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0"
|
||||
log = "0.4"
|
||||
derivative = "2.2.0"
|
||||
tracing.workspace = true
|
||||
zbus.workspace = true
|
||||
cfg-if = "1.0"
|
||||
derivative = "2.2.0"
|
||||
zvariant = { version = "4", features = ["serde_bytes"] }
|
||||
libc = "0.2.86"
|
||||
enumflags2 = { version = "0.7", features = ["serde"] }
|
||||
|
@ -24,9 +24,9 @@ futures-util = { version = "0.3.8", features = ["async-await-macro"] }
|
|||
once_cell = "1.5"
|
||||
futures = "0.3.13"
|
||||
usbredirhost = "0.0.1"
|
||||
async-broadcast = "0.3.3"
|
||||
async-trait = "0.1.48"
|
||||
async-lock = "2.3.0"
|
||||
async-broadcast = "0.7"
|
||||
async-trait = "0.1.77"
|
||||
async-lock = "3.3.0"
|
||||
qapi = { version = "0.9.0", features = ["qmp"], optional = true }
|
||||
base64 = { version = "0.13", optional = true }
|
||||
|
||||
|
@ -38,6 +38,12 @@ windows = { version = "0.43.0", features = ["Win32_Networking_WinSock", "Win32_F
|
|||
async-std = { version = "1.12.0", features = ["attributes"] }
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter" , "fmt"], default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pixman-sys.workspace = true
|
||||
async-std = { version = "1.12", features = ["attributes"] }
|
||||
tracing-subscriber = "0.3"
|
||||
rand = "0.8"
|
||||
|
||||
[[example]]
|
||||
name = 'win32-test'
|
||||
required-features = ["qmp"]
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
use std::{
|
||||
cmp,
|
||||
os::{fd::AsFd, unix::net::UnixStream},
|
||||
};
|
||||
|
||||
use serde_bytes::ByteBuf;
|
||||
use zbus::connection;
|
||||
|
||||
const WIDTH: u32 = 1024;
|
||||
const HEIGHT: u32 = 768;
|
||||
|
||||
struct VM;
|
||||
|
||||
#[zbus::interface(name = "org.qemu.Display1.VM")]
|
||||
impl VM {
|
||||
#[zbus(property, name = "ConsoleIDs")]
|
||||
async fn console_ids(&self) -> Vec<u8> {
|
||||
vec![0]
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn interfaces(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
#[zbus(property, name = "UUID")]
|
||||
async fn uuid(&self) -> String {
|
||||
"00000000-0000-0000-0000-000000000000".into()
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn name(&self) -> &str {
|
||||
"VM Name"
|
||||
}
|
||||
}
|
||||
|
||||
#[zbus::proxy(
|
||||
interface = "org.qemu.Display1.Listener",
|
||||
default_path = "/org/qemu/Display1/Listener",
|
||||
default_service = "org.qemu"
|
||||
)]
|
||||
trait Listener {
|
||||
fn scanout(
|
||||
&self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
stride: u32,
|
||||
format: u32,
|
||||
data: serde_bytes::ByteBuf,
|
||||
) -> zbus::Result<()>;
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
x: i32,
|
||||
y: i32,
|
||||
w: i32,
|
||||
h: i32,
|
||||
stride: u32,
|
||||
format: u32,
|
||||
data: serde_bytes::ByteBuf,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
struct Console {
|
||||
task: Option<zbus::Task<()>>,
|
||||
}
|
||||
|
||||
#[zbus::interface(name = "org.qemu.Display1.Console")]
|
||||
impl Console {
|
||||
async fn register_listener(
|
||||
&mut self,
|
||||
#[zbus(connection)] conn: &zbus::Connection,
|
||||
listener: zbus::zvariant::Fd<'_>,
|
||||
) {
|
||||
let fd = listener.as_fd().try_clone_to_owned().unwrap();
|
||||
let task = conn.executor().spawn(
|
||||
async move {
|
||||
let stream = UnixStream::from(fd);
|
||||
let conn = connection::Builder::unix_stream(stream)
|
||||
.server(zbus::Guid::generate())
|
||||
.unwrap()
|
||||
.p2p()
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
let listener = ListenerProxy::new(&conn).await.unwrap();
|
||||
let format = pixman_sys::pixman_format_code_t_PIXMAN_x8r8g8b8;
|
||||
let data = vec![0u8; WIDTH as usize * HEIGHT as usize * 4];
|
||||
listener
|
||||
.scanout(WIDTH, HEIGHT, WIDTH * 4, format, ByteBuf::from(data))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut x = 0i32;
|
||||
let mut y = 0i32;
|
||||
loop {
|
||||
let w = cmp::min(256, WIDTH - x as u32) as _;
|
||||
let h = cmp::min(256, HEIGHT - y as u32) as _;
|
||||
let data: Vec<u8> = (0..w * h * 4).map(|_| rand::random()).collect();
|
||||
listener
|
||||
.update(x, y, w, h, w as u32 * 4, format, ByteBuf::from(data))
|
||||
.await
|
||||
.unwrap();
|
||||
x += 1;
|
||||
if x >= WIDTH as _ {
|
||||
x = 0;
|
||||
y += 1;
|
||||
}
|
||||
// async_std::task::sleep(core::time::Duration::from_millis(1)).await;
|
||||
}
|
||||
},
|
||||
"display",
|
||||
);
|
||||
self.task = Some(task);
|
||||
}
|
||||
|
||||
#[zbus(name = "SetUIInfo")]
|
||||
fn set_ui_info(
|
||||
&self,
|
||||
_width_mm: u16,
|
||||
_height_mm: u16,
|
||||
_xoff: i32,
|
||||
_yoff: i32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) {
|
||||
tracing::warn!(%width, %height, "set-ui");
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
fn label(&self) -> String {
|
||||
"label".into()
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
fn head(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
fn type_(&self) -> String {
|
||||
"Graphic".into()
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
fn width(&self) -> u32 {
|
||||
1024
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
fn height(&self) -> u32 {
|
||||
768
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> zbus::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let _connection = connection::Builder::session()?
|
||||
.name("org.qemu")?
|
||||
.serve_at("/org/qemu/Display1/VM", VM)?
|
||||
.serve_at("/org/qemu/Display1/Console_0", Console { task: None })?
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
loop {
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
}
|
|
@ -86,7 +86,7 @@ impl<H: ClipboardHandler> ClipboardListener<H> {
|
|||
}
|
||||
|
||||
#[derive(derivative::Derivative)]
|
||||
#[derivative(Debug)]
|
||||
#[derivative(Clone, Debug)]
|
||||
pub struct Clipboard {
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub proxy: ClipboardProxy<'static>,
|
||||
|
|
|
@ -9,7 +9,10 @@ use uds_windows::UnixStream;
|
|||
use zbus::zvariant::Fd;
|
||||
use zbus::{zvariant::ObjectPath, Connection};
|
||||
|
||||
use crate::{util, ConsoleListener, ConsoleListenerHandler, KeyboardProxy, MouseProxy, Result};
|
||||
use crate::{
|
||||
util, ConsoleListener, ConsoleListenerHandler, KeyboardProxy, MouseProxy, MultiTouchProxy,
|
||||
Result,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use crate::{
|
||||
ConsoleListenerD3d11, ConsoleListenerD3d11Handler, ConsoleListenerMap,
|
||||
|
@ -58,6 +61,8 @@ pub struct Console {
|
|||
pub keyboard: KeyboardProxy<'static>,
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub mouse: MouseProxy<'static>,
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub multi_touch: MultiTouchProxy<'static>,
|
||||
listener: RwLock<Option<Connection>>,
|
||||
#[cfg(windows)]
|
||||
peer_pid: u32,
|
||||
|
@ -72,10 +77,15 @@ impl Console {
|
|||
.build()
|
||||
.await?;
|
||||
let mouse = MouseProxy::builder(conn).path(&obj_path)?.build().await?;
|
||||
let multi_touch = MultiTouchProxy::builder(conn)
|
||||
.path(&obj_path)?
|
||||
.build()
|
||||
.await?;
|
||||
Ok(Self {
|
||||
proxy,
|
||||
keyboard,
|
||||
mouse,
|
||||
multi_touch,
|
||||
listener: RwLock::new(None),
|
||||
#[cfg(windows)]
|
||||
peer_pid,
|
||||
|
@ -94,6 +104,7 @@ impl Console {
|
|||
Ok(self.proxy.height().await?)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, handler))]
|
||||
pub async fn register_listener<H: ConsoleListenerHandler>(&self, handler: H) -> Result<()> {
|
||||
let (p0, p1) = UnixStream::pair()?;
|
||||
let p0 = util::prepare_uds_pass(
|
||||
|
|
|
@ -36,6 +36,9 @@ pub use mouse::*;
|
|||
mod display;
|
||||
pub use display::*;
|
||||
|
||||
mod multi_touch;
|
||||
pub use multi_touch::*;
|
||||
|
||||
#[cfg(unix)]
|
||||
mod usbredir;
|
||||
#[cfg(unix)]
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use zbus::zvariant::Type;
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Type, Debug, PartialEq, Copy, Clone, Eq, Serialize, Deserialize)]
|
||||
pub enum TouchEventKind {
|
||||
Begin = 0,
|
||||
Update = 1,
|
||||
End = 2,
|
||||
Cancel = 3,
|
||||
}
|
||||
|
||||
#[zbus::proxy(
|
||||
default_service = "org.qemu",
|
||||
interface = "org.qemu.Display1.MultiTouch"
|
||||
)]
|
||||
pub trait MultiTouch {
|
||||
fn send_event(&self, kind: TouchEventKind, num_slot: u64, x: f64, y: f64) -> zbus::Result<()>;
|
||||
|
||||
#[dbus_proxy(property)]
|
||||
fn max_slots(&self) -> zbus::Result<i32>;
|
||||
}
|
|
@ -8,10 +8,11 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
qemu-display = { path = "../qemu-display" }
|
||||
tracing = "0.1.37"
|
||||
tracing.workspace = true
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
keycodemap = { path = "../keycodemap" }
|
||||
bytes = "1.4"
|
||||
pixman-sys = "0.1.0"
|
||||
rustls = { version = "0.21" }
|
||||
rustls-pemfile = "1.0"
|
||||
tokio = { version = "1.28", features = ["full"] }
|
||||
|
@ -19,4 +20,4 @@ tokio-rustls = "0.24"
|
|||
anyhow = "1.0"
|
||||
clap = { version = "4.2", features = ["derive", "cargo"] }
|
||||
async-trait = "0.1"
|
||||
ironrdp = { git = "https://github.com/Devolutions/IronRDP", features = ["server"] }
|
||||
ironrdp = { git = "https://github.com/Devolutions/IronRDP", features = ["server", "svc", "cliprdr"] }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use clap::clap_derive::ValueEnum;
|
||||
use clap::{crate_name, Parser};
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
pub enum SecurityProtocol {
|
||||
|
@ -43,9 +43,6 @@ pub struct ServerArgs {
|
|||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Args {
|
||||
#[clap(short, long, value_parser, default_value_t = format!("{}.log", crate_name!()))]
|
||||
pub log_file: String,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub server: ServerArgs,
|
||||
|
||||
|
|
|
@ -3,12 +3,13 @@ use qemu_display::zbus;
|
|||
|
||||
mod args;
|
||||
mod server;
|
||||
mod util;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
let mut args = args::parse();
|
||||
|
||||
setup_logging(args.log_file.as_str()).context("unable to initialize logging")?;
|
||||
setup_logging().context("unable to initialize logging")?;
|
||||
|
||||
let dbus = match args.dbus_address.take() {
|
||||
None => zbus::Connection::session().await,
|
||||
|
@ -25,27 +26,16 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_logging(log_file: &str) -> anyhow::Result<()> {
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
fn setup_logging() -> anyhow::Result<()> {
|
||||
use tracing::metadata::LevelFilter;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_file)
|
||||
.with_context(|| format!("couldn’t open {log_file}"))?;
|
||||
|
||||
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
.with_ansi(false)
|
||||
.with_writer(file);
|
||||
let fmt_layer = tracing_subscriber::fmt::layer().compact();
|
||||
|
||||
let env_filter = EnvFilter::builder()
|
||||
.with_default_directive(LevelFilter::WARN.into())
|
||||
.with_env_var("QEMURDP_LOG_LEVEL")
|
||||
.with_env_var("QEMURDP_LOG")
|
||||
.from_env_lossy();
|
||||
|
||||
tracing_subscriber::registry()
|
||||
|
|
|
@ -0,0 +1,445 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::Result;
|
||||
use ironrdp::{
|
||||
cliprdr::{
|
||||
backend::{ClipboardMessage, CliprdrBackend, CliprdrBackendFactory},
|
||||
pdu::{
|
||||
ClipboardFormat, ClipboardFormatId, ClipboardGeneralCapabilityFlags,
|
||||
FileContentsRequest, FileContentsResponse, FormatDataRequest, FormatDataResponse,
|
||||
LockDataId, OwnedFormatDataResponse,
|
||||
},
|
||||
},
|
||||
pdu::{
|
||||
cursor::ReadCursor,
|
||||
utils::{read_string_from_cursor, CharacterSet},
|
||||
},
|
||||
server::{CliprdrServerFactory, ServerEvent, ServerEventSender},
|
||||
svc::impl_as_any,
|
||||
};
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use qemu_display::{zbus, Clipboard, ClipboardSelection};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc::{self, Receiver, Sender},
|
||||
oneshot,
|
||||
},
|
||||
task,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Inner {
|
||||
clipboard: Clipboard,
|
||||
tx: Sender<ClipboardEvent>,
|
||||
selection: ClipboardSelection,
|
||||
serial: u32,
|
||||
ev_sender: Option<mpsc::UnboundedSender<ServerEvent>>,
|
||||
dbus_request: Option<(oneshot::Sender<Vec<u8>>, ClipboardFormatId)>,
|
||||
_task: Option<task::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClipboardHandler {
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl qemu_display::ClipboardHandler for ClipboardHandler {
|
||||
async fn register(&mut self) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
inner.serial = 0;
|
||||
inner.dbus_request = None;
|
||||
}
|
||||
|
||||
async fn unregister(&mut self) {}
|
||||
|
||||
async fn grab(&mut self, selection: ClipboardSelection, serial: u32, mimes: Vec<String>) {
|
||||
debug!(?selection, ?serial, "Grab -> RDP");
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
if selection != inner.selection {
|
||||
return;
|
||||
}
|
||||
|
||||
if serial < inner.serial {
|
||||
warn!(?serial, ?inner.serial, "discarding");
|
||||
return;
|
||||
}
|
||||
|
||||
inner.serial = serial;
|
||||
|
||||
let format_list = mimes
|
||||
.iter()
|
||||
.filter_map(|f| match f.as_str() {
|
||||
"text/plain;charset=utf-8" => {
|
||||
let cf = ClipboardFormat::new(ClipboardFormatId::CF_UNICODETEXT);
|
||||
Some(cf)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let Some(svc_sender) = inner.ev_sender.as_mut() {
|
||||
if let Err(e) = svc_sender.send(ServerEvent::Clipboard(
|
||||
ClipboardMessage::SendInitiateCopy(format_list),
|
||||
)) {
|
||||
error!(?e, "failed to send SVC message");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn release(&mut self, selection: ClipboardSelection) {
|
||||
debug!(?selection, "Release -> RDP");
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
if selection != inner.selection {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(svc_sender) = inner.ev_sender.as_mut() {
|
||||
if let Err(e) = svc_sender.send(ServerEvent::Clipboard(
|
||||
ClipboardMessage::SendInitiateCopy(vec![]),
|
||||
)) {
|
||||
error!(?e, "failed to send SVC message");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn request(
|
||||
&mut self,
|
||||
selection: ClipboardSelection,
|
||||
mimes: Vec<String>,
|
||||
) -> qemu_display::Result<(String, Vec<u8>)> {
|
||||
debug!(?selection, ?mimes, "Request -> RDP");
|
||||
|
||||
let (rx, format) = {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
if selection != inner.selection {
|
||||
return Err(qemu_display::Error::Failed(
|
||||
"Unhandled clipboard selection".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// since we are blocking on Request method handling, we shouldn't get there again
|
||||
if inner.dbus_request.is_some() {
|
||||
return Err(qemu_display::Error::Failed(
|
||||
"Pending clipboard request!".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let format = {
|
||||
let mut format_found = None;
|
||||
for mime in mimes.iter() {
|
||||
match mime.as_str() {
|
||||
"text/plain;charset=utf-8" => {
|
||||
format_found = Some(ClipboardFormatId::CF_UNICODETEXT);
|
||||
break;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
format_found
|
||||
};
|
||||
|
||||
let Some(format) = format else {
|
||||
return Err(qemu_display::Error::Failed("Unhandled MIMEs".into()));
|
||||
};
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
inner.dbus_request = Some((tx, format));
|
||||
if let Some(svc_sender) = inner.ev_sender.as_mut() {
|
||||
if let Err(e) = svc_sender.send(ServerEvent::Clipboard(
|
||||
ClipboardMessage::SendInitiatePaste(format),
|
||||
)) {
|
||||
error!(?e, "failed to send SVC message");
|
||||
}
|
||||
}
|
||||
|
||||
(rx, format)
|
||||
};
|
||||
|
||||
let data = rx
|
||||
.await
|
||||
.map_err(|_| qemu_display::Error::Failed("Failed to get clipboard data".into()))?;
|
||||
|
||||
let data = match format {
|
||||
ClipboardFormatId::CF_UNICODETEXT => {
|
||||
let mut cursor = ReadCursor::new(&data);
|
||||
let s = read_string_from_cursor(&mut cursor, CharacterSet::Unicode, true)
|
||||
.map_err(|_| qemu_display::Error::Failed("Failed to convert string".into()))?;
|
||||
s.into_bytes()
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
Ok(("text/plain;charset=utf-8".into(), data))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ClipboardEvent {
|
||||
Register,
|
||||
Grab {
|
||||
selection: ClipboardSelection,
|
||||
serial: u32,
|
||||
available_formats: Vec<ClipboardFormat>,
|
||||
},
|
||||
Release {
|
||||
selection: ClipboardSelection,
|
||||
},
|
||||
Request {
|
||||
selection: ClipboardSelection,
|
||||
request: FormatDataRequest,
|
||||
},
|
||||
}
|
||||
|
||||
async fn rdp_clipboard_receive_task(mut rx: Receiver<ClipboardEvent>, cb: ClipboardHandler) {
|
||||
let clipboard = {
|
||||
let inner = cb.inner.lock().unwrap();
|
||||
inner.clipboard.clone()
|
||||
};
|
||||
|
||||
loop {
|
||||
let res = match rx.recv().await {
|
||||
Some(ClipboardEvent::Register) => {
|
||||
debug!("Register -> dbus");
|
||||
|
||||
clipboard.proxy.register().await
|
||||
}
|
||||
Some(ClipboardEvent::Grab {
|
||||
selection,
|
||||
serial,
|
||||
available_formats,
|
||||
}) => {
|
||||
let mimes = available_formats
|
||||
.iter()
|
||||
.filter_map(|f| match f.id {
|
||||
ClipboardFormatId::CF_UNICODETEXT => {
|
||||
Some("text/plain;charset=utf-8".to_string())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
debug!(?mimes, "Grab -> dbus");
|
||||
|
||||
let mimes: Vec<&str> = mimes.iter().map(AsRef::as_ref).collect();
|
||||
|
||||
clipboard.proxy.grab(selection, serial, &mimes).await
|
||||
}
|
||||
Some(ClipboardEvent::Release { selection }) => {
|
||||
debug!("Release -> dbus");
|
||||
|
||||
clipboard.proxy.release(selection).await
|
||||
}
|
||||
Some(ClipboardEvent::Request { selection, request }) => {
|
||||
debug!(?request, "Request -> dbus");
|
||||
|
||||
let mime = match request.format {
|
||||
ClipboardFormatId::CF_UNICODETEXT => "text/plain;charset=utf-8",
|
||||
fmt => {
|
||||
debug!(?fmt, "unhandled requested format");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let res = clipboard.proxy.request(selection, &[mime]).await;
|
||||
if let Ok(res) = res {
|
||||
let data = match (request.format, res.0.as_str()) {
|
||||
(ClipboardFormatId::CF_UNICODETEXT, "text/plain;charset=utf-8") => {
|
||||
let Ok(s) = std::str::from_utf8(&res.1) else {
|
||||
error!("Invalid text data");
|
||||
continue;
|
||||
};
|
||||
OwnedFormatDataResponse::new_unicode_string(s)
|
||||
}
|
||||
(_, mime) => {
|
||||
debug!(?mime, "Unsupported data format");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut inner = cb.inner.lock().unwrap();
|
||||
if let Some(svc_sender) = inner.ev_sender.as_mut() {
|
||||
if let Err(e) = svc_sender.send(ServerEvent::Clipboard(
|
||||
ClipboardMessage::SendFormatData(data),
|
||||
)) {
|
||||
error!(?e, "failed to send SVC message");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(?res, "Request dbus reply");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => break,
|
||||
};
|
||||
|
||||
if let Err(e) = res {
|
||||
error!(?e, "input handling error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn register(&mut self) {
|
||||
if let Err(e) = self.tx.try_send(ClipboardEvent::Register) {
|
||||
error!(?e, "clipboard register error");
|
||||
}
|
||||
}
|
||||
|
||||
fn grab(&mut self, available_formats: Vec<ClipboardFormat>) {
|
||||
if let Err(e) = self.tx.try_send(ClipboardEvent::Grab {
|
||||
selection: self.selection,
|
||||
serial: self.serial,
|
||||
available_formats,
|
||||
}) {
|
||||
error!(?e, "clipboard grab error");
|
||||
} else {
|
||||
self.serial += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn release(&mut self) {
|
||||
if let Err(e) = self.tx.try_send(ClipboardEvent::Release {
|
||||
selection: self.selection,
|
||||
}) {
|
||||
error!(?e, "clipboard release error");
|
||||
}
|
||||
}
|
||||
|
||||
fn request(&mut self, request: FormatDataRequest) {
|
||||
if let Err(e) = self.tx.try_send(ClipboardEvent::Request {
|
||||
selection: self.selection,
|
||||
request,
|
||||
}) {
|
||||
error!(?e, "clipboard request error");
|
||||
}
|
||||
}
|
||||
|
||||
fn data(&mut self, data: Vec<u8>) {
|
||||
debug!("Data -> dbus");
|
||||
|
||||
if let Some((data_tx, _fmt)) = self.dbus_request.take() {
|
||||
if let Err(e) = data_tx.send(data) {
|
||||
error!(?e, "failed to send clipboard data to dbus");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClipboardHandler {
|
||||
pub async fn connect(dbus: zbus::Connection) -> Result<Self> {
|
||||
let clipboard = Clipboard::new(&dbus).await?;
|
||||
let selection = ClipboardSelection::Clipboard;
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(30);
|
||||
|
||||
let inner = Arc::new(Mutex::new(Inner {
|
||||
tx,
|
||||
selection,
|
||||
serial: 0,
|
||||
clipboard,
|
||||
ev_sender: None,
|
||||
dbus_request: None,
|
||||
_task: None,
|
||||
}));
|
||||
|
||||
let s = Self { inner };
|
||||
let clone = s.clone();
|
||||
let _task = task::spawn(async move { rdp_clipboard_receive_task(rx, clone).await });
|
||||
s.inner.lock().unwrap()._task = Some(_task);
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
// impl Server
|
||||
impl ServerEventSender for ClipboardHandler {
|
||||
fn set_sender(&mut self, sender: mpsc::UnboundedSender<ServerEvent>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
inner.ev_sender = Some(sender);
|
||||
}
|
||||
}
|
||||
|
||||
impl CliprdrServerFactory for ClipboardHandler {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RDPCliprdrBackend {
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
}
|
||||
|
||||
impl_as_any!(RDPCliprdrBackend);
|
||||
|
||||
impl CliprdrBackendFactory for ClipboardHandler {
|
||||
fn build_cliprdr_backend(&self) -> Box<dyn CliprdrBackend> {
|
||||
Box::new(RDPCliprdrBackend {
|
||||
inner: self.inner.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CliprdrBackend for RDPCliprdrBackend {
|
||||
fn temporary_directory(&self) -> &str {
|
||||
".cliprdr"
|
||||
}
|
||||
|
||||
fn client_capabilities(&self) -> ClipboardGeneralCapabilityFlags {
|
||||
self.inner.lock().unwrap().register();
|
||||
|
||||
// No additional capabilities yet
|
||||
ClipboardGeneralCapabilityFlags::empty()
|
||||
}
|
||||
|
||||
fn on_process_negotiated_capabilities(
|
||||
&mut self,
|
||||
capabilities: ClipboardGeneralCapabilityFlags,
|
||||
) {
|
||||
debug!(?capabilities);
|
||||
}
|
||||
|
||||
fn on_remote_copy(&mut self, available_formats: &[ClipboardFormat]) {
|
||||
debug!(?available_formats);
|
||||
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if available_formats.is_empty() {
|
||||
inner.release();
|
||||
} else {
|
||||
inner.grab(available_formats.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn on_format_data_request(&mut self, request: FormatDataRequest) {
|
||||
debug!(?request);
|
||||
|
||||
self.inner.lock().unwrap().request(request);
|
||||
}
|
||||
|
||||
fn on_format_data_response(&mut self, response: FormatDataResponse<'_>) {
|
||||
debug!(?response);
|
||||
|
||||
self.inner.lock().unwrap().data(response.into_data().into());
|
||||
}
|
||||
|
||||
fn on_file_contents_request(&mut self, request: FileContentsRequest) {
|
||||
debug!(?request);
|
||||
}
|
||||
|
||||
fn on_file_contents_response(&mut self, response: FileContentsResponse<'_>) {
|
||||
debug!(?response);
|
||||
}
|
||||
|
||||
fn on_lock(&mut self, data_id: LockDataId) {
|
||||
debug!(?data_id);
|
||||
}
|
||||
|
||||
fn on_unlock(&mut self, data_id: LockDataId) {
|
||||
debug!(?data_id);
|
||||
}
|
||||
|
||||
fn on_request_format_list(&mut self) {
|
||||
debug!("on_request_format_list");
|
||||
}
|
||||
}
|
|
@ -1,23 +1,47 @@
|
|||
use anyhow::Result;
|
||||
use ironrdp::connector::DesktopSize;
|
||||
use qemu_display::{zbus, Console, ConsoleListenerHandler, Cursor, MouseSet, Scanout, Update};
|
||||
|
||||
use ironrdp::server::{BitmapUpdate, DisplayUpdate, PixelFormat, PixelOrder, RdpServerDisplay};
|
||||
use ironrdp::connector::DesktopSize;
|
||||
use ironrdp::server::{
|
||||
BitmapUpdate, DisplayUpdate, PixelOrder, RGBAPointer, RdpServerDisplay, RdpServerDisplayUpdates,
|
||||
};
|
||||
|
||||
use crate::{cast, util::PixmanFormat};
|
||||
|
||||
pub struct DisplayHandler {
|
||||
console: Console,
|
||||
}
|
||||
|
||||
struct DisplayUpdates {
|
||||
receiver: tokio::sync::mpsc::Receiver<DisplayUpdate>,
|
||||
}
|
||||
|
||||
impl DisplayHandler {
|
||||
pub async fn connect(dbus: zbus::Connection) -> Result<Self> {
|
||||
let (sender, receiver) = tokio::sync::mpsc::channel::<DisplayUpdate>(32);
|
||||
let listener = Listener::new(sender);
|
||||
|
||||
let console = Console::new(&dbus, 0).await?;
|
||||
console.register_listener(listener).await?;
|
||||
|
||||
Ok(Self { console, receiver })
|
||||
Ok(Self { console })
|
||||
}
|
||||
|
||||
async fn listen(&self) -> Result<DisplayUpdates> {
|
||||
let (sender, receiver) = tokio::sync::mpsc::channel::<DisplayUpdate>(32);
|
||||
let (width, height) = (
|
||||
self.console.width().await? as _,
|
||||
self.console.height().await? as _,
|
||||
);
|
||||
let desktop_size = DesktopSize { width, height };
|
||||
let listener = Listener::new(sender, desktop_size);
|
||||
self.console.unregister_listener();
|
||||
self.console.register_listener(listener).await?;
|
||||
|
||||
Ok(DisplayUpdates { receiver })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RdpServerDisplayUpdates for DisplayUpdates {
|
||||
async fn next_update(&mut self) -> Option<DisplayUpdate> {
|
||||
self.receiver.recv().await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,18 +53,24 @@ impl RdpServerDisplay for DisplayHandler {
|
|||
DesktopSize { height, width }
|
||||
}
|
||||
|
||||
async fn get_update(&mut self) -> Option<DisplayUpdate> {
|
||||
self.receiver.recv().await
|
||||
async fn updates(&mut self) -> Result<Box<dyn RdpServerDisplayUpdates>> {
|
||||
Ok(Box::new(self.listen().await?))
|
||||
}
|
||||
}
|
||||
|
||||
struct Listener {
|
||||
sender: tokio::sync::mpsc::Sender<DisplayUpdate>,
|
||||
_desktop_size: DesktopSize,
|
||||
cursor_hot: (i32, i32),
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
fn new(sender: tokio::sync::mpsc::Sender<DisplayUpdate>) -> Self {
|
||||
Self { sender }
|
||||
fn new(sender: tokio::sync::mpsc::Sender<DisplayUpdate>, _desktop_size: DesktopSize) -> Self {
|
||||
Self {
|
||||
sender,
|
||||
_desktop_size,
|
||||
cursor_hot: (0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send(&mut self, update: DisplayUpdate) {
|
||||
|
@ -53,36 +83,45 @@ impl Listener {
|
|||
#[async_trait::async_trait]
|
||||
impl ConsoleListenerHandler for Listener {
|
||||
async fn scanout(&mut self, scanout: Scanout) {
|
||||
let format = match scanout.format {
|
||||
537_004_168 => PixelFormat::BgrA32,
|
||||
_ => PixelFormat::RgbA32,
|
||||
let desktop_size = DesktopSize {
|
||||
width: cast!(scanout.width),
|
||||
height: cast!(scanout.height),
|
||||
};
|
||||
|
||||
let bitmap = DisplayUpdate::Bitmap(BitmapUpdate {
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: scanout.width,
|
||||
height: scanout.height,
|
||||
format,
|
||||
order: PixelOrder::TopToBottom,
|
||||
data: scanout.data,
|
||||
});
|
||||
tracing::debug!(?desktop_size);
|
||||
|
||||
self.send(bitmap).await;
|
||||
// if desktop_size != self.desktop_size {
|
||||
// self.desktop_size = desktop_size;
|
||||
// self.send(DisplayUpdate::Resize(desktop_size)).await;
|
||||
// }
|
||||
|
||||
self.update(Update {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: cast!(scanout.width),
|
||||
h: cast!(scanout.height),
|
||||
stride: scanout.stride,
|
||||
format: scanout.format,
|
||||
data: scanout.data,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update(&mut self, update: Update) {
|
||||
let format = match update.format {
|
||||
537_004_168 => PixelFormat::BgrA32,
|
||||
_ => PixelFormat::RgbA32,
|
||||
let Ok(format) = PixmanFormat(update.format).try_into() else {
|
||||
println!("Unhandled format {}", update.format);
|
||||
return;
|
||||
};
|
||||
|
||||
let width: u16 = cast!(update.w);
|
||||
let height: u16 = cast!(update.h);
|
||||
|
||||
let bitmap = DisplayUpdate::Bitmap(BitmapUpdate {
|
||||
// TODO: fix scary conversion
|
||||
top: update.y as u32,
|
||||
left: update.x as u32,
|
||||
width: update.w as u32,
|
||||
height: update.h as u32,
|
||||
left: cast!(update.x),
|
||||
top: cast!(update.y),
|
||||
width: cast!(width),
|
||||
height: cast!(height),
|
||||
format,
|
||||
order: PixelOrder::TopToBottom,
|
||||
data: update.data,
|
||||
|
@ -92,15 +131,72 @@ impl ConsoleListenerHandler for Listener {
|
|||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn scanout_dmabuf(&mut self, _scanout: qemu_display::ScanoutDMABUF) {}
|
||||
async fn scanout_dmabuf(&mut self, scanout: qemu_display::ScanoutDMABUF) {
|
||||
tracing::debug!(?scanout);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn update_dmabuf(&mut self, _update: qemu_display::UpdateDMABUF) {}
|
||||
async fn update_dmabuf(&mut self, update: qemu_display::UpdateDMABUF) {
|
||||
tracing::debug!(?update);
|
||||
}
|
||||
|
||||
async fn disable(&mut self) {
|
||||
tracing::debug!("disable");
|
||||
}
|
||||
|
||||
async fn mouse_set(&mut self, set: MouseSet) {
|
||||
tracing::debug!(?set);
|
||||
|
||||
// FIXME: this create weird effects on the client
|
||||
//
|
||||
// self.send(DisplayUpdate::PointerPosition(ironrdp_pdu::PointerPositionAttribute {
|
||||
// x: cast!(set.x + self.cursor_hot.0),
|
||||
// y: cast!(set.y + self.cursor_hot.1),
|
||||
// }))
|
||||
// .await;
|
||||
//
|
||||
|
||||
if set.on == 0 {
|
||||
self.send(DisplayUpdate::HidePointer).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn cursor_define(&mut self, cursor: Cursor) {
|
||||
tracing::debug!(?cursor);
|
||||
self.cursor_hot = (cursor.hot_x, cursor.hot_y);
|
||||
|
||||
fn flip_vertically(image: Vec<u8>, width: usize, height: usize) -> Vec<u8> {
|
||||
let row_length = width * 4; // 4 bytes per pixel
|
||||
let mut flipped_image = vec![0; image.len()]; // Initialize a vector for the flipped image
|
||||
|
||||
for y in 0..height {
|
||||
let source_row_start = y * row_length;
|
||||
let dest_row_start = (height - 1 - y) * row_length;
|
||||
|
||||
// Copy the whole row from the source position to the destination position
|
||||
flipped_image[dest_row_start..dest_row_start + row_length]
|
||||
.copy_from_slice(&image[source_row_start..source_row_start + row_length]);
|
||||
}
|
||||
|
||||
flipped_image
|
||||
}
|
||||
|
||||
let data = flip_vertically(cursor.data, cast!(cursor.width), cast!(cursor.height));
|
||||
|
||||
self.send(DisplayUpdate::RGBAPointer(RGBAPointer {
|
||||
width: cast!(cursor.width),
|
||||
height: cast!(cursor.height),
|
||||
hot_x: cast!(cursor.hot_x),
|
||||
hot_y: cast!(cursor.hot_y),
|
||||
data,
|
||||
}))
|
||||
.await;
|
||||
}
|
||||
|
||||
fn disconnected(&mut self) {
|
||||
tracing::debug!("console listener disconnected");
|
||||
}
|
||||
|
||||
async fn disable(&mut self) {}
|
||||
async fn mouse_set(&mut self, _set: MouseSet) {}
|
||||
async fn cursor_define(&mut self, _cursor: Cursor) {}
|
||||
fn disconnected(&mut self) {}
|
||||
fn interfaces(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
|
|
@ -1,74 +1,89 @@
|
|||
use qemu_display::{zbus, Console, KeyboardProxy, MouseButton, MouseProxy};
|
||||
use qemu_display::{zbus, Console, MouseButton};
|
||||
|
||||
use ironrdp::server::{KeyboardEvent, MouseEvent, RdpServerInputHandler};
|
||||
use tokio::{
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
task,
|
||||
};
|
||||
|
||||
pub struct InputHandler<'a> {
|
||||
pos: (u16, u16),
|
||||
mouse: MouseProxy<'a>,
|
||||
keyboard: KeyboardProxy<'a>,
|
||||
use crate::cast;
|
||||
|
||||
pub struct InputHandler {
|
||||
tx: Sender<InputEvent>,
|
||||
_task: task::JoinHandle<()>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> RdpServerInputHandler for InputHandler<'a> {
|
||||
async fn keyboard(&mut self, event: KeyboardEvent) {
|
||||
let result = match event {
|
||||
KeyboardEvent::Pressed { code, .. } => self.keyboard.press(code as u32).await,
|
||||
KeyboardEvent::Released { code, .. } => self.keyboard.release(code as u32).await,
|
||||
other => {
|
||||
eprintln!("unhandled keyboard event: {:?}", other);
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
#[derive(Debug)]
|
||||
enum InputEvent {
|
||||
Keyboard(KeyboardEvent),
|
||||
Mouse(MouseEvent),
|
||||
}
|
||||
|
||||
if let Err(e) = result {
|
||||
impl RdpServerInputHandler for InputHandler {
|
||||
fn keyboard(&mut self, event: KeyboardEvent) {
|
||||
tracing::debug!(?event);
|
||||
if let Err(e) = self.tx.try_send(InputEvent::Keyboard(event)) {
|
||||
eprintln!("keyboard error: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
async fn mouse(&mut self, event: MouseEvent) {
|
||||
let result = match event {
|
||||
MouseEvent::Move { x, y } => self.mouse_move(x, y).await,
|
||||
MouseEvent::RightPressed => self.mouse.press(MouseButton::Right).await,
|
||||
MouseEvent::RightReleased => self.mouse.release(MouseButton::Right).await,
|
||||
MouseEvent::LeftPressed => self.mouse.press(MouseButton::Left).await,
|
||||
MouseEvent::LeftReleased => self.mouse.release(MouseButton::Left).await,
|
||||
MouseEvent::VerticalScroll { value } => {
|
||||
let motion = if value > 0 {
|
||||
MouseButton::WheelUp
|
||||
} else {
|
||||
MouseButton::WheelDown
|
||||
};
|
||||
|
||||
self.mouse.press(motion).await
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
eprintln!("keyboard error: {:?}", e);
|
||||
fn mouse(&mut self, event: MouseEvent) {
|
||||
tracing::debug!(?event);
|
||||
if let Err(e) = self.tx.try_send(InputEvent::Mouse(event)) {
|
||||
eprintln!("mouse error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> InputHandler<'a> {
|
||||
pub async fn connect(dbus: zbus::Connection) -> anyhow::Result<InputHandler<'a>> {
|
||||
async fn input_receive_task(mut rx: Receiver<InputEvent>, console: Console) {
|
||||
loop {
|
||||
let res = match rx.recv().await {
|
||||
Some(InputEvent::Keyboard(ev)) => match ev {
|
||||
KeyboardEvent::Pressed { code, .. } => console.keyboard.press(code as u32).await,
|
||||
KeyboardEvent::Released { code, .. } => console.keyboard.release(code as u32).await,
|
||||
other => {
|
||||
eprintln!("unhandled keyboard event: {:?}", other);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
Some(InputEvent::Mouse(ev)) => match ev {
|
||||
MouseEvent::Move { x, y } => {
|
||||
tracing::debug!(?x, ?y);
|
||||
console.mouse.set_abs_position(cast!(x), cast!(y)).await
|
||||
}
|
||||
MouseEvent::RightPressed => console.mouse.press(MouseButton::Right).await,
|
||||
MouseEvent::RightReleased => console.mouse.release(MouseButton::Right).await,
|
||||
MouseEvent::LeftPressed => console.mouse.press(MouseButton::Left).await,
|
||||
MouseEvent::LeftReleased => console.mouse.release(MouseButton::Left).await,
|
||||
MouseEvent::VerticalScroll { value } => {
|
||||
let motion = if value > 0 {
|
||||
MouseButton::WheelUp
|
||||
} else {
|
||||
MouseButton::WheelDown
|
||||
};
|
||||
|
||||
console.mouse.press(motion).await
|
||||
}
|
||||
other => {
|
||||
eprintln!("unhandled input event: {:?}", other);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
None => break,
|
||||
};
|
||||
|
||||
if let Err(e) = res {
|
||||
eprintln!("input handling error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InputHandler {
|
||||
pub async fn connect(dbus: zbus::Connection) -> anyhow::Result<InputHandler> {
|
||||
let console = Console::new(&dbus, 0).await?;
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(30);
|
||||
let _task = task::spawn(async move { input_receive_task(rx, console).await });
|
||||
|
||||
Ok(Self {
|
||||
pos: (0, 0),
|
||||
mouse: console.mouse,
|
||||
keyboard: console.keyboard,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn mouse_move(&mut self, x: u16, y: u16) -> Result<(), zbus::Error> {
|
||||
if self.mouse.is_absolute().await.unwrap_or(true) {
|
||||
self.mouse.set_abs_position(x.into(), y.into()).await
|
||||
} else {
|
||||
let (dx, dy) = (x as i32 - self.pos.0 as i32, y as i32 - self.pos.1 as i32);
|
||||
let res = self.mouse.rel_motion(dx, dy).await;
|
||||
self.pos = (x, y);
|
||||
|
||||
res
|
||||
}
|
||||
Ok(Self { _task, tx })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
mod clipboard;
|
||||
mod display;
|
||||
mod input;
|
||||
|
||||
|
@ -12,6 +13,7 @@ use ironrdp::server::RdpServer;
|
|||
|
||||
use crate::args::ServerArgs;
|
||||
|
||||
use clipboard::ClipboardHandler;
|
||||
use display::DisplayHandler;
|
||||
use input::InputHandler;
|
||||
|
||||
|
@ -35,12 +37,14 @@ impl Server {
|
|||
|
||||
let handler = InputHandler::connect(self.dbus.clone()).await?;
|
||||
let display = DisplayHandler::connect(self.dbus.clone()).await?;
|
||||
let clipboard = ClipboardHandler::connect(self.dbus.clone()).await?;
|
||||
|
||||
let mut server = RdpServer::builder()
|
||||
.with_addr((self.args.address, self.args.port))
|
||||
.with_tls(tls.unwrap())
|
||||
.with_input_handler(handler)
|
||||
.with_display_handler(display)
|
||||
.with_cliprdr_factory(Some(Box::new(clipboard)))
|
||||
.build();
|
||||
|
||||
server.run().await
|
||||
|
@ -51,11 +55,14 @@ fn acceptor(cert_path: &str, key_path: &str) -> Result<TlsAcceptor, Error> {
|
|||
let cert = certs(&mut BufReader::new(File::open(cert_path)?))?[0].clone();
|
||||
let key = pkcs8_private_keys(&mut BufReader::new(File::open(key_path)?))?[0].clone();
|
||||
|
||||
let server_config = ServerConfig::builder()
|
||||
let mut server_config = ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(vec![rustls::Certificate(cert)], rustls::PrivateKey(key))
|
||||
.expect("bad certificate/key");
|
||||
|
||||
// This adds support for the SSLKEYLOGFILE env variable (https://wiki.wireshark.org/TLS#using-the-pre-master-secret)
|
||||
server_config.key_log = Arc::new(rustls::KeyLogFile::new());
|
||||
|
||||
Ok(TlsAcceptor::from(Arc::new(server_config)))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
use ironrdp::server::PixelFormat;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cast {
|
||||
($value:expr) => {
|
||||
match $value.try_into() {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
eprintln!("Error casting value: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) struct PixmanFormat(pub u32);
|
||||
|
||||
#[cfg(target_endian = "little")]
|
||||
impl TryFrom<PixmanFormat> for PixelFormat {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: PixmanFormat) -> Result<Self, Self::Error> {
|
||||
use pixman_sys::*;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
match value.0 {
|
||||
pixman_format_code_t_PIXMAN_x8r8g8b8 => Ok(PixelFormat::BgrX32),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_endian = "big")]
|
||||
impl TryFrom<PixmanFormat> for PixelFormat {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: PixmanFormat) -> Result<Self, Self::Error> {
|
||||
use pixman_sys::*;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
match value.0 {
|
||||
pixman_format_code_t_PIXMAN_x8r8g8b8 => Ok(PixelFormat::XRgb32),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -221,9 +221,7 @@ fn watch_clipboard(
|
|||
}
|
||||
|
||||
fn clipboard_from_selection(selection: ClipboardSelection) -> Option<(gdk::Clipboard, usize)> {
|
||||
let Some(display) = gdk::Display::default() else {
|
||||
return None;
|
||||
};
|
||||
let display = gdk::Display::default()?;
|
||||
|
||||
match selection {
|
||||
ClipboardSelection::Clipboard => Some((display.clipboard(), 0)),
|
||||
|
|
Loading…
Reference in New Issue