mirror of
https://github.com/rustdesk/qemu-display.git
synced 2026-10-25 21:50:12 +00:00
Follow zbus API break, everything async /o\
for better or worse... Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
This commit is contained in:
1 parent
7242c46513
commit
fabfa85adc
22 files changed
+769
-894
No files matched your search
+3
-3
@@ -11,9 +11,9 @@ log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
once_cell = "1.5"
|
||||
zbus = { version = "2.0.0-beta" }
|
||||
qemu-display = { path = "../qemu-display", features = ["glib"] }
|
||||
qemu-display = { path = "../qemu-display" }
|
||||
keycodemap = { path = "../keycodemap" }
|
||||
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs" }
|
||||
rdw = { git = "https://gitlab.gnome.org/malureau/rdw.git" }
|
||||
rdw = { package = "rdw4", git = "https://gitlab.gnome.org/malureau/rdw.git" }
|
||||
futures-util = "0.3.13"
|
||||
futures = "0.3.13"
|
||||
async-trait = "0.1.48"
|
||||
+49
-52
@@ -1,60 +1,57 @@
|
||||
use std::error::Error;
|
||||
use std::result::Result;
|
||||
use std::thread;
|
||||
use std::{error::Error, result::Result};
|
||||
|
||||
use qemu_display::Audio;
|
||||
use qemu_display::{Audio, AudioOutHandler};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Handler {
|
||||
#[allow(unused)]
|
||||
audio: Audio,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Handler {
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
pub struct OutListener {
|
||||
gst: rdw::GstAudio,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AudioOutHandler for OutListener {
|
||||
async fn init(&mut self, id: u64, info: qemu_display::PCMInfo) {
|
||||
if let Err(e) = self.gst.init_out(id, &info.gst_caps()) {
|
||||
log::warn!("Failed to initialize audio stream: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
async fn fini(&mut self, id: u64) {
|
||||
self.gst.fini_out(id);
|
||||
}
|
||||
|
||||
async fn set_enabled(&mut self, id: u64, enabled: bool) {
|
||||
if let Err(e) = self.gst.set_enabled_out(id, enabled) {
|
||||
log::warn!("Failed to set enabled audio stream: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_volume(&mut self, id: u64, volume: qemu_display::Volume) {
|
||||
if let Err(e) = self.gst.set_volume_out(
|
||||
id,
|
||||
volume.mute,
|
||||
volume.volume.first().map(|v| *v as f64 / 255f64),
|
||||
) {
|
||||
log::warn!("Failed to set volume: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
async fn write(&mut self, id: u64, data: Vec<u8>) {
|
||||
if let Err(e) = self.gst.write_out(id, data) {
|
||||
log::warn!("Failed to output stream: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub async fn new(audio: Audio) -> Result<Self, Box<dyn Error>> {
|
||||
let rx = audio.listen_out().await?;
|
||||
let mut gst = rdw::GstAudio::new()?;
|
||||
|
||||
let thread = thread::spawn(move || loop {
|
||||
match rx.recv() {
|
||||
Ok(event) => {
|
||||
use qemu_display::AudioOutEvent::*;
|
||||
|
||||
match event {
|
||||
Init { id, info } => {
|
||||
if let Err(e) = gst.init_out(id, &info.gst_caps()) {
|
||||
log::warn!("Failed to initialize audio stream: {}", e);
|
||||
}
|
||||
}
|
||||
Fini { id } => {
|
||||
gst.fini_out(id);
|
||||
}
|
||||
SetEnabled { id, enabled } => {
|
||||
if let Err(e) = gst.set_enabled_out(id, enabled) {
|
||||
log::warn!("Failed to set enabled audio stream: {}", e);
|
||||
}
|
||||
}
|
||||
SetVolume { id, volume } => {
|
||||
if let Err(e) = gst.set_volume_out(
|
||||
id,
|
||||
volume.mute,
|
||||
volume.volume.first().map(|v| *v as f64 / 255f64),
|
||||
) {
|
||||
log::warn!("Failed to set volume: {}", e);
|
||||
}
|
||||
}
|
||||
Write { id, data } => {
|
||||
if let Err(e) = gst.write_out(id, data) {
|
||||
log::warn!("Failed to output stream: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => log::warn!("Audio thread error: {}", e),
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
thread: Some(thread),
|
||||
})
|
||||
pub async fn new(mut audio: Audio) -> Result<Handler, Box<dyn Error>> {
|
||||
let gst = rdw::GstAudio::new()?;
|
||||
audio.register_out_listener(OutListener { gst }).await?;
|
||||
Ok(Handler { audio })
|
||||
}
|
||||
}
|
||||
+163
-113
@@ -1,145 +1,195 @@
|
||||
use std::cell::Cell;
|
||||
use std::error::Error;
|
||||
use std::rc::Rc;
|
||||
use std::result::Result;
|
||||
|
||||
use crate::glib::{self, clone, prelude::*, SignalHandlerId, SourceId};
|
||||
use gtk::{gdk, gio, prelude::DisplayExt, prelude::*};
|
||||
use qemu_display::{
|
||||
self as qdl, AsyncClipboardProxy, Clipboard, ClipboardEvent, ClipboardSelection,
|
||||
use std::{
|
||||
error::Error,
|
||||
result::Result,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use glib::{clone, SignalHandlerId};
|
||||
use gtk::{
|
||||
gdk, gio, glib,
|
||||
prelude::{DisplayExt, *},
|
||||
};
|
||||
use qemu_display::{AsyncClipboardProxy, Clipboard, ClipboardHandler, ClipboardSelection};
|
||||
use rdw::gtk;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Handler {
|
||||
rx: SourceId,
|
||||
#[allow(unused)]
|
||||
clipboard: Clipboard,
|
||||
cb_handler: Option<SignalHandlerId>,
|
||||
cb_primary_handler: Option<SignalHandlerId>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub async fn new(ctxt: Clipboard) -> Result<Self, Box<dyn Error>> {
|
||||
let rx = ctxt
|
||||
.glib_listen()
|
||||
.await
|
||||
.expect("Failed to listen to the clipboard");
|
||||
let proxy = ctxt.proxy.clone();
|
||||
let serials = Rc::new([Cell::new(0), Cell::new(0)]);
|
||||
let current_serials = serials.clone();
|
||||
let rx = rx.attach(None, move |evt| {
|
||||
use ClipboardEvent::*;
|
||||
#[derive(Debug)]
|
||||
struct InnerHandler {
|
||||
proxy: AsyncClipboardProxy<'static>,
|
||||
serials: Arc<[AtomicU32; 2]>,
|
||||
}
|
||||
|
||||
log::debug!("Clipboard event: {:?}", evt);
|
||||
match evt {
|
||||
Register | Unregister => {
|
||||
current_serials[0].set(0);
|
||||
current_serials[1].set(0);
|
||||
}
|
||||
Grab {
|
||||
selection,
|
||||
serial,
|
||||
mimes,
|
||||
} => {
|
||||
if let Some((clipboard, idx)) = clipboard_from_selection(selection) {
|
||||
if serial < current_serials[idx].get() {
|
||||
log::debug!("Ignored peer grab: {} < {}", serial, current_serials[idx].get());
|
||||
return Continue(true);
|
||||
}
|
||||
impl InnerHandler {
|
||||
fn reset_serials(&mut self) {
|
||||
self.serials[0].store(0, Ordering::SeqCst);
|
||||
self.serials[1].store(0, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
current_serials[idx].set(serial);
|
||||
let m: Vec<_> = mimes.iter().map(|s|s.as_str()).collect();
|
||||
let p = proxy.clone();
|
||||
let content = rdw::ContentProvider::new(&m, move |mime, stream, prio| {
|
||||
log::debug!("content-provider-write: {:?}", (mime, stream));
|
||||
#[async_trait::async_trait]
|
||||
impl ClipboardHandler for InnerHandler {
|
||||
async fn register(&mut self) {
|
||||
self.reset_serials();
|
||||
}
|
||||
|
||||
let p = p.clone();
|
||||
let mime = mime.to_string();
|
||||
Some(Box::pin(clone!(@strong stream => @default-return panic!(), async move {
|
||||
match p.request(selection, &[&mime]).await {
|
||||
Ok((_, data)) => {
|
||||
let bytes = glib::Bytes::from(&data);
|
||||
stream.write_bytes_async_future(&bytes, prio).await.map(|_| ())
|
||||
}
|
||||
Err(e) => {
|
||||
let err = format!("failed to request clipboard data: {}", e);
|
||||
log::warn!("{}", err);
|
||||
Err(glib::Error::new(gio::IOErrorEnum::Failed, &err))
|
||||
}
|
||||
}
|
||||
})))
|
||||
});
|
||||
async fn unregister(&mut self) {
|
||||
self.reset_serials();
|
||||
}
|
||||
|
||||
if let Err(e) = clipboard.set_content(Some(&content)) {
|
||||
log::warn!("Failed to set clipboard grab: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Release { selection } => {
|
||||
if let Some((clipboard, _)) = clipboard_from_selection(selection) {
|
||||
// TODO: track if the outside/app changed the clipboard
|
||||
if let Err(e) = clipboard.set_content(gdk::NONE_CONTENT_PROVIDER) {
|
||||
log::warn!("Failed to release clipboard: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Request { selection, mimes, tx } => {
|
||||
if let Some((clipboard, _)) = clipboard_from_selection(selection) {
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
let m: Vec<_> = mimes.iter().map(|s|s.as_str()).collect();
|
||||
let res = clipboard.read_async_future(&m, glib::Priority::default()).await;
|
||||
log::debug!("clipboard-read: {}", res.is_ok());
|
||||
let reply = match res {
|
||||
Ok((stream, mime)) => {
|
||||
let out = gio::MemoryOutputStream::new_resizable();
|
||||
let res = out.splice_async_future(
|
||||
&stream,
|
||||
gio::OutputStreamSpliceFlags::CLOSE_SOURCE | gio::OutputStreamSpliceFlags::CLOSE_TARGET,
|
||||
glib::Priority::default()).await;
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let data = out.steal_as_bytes();
|
||||
Ok((mime.to_string(), data.as_ref().to_vec()))
|
||||
}
|
||||
Err(e) => {
|
||||
Err(qdl::Error::Failed(format!("{}", e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
Err(qdl::Error::Failed(format!("{}", e)))
|
||||
}
|
||||
};
|
||||
let _ = tx.lock().unwrap().send(reply);
|
||||
});
|
||||
}
|
||||
}
|
||||
async fn grab(&mut self, selection: ClipboardSelection, serial: u32, mimes: Vec<String>) {
|
||||
if let Some((clipboard, idx)) = clipboard_from_selection(selection) {
|
||||
let cur_serial = self.serials[idx].load(Ordering::SeqCst);
|
||||
if serial < cur_serial {
|
||||
log::debug!("Ignored peer grab: {} < {}", serial, cur_serial);
|
||||
return;
|
||||
}
|
||||
Continue(true)
|
||||
});
|
||||
|
||||
self.serials[idx].store(serial, Ordering::SeqCst);
|
||||
let m: Vec<_> = mimes.iter().map(|s| s.as_str()).collect();
|
||||
let p = self.proxy.clone();
|
||||
let content = rdw::ContentProvider::new(&m, move |mime, stream, prio| {
|
||||
log::debug!("content-provider-write: {:?}", (mime, stream));
|
||||
|
||||
let p = p.clone();
|
||||
let mime = mime.to_string();
|
||||
Some(Box::pin(
|
||||
clone!(@strong stream => @default-return panic!(), async move {
|
||||
match p.request(selection, &[&mime]).await {
|
||||
Ok((_, data)) => {
|
||||
let bytes = glib::Bytes::from(&data);
|
||||
stream.write_bytes_async_future(&bytes, prio).await.map(|_| ())
|
||||
}
|
||||
Err(e) => {
|
||||
let err = format!("failed to request clipboard data: {}", e);
|
||||
log::warn!("{}", err);
|
||||
Err(glib::Error::new(gio::IOErrorEnum::Failed, &err))
|
||||
}
|
||||
}
|
||||
}),
|
||||
))
|
||||
});
|
||||
|
||||
if let Err(e) = clipboard.set_content(Some(&content)) {
|
||||
log::warn!("Failed to set clipboard grab: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn release(&mut self, selection: ClipboardSelection) {
|
||||
if let Some((clipboard, _)) = clipboard_from_selection(selection) {
|
||||
// TODO: track if the outside/app changed the clipboard
|
||||
if let Err(e) = clipboard.set_content(gdk::NONE_CONTENT_PROVIDER) {
|
||||
log::warn!("Failed to release clipboard: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn request(
|
||||
&mut self,
|
||||
selection: ClipboardSelection,
|
||||
mimes: Vec<String>,
|
||||
) -> qemu_display::Result<(String, Vec<u8>)> {
|
||||
// we have to spawn a local future, because clipboard is not Send
|
||||
let (sender, receiver) = futures::channel::oneshot::channel();
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
let res = if let Some((clipboard, _)) = clipboard_from_selection(selection) {
|
||||
let m: Vec<_> = mimes.iter().map(|s| s.as_str()).collect();
|
||||
let res = clipboard
|
||||
.read_async_future(&m, glib::Priority::default())
|
||||
.await;
|
||||
log::debug!("clipboard-read: {}", res.is_ok());
|
||||
match res {
|
||||
Ok((stream, mime)) => {
|
||||
let out = gio::MemoryOutputStream::new_resizable();
|
||||
let res = out
|
||||
.splice_async_future(
|
||||
&stream,
|
||||
gio::OutputStreamSpliceFlags::CLOSE_SOURCE
|
||||
| gio::OutputStreamSpliceFlags::CLOSE_TARGET,
|
||||
glib::Priority::default(),
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(_) => {
|
||||
let data = out.steal_as_bytes();
|
||||
Ok((mime.to_string(), data.as_ref().to_vec()))
|
||||
}
|
||||
Err(e) => Err(qemu_display::Error::Failed(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(qemu_display::Error::Failed(format!("{}", e))),
|
||||
}
|
||||
} else {
|
||||
Err(qemu_display::Error::Failed(
|
||||
"Clipboard request failed".into(),
|
||||
))
|
||||
};
|
||||
sender.send(res).unwrap()
|
||||
});
|
||||
match receiver.await {
|
||||
Ok(res) => res,
|
||||
Err(e) => Err(qemu_display::Error::Failed(format!(
|
||||
"Clipboard request failed: {}",
|
||||
e
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub async fn new(clipboard: Clipboard) -> Result<Handler, Box<dyn Error>> {
|
||||
let proxy = clipboard.proxy.clone();
|
||||
let serials = Arc::new([AtomicU32::new(0), AtomicU32::new(0)]);
|
||||
let cb_handler = watch_clipboard(
|
||||
ctxt.proxy.clone(),
|
||||
clipboard.proxy.clone(),
|
||||
ClipboardSelection::Clipboard,
|
||||
serials.clone(),
|
||||
);
|
||||
let cb_primary_handler = watch_clipboard(
|
||||
ctxt.proxy.clone(),
|
||||
clipboard.proxy.clone(),
|
||||
ClipboardSelection::Primary,
|
||||
serials.clone(),
|
||||
);
|
||||
|
||||
ctxt.register().await?;
|
||||
Ok(Self {
|
||||
rx,
|
||||
clipboard.register(InnerHandler { proxy, serials }).await?;
|
||||
Ok(Handler {
|
||||
clipboard,
|
||||
cb_handler,
|
||||
cb_primary_handler,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Handler {
|
||||
fn drop(&mut self) {
|
||||
if let Some(id) = self.cb_primary_handler.take() {
|
||||
clipboard_from_selection(ClipboardSelection::Primary)
|
||||
.unwrap()
|
||||
.0
|
||||
.disconnect(id);
|
||||
}
|
||||
if let Some(id) = self.cb_handler.take() {
|
||||
clipboard_from_selection(ClipboardSelection::Clipboard)
|
||||
.unwrap()
|
||||
.0
|
||||
.disconnect(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn watch_clipboard(
|
||||
proxy: AsyncClipboardProxy<'static>,
|
||||
selection: ClipboardSelection,
|
||||
serials: Rc<[Cell<u32>; 2]>,
|
||||
serials: Arc<[AtomicU32; 2]>,
|
||||
) -> Option<SignalHandlerId> {
|
||||
let (clipboard, idx) = match clipboard_from_selection(selection) {
|
||||
Some(it) => it,
|
||||
@@ -161,9 +211,9 @@ fn watch_clipboard(
|
||||
let _ = proxy.release(selection).await;
|
||||
} else {
|
||||
let mimes: Vec<_> = types.iter().map(|s| s.as_str()).collect();
|
||||
let ser = serials[idx].get();
|
||||
let ser = serials[idx].load(Ordering::SeqCst);
|
||||
let _ = proxy.grab(selection, ser, &mimes).await;
|
||||
serials[idx].set(ser + 1);
|
||||
serials[idx].store(ser + 1, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+92
-55
@@ -1,15 +1,15 @@
|
||||
use futures_util::StreamExt;
|
||||
use glib::{clone, subclass::prelude::*, MainContext};
|
||||
use gtk::{glib, prelude::*};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use gtk::glib;
|
||||
use keycodemap::KEYMAP_XORGEVDEV2QNUM;
|
||||
use qemu_display::Console;
|
||||
use rdw::DisplayExt;
|
||||
use once_cell::sync::OnceCell;
|
||||
use qemu_display::{Console, ConsoleListenerHandler};
|
||||
use rdw::{gtk, DisplayExt};
|
||||
use std::os::unix::io::IntoRawFd;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
use gtk::subclass::prelude::*;
|
||||
use std::{convert::TryInto, os::unix::io::IntoRawFd};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RdwDisplayQemuClass {
|
||||
@@ -136,22 +136,17 @@ mod imp {
|
||||
MainContext::default().spawn_local(clone!(@weak widget => async move {
|
||||
let self_ = Self::from_instance(&widget);
|
||||
let console = self_.console.get().unwrap();
|
||||
|
||||
let (rx, wait_tx) = console
|
||||
.glib_listen()
|
||||
.await
|
||||
.expect("Failed to listen to the console");
|
||||
rx.attach(
|
||||
None,
|
||||
clone!(@weak widget => @default-panic, move |evt| {
|
||||
use qemu_display::ConsoleEvent::*;
|
||||
|
||||
log::debug!("Console event: {:?}", evt);
|
||||
match evt {
|
||||
// we have to use a channel, because widget is not Send..
|
||||
let (sender, mut receiver) = futures::channel::mpsc::unbounded();
|
||||
console.register_listener(ConsoleHandler { sender }).await.unwrap();
|
||||
MainContext::default().spawn_local(clone!(@weak widget => async move {
|
||||
while let Some(e) = receiver.next().await {
|
||||
use ConsoleEvent::*;
|
||||
match e {
|
||||
Scanout(s) => {
|
||||
if s.format != 0x20020888 {
|
||||
log::warn!("Format not yet supported: {:X}", s.format);
|
||||
return Continue(true);
|
||||
continue;
|
||||
}
|
||||
widget.set_display_size(Some((s.width as _, s.height as _)));
|
||||
widget.update_area(0, 0, s.width as _, s.height as _, s.stride as _, &s.data);
|
||||
@@ -159,7 +154,7 @@ mod imp {
|
||||
Update(u) => {
|
||||
if u.format != 0x20020888 {
|
||||
log::warn!("Format not yet supported: {:X}", u.format);
|
||||
return Continue(true);
|
||||
continue;
|
||||
}
|
||||
widget.update_area(u.x as _, u.y as _, u.w as _, u.h as _, u.stride as _, &u.data);
|
||||
}
|
||||
@@ -175,19 +170,20 @@ mod imp {
|
||||
fd: s.into_raw_fd(),
|
||||
});
|
||||
}
|
||||
UpdateDMABUF { .. } => {
|
||||
UpdateDMABUF { wait_tx, .. } => {
|
||||
widget.render();
|
||||
let _ = wait_tx.send(());
|
||||
}
|
||||
Disconnected => {
|
||||
log::warn!("Console disconnected");
|
||||
}
|
||||
CursorDefine { width, height, hot_x, hot_y, data }=> {
|
||||
CursorDefine(c) => {
|
||||
let cursor = rdw::Display::make_cursor(
|
||||
&data,
|
||||
width,
|
||||
height,
|
||||
hot_x,
|
||||
hot_y,
|
||||
&c.data,
|
||||
c.width,
|
||||
c.height,
|
||||
c.hot_x,
|
||||
c.hot_y,
|
||||
1,
|
||||
);
|
||||
widget.define_cursor(Some(cursor));
|
||||
@@ -200,41 +196,21 @@ mod imp {
|
||||
}
|
||||
}
|
||||
}
|
||||
Continue(true)
|
||||
})
|
||||
);
|
||||
|
||||
let mut abs_changed = console.mouse.receive_is_absolute_changed().await;
|
||||
MainContext::default().spawn_local(clone!(@weak widget => async move {
|
||||
use futures_util::StreamExt;
|
||||
|
||||
while let Some(abs) = abs_changed.next().await {
|
||||
let abs = if let Some(abs) = abs {
|
||||
abs.try_into().unwrap_or(false)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
widget.set_mouse_absolute(abs);
|
||||
}
|
||||
}));
|
||||
|
||||
loop {
|
||||
if let Err(e) = console.dispatch_signals().await {
|
||||
log::warn!("Console dispatching error: {}", e);
|
||||
break;
|
||||
let mut abs_changed = console.mouse.receive_is_absolute_changed().await;
|
||||
MainContext::default().spawn_local(clone!(@weak widget => async move {
|
||||
while let Some(abs) = abs_changed.next().await {
|
||||
if let Some(abs) = abs {
|
||||
widget.set_mouse_absolute(abs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl rdw::DisplayImpl for Display {}
|
||||
|
||||
impl Display {
|
||||
pub(crate) fn set_console(&self, console: Console) {
|
||||
self.console.set(console).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
@@ -245,7 +221,7 @@ impl Display {
|
||||
pub fn new(console: Console) -> Self {
|
||||
let obj = glib::Object::new::<Self>(&[]).unwrap();
|
||||
let self_ = imp::Display::from_instance(&obj);
|
||||
self_.set_console(console);
|
||||
self_.console.set(console).unwrap();
|
||||
obj
|
||||
}
|
||||
|
||||
@@ -255,6 +231,67 @@ impl Display {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ConsoleEvent {
|
||||
Scanout(qemu_display::Scanout),
|
||||
Update(qemu_display::Update),
|
||||
ScanoutDMABUF(qemu_display::ScanoutDMABUF),
|
||||
UpdateDMABUF {
|
||||
_update: qemu_display::UpdateDMABUF,
|
||||
wait_tx: futures::channel::oneshot::Sender<()>,
|
||||
},
|
||||
MouseSet(qemu_display::MouseSet),
|
||||
CursorDefine(qemu_display::Cursor),
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
struct ConsoleHandler {
|
||||
sender: futures::channel::mpsc::UnboundedSender<ConsoleEvent>,
|
||||
}
|
||||
|
||||
impl ConsoleHandler {
|
||||
fn send(&self, event: ConsoleEvent) {
|
||||
if let Err(e) = self.sender.unbounded_send(event) {
|
||||
log::warn!("failed to send console event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ConsoleListenerHandler for ConsoleHandler {
|
||||
async fn scanout(&mut self, scanout: qemu_display::Scanout) {
|
||||
self.send(ConsoleEvent::Scanout(scanout));
|
||||
}
|
||||
|
||||
async fn update(&mut self, update: qemu_display::Update) {
|
||||
self.send(ConsoleEvent::Update(update));
|
||||
}
|
||||
|
||||
async fn scanout_dmabuf(&mut self, scanout: qemu_display::ScanoutDMABUF) {
|
||||
self.send(ConsoleEvent::ScanoutDMABUF(scanout));
|
||||
}
|
||||
|
||||
async fn update_dmabuf(&mut self, _update: qemu_display::UpdateDMABUF) {
|
||||
let (wait_tx, wait_rx) = futures::channel::oneshot::channel();
|
||||
self.send(ConsoleEvent::UpdateDMABUF { _update, wait_tx });
|
||||
if let Err(e) = wait_rx.await {
|
||||
log::warn!("wait update dmabuf failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
async fn mouse_set(&mut self, set: qemu_display::MouseSet) {
|
||||
self.send(ConsoleEvent::MouseSet(set));
|
||||
}
|
||||
|
||||
async fn cursor_define(&mut self, cursor: qemu_display::Cursor) {
|
||||
self.send(ConsoleEvent::CursorDefine(cursor));
|
||||
}
|
||||
|
||||
fn disconnected(&mut self) {
|
||||
self.send(ConsoleEvent::Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
fn from_gdk_button(button: u32) -> qemu_display::MouseButton {
|
||||
use qemu_display::MouseButton::*;
|
||||
|
||||
|
||||
+38
-35
@@ -2,9 +2,8 @@ use gio::ApplicationFlags;
|
||||
use glib::MainContext;
|
||||
use gtk::{gio, glib, prelude::*};
|
||||
use qemu_display::{Chardev, Console, Display};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use zbus::Connection;
|
||||
use rdw::gtk;
|
||||
use std::{cell::RefCell, sync::Arc};
|
||||
|
||||
mod audio;
|
||||
mod clipboard;
|
||||
@@ -13,7 +12,6 @@ mod usbredir;
|
||||
|
||||
struct Inner {
|
||||
app: gtk::Application,
|
||||
conn: zbus::azync::Connection,
|
||||
usbredir: RefCell<Option<usbredir::Handler>>,
|
||||
audio: RefCell<Option<audio::Handler>>,
|
||||
clipboard: RefCell<Option<clipboard::Handler>>,
|
||||
@@ -69,20 +67,15 @@ impl App {
|
||||
if opt.lookup_value("list", None).is_some() {
|
||||
app_opt.list = true;
|
||||
}
|
||||
app_opt.vm_name =
|
||||
opt.lookup_value(&glib::OPTION_REMAINING, None)
|
||||
.and_then(|args| args.child_value(0).get::<String>());
|
||||
app_opt.vm_name = opt
|
||||
.lookup_value(&glib::OPTION_REMAINING, None)
|
||||
.and_then(|args| args.child_value(0).get::<String>());
|
||||
-1
|
||||
});
|
||||
|
||||
let conn = Connection::session()
|
||||
.expect("Failed to connect to DBus")
|
||||
.into();
|
||||
|
||||
let app = App {
|
||||
inner: Arc::new(Inner {
|
||||
app,
|
||||
conn,
|
||||
usbredir: Default::default(),
|
||||
audio: Default::default(),
|
||||
clipboard: Default::default(),
|
||||
@@ -90,7 +83,6 @@ impl App {
|
||||
};
|
||||
|
||||
let app_clone = app.clone();
|
||||
let opt_clone = opt.clone();
|
||||
app.inner.app.connect_activate(move |app| {
|
||||
let ui_src = include_str!("main.ui");
|
||||
let builder = gtk::Builder::new();
|
||||
@@ -102,11 +94,24 @@ impl App {
|
||||
window.set_application(Some(app));
|
||||
|
||||
let app_clone = app_clone.clone();
|
||||
let opt_clone = opt_clone.clone();
|
||||
let opt_clone = opt.clone();
|
||||
MainContext::default().spawn_local(async move {
|
||||
// let opt = opt_clone.borrow();
|
||||
let conn = zbus::ConnectionBuilder::session()
|
||||
.unwrap()
|
||||
.internal_executor(false)
|
||||
.build()
|
||||
.await
|
||||
.expect("Failed to connect to DBus");
|
||||
|
||||
let conn_clone = conn.clone();
|
||||
MainContext::default().spawn_local(async move {
|
||||
loop {
|
||||
conn_clone.executor().tick().await;
|
||||
}
|
||||
});
|
||||
|
||||
if opt_clone.borrow().list {
|
||||
let list = Display::by_name(app_clone.connection()).await.unwrap();
|
||||
let list = Display::by_name(&conn).await.unwrap();
|
||||
for (name, dest) in list {
|
||||
println!("{} (at {})", name, dest);
|
||||
}
|
||||
@@ -114,20 +119,18 @@ impl App {
|
||||
return;
|
||||
}
|
||||
let dest = if let Some(name) = opt_clone.borrow().vm_name.as_ref() {
|
||||
let list = Display::by_name(app_clone.connection()).await.unwrap();
|
||||
let list = Display::by_name(&conn).await.unwrap();
|
||||
Some(
|
||||
list.get(name)
|
||||
.expect(&format!("Can't find VM name: {}", name))
|
||||
.unwrap_or_else(|| panic!("Can't find VM name: {}", name))
|
||||
.clone(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let display = Display::new(app_clone.connection(), dest.as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
let display = Display::new(&conn, dest.as_ref()).await.unwrap();
|
||||
|
||||
let console = Console::new(app_clone.connection(), 0)
|
||||
let console = Console::new(&conn, 0)
|
||||
.await
|
||||
.expect("Failed to get the QEMU console");
|
||||
let rdw = display::Display::new(console);
|
||||
@@ -143,22 +146,26 @@ impl App {
|
||||
if let Ok(Some(audio)) = display.audio().await {
|
||||
match audio::Handler::new(audio).await {
|
||||
Ok(handler) => app_clone.set_audio(handler),
|
||||
Err(e) => log::warn!("Failed to setup audio: {}", e),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to setup audio handler: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(Some(clipboard)) = display.clipboard().await {
|
||||
match clipboard::Handler::new(clipboard).await {
|
||||
Ok(handler) => app_clone.set_clipboard(handler),
|
||||
Err(e) => log::warn!("Failed to setup clipboard: {}", e),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to setup clipboard handler: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(c) = Chardev::new(app_clone.connection(), "qmp").await {
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
if let Ok(c) = Chardev::new(&conn, "qmp").await {
|
||||
use std::{
|
||||
io::{prelude::*, BufReader},
|
||||
os::unix::{io::AsRawFd, net::UnixStream},
|
||||
};
|
||||
|
||||
let (p0, p1) = UnixStream::pair().unwrap();
|
||||
if c.proxy.register(p1.as_raw_fd().into()).await.is_ok() {
|
||||
@@ -192,10 +199,6 @@ impl App {
|
||||
app
|
||||
}
|
||||
|
||||
fn connection(&self) -> &zbus::azync::Connection {
|
||||
&self.inner.conn
|
||||
}
|
||||
|
||||
fn set_usbredir(&self, usbredir: usbredir::Handler) {
|
||||
self.inner.usbredir.replace(Some(usbredir));
|
||||
}
|
||||
@@ -204,8 +207,8 @@ impl App {
|
||||
self.inner.audio.replace(Some(audio));
|
||||
}
|
||||
|
||||
fn set_clipboard(&self, clipboard: clipboard::Handler) {
|
||||
self.inner.clipboard.replace(Some(clipboard));
|
||||
fn set_clipboard(&self, cb: clipboard::Handler) {
|
||||
self.inner.clipboard.replace(Some(cb));
|
||||
}
|
||||
|
||||
fn run(&self) -> i32 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use glib::{clone, MainContext};
|
||||
use gtk::{glib, prelude::*};
|
||||
use qemu_display::UsbRedir;
|
||||
use rdw::gtk;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Handler {
|
||||
@@ -30,7 +31,7 @@ impl Handler {
|
||||
let usbredir = self.usbredir.clone();
|
||||
widget.connect_device_state_set(move |widget, item, state| {
|
||||
let device = match item.device() {
|
||||
Some(it) => it.clone(),
|
||||
Some(it) => it,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user