mirror of
https://github.com/rustdesk/qemu-display.git
synced 2025-08-17 16:25:39 +00:00
More progress with inputs
This commit is contained in:
parent
d1e0ba0dde
commit
73c30fd2d4
@ -1,6 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["qemu-display-listener", "qemu-gtk4"]
|
members = ["qemu-display-listener", "qemu-gtk4"]
|
||||||
|
|
||||||
#[patch.crates-io]
|
[patch.crates-io]
|
||||||
#zbus = { path = '/home/elmarco/src/zbus/zbus' }
|
zbus = { path = '/home/elmarco/src/zbus/zbus' }
|
||||||
|
zvariant = { path = '/home/elmarco/src/zbus/zvariant' }
|
||||||
|
@ -12,3 +12,6 @@ derivative = "2.1.3"
|
|||||||
zvariant = "2.4.0"
|
zvariant = "2.4.0"
|
||||||
libc = "0.2.86"
|
libc = "0.2.86"
|
||||||
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
|
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
|
||||||
|
enumflags2 = { version = "0.6.4", features = ["serde"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_repr = "0.1.6"
|
||||||
|
@ -7,7 +7,7 @@ use std::{os::unix::io::AsRawFd, thread};
|
|||||||
use zbus::{dbus_proxy, export::zvariant::Fd};
|
use zbus::{dbus_proxy, export::zvariant::Fd};
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::{Event, Listener};
|
use crate::{Event, KeyboardProxy, Listener, MouseProxy};
|
||||||
|
|
||||||
#[dbus_proxy(default_service = "org.qemu", interface = "org.qemu.Display1.Console")]
|
#[dbus_proxy(default_service = "org.qemu", interface = "org.qemu.Display1.Console")]
|
||||||
pub trait Console {
|
pub trait Console {
|
||||||
@ -34,16 +34,24 @@ pub trait Console {
|
|||||||
#[derivative(Debug)]
|
#[derivative(Debug)]
|
||||||
pub struct Console {
|
pub struct Console {
|
||||||
#[derivative(Debug = "ignore")]
|
#[derivative(Debug = "ignore")]
|
||||||
proxy: ConsoleProxy<'static>,
|
pub proxy: ConsoleProxy<'static>,
|
||||||
|
#[derivative(Debug = "ignore")]
|
||||||
|
pub keyboard: KeyboardProxy<'static>,
|
||||||
|
#[derivative(Debug = "ignore")]
|
||||||
|
pub mouse: MouseProxy<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Console {
|
impl Console {
|
||||||
pub fn new(conn: &zbus::Connection, idx: u32) -> Result<Self> {
|
pub fn new(conn: &zbus::Connection, idx: u32) -> Result<Self> {
|
||||||
let proxy = ConsoleProxy::new_for_owned_path(
|
let obj_path = format!("/org/qemu/Display1/Console_{}", idx);
|
||||||
conn.clone(),
|
let proxy = ConsoleProxy::new_for_owned_path(conn.clone(), obj_path.clone())?;
|
||||||
format!("/org/qemu/Display1/Console_{}", idx),
|
let keyboard = KeyboardProxy::new_for_owned_path(conn.clone(), obj_path.clone())?;
|
||||||
)?;
|
let mouse = MouseProxy::new_for_owned_path(conn.clone(), obj_path)?;
|
||||||
Ok(Self { proxy })
|
Ok(Self {
|
||||||
|
proxy,
|
||||||
|
keyboard,
|
||||||
|
mouse,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn label(&self) -> Result<String> {
|
pub fn label(&self) -> Result<String> {
|
||||||
@ -68,7 +76,7 @@ impl Console {
|
|||||||
let mut s = zbus::ObjectServer::new(&c);
|
let mut s = zbus::ObjectServer::new(&c);
|
||||||
let err = Rc::new(RefCell::new(None));
|
let err = Rc::new(RefCell::new(None));
|
||||||
s.at(
|
s.at(
|
||||||
&zvariant::ObjectPath::from_str_unchecked("/org/qemu/Display1/Listener"),
|
"/org/qemu/Display1/Listener",
|
||||||
Listener::new(tx, err.clone()),
|
Listener::new(tx, err.clone()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -100,18 +108,18 @@ impl Console {
|
|||||||
let mut s = zbus::ObjectServer::new(&c);
|
let mut s = zbus::ObjectServer::new(&c);
|
||||||
let err = Rc::new(RefCell::new(None));
|
let err = Rc::new(RefCell::new(None));
|
||||||
s.at(
|
s.at(
|
||||||
&zvariant::ObjectPath::from_str_unchecked("/org/qemu/Display1/Listener"),
|
"/org/qemu/Display1/Listener",
|
||||||
Listener::new(tx, err.clone()),
|
Listener::new(tx, err.clone()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
loop {
|
loop {
|
||||||
if let Err(e) = s.try_handle_next() {
|
if let Err(e) = s.try_handle_next() {
|
||||||
eprintln!("Listener DBus error: {}", e);
|
eprintln!("Listener DBus error: {}", e);
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
if let Some(e) = &*err.borrow() {
|
if let Some(e) = &*err.borrow() {
|
||||||
eprintln!("Listener channel error: {}", e);
|
eprintln!("Listener channel error: {}", e);
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
24
qemu-display-listener/src/keyboard.rs
Normal file
24
qemu-display-listener/src/keyboard.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use enumflags2::BitFlags;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use zbus::dbus_proxy;
|
||||||
|
use zvariant::derive::Type;
|
||||||
|
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(Type, BitFlags, Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum KeyboardModifiers {
|
||||||
|
Scroll = 0x1,
|
||||||
|
Num = 0x2,
|
||||||
|
Caps = 0x4,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[dbus_proxy(default_service = "org.qemu", interface = "org.qemu.Display1.Keyboard")]
|
||||||
|
pub trait Keyboard {
|
||||||
|
/// Press method
|
||||||
|
fn press(&self, keycode: u32) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// Release method
|
||||||
|
fn release(&self, keycode: u32) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
#[dbus_proxy(property)]
|
||||||
|
fn modifiers(&self) -> zbus::Result<BitFlags<KeyboardModifiers>>;
|
||||||
|
}
|
@ -9,6 +9,12 @@ pub use vm::*;
|
|||||||
mod console;
|
mod console;
|
||||||
pub use console::*;
|
pub use console::*;
|
||||||
|
|
||||||
|
mod keyboard;
|
||||||
|
pub use keyboard::*;
|
||||||
|
|
||||||
|
mod mouse;
|
||||||
|
pub use mouse::*;
|
||||||
|
|
||||||
mod listener;
|
mod listener;
|
||||||
pub use listener::*;
|
pub use listener::*;
|
||||||
|
|
||||||
|
@ -1,26 +1,28 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::ops::Drop;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::mpsc::{SendError, Sender};
|
use std::sync::mpsc::{SendError, Sender};
|
||||||
use std::ops::Drop;
|
|
||||||
|
|
||||||
use zbus::{dbus_interface, export::zvariant::Fd};
|
use zbus::{dbus_interface, export::zvariant::Fd};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scanout {
|
pub struct Scanout {
|
||||||
fd: RawFd,
|
pub fd: RawFd,
|
||||||
width: u32,
|
pub width: u32,
|
||||||
height: u32,
|
pub height: u32,
|
||||||
stride: u32,
|
pub stride: u32,
|
||||||
fourcc: u32,
|
pub fourcc: u32,
|
||||||
modifier: u64,
|
pub modifier: u64,
|
||||||
y0_top: bool,
|
pub y0_top: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Scanout {
|
impl Drop for Scanout {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.fd >= 0 {
|
if self.fd >= 0 {
|
||||||
unsafe { libc::close(self.fd); }
|
unsafe {
|
||||||
|
libc::close(self.fd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,6 +53,7 @@ pub enum Event {
|
|||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
},
|
},
|
||||||
Scanout(Scanout),
|
Scanout(Scanout),
|
||||||
|
Disconnected,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait EventSender {
|
pub(crate) trait EventSender {
|
||||||
@ -134,3 +137,9 @@ impl<E: EventSender> Listener<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<E: EventSender> Drop for Listener<E> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.send(Event::Disconnected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
27
qemu-display-listener/src/mouse.rs
Normal file
27
qemu-display-listener/src/mouse.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
use zbus::dbus_proxy;
|
||||||
|
use zvariant::derive::Type;
|
||||||
|
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(Deserialize_repr, Serialize_repr, Type, Debug, PartialEq)]
|
||||||
|
pub enum MouseButton {
|
||||||
|
Left,
|
||||||
|
Middle,
|
||||||
|
Right,
|
||||||
|
WheelUp,
|
||||||
|
WheelDown,
|
||||||
|
Side,
|
||||||
|
Extra,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[dbus_proxy(default_service = "org.qemu", interface = "org.qemu.Display1.Mouse")]
|
||||||
|
pub trait Mouse {
|
||||||
|
/// Press method
|
||||||
|
fn press(&self, button: MouseButton) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// Release method
|
||||||
|
fn release(&self, button: MouseButton) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// SetAbsPosition method
|
||||||
|
fn set_abs_position(&self, x: u32, y: u32) -> zbus::Result<()>;
|
||||||
|
}
|
@ -12,8 +12,19 @@ pretty_env_logger = "0.4"
|
|||||||
gettext-rs = { version = "0.5", features = ["gettext-system"] }
|
gettext-rs = { version = "0.5", features = ["gettext-system"] }
|
||||||
gtk-macros = "0.2"
|
gtk-macros = "0.2"
|
||||||
once_cell = "1.5"
|
once_cell = "1.5"
|
||||||
|
khronos-egl = { version = "3.0.0", features = ["dynamic"] }
|
||||||
|
libloading = "0.6"
|
||||||
|
gl = "0.14.0"
|
||||||
|
|
||||||
[dependencies.gtk]
|
[dependencies.gtk]
|
||||||
package = "gtk4"
|
package = "gtk4"
|
||||||
git = "https://github.com/gtk-rs/gtk4-rs"
|
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::clone;
|
||||||
|
use glib::subclass::prelude::*;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::subclass::widget::WidgetImplExt;
|
use gtk::subclass::widget::WidgetImplExt;
|
||||||
use gtk::{glib, CompositeTemplate};
|
use gtk::{glib, CompositeTemplate};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
use qemu_display_listener::{Console, Event};
|
use qemu_display_listener::{Console, Event, MouseButton};
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -48,6 +48,45 @@ mod imp {
|
|||||||
impl ObjectImpl for QemuConsole {
|
impl ObjectImpl for QemuConsole {
|
||||||
fn constructed(&self, obj: &Self::Type) {
|
fn constructed(&self, obj: &Self::Type) {
|
||||||
self.parent_constructed(obj);
|
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;
|
// Needed for direct subclasses of GtkWidget;
|
||||||
@ -86,6 +125,9 @@ impl QemuConsole {
|
|||||||
con.label.set_label(&format!("{:?}", s));
|
con.label.set_label(&format!("{:?}", s));
|
||||||
con.area.set_scanout(s);
|
con.area.set_scanout(s);
|
||||||
}
|
}
|
||||||
|
Event::Disconnected => {
|
||||||
|
con.label.set_label("Console disconnected!");
|
||||||
|
}
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
Continue(true)
|
Continue(true)
|
||||||
@ -93,4 +135,36 @@ impl QemuConsole {
|
|||||||
);
|
);
|
||||||
priv_.console.set(console).unwrap();
|
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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use std::cell::Cell;
|
use gdk_wl::WaylandDisplayManualExt;
|
||||||
use glib::subclass::prelude::*;
|
use glib::subclass::prelude::*;
|
||||||
use glib::clone;
|
|
||||||
use gtk::prelude::*;
|
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;
|
use qemu_display_listener::Scanout;
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
@ -13,6 +15,9 @@ mod imp {
|
|||||||
|
|
||||||
pub struct QemuConsoleArea {
|
pub struct QemuConsoleArea {
|
||||||
pub scanout: Cell<Option<Scanout>>,
|
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 {
|
impl ObjectSubclass for QemuConsoleArea {
|
||||||
@ -28,24 +33,27 @@ mod imp {
|
|||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
scanout: Cell::new(None),
|
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 {
|
impl ObjectImpl for QemuConsoleArea {
|
||||||
fn constructed(&self, obj: &Self::Type) {
|
fn constructed(&self, obj: &Self::Type) {
|
||||||
self.parent_constructed(obj);
|
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 (width, height) = (widget.get_width() as f32, widget.get_height() as f32);
|
||||||
let whole = &graphene::Rect::new(0_f32, 0_f32, width, height);
|
let whole = &graphene::Rect::new(0_f32, 0_f32, width, height);
|
||||||
// TODO: make this a CSS style?
|
// TODO: make this a CSS style?
|
||||||
snapshot.append_color(&gdk::RGBA::black(), whole);
|
//snapshot.append_color(&gdk::RGBA::black(), whole);
|
||||||
//snapshot.append_texture(priv_.texture, whole);
|
if let Some(texture) = &*self.texture.borrow() {
|
||||||
|
snapshot.append_texture(texture, whole);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,8 +75,93 @@ glib::wrapper! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl QemuConsoleArea {
|
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) {
|
pub fn set_scanout(&self, s: Scanout) {
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
qemu-gtk4/src/egl.rs
Normal file
35
qemu-gtk4/src/egl.rs
Normal file
@ -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)]
|
#[allow(clippy::new_without_default)]
|
||||||
|
|
||||||
mod application;
|
mod application;
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
mod config;
|
mod config;
|
||||||
mod window;
|
|
||||||
mod console;
|
mod console;
|
||||||
mod console_area;
|
mod console_area;
|
||||||
|
mod egl;
|
||||||
|
mod window;
|
||||||
|
|
||||||
use application::QemuApplication;
|
use application::QemuApplication;
|
||||||
use config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE};
|
use config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE};
|
||||||
|
@ -23,6 +23,7 @@ sources = files(
|
|||||||
'config.rs',
|
'config.rs',
|
||||||
'console.rs',
|
'console.rs',
|
||||||
'console_area.rs',
|
'console_area.rs',
|
||||||
|
'egl.rs',
|
||||||
'main.rs',
|
'main.rs',
|
||||||
'window.rs',
|
'window.rs',
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::application::QemuApplication;
|
use crate::application::QemuApplication;
|
||||||
use crate::console::QemuConsole;
|
|
||||||
use crate::config::{APP_ID, PROFILE};
|
use crate::config::{APP_ID, PROFILE};
|
||||||
|
use crate::console::QemuConsole;
|
||||||
use glib::signal::Inhibit;
|
use glib::signal::Inhibit;
|
||||||
use gtk::subclass::prelude::*;
|
use gtk::subclass::prelude::*;
|
||||||
use gtk::{self, prelude::*};
|
use gtk::{self, prelude::*};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user