mirror of
https://github.com/rustdesk/qemu-display.git
synced 2026-06-22 16:51:11 +00:00
More progress with inputs
This commit is contained in:
+12
-1
@@ -12,8 +12,19 @@ pretty_env_logger = "0.4"
|
||||
gettext-rs = { version = "0.5", features = ["gettext-system"] }
|
||||
gtk-macros = "0.2"
|
||||
once_cell = "1.5"
|
||||
khronos-egl = { version = "3.0.0", features = ["dynamic"] }
|
||||
libloading = "0.6"
|
||||
gl = "0.14.0"
|
||||
|
||||
[dependencies.gtk]
|
||||
package = "gtk4"
|
||||
git = "https://github.com/gtk-rs/gtk4-rs"
|
||||
rev = "abea0c9980bc083494eceb30dfab5eeb99a73118"
|
||||
rev = "c43025157b12dba1112fad55962966769908a269"
|
||||
|
||||
[dependencies.gdk-wl]
|
||||
package = "gdk4-wayland"
|
||||
git = "https://github.com/gtk-rs/gtk4-rs"
|
||||
rev = "c43025157b12dba1112fad55962966769908a269"
|
||||
|
||||
[build-dependencies]
|
||||
gl_generator = "0.5.0"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use glib::subclass::prelude::*;
|
||||
use glib::clone;
|
||||
use glib::subclass::prelude::*;
|
||||
use gtk::prelude::*;
|
||||
use gtk::subclass::widget::WidgetImplExt;
|
||||
use gtk::{glib, CompositeTemplate};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use qemu_display_listener::{Console, Event};
|
||||
use qemu_display_listener::{Console, Event, MouseButton};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
@@ -48,6 +48,45 @@ mod imp {
|
||||
impl ObjectImpl for QemuConsole {
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
let ec = gtk::EventControllerKey::new();
|
||||
ec.set_propagation_phase(gtk::PropagationPhase::Capture);
|
||||
self.area.add_controller(&ec);
|
||||
ec.connect_key_pressed(clone!(@weak obj => move |_, _keyval, keycode, _state| {
|
||||
let c = obj.qemu_console();
|
||||
let _ = c.keyboard.press(keycode);
|
||||
glib::signal::Inhibit(true)
|
||||
}));
|
||||
ec.connect_key_released(clone!(@weak obj => move |_, _keyval, keycode, _state| {
|
||||
let c = obj.qemu_console();
|
||||
let _ = c.keyboard.release(keycode);
|
||||
}));
|
||||
|
||||
let ec = gtk::EventControllerMotion::new();
|
||||
self.area.add_controller(&ec);
|
||||
ec.connect_motion(clone!(@weak obj => move |_, x, y| {
|
||||
obj.motion(x, y);
|
||||
}));
|
||||
|
||||
let ec = gtk::GestureClick::new();
|
||||
ec.set_button(0);
|
||||
self.area.add_controller(&ec);
|
||||
ec.connect_pressed(clone!(@weak obj => move |gesture, _n_press, x, y| {
|
||||
let c = obj.qemu_console();
|
||||
let button = from_gdk_button(gesture.get_current_button());
|
||||
obj.motion(x, y);
|
||||
let _ = c.mouse.press(button);
|
||||
}));
|
||||
ec.connect_released(clone!(@weak obj => move |gesture, _n_press, x, y| {
|
||||
let c = obj.qemu_console();
|
||||
let button = from_gdk_button(gesture.get_current_button());
|
||||
obj.motion(x, y);
|
||||
let _ = c.mouse.release(button);
|
||||
}));
|
||||
|
||||
self.area.set_sensitive(true);
|
||||
self.area.set_focusable(true);
|
||||
self.area.set_focus_on_click(true);
|
||||
}
|
||||
|
||||
// Needed for direct subclasses of GtkWidget;
|
||||
@@ -86,6 +125,9 @@ impl QemuConsole {
|
||||
con.label.set_label(&format!("{:?}", s));
|
||||
con.area.set_scanout(s);
|
||||
}
|
||||
Event::Disconnected => {
|
||||
con.label.set_label("Console disconnected!");
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
Continue(true)
|
||||
@@ -93,4 +135,36 @@ impl QemuConsole {
|
||||
);
|
||||
priv_.console.set(console).unwrap();
|
||||
}
|
||||
|
||||
fn qemu_console(&self) -> &Console {
|
||||
let priv_ = imp::QemuConsole::from_instance(self);
|
||||
priv_.console.get().expect("Console is not yet set!")
|
||||
}
|
||||
|
||||
fn motion(&self, x: f64, y: f64) {
|
||||
let priv_ = imp::QemuConsole::from_instance(self);
|
||||
|
||||
// FIXME: scaling, centering etc..
|
||||
let widget_w = self.get_width();
|
||||
let widget_h = self.get_height();
|
||||
let _widget_scale = self.get_scale_factor();
|
||||
|
||||
let c = self.qemu_console();
|
||||
// FIXME: ideally, we would use ConsoleProxy cached properties instead
|
||||
let x = (x / widget_w as f64) * priv_.area.scanout_size().0 as f64;
|
||||
let y = (y / widget_h as f64) * priv_.area.scanout_size().1 as f64;
|
||||
let _ = c.mouse.set_abs_position(x as u32, y as u32);
|
||||
|
||||
// FIXME: focus on click doesn't work
|
||||
priv_.area.grab_focus();
|
||||
}
|
||||
}
|
||||
|
||||
fn from_gdk_button(button: u32) -> MouseButton {
|
||||
match button {
|
||||
1 => MouseButton::Left,
|
||||
2 => MouseButton::Middle,
|
||||
3 => MouseButton::Right,
|
||||
_ => MouseButton::Extra,
|
||||
}
|
||||
}
|
||||
|
||||
+112
-17
@@ -1,9 +1,11 @@
|
||||
use std::cell::Cell;
|
||||
use gdk_wl::WaylandDisplayManualExt;
|
||||
use glib::subclass::prelude::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{glib, graphene, gdk};
|
||||
use gtk::{gdk, glib, graphene};
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
use crate::egl;
|
||||
use gl::{self, types::*};
|
||||
use qemu_display_listener::Scanout;
|
||||
|
||||
mod imp {
|
||||
@@ -13,6 +15,9 @@ mod imp {
|
||||
|
||||
pub struct QemuConsoleArea {
|
||||
pub scanout: Cell<Option<Scanout>>,
|
||||
pub scanout_size: Cell<(u32, u32)>,
|
||||
pub tex_id: Cell<GLuint>,
|
||||
pub texture: RefCell<Option<gdk::Texture>>,
|
||||
}
|
||||
|
||||
impl ObjectSubclass for QemuConsoleArea {
|
||||
@@ -28,24 +33,27 @@ mod imp {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
scanout: Cell::new(None),
|
||||
scanout_size: Cell::new((0, 0)),
|
||||
tex_id: Cell::new(0),
|
||||
texture: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn class_init(_klass: &mut Self::Class) {
|
||||
// GL loading could be done earlier?
|
||||
let egl = egl::egl();
|
||||
|
||||
gl::load_with(|s| {
|
||||
egl.get_proc_address(s)
|
||||
.map(|f| f as _)
|
||||
.unwrap_or(std::ptr::null())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for QemuConsoleArea {
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
let ec = gtk::EventControllerLegacy::new();
|
||||
// XXX: where are the key events?
|
||||
// ec.set_propagation_phase(gtk::PropagationPhase::Bubble);
|
||||
obj.add_controller(&ec);
|
||||
ec.connect_event(clone!(@weak obj => move |_, e| {
|
||||
dbg!(e);
|
||||
true
|
||||
}));
|
||||
obj.set_focusable(true);
|
||||
obj.set_focus_on_click(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +62,10 @@ mod imp {
|
||||
let (width, height) = (widget.get_width() as f32, widget.get_height() as f32);
|
||||
let whole = &graphene::Rect::new(0_f32, 0_f32, width, height);
|
||||
// TODO: make this a CSS style?
|
||||
snapshot.append_color(&gdk::RGBA::black(), whole);
|
||||
//snapshot.append_texture(priv_.texture, whole);
|
||||
//snapshot.append_color(&gdk::RGBA::black(), whole);
|
||||
if let Some(texture) = &*self.texture.borrow() {
|
||||
snapshot.append_texture(texture, whole);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,8 +75,93 @@ glib::wrapper! {
|
||||
}
|
||||
|
||||
impl QemuConsoleArea {
|
||||
pub fn tex_id(&self) -> GLuint {
|
||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
||||
let mut tex_id = priv_.tex_id.get();
|
||||
if tex_id == 0 {
|
||||
unsafe { gl::GenTextures(1, &mut tex_id) }
|
||||
priv_.tex_id.set(tex_id);
|
||||
}
|
||||
tex_id
|
||||
}
|
||||
|
||||
fn update_texture(&self, s: &Scanout) {
|
||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
||||
let ctxt = gdk::GLContext::get_current().unwrap();
|
||||
let tex =
|
||||
unsafe { gdk::GLTexture::new(&ctxt, self.tex_id(), s.width as i32, s.height as i32) };
|
||||
|
||||
//tex.save_to_png("/tmp/tex.png");
|
||||
//tex.clone().downcast::<gdk::GLTexture>().unwrap().release();
|
||||
tex.release();
|
||||
*priv_.texture.borrow_mut() = Some(tex.upcast());
|
||||
}
|
||||
|
||||
pub fn scanout_size(&self) -> (u32, u32) {
|
||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
||||
|
||||
priv_.scanout_size.get()
|
||||
}
|
||||
|
||||
pub fn set_scanout(&self, s: Scanout) {
|
||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
||||
priv_.scanout.replace(Some(s));
|
||||
let egl = egl::egl();
|
||||
|
||||
let egl_dpy = if let Ok(dpy) = self.get_display().downcast::<gdk_wl::WaylandDisplay>() {
|
||||
let wl_dpy = dpy.get_wl_display();
|
||||
egl.get_display(wl_dpy.as_ref().c_ptr() as _)
|
||||
.expect("Failed to get EGL display")
|
||||
} else {
|
||||
eprintln!("Unsupported display kind");
|
||||
return;
|
||||
};
|
||||
|
||||
let attribs = vec![
|
||||
egl::WIDTH as usize,
|
||||
s.width as usize,
|
||||
egl::HEIGHT as usize,
|
||||
s.height as usize,
|
||||
egl::LINUX_DRM_FOURCC_EXT as usize,
|
||||
s.fourcc as usize,
|
||||
egl::DMA_BUF_PLANE0_FD_EXT as usize,
|
||||
s.fd as usize,
|
||||
egl::DMA_BUF_PLANE0_PITCH_EXT as usize,
|
||||
s.stride as usize,
|
||||
egl::DMA_BUF_PLANE0_OFFSET_EXT as usize,
|
||||
0,
|
||||
egl::DMA_BUF_PLANE0_MODIFIER_LO_EXT as usize,
|
||||
(s.modifier & 0xffffffff) as usize,
|
||||
egl::DMA_BUF_PLANE0_MODIFIER_HI_EXT as usize,
|
||||
(s.modifier >> 32 & 0xffffffff) as usize,
|
||||
egl::NONE as usize,
|
||||
];
|
||||
|
||||
let img = egl
|
||||
.create_image(
|
||||
egl_dpy,
|
||||
unsafe { egl::Context::from_ptr(egl::NO_CONTEXT) },
|
||||
egl::LINUX_DMA_BUF_EXT,
|
||||
unsafe { egl::ClientBuffer::from_ptr(std::ptr::null_mut()) },
|
||||
&attribs,
|
||||
)
|
||||
.expect("Failed to eglCreateImage");
|
||||
|
||||
let tex_id = self.tex_id();
|
||||
unsafe { gl::BindTexture(gl::TEXTURE_2D, tex_id) }
|
||||
if let Some(image_target) = egl::image_target_texture_2d_oes() {
|
||||
image_target(gl::TEXTURE_2D, img.as_ptr() as gl::types::GLeglImageOES);
|
||||
} else {
|
||||
eprintln!("Failed to set texture image");
|
||||
}
|
||||
|
||||
self.update_texture(&s);
|
||||
self.queue_draw();
|
||||
|
||||
if let Err(e) = egl.destroy_image(egl_dpy, img) {
|
||||
eprintln!("Destroy image failed: {}", e);
|
||||
}
|
||||
|
||||
priv_.scanout_size.set((s.width, s.height));
|
||||
priv_.scanout.set(Some(s));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
pub use khronos_egl::*;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
type EglInstance = Instance<khronos_egl::Dynamic<libloading::Library, khronos_egl::EGL1_5>>;
|
||||
|
||||
pub(crate) fn egl() -> &'static EglInstance {
|
||||
static INSTANCE: OnceCell<EglInstance> = OnceCell::new();
|
||||
INSTANCE.get_or_init(|| {
|
||||
let lib = libloading::Library::new("libEGL.so").expect("unable to find libEGL.so");
|
||||
unsafe {
|
||||
khronos_egl::DynamicInstance::<khronos_egl::EGL1_5>::load_required_from(lib)
|
||||
.expect("unable to load libEGL.so")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub const LINUX_DMA_BUF_EXT: Enum = 0x3270;
|
||||
pub const LINUX_DRM_FOURCC_EXT: Int = 0x3271;
|
||||
pub const DMA_BUF_PLANE0_FD_EXT: Int = 0x3272;
|
||||
pub const DMA_BUF_PLANE0_OFFSET_EXT: Int = 0x3273;
|
||||
pub const DMA_BUF_PLANE0_PITCH_EXT: Int = 0x3274;
|
||||
pub const DMA_BUF_PLANE0_MODIFIER_LO_EXT: Int = 0x3443;
|
||||
pub const DMA_BUF_PLANE0_MODIFIER_HI_EXT: Int = 0x3444;
|
||||
|
||||
// GLAPI void APIENTRY glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image);
|
||||
|
||||
pub type ImageTargetTexture2DOesFn = extern "C" fn(gl::types::GLenum, gl::types::GLeglImageOES);
|
||||
|
||||
pub fn image_target_texture_2d_oes() -> Option<ImageTargetTexture2DOesFn> {
|
||||
unsafe {
|
||||
egl()
|
||||
.get_proc_address("glEGLImageTargetTexture2DOES")
|
||||
.map(|f| std::mem::transmute::<_, ImageTargetTexture2DOesFn>(f))
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
#[allow(clippy::new_without_default)]
|
||||
|
||||
mod application;
|
||||
#[rustfmt::skip]
|
||||
mod config;
|
||||
mod window;
|
||||
mod console;
|
||||
mod console_area;
|
||||
mod egl;
|
||||
mod window;
|
||||
|
||||
use application::QemuApplication;
|
||||
use config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE};
|
||||
|
||||
@@ -23,6 +23,7 @@ sources = files(
|
||||
'config.rs',
|
||||
'console.rs',
|
||||
'console_area.rs',
|
||||
'egl.rs',
|
||||
'main.rs',
|
||||
'window.rs',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::application::QemuApplication;
|
||||
use crate::console::QemuConsole;
|
||||
use crate::config::{APP_ID, PROFILE};
|
||||
use crate::console::QemuConsole;
|
||||
use glib::signal::Inhibit;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{self, prelude::*};
|
||||
|
||||
Reference in New Issue
Block a user