mirror of
https://gitlab.com/marcandre.lureau/qemu-display.git
synced 2025-01-30 15:25:15 +00:00
qemu-gtk4: work in progress
This commit is contained in:
parent
b0a1f355a6
commit
437b739c0e
@ -6,6 +6,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
qemu-display-listener = { path = "../qemu-display-listener", features = ["glib"] }
|
||||
keycodemap = { path = "../keycodemap" }
|
||||
zbus = { version = "2.0.0-beta" }
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
@ -15,6 +16,7 @@ once_cell = "1.5"
|
||||
khronos-egl = { version = "3.0.0", features = ["dynamic"] }
|
||||
libloading = "0.6"
|
||||
gl = "0.14.0"
|
||||
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
|
||||
|
||||
[dependencies.gtk]
|
||||
package = "gtk4"
|
||||
@ -26,5 +28,7 @@ package = "gdk4-wayland"
|
||||
git = "https://github.com/gtk-rs/gtk4-rs"
|
||||
rev = "c43025157b12dba1112fad55962966769908a269"
|
||||
|
||||
[build-dependencies]
|
||||
gl_generator = "0.5.0"
|
||||
[dependencies.gdk-x11]
|
||||
package = "gdk4-x11"
|
||||
git = "https://github.com/gtk-rs/gtk4-rs"
|
||||
rev = "c43025157b12dba1112fad55962966769908a269"
|
||||
|
@ -5,6 +5,7 @@ use gtk::subclass::widget::WidgetImplExt;
|
||||
use gtk::{glib, CompositeTemplate};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use keycodemap::*;
|
||||
use qemu_display_listener::{Console, Event, MouseButton};
|
||||
|
||||
mod imp {
|
||||
@ -54,12 +55,16 @@ mod imp {
|
||||
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);
|
||||
if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) {
|
||||
let _ = c.keyboard.press(*qnum as u32);
|
||||
}
|
||||
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);
|
||||
if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) {
|
||||
let _ = c.keyboard.release(*qnum as u32);
|
||||
}
|
||||
}));
|
||||
|
||||
let ec = gtk::EventControllerMotion::new();
|
||||
@ -121,6 +126,9 @@ impl QemuConsole {
|
||||
clone!(@weak self as con => move |t| {
|
||||
let con = imp::QemuConsole::from_instance(&con);
|
||||
match t {
|
||||
Event::Update { .. } => {
|
||||
con.area.queue_render();
|
||||
}
|
||||
Event::Scanout(s) => {
|
||||
con.label.set_label(&format!("{:?}", s));
|
||||
con.area.set_scanout(s);
|
||||
@ -144,16 +152,10 @@ impl QemuConsole {
|
||||
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);
|
||||
if let Some((x, y)) = priv_.area.transform_input(x, y) {
|
||||
let c = self.qemu_console();
|
||||
let _ = c.mouse.set_abs_position(x, y);
|
||||
}
|
||||
|
||||
// FIXME: focus on click doesn't work
|
||||
priv_.area.grab_focus();
|
||||
|
@ -1,10 +1,14 @@
|
||||
use gdk_wl::WaylandDisplayManualExt;
|
||||
use glib::subclass::prelude::*;
|
||||
use glib::translate::*;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{gdk, glib, graphene};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use gtk::subclass::widget::WidgetImplExt;
|
||||
use gtk::{gdk, glib};
|
||||
use std::cell::Cell;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
use crate::egl;
|
||||
use crate::error::*;
|
||||
use gl::{self, types::*};
|
||||
use qemu_display_listener::Scanout;
|
||||
|
||||
@ -14,16 +18,18 @@ mod imp {
|
||||
use gtk::subclass::prelude::*;
|
||||
|
||||
pub struct QemuConsoleArea {
|
||||
pub tex_id: Cell<GLuint>,
|
||||
pub texture_blit_vao: Cell<GLuint>,
|
||||
pub texture_blit_prog: Cell<GLuint>,
|
||||
pub texture_blit_flip_prog: Cell<GLuint>,
|
||||
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 {
|
||||
const NAME: &'static str = "QemuConsoleArea";
|
||||
type Type = super::QemuConsoleArea;
|
||||
type ParentType = gtk::Widget;
|
||||
type ParentType = gtk::GLArea;
|
||||
type Interfaces = ();
|
||||
type Instance = subclass::simple::InstanceStruct<Self>;
|
||||
type Class = subclass::simple::ClassStruct<Self>;
|
||||
@ -32,10 +38,12 @@ mod imp {
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
tex_id: Cell::new(0),
|
||||
texture_blit_vao: Cell::new(0),
|
||||
texture_blit_prog: Cell::new(0),
|
||||
texture_blit_flip_prog: Cell::new(0),
|
||||
scanout: Cell::new(None),
|
||||
scanout_size: Cell::new((0, 0)),
|
||||
tex_id: Cell::new(0),
|
||||
texture: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,45 +66,214 @@ mod imp {
|
||||
}
|
||||
|
||||
impl WidgetImpl for QemuConsoleArea {
|
||||
fn snapshot(&self, widget: &Self::Type, snapshot: >k::Snapshot) {
|
||||
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);
|
||||
if let Some(texture) = &*self.texture.borrow() {
|
||||
snapshot.append_texture(texture, whole);
|
||||
fn realize(&self, widget: &Self::Type) {
|
||||
widget.set_has_depth_buffer(false);
|
||||
widget.set_has_stencil_buffer(false);
|
||||
widget.set_auto_render(false);
|
||||
widget.set_required_version(3, 2);
|
||||
self.parent_realize(widget);
|
||||
widget.make_current();
|
||||
|
||||
if let Err(e) = unsafe { self.realize_gl() } {
|
||||
let e = glib::Error::new(AppError::GL, &format!("{}", e));
|
||||
widget.set_error(Some(&e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GLAreaImpl for QemuConsoleArea {
|
||||
fn render(&self, gl_area: &Self::Type, _context: &gdk::GLContext) -> bool {
|
||||
unsafe {
|
||||
gl::ClearColor(0.1, 0.1, 0.1, 1.0);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
gl::Disable(gl::BLEND);
|
||||
|
||||
let vp = self.viewport(gl_area);
|
||||
gl::Viewport(vp.x, vp.y, vp.width, vp.height);
|
||||
self.texture_blit(false);
|
||||
}
|
||||
return true; /* FIXME: Inibit */
|
||||
}
|
||||
}
|
||||
|
||||
impl QemuConsoleArea {
|
||||
pub fn borders(&self, gl_area: &super::QemuConsoleArea) -> (u32, u32) {
|
||||
let sf = gl_area.get_scale_factor();
|
||||
let (w, h) = (gl_area.get_width() * sf, gl_area.get_height() * sf);
|
||||
let (gw, gh) = gl_area.scanout_size();
|
||||
let (sw, sh) = (w as f32 / gw as f32, h as f32 / gh as f32);
|
||||
|
||||
if sw < sh {
|
||||
let bh = h - (h as f32 * sw / sh) as i32;
|
||||
(0, bh as u32 / 2)
|
||||
} else {
|
||||
let bw = w - (w as f32 * sh / sw) as i32;
|
||||
(bw as u32 / 2, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn viewport(&self, gl_area: &super::QemuConsoleArea) -> gdk::Rectangle {
|
||||
let sf = gl_area.get_scale_factor();
|
||||
let (w, h) = (gl_area.get_width() * sf, gl_area.get_height() * sf);
|
||||
let (borderw, borderh) = self.borders(gl_area);
|
||||
let (borderw, borderh) = (borderw as i32, borderh as i32);
|
||||
gdk::Rectangle {
|
||||
x: borderw,
|
||||
y: borderh,
|
||||
width: w - borderw * 2,
|
||||
height: h - borderh * 2,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn realize_gl(&self) -> Result<(), String> {
|
||||
let texture_blit_vs = CString::new(include_str!("texture-blit.vert")).unwrap();
|
||||
let texture_blit_flip_vs =
|
||||
CString::new(include_str!("texture-blit-flip.vert")).unwrap();
|
||||
let texture_blit_fs = CString::new(include_str!("texture-blit.frag")).unwrap();
|
||||
|
||||
let texture_blit_prg =
|
||||
compile_prog(texture_blit_vs.as_c_str(), texture_blit_fs.as_c_str())?;
|
||||
self.texture_blit_prog.set(texture_blit_prg);
|
||||
let texture_blit_flip_prg =
|
||||
compile_prog(texture_blit_flip_vs.as_c_str(), texture_blit_fs.as_c_str())?;
|
||||
self.texture_blit_flip_prog.set(texture_blit_flip_prg);
|
||||
|
||||
let mut vao = 0;
|
||||
gl::GenVertexArrays(1, &mut vao);
|
||||
gl::BindVertexArray(vao);
|
||||
let mut vb = 0;
|
||||
gl::GenBuffers(1, &mut vb);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, vb);
|
||||
static POS: [f32; 8] = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
|
||||
gl::BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
std::mem::size_of::<[f32; 8]>() as _,
|
||||
POS.as_ptr() as _,
|
||||
gl::STATIC_DRAW,
|
||||
);
|
||||
let in_pos = gl::GetAttribLocation(
|
||||
texture_blit_prg,
|
||||
CString::new("in_position").unwrap().as_c_str().as_ptr(),
|
||||
) as u32;
|
||||
gl::VertexAttribPointer(in_pos, 2, gl::FLOAT, gl::FALSE, 0, std::ptr::null());
|
||||
gl::EnableVertexAttribArray(in_pos);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
gl::BindVertexArray(0);
|
||||
self.texture_blit_vao.set(vao);
|
||||
|
||||
let tex_unit = gl::GetUniformLocation(
|
||||
texture_blit_prg,
|
||||
CString::new("tex_unit").unwrap().as_c_str().as_ptr(),
|
||||
);
|
||||
gl::ProgramUniform1i(texture_blit_prg, tex_unit, 0);
|
||||
|
||||
let mut tex_id = 0;
|
||||
gl::GenTextures(1, &mut tex_id);
|
||||
self.tex_id.set(tex_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn texture_blit(&self, flip: bool) {
|
||||
gl::UseProgram(if flip {
|
||||
todo!();
|
||||
//self.texture_blit_flip_prog.get()
|
||||
} else {
|
||||
self.texture_blit_prog.get()
|
||||
});
|
||||
gl::ActiveTexture(gl::TEXTURE0);
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.tex_id());
|
||||
gl::BindVertexArray(self.texture_blit_vao.get());
|
||||
gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
pub fn tex_id(&self) -> GLuint {
|
||||
self.tex_id.get()
|
||||
}
|
||||
|
||||
pub fn save_to_png(&self, widget: &super::QemuConsoleArea, filename: &str) {
|
||||
let (gw, gh) = self.scanout_size.get();
|
||||
let ctxt = widget.get_context().unwrap();
|
||||
let tex = unsafe { gdk::GLTexture::new(&ctxt, self.tex_id(), gw as _, gh as _) };
|
||||
tex.save_to_png(filename);
|
||||
}
|
||||
|
||||
pub fn set_scanout(&self, widget: &super::QemuConsoleArea, s: Scanout) {
|
||||
widget.make_current();
|
||||
let egl = egl::egl();
|
||||
|
||||
let egl_dpy = if let Ok(dpy) = widget.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 if let Ok(dpy) = widget.get_display().downcast::<gdk_x11::X11Display>() {
|
||||
let _dpy =
|
||||
unsafe { gdk_x11::ffi::gdk_x11_display_get_xdisplay(dpy.to_glib_none().0) };
|
||||
eprintln!("X11: unsupported display kind, todo: EGL");
|
||||
return;
|
||||
} 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");
|
||||
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.tex_id());
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
|
||||
}
|
||||
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.scanout_size.set((s.width, s.height));
|
||||
self.scanout.set(Some(s));
|
||||
|
||||
if let Err(e) = egl.destroy_image(egl_dpy, img) {
|
||||
eprintln!("Destroy image failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct QemuConsoleArea(ObjectSubclass<imp::QemuConsoleArea>) @extends gtk::Widget;
|
||||
pub struct QemuConsoleArea(ObjectSubclass<imp::QemuConsoleArea>)
|
||||
@extends gtk::Widget, gtk::GLArea;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -105,63 +282,68 @@ impl QemuConsoleArea {
|
||||
|
||||
pub fn set_scanout(&self, s: Scanout) {
|
||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
||||
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;
|
||||
};
|
||||
priv_.set_scanout(self, s);
|
||||
}
|
||||
|
||||
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,
|
||||
];
|
||||
pub fn save_to_png(&self, filename: &str) {
|
||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
||||
|
||||
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");
|
||||
priv_.save_to_png(self, filename);
|
||||
}
|
||||
|
||||
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");
|
||||
pub fn transform_input(&self, x: f64, y: f64) -> Option<(u32, u32)> {
|
||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
||||
|
||||
let vp = priv_.viewport(self);
|
||||
let x = x as i32 * self.get_scale_factor();
|
||||
let y = y as i32 * self.get_scale_factor();
|
||||
if !vp.contains_point(x, y) {
|
||||
return None;
|
||||
}
|
||||
|
||||
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));
|
||||
let (sw, sh) = priv_.scanout_size.get();
|
||||
let x = (x - vp.x) as f64 * (sw as f64 / vp.width as f64);
|
||||
let y = (y - vp.y) as f64 * (sh as f64 / vp.height as f64);
|
||||
Some((x as u32, y as u32))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn compile_shader(type_: GLenum, src: &CStr) -> Result<GLuint, String> {
|
||||
let shader = gl::CreateShader(type_);
|
||||
gl::ShaderSource(shader, 1, &src.as_ptr(), std::ptr::null());
|
||||
gl::CompileShader(shader);
|
||||
Ok(shader)
|
||||
}
|
||||
|
||||
fn cstring_new_len(len: usize) -> CString {
|
||||
let buffer: Vec<u8> = Vec::with_capacity(len + 1);
|
||||
unsafe { CString::from_vec_unchecked(buffer) }
|
||||
}
|
||||
|
||||
unsafe fn compile_prog(vs: &CStr, fs: &CStr) -> Result<GLuint, String> {
|
||||
let vs = compile_shader(gl::VERTEX_SHADER, vs)?;
|
||||
let fs = compile_shader(gl::FRAGMENT_SHADER, fs)?;
|
||||
let prog = gl::CreateProgram();
|
||||
|
||||
gl::AttachShader(prog, vs);
|
||||
gl::AttachShader(prog, fs);
|
||||
gl::LinkProgram(prog);
|
||||
|
||||
let mut status: i32 = 0;
|
||||
gl::GetProgramiv(prog, gl::LINK_STATUS, &mut status);
|
||||
if status == 0 {
|
||||
let mut len: GLint = 0;
|
||||
gl::GetProgramiv(prog, gl::INFO_LOG_LENGTH, &mut len);
|
||||
let error = cstring_new_len(len as usize);
|
||||
gl::GetProgramInfoLog(
|
||||
prog,
|
||||
len,
|
||||
std::ptr::null_mut(),
|
||||
error.as_ptr() as *mut gl::types::GLchar,
|
||||
);
|
||||
return Err(error.to_string_lossy().into_owned());
|
||||
}
|
||||
gl::DeleteShader(vs);
|
||||
gl::DeleteShader(fs);
|
||||
Ok(prog)
|
||||
}
|
||||
|
28
qemu-gtk4/src/error.rs
Normal file
28
qemu-gtk4/src/error.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use gtk::glib;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum AppError {
|
||||
GL = 1,
|
||||
Failed = 2,
|
||||
}
|
||||
|
||||
impl glib::error::ErrorDomain for AppError {
|
||||
fn domain() -> glib::Quark {
|
||||
glib::Quark::from_string("qemu-gtk4")
|
||||
}
|
||||
|
||||
fn code(self) -> i32 {
|
||||
self as _
|
||||
}
|
||||
|
||||
fn from(code: i32) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
use self::AppError::*;
|
||||
match code {
|
||||
x if x == GL as i32 => Some(GL),
|
||||
_ => Some(Failed),
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ mod config;
|
||||
mod console;
|
||||
mod console_area;
|
||||
mod egl;
|
||||
mod error;
|
||||
mod window;
|
||||
|
||||
use application::QemuApplication;
|
||||
|
@ -55,7 +55,9 @@ fn codegen() -> Result<(), DynError> {
|
||||
];
|
||||
for km in &keymaps {
|
||||
let varname = format!("keymap_{}2qnum", km);
|
||||
let out = cmd!("{keymap_gen} code-map --lang rust --varname {varname} {keymaps_csv} {km} qnum").read()?;
|
||||
let out =
|
||||
cmd!("{keymap_gen} code-map --lang rust --varname {varname} {keymaps_csv} {km} qnum")
|
||||
.read()?;
|
||||
write_file(keycodemap_src.join(format!("{}.rs", varname)), out)?;
|
||||
}
|
||||
Ok(())
|
||||
|
Loading…
x
Reference in New Issue
Block a user