autocommit 2022-04-13 21:01:32 CEST
This commit is contained in:
parent
661a28e5b0
commit
916e3644c3
30 changed files with 681 additions and 73 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -314,6 +314,7 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bstr",
|
"bstr",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"default-config",
|
"default-config",
|
||||||
|
|
@ -328,6 +329,7 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
|
"qoi",
|
||||||
"rand",
|
"rand",
|
||||||
"repc",
|
"repc",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
|
@ -515,6 +517,10 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "qoi"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||||
build = "build/build.rs"
|
build = "build/build.rs"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["jay-config", "default-config"]
|
members = ["jay-config", "default-config", "qoi"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
@ -32,12 +32,14 @@ byteorder = "1.4.3"
|
||||||
bincode = "2.0.0-rc.1"
|
bincode = "2.0.0-rc.1"
|
||||||
jay-config = { path = "jay-config" }
|
jay-config = { path = "jay-config" }
|
||||||
default-config = { path = "default-config" }
|
default-config = { path = "default-config" }
|
||||||
|
qoi = { path = "qoi" }
|
||||||
pin-project = "1.0.10"
|
pin-project = "1.0.10"
|
||||||
clap = { version = "3.1.6", features = ["derive", "wrap_help"] }
|
clap = { version = "3.1.6", features = ["derive", "wrap_help"] }
|
||||||
clap_complete = "3.1.1"
|
clap_complete = "3.1.1"
|
||||||
humantime = "2.1.0"
|
humantime = "2.1.0"
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
backtrace = "0.3.64"
|
backtrace = "0.3.64"
|
||||||
|
chrono = "0.4.19"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
repc = "0.1.1"
|
repc = "0.1.1"
|
||||||
|
|
@ -47,5 +49,8 @@ bstr = { version = "0.2.17", default-features = false, features = ["std"] }
|
||||||
#[profile.dev.build-override]
|
#[profile.dev.build-override]
|
||||||
#opt-level = 3
|
#opt-level = 3
|
||||||
|
|
||||||
|
[profile.dev.package."qoi"]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
rc_tracking = []
|
rc_tracking = []
|
||||||
|
|
|
||||||
91
qoi/src/lib.rs
Normal file
91
qoi/src/lib.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
pub fn xrgb8888_encode_qoi(bytes: &[u8], width: u32, height: u32, stride: u32) -> Vec<u8> {
|
||||||
|
const OP_RGB: u8 = 0b1111_1110;
|
||||||
|
const OP_INDEX: u8 = 0b0000_0000;
|
||||||
|
const OP_DIFF: u8 = 0b0100_0000;
|
||||||
|
const OP_LUMA: u8 = 0b1000_0000;
|
||||||
|
const OP_RUN: u8 = 0b1100_0000;
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
let width_bytes_be = width.to_be_bytes();
|
||||||
|
let height_bytes_be = height.to_be_bytes();
|
||||||
|
let header = [
|
||||||
|
b'q',
|
||||||
|
b'o',
|
||||||
|
b'i',
|
||||||
|
b'f',
|
||||||
|
width_bytes_be[0],
|
||||||
|
width_bytes_be[1],
|
||||||
|
width_bytes_be[2],
|
||||||
|
width_bytes_be[3],
|
||||||
|
height_bytes_be[0],
|
||||||
|
height_bytes_be[1],
|
||||||
|
height_bytes_be[2],
|
||||||
|
height_bytes_be[3],
|
||||||
|
3,
|
||||||
|
0,
|
||||||
|
];
|
||||||
|
res.extend_from_slice(&header);
|
||||||
|
let mut prev_pixel = [0, 0, 0, 0xff];
|
||||||
|
let mut array = [[0; 4]; 64];
|
||||||
|
let mut run_length = 0;
|
||||||
|
for line in bytes.chunks_exact(stride as _) {
|
||||||
|
for &pixel in array_chunks::<_, 4>(&line[..(width * 4) as _]) {
|
||||||
|
let pixel = [pixel[2], pixel[1], pixel[0], 0xff];
|
||||||
|
if pixel == prev_pixel {
|
||||||
|
run_length += 1;
|
||||||
|
if run_length == 62 {
|
||||||
|
res.push(OP_RUN | (run_length - 1));
|
||||||
|
run_length = 0;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if run_length > 0 {
|
||||||
|
res.push(OP_RUN | (run_length - 1));
|
||||||
|
run_length = 0;
|
||||||
|
}
|
||||||
|
let prev = prev_pixel;
|
||||||
|
prev_pixel = pixel;
|
||||||
|
let index = {
|
||||||
|
let sum = 0u8
|
||||||
|
.wrapping_add(pixel[0].wrapping_mul(3))
|
||||||
|
.wrapping_add(pixel[1].wrapping_mul(5))
|
||||||
|
.wrapping_add(pixel[2].wrapping_mul(7))
|
||||||
|
.wrapping_add(255u8.wrapping_mul(11));
|
||||||
|
sum & 63
|
||||||
|
};
|
||||||
|
if array[index as usize] == pixel {
|
||||||
|
res.push(OP_INDEX | index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
array[index as usize] = pixel;
|
||||||
|
let dr = pixel[0].wrapping_sub(prev[0]);
|
||||||
|
let dg = pixel[1].wrapping_sub(prev[1]);
|
||||||
|
let db = pixel[2].wrapping_sub(prev[2]);
|
||||||
|
let dr_2 = dr.wrapping_add(2);
|
||||||
|
let dg_2 = dg.wrapping_add(2);
|
||||||
|
let db_2 = db.wrapping_add(2);
|
||||||
|
if dr_2 | dg_2 | db_2 | 3 == 3 {
|
||||||
|
res.push(OP_DIFF | (dr_2 << 4) | (dg_2 << 2) | db_2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let dr_dg_8 = dr.wrapping_sub(dg).wrapping_add(8);
|
||||||
|
let db_dg_8 = db.wrapping_sub(dg).wrapping_add(8);
|
||||||
|
let dg_32 = dg.wrapping_add(32);
|
||||||
|
if (dg_32 | 63 == 63) && (dr_dg_8 | db_dg_8 | 15 == 15) {
|
||||||
|
res.extend_from_slice(&[OP_LUMA | dg_32, (dr_dg_8 << 4) | db_dg_8]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
res.extend_from_slice(&[OP_RGB, pixel[0], pixel[1], pixel[2]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if run_length > 0 {
|
||||||
|
res.push(OP_RUN | (run_length - 1));
|
||||||
|
}
|
||||||
|
res.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 1]);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn array_chunks<T, const N: usize>(slice: &[T]) -> &[[T; N]] {
|
||||||
|
let len = slice.len() / N;
|
||||||
|
unsafe { std::slice::from_raw_parts(slice.as_ptr() as _, len) }
|
||||||
|
}
|
||||||
|
|
@ -869,11 +869,11 @@ impl MetalBackend {
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
Err(e) => return Err(MetalError::ScanoutBuffer(e)),
|
Err(e) => return Err(MetalError::ScanoutBuffer(e)),
|
||||||
};
|
};
|
||||||
let drm_fb = match dev.dev.master.add_fb(bo.dma()) {
|
let drm_fb = match dev.dev.master.add_fb(bo.dmabuf()) {
|
||||||
Ok(fb) => Rc::new(fb),
|
Ok(fb) => Rc::new(fb),
|
||||||
Err(e) => return Err(MetalError::Framebuffer(e)),
|
Err(e) => return Err(MetalError::Framebuffer(e)),
|
||||||
};
|
};
|
||||||
let egl_fb = match dev.dev.egl.dmabuf_fb(bo.dma()) {
|
let egl_fb = match dev.dev.egl.dmabuf_fb(bo.dmabuf()) {
|
||||||
Ok(fb) => fb,
|
Ok(fb) => fb,
|
||||||
Err(e) => return Err(MetalError::ImportFb(e)),
|
Err(e) => return Err(MetalError::ImportFb(e)),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,7 @@ impl XBackendData {
|
||||||
let bo = self
|
let bo = self
|
||||||
.gbm
|
.gbm
|
||||||
.create_bo(width, height, &format, GBM_BO_USE_RENDERING)?;
|
.create_bo(width, height, &format, GBM_BO_USE_RENDERING)?;
|
||||||
let dma = bo.dma();
|
let dma = bo.dmabuf();
|
||||||
assert!(dma.planes.len() == 1);
|
assert!(dma.planes.len() == 1);
|
||||||
let plane = dma.planes.first().unwrap();
|
let plane = dma.planes.first().unwrap();
|
||||||
let size = plane.stride * dma.height as u32;
|
let size = plane.stride * dma.height as u32;
|
||||||
|
|
|
||||||
15
src/cli.rs
15
src/cli.rs
|
|
@ -1,6 +1,7 @@
|
||||||
mod generate;
|
mod generate;
|
||||||
mod log;
|
mod log;
|
||||||
mod quit;
|
mod quit;
|
||||||
|
mod screenshot;
|
||||||
mod set_log_level;
|
mod set_log_level;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
|
|
@ -38,6 +39,19 @@ pub enum Cmd {
|
||||||
SetLogLevel(SetLogArgs),
|
SetLogLevel(SetLogArgs),
|
||||||
/// Stop the compositor.
|
/// Stop the compositor.
|
||||||
Quit,
|
Quit,
|
||||||
|
/// Take a screenshot.
|
||||||
|
Screenshot(ScreenshotArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct ScreenshotArgs {
|
||||||
|
/// The filename of the saved screenshot
|
||||||
|
///
|
||||||
|
/// If no filename is given, the screenshot will be saved under jay-%Y-%m-%d-%H:%M:%S.qoi
|
||||||
|
/// in the current directory.
|
||||||
|
///
|
||||||
|
/// The filename can contain the usual strftime parameters.
|
||||||
|
pub filename: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
|
|
@ -121,5 +135,6 @@ pub fn main() {
|
||||||
Cmd::Log(a) => log::main(cli.global, a),
|
Cmd::Log(a) => log::main(cli.global, a),
|
||||||
Cmd::Quit => quit::main(cli.global),
|
Cmd::Quit => quit::main(cli.global),
|
||||||
Cmd::SetLogLevel(a) => set_log_level::main(cli.global, a),
|
Cmd::SetLogLevel(a) => set_log_level::main(cli.global, a),
|
||||||
|
Cmd::Screenshot(a) => screenshot::main(cli.global, a),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
src/cli/screenshot.rs
Normal file
108
src/cli/screenshot.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
cli::{GlobalArgs, ScreenshotArgs},
|
||||||
|
format::XRGB8888,
|
||||||
|
tools::tool_client::{Handle, ToolClient},
|
||||||
|
utils::{errorfmt::ErrorFmt, queue::AsyncQueue},
|
||||||
|
video::{
|
||||||
|
dmabuf::{DmaBuf, DmaBufPlane},
|
||||||
|
drm::Drm,
|
||||||
|
gbm::{GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING},
|
||||||
|
INVALID_MODIFIER,
|
||||||
|
},
|
||||||
|
wire::{
|
||||||
|
jay_compositor::TakeScreenshot,
|
||||||
|
jay_screenshot::{Dmabuf, Error},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
chrono::Local,
|
||||||
|
qoi::xrgb8888_encode_qoi,
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main(global: GlobalArgs, args: ScreenshotArgs) {
|
||||||
|
let tc = ToolClient::new(global.log_level.into());
|
||||||
|
let screenshot = Rc::new(Screenshot {
|
||||||
|
tc: tc.clone(),
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
tc.run(run(screenshot));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Screenshot {
|
||||||
|
tc: Rc<ToolClient>,
|
||||||
|
args: ScreenshotArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(screenshot: Rc<Screenshot>) {
|
||||||
|
let tc = &screenshot.tc;
|
||||||
|
let comp = tc.jay_compositor().await;
|
||||||
|
let sid = tc.id();
|
||||||
|
tc.send(TakeScreenshot {
|
||||||
|
self_id: comp,
|
||||||
|
id: sid,
|
||||||
|
});
|
||||||
|
let result = Rc::new(AsyncQueue::new());
|
||||||
|
Error::handle(&tc, sid, result.clone(), |res, err| {
|
||||||
|
res.push(Err(err.msg.to_owned()));
|
||||||
|
});
|
||||||
|
Dmabuf::handle(&tc, sid, result.clone(), |res, buf| {
|
||||||
|
res.push(Ok(buf));
|
||||||
|
});
|
||||||
|
let buf = match result.pop().await {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(e) => {
|
||||||
|
fatal!("Could not take a screenshot: {}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let data = buf_to_qoi(&buf);
|
||||||
|
let filename = screenshot
|
||||||
|
.args
|
||||||
|
.filename
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("jay-%Y-%m-%d-%H:%M:%S.qoi");
|
||||||
|
let filename = Local::now().format(filename).to_string();
|
||||||
|
if let Err(e) = std::fs::write(&filename, &data) {
|
||||||
|
fatal!("Could not write `{}`: {}", filename, ErrorFmt(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buf_to_qoi(buf: &Dmabuf) -> Vec<u8> {
|
||||||
|
let drm = match Drm::reopen(buf.drm_dev.raw(), false) {
|
||||||
|
Ok(drm) => drm,
|
||||||
|
Err(e) => {
|
||||||
|
fatal!("Could not open the drm device: {}", ErrorFmt(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let gbm = match GbmDevice::new(&drm) {
|
||||||
|
Ok(g) => g,
|
||||||
|
Err(e) => {
|
||||||
|
fatal!("Could not create a gbm device: {}", ErrorFmt(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let dmabuf = DmaBuf {
|
||||||
|
width: buf.width as _,
|
||||||
|
height: buf.height as _,
|
||||||
|
format: XRGB8888,
|
||||||
|
modifier: INVALID_MODIFIER,
|
||||||
|
planes: vec![DmaBufPlane {
|
||||||
|
offset: buf.offset,
|
||||||
|
stride: buf.stride,
|
||||||
|
fd: buf.fd.clone(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let bo = match gbm.import_dmabuf(&dmabuf, GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING) {
|
||||||
|
Ok(bo) => Rc::new(bo),
|
||||||
|
Err(e) => {
|
||||||
|
fatal!("Could not import screenshot dmabuf: {}", ErrorFmt(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let bo_map = match bo.map() {
|
||||||
|
Ok(map) => map,
|
||||||
|
Err(e) => {
|
||||||
|
fatal!("Could not map dmabuf: {}", ErrorFmt(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let data = unsafe { bo_map.data() };
|
||||||
|
xrgb8888_encode_qoi(data, buf.width, buf.height, buf.stride)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
pub use error::{ClientError, ObjectError};
|
pub use error::{ClientError, MethodError, ObjectError};
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
async_engine::{AsyncFd, SpawnedFuture},
|
async_engine::{AsyncFd, SpawnedFuture},
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,14 @@ impl ClientError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("Could not process a `{method}` request")]
|
||||||
|
pub struct MethodError {
|
||||||
|
pub method: &'static str,
|
||||||
|
#[source]
|
||||||
|
pub error: Box<dyn Error + 'static>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[error("An error occurred in a `{}`", .interface.name())]
|
#[error("An error occurred in a `{}`", .interface.name())]
|
||||||
pub struct ObjectError {
|
pub struct ObjectError {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
pub mod jay_compositor;
|
pub mod jay_compositor;
|
||||||
pub mod jay_log_file;
|
pub mod jay_log_file;
|
||||||
|
pub mod jay_screenshot;
|
||||||
pub mod org_kde_kwin_server_decoration;
|
pub mod org_kde_kwin_server_decoration;
|
||||||
pub mod org_kde_kwin_server_decoration_manager;
|
pub mod org_kde_kwin_server_decoration_manager;
|
||||||
pub mod wl_buffer;
|
pub mod wl_buffer;
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,18 @@ use {
|
||||||
cli::CliLogLevel,
|
cli::CliLogLevel,
|
||||||
client::{Client, ClientError},
|
client::{Client, ClientError},
|
||||||
globals::{Global, GlobalName},
|
globals::{Global, GlobalName},
|
||||||
ifs::jay_log_file::JayLogFile,
|
ifs::{jay_log_file::JayLogFile, jay_screenshot::JayScreenshot},
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::Object,
|
object::Object,
|
||||||
utils::buffd::{MsgParser, MsgParserError},
|
screenshoter::take_screenshot,
|
||||||
|
utils::{
|
||||||
|
buffd::{MsgParser, MsgParserError},
|
||||||
|
errorfmt::ErrorFmt,
|
||||||
|
},
|
||||||
wire::{jay_compositor::*, JayCompositorId},
|
wire::{jay_compositor::*, JayCompositorId},
|
||||||
},
|
},
|
||||||
log::Level,
|
log::Level,
|
||||||
std::rc::Rc,
|
std::{ops::Deref, rc::Rc},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -65,13 +69,13 @@ pub struct JayCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JayCompositor {
|
impl JayCompositor {
|
||||||
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), DestroyError> {
|
fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> {
|
||||||
let _req: Destroy = self.client.parse(self, parser)?;
|
let _req: Destroy = self.client.parse(self, parser)?;
|
||||||
self.client.remove_obj(self)?;
|
self.client.remove_obj(self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_log_file(&self, parser: MsgParser<'_, '_>) -> Result<(), GetLogFileError> {
|
fn get_log_file(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> {
|
||||||
let req: GetLogFile = self.client.parse(self, parser)?;
|
let req: GetLogFile = self.client.parse(self, parser)?;
|
||||||
let log_file = Rc::new(JayLogFile::new(req.id, &self.client));
|
let log_file = Rc::new(JayLogFile::new(req.id, &self.client));
|
||||||
track!(self.client, log_file);
|
track!(self.client, log_file);
|
||||||
|
|
@ -80,14 +84,14 @@ impl JayCompositor {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quit(&self, parser: MsgParser<'_, '_>) -> Result<(), QuitError> {
|
fn quit(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> {
|
||||||
let _req: Quit = self.client.parse(self, parser)?;
|
let _req: Quit = self.client.parse(self, parser)?;
|
||||||
log::info!("Quitting");
|
log::info!("Quitting");
|
||||||
self.client.state.el.stop();
|
self.client.state.el.stop();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_log_level(&self, parser: MsgParser<'_, '_>) -> Result<(), SetLogLevelError> {
|
fn set_log_level(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> {
|
||||||
let req: SetLogLevel = self.client.parse(self, parser)?;
|
let req: SetLogLevel = self.client.parse(self, parser)?;
|
||||||
const ERROR: u32 = CliLogLevel::Error as u32;
|
const ERROR: u32 = CliLogLevel::Error as u32;
|
||||||
const WARN: u32 = CliLogLevel::Warn as u32;
|
const WARN: u32 = CliLogLevel::Warn as u32;
|
||||||
|
|
@ -100,25 +104,57 @@ impl JayCompositor {
|
||||||
INFO => Level::Info,
|
INFO => Level::Info,
|
||||||
DEBUG => Level::Debug,
|
DEBUG => Level::Debug,
|
||||||
TRACE => Level::Trace,
|
TRACE => Level::Trace,
|
||||||
_ => return Err(SetLogLevelError::UnknownLogLevel(req.level)),
|
_ => return Err(JayCompositorError::UnknownLogLevel(req.level)),
|
||||||
};
|
};
|
||||||
self.client.state.logger.set_level(level);
|
self.client.state.logger.set_level(level);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn take_screenshot(&self, parser: MsgParser<'_, '_>) -> Result<(), JayCompositorError> {
|
||||||
|
let req: TakeScreenshot = self.client.parse(self, parser)?;
|
||||||
|
let ss = Rc::new(JayScreenshot {
|
||||||
|
id: req.id,
|
||||||
|
client: self.client.clone(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
});
|
||||||
|
track!(self.client, ss);
|
||||||
|
self.client.add_client_obj(&ss)?;
|
||||||
|
match take_screenshot(&self.client.state) {
|
||||||
|
Ok(s) => {
|
||||||
|
let dmabuf = s.bo.dmabuf();
|
||||||
|
let plane = &dmabuf.planes[0];
|
||||||
|
ss.send_dmabuf(
|
||||||
|
&s.drm,
|
||||||
|
&plane.fd,
|
||||||
|
dmabuf.width,
|
||||||
|
dmabuf.height,
|
||||||
|
plane.offset,
|
||||||
|
plane.stride,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let msg = ErrorFmt(e).to_string();
|
||||||
|
ss.send_error(&msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.client.remove_obj(ss.deref())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object_base! {
|
object_base2! {
|
||||||
JayCompositor, JayCompositorError;
|
JayCompositor;
|
||||||
|
|
||||||
DESTROY => destroy,
|
DESTROY => destroy,
|
||||||
GET_LOG_FILE => get_log_file,
|
GET_LOG_FILE => get_log_file,
|
||||||
QUIT => quit,
|
QUIT => quit,
|
||||||
SET_LOG_LEVEL => set_log_level,
|
SET_LOG_LEVEL => set_log_level,
|
||||||
|
TAKE_SCREENSHOT => take_screenshot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object for JayCompositor {
|
impl Object for JayCompositor {
|
||||||
fn num_requests(&self) -> u32 {
|
fn num_requests(&self) -> u32 {
|
||||||
SET_LOG_LEVEL + 1
|
TAKE_SCREENSHOT + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,51 +162,12 @@ simple_add_obj!(JayCompositor);
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum JayCompositorError {
|
pub enum JayCompositorError {
|
||||||
#[error("Could not process a `destroy` request")]
|
|
||||||
DestroyError(#[from] DestroyError),
|
|
||||||
#[error("Could not process a `get_log_file` request")]
|
|
||||||
GetLogFileError(#[from] GetLogFileError),
|
|
||||||
#[error("Could not process a `quit` request")]
|
|
||||||
QuitError(#[from] QuitError),
|
|
||||||
#[error("Could not process a `set_log_level` request")]
|
|
||||||
SetLogLevelError(#[from] SetLogLevelError),
|
|
||||||
#[error(transparent)]
|
|
||||||
ClientError(Box<ClientError>),
|
|
||||||
}
|
|
||||||
efrom!(JayCompositorError, ClientError);
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum DestroyError {
|
|
||||||
#[error("Parsing failed")]
|
#[error("Parsing failed")]
|
||||||
MsgParserError(#[source] Box<MsgParserError>),
|
MsgParserError(#[source] Box<MsgParserError>),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ClientError(Box<ClientError>),
|
ClientError(Box<ClientError>),
|
||||||
}
|
|
||||||
efrom!(DestroyError, ClientError);
|
|
||||||
efrom!(DestroyError, MsgParserError);
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum GetLogFileError {
|
|
||||||
#[error("Parsing failed")]
|
|
||||||
MsgParserError(#[source] Box<MsgParserError>),
|
|
||||||
#[error(transparent)]
|
|
||||||
ClientError(Box<ClientError>),
|
|
||||||
}
|
|
||||||
efrom!(GetLogFileError, ClientError);
|
|
||||||
efrom!(GetLogFileError, MsgParserError);
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum QuitError {
|
|
||||||
#[error("Parsing failed")]
|
|
||||||
MsgParserError(#[source] Box<MsgParserError>),
|
|
||||||
}
|
|
||||||
efrom!(QuitError, MsgParserError);
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum SetLogLevelError {
|
|
||||||
#[error("Parsing failed")]
|
|
||||||
MsgParserError(#[source] Box<MsgParserError>),
|
|
||||||
#[error("Unknown log level {0}")]
|
#[error("Unknown log level {0}")]
|
||||||
UnknownLogLevel(u32),
|
UnknownLogLevel(u32),
|
||||||
}
|
}
|
||||||
efrom!(SetLogLevelError, MsgParserError);
|
efrom!(JayCompositorError, ClientError);
|
||||||
|
efrom!(JayCompositorError, MsgParserError);
|
||||||
|
|
|
||||||
57
src/ifs/jay_screenshot.rs
Normal file
57
src/ifs/jay_screenshot.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::Client,
|
||||||
|
leaks::Tracker,
|
||||||
|
object::Object,
|
||||||
|
wire::{jay_screenshot::*, JayScreenshotId},
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
uapi::OwnedFd,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct JayScreenshot {
|
||||||
|
pub id: JayScreenshotId,
|
||||||
|
pub client: Rc<Client>,
|
||||||
|
pub tracker: Tracker<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JayScreenshot {
|
||||||
|
pub fn send_dmabuf(
|
||||||
|
&self,
|
||||||
|
drm_dev: &Rc<OwnedFd>,
|
||||||
|
fd: &Rc<OwnedFd>,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
offset: u32,
|
||||||
|
stride: u32,
|
||||||
|
) {
|
||||||
|
self.client.event(Dmabuf {
|
||||||
|
self_id: self.id,
|
||||||
|
drm_dev: drm_dev.clone(),
|
||||||
|
fd: fd.clone(),
|
||||||
|
width: width as _,
|
||||||
|
height: height as _,
|
||||||
|
offset,
|
||||||
|
stride,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_error(&self, msg: &str) {
|
||||||
|
self.client.event(Error {
|
||||||
|
self_id: self.id,
|
||||||
|
msg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_base2! {
|
||||||
|
JayScreenshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for JayScreenshot {
|
||||||
|
fn num_requests(&self) -> u32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_add_obj!(JayScreenshot);
|
||||||
|
|
@ -11,6 +11,44 @@ macro_rules! efrom {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! object_base2 {
|
||||||
|
($oname:ident; $($code:ident => $f:ident,)*) => {
|
||||||
|
impl crate::object::ObjectBase for $oname {
|
||||||
|
fn id(&self) -> crate::object::ObjectId {
|
||||||
|
self.id.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables, unreachable_code)]
|
||||||
|
fn handle_request(
|
||||||
|
self: std::rc::Rc<Self>,
|
||||||
|
request: u32,
|
||||||
|
parser: crate::utils::buffd::MsgParser<'_, '_>,
|
||||||
|
) -> Result<(), crate::client::ClientError> {
|
||||||
|
let res: Result<(), crate::client::MethodError> = match request {
|
||||||
|
$(
|
||||||
|
$code => $oname::$f(&self, parser).map_err(|e| crate::client::MethodError {
|
||||||
|
method: stringify!($f),
|
||||||
|
error: Box::new(e),
|
||||||
|
}),
|
||||||
|
)*
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(crate::client::ClientError::ObjectError(crate::client::ObjectError {
|
||||||
|
interface: crate::wire::$oname,
|
||||||
|
error: Box::new(e),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interface(&self) -> crate::object::Interface {
|
||||||
|
crate::wire::$oname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! object_base {
|
macro_rules! object_base {
|
||||||
($oname:ident, $ename:ty; $($code:ident => $f:ident,)*) => {
|
($oname:ident, $ename:ty; $($code:ident => $f:ident,)*) => {
|
||||||
impl crate::object::ObjectBase for $oname {
|
impl crate::object::ObjectBase for $oname {
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ mod object;
|
||||||
mod pango;
|
mod pango;
|
||||||
mod rect;
|
mod rect;
|
||||||
mod render;
|
mod render;
|
||||||
|
mod screenshoter;
|
||||||
mod sighand;
|
mod sighand;
|
||||||
mod state;
|
mod state;
|
||||||
mod tasks;
|
mod tasks;
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ use {
|
||||||
pub struct EglDisplay {
|
pub struct EglDisplay {
|
||||||
pub exts: DisplayExt,
|
pub exts: DisplayExt,
|
||||||
pub formats: Rc<AHashMap<u32, &'static Format>>,
|
pub formats: Rc<AHashMap<u32, &'static Format>>,
|
||||||
pub gbm: GbmDevice,
|
pub gbm: Rc<GbmDevice>,
|
||||||
pub dpy: EGLDisplay,
|
pub dpy: EGLDisplay,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ impl EglDisplay {
|
||||||
let mut dpy = EglDisplay {
|
let mut dpy = EglDisplay {
|
||||||
exts: DisplayExt::empty(),
|
exts: DisplayExt::empty(),
|
||||||
formats: Rc::new(AHashMap::new()),
|
formats: Rc::new(AHashMap::new()),
|
||||||
gbm,
|
gbm: Rc::new(gbm),
|
||||||
dpy,
|
dpy,
|
||||||
};
|
};
|
||||||
let mut major = 0;
|
let mut major = 0;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use {
|
||||||
video::{
|
video::{
|
||||||
dmabuf::DmaBuf,
|
dmabuf::DmaBuf,
|
||||||
drm::{Drm, NodeType},
|
drm::{Drm, NodeType},
|
||||||
|
gbm::GbmDevice,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ahash::AHashMap,
|
ahash::AHashMap,
|
||||||
|
|
@ -44,6 +45,7 @@ impl TexProg {
|
||||||
|
|
||||||
pub struct RenderContext {
|
pub struct RenderContext {
|
||||||
pub(super) ctx: Rc<EglContext>,
|
pub(super) ctx: Rc<EglContext>,
|
||||||
|
pub gbm: Rc<GbmDevice>,
|
||||||
|
|
||||||
pub(super) render_node: Rc<CString>,
|
pub(super) render_node: Rc<CString>,
|
||||||
|
|
||||||
|
|
@ -97,6 +99,7 @@ impl RenderContext {
|
||||||
)?;
|
)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ctx: ctx.clone(),
|
ctx: ctx.clone(),
|
||||||
|
gbm: ctx.dpy.gbm.clone(),
|
||||||
|
|
||||||
render_node: node.clone(),
|
render_node: node.clone(),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,11 @@ impl Framebuffer {
|
||||||
|
|
||||||
pub fn render(&self, node: &dyn Node, state: &State, cursor_rect: Option<Rect>) {
|
pub fn render(&self, node: &dyn Node, state: &State, cursor_rect: Option<Rect>) {
|
||||||
let _ = self.ctx.ctx.with_current(|| {
|
let _ = self.ctx.ctx.with_current(|| {
|
||||||
|
let c = state.theme.background_color.get();
|
||||||
unsafe {
|
unsafe {
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo);
|
||||||
glViewport(0, 0, self.gl.width, self.gl.height);
|
glViewport(0, 0, self.gl.width, self.gl.height);
|
||||||
glClearColor(0.0, 0.0, 0.0, 1.0);
|
glClearColor(c.r, c.g, c.b, 1.0);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use {
|
||||||
},
|
},
|
||||||
state::State,
|
state::State,
|
||||||
theme::Color,
|
theme::Color,
|
||||||
tree::{ContainerNode, FloatNode, OutputNode, WorkspaceNode},
|
tree::{ContainerNode, DisplayNode, FloatNode, OutputNode, WorkspaceNode},
|
||||||
utils::rc_eq::rc_eq,
|
utils::rc_eq::rc_eq,
|
||||||
},
|
},
|
||||||
std::{ops::Deref, rc::Rc, slice},
|
std::{ops::Deref, rc::Rc, slice},
|
||||||
|
|
@ -38,6 +38,16 @@ pub struct Renderer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer<'_> {
|
impl Renderer<'_> {
|
||||||
|
pub fn render_display(&mut self, display: &DisplayNode, x: i32, y: i32) {
|
||||||
|
let ext = display.extents.get();
|
||||||
|
let outputs = display.outputs.lock();
|
||||||
|
for output in outputs.values() {
|
||||||
|
let opos = output.global.pos.get();
|
||||||
|
let (ox, oy) = ext.translate(opos.x1(), opos.y1());
|
||||||
|
self.render_output(output, x + ox, y + oy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_output(&mut self, output: &OutputNode, x: i32, y: i32) {
|
pub fn render_output(&mut self, output: &OutputNode, x: i32, y: i32) {
|
||||||
let opos = output.global.pos.get();
|
let opos = output.global.pos.get();
|
||||||
macro_rules! render_layer {
|
macro_rules! render_layer {
|
||||||
|
|
@ -57,15 +67,22 @@ impl Renderer<'_> {
|
||||||
let theme = &self.state.theme;
|
let theme = &self.state.theme;
|
||||||
let th = theme.title_height.get();
|
let th = theme.title_height.get();
|
||||||
{
|
{
|
||||||
|
let c = Color::BLACK;
|
||||||
|
self.fill_boxes2(
|
||||||
|
slice::from_ref(&Rect::new_sized(0, 0, opos.width(), th).unwrap()),
|
||||||
|
&c,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
);
|
||||||
let rd = output.render_data.borrow_mut();
|
let rd = output.render_data.borrow_mut();
|
||||||
if let Some(aw) = &rd.active_workspace {
|
if let Some(aw) = &rd.active_workspace {
|
||||||
let c = theme.active_title_color.get();
|
let c = theme.active_title_color.get();
|
||||||
self.fill_boxes(slice::from_ref(aw), &c);
|
self.fill_boxes2(slice::from_ref(aw), &c, x, y);
|
||||||
}
|
}
|
||||||
let c = theme.underline_color.get();
|
let c = theme.underline_color.get();
|
||||||
self.fill_boxes(slice::from_ref(&rd.underline), &c);
|
self.fill_boxes2(slice::from_ref(&rd.underline), &c, x, y);
|
||||||
let c = theme.title_color.get();
|
let c = theme.title_color.get();
|
||||||
self.fill_boxes(&rd.inactive_workspaces, &c);
|
self.fill_boxes2(&rd.inactive_workspaces, &c, x, y);
|
||||||
for title in &rd.titles {
|
for title in &rd.titles {
|
||||||
self.render_texture(&title.tex, x + title.x, y + title.y, ARGB8888);
|
self.render_texture(&title.tex, x + title.x, y + title.y, ARGB8888);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
src/screenshoter.rs
Normal file
59
src/screenshoter.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
format::XRGB8888,
|
||||||
|
render::RenderError,
|
||||||
|
state::State,
|
||||||
|
video::{
|
||||||
|
drm::DrmError,
|
||||||
|
gbm::{GbmBo, GbmError, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING},
|
||||||
|
ModifiedFormat, INVALID_MODIFIER,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::{ops::Deref, rc::Rc},
|
||||||
|
thiserror::Error,
|
||||||
|
uapi::OwnedFd,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ScreenshooterError {
|
||||||
|
#[error("There is no render context")]
|
||||||
|
NoRenderContext,
|
||||||
|
#[error("Display is empty")]
|
||||||
|
EmptyDisplay,
|
||||||
|
#[error(transparent)]
|
||||||
|
GbmError(#[from] GbmError),
|
||||||
|
#[error(transparent)]
|
||||||
|
RenderError(#[from] RenderError),
|
||||||
|
#[error(transparent)]
|
||||||
|
DrmError(#[from] DrmError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Screenshot {
|
||||||
|
pub drm: Rc<OwnedFd>,
|
||||||
|
pub bo: GbmBo,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_screenshot(state: &State) -> Result<Screenshot, ScreenshooterError> {
|
||||||
|
let ctx = match state.render_ctx.get() {
|
||||||
|
Some(ctx) => ctx,
|
||||||
|
_ => return Err(ScreenshooterError::NoRenderContext),
|
||||||
|
};
|
||||||
|
let extents = state.root.extents.get();
|
||||||
|
if extents.is_empty() {
|
||||||
|
return Err(ScreenshooterError::EmptyDisplay);
|
||||||
|
}
|
||||||
|
let format = ModifiedFormat {
|
||||||
|
format: XRGB8888,
|
||||||
|
modifier: INVALID_MODIFIER,
|
||||||
|
};
|
||||||
|
let bo = ctx.gbm.create_bo(
|
||||||
|
extents.width(),
|
||||||
|
extents.height(),
|
||||||
|
&format,
|
||||||
|
GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR,
|
||||||
|
)?;
|
||||||
|
let fb = ctx.dmabuf_fb(bo.dmabuf())?;
|
||||||
|
fb.render(state.root.deref(), state, Some(state.root.extents.get()));
|
||||||
|
let drm = ctx.gbm.drm.dup_render()?.fd().clone();
|
||||||
|
Ok(Screenshot { drm, bo })
|
||||||
|
}
|
||||||
|
|
@ -123,6 +123,7 @@ impl ConnectorHandler {
|
||||||
}
|
}
|
||||||
on.update_render_data();
|
on.update_render_data();
|
||||||
self.state.root.outputs.set(self.id, on.clone());
|
self.state.root.outputs.set(self.id, on.clone());
|
||||||
|
self.state.root.update_extents();
|
||||||
self.state.add_global(&global);
|
self.state.add_global(&global);
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
while let Some(event) = self.data.connector.event() {
|
while let Some(event) = self.data.connector.event() {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,13 @@ impl Color {
|
||||||
b: 0.8,
|
b: 0.8,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const BLACK: Self = Self {
|
||||||
|
r: 0.0,
|
||||||
|
g: 0.0,
|
||||||
|
b: 0.0,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_f32(c: u8) -> f32 {
|
fn to_f32(c: u8) -> f32 {
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,19 @@ use {
|
||||||
wl_seat::{NodeSeatState, WlSeatGlobal},
|
wl_seat::{NodeSeatState, WlSeatGlobal},
|
||||||
zwlr_layer_shell_v1::{OVERLAY, TOP},
|
zwlr_layer_shell_v1::{OVERLAY, TOP},
|
||||||
},
|
},
|
||||||
|
rect::Rect,
|
||||||
|
render::Renderer,
|
||||||
tree::{
|
tree::{
|
||||||
walker::NodeVisitor, FindTreeResult, FoundNode, Node, NodeId, OutputNode, SizedNode,
|
walker::NodeVisitor, FindTreeResult, FoundNode, Node, NodeId, OutputNode, SizedNode,
|
||||||
},
|
},
|
||||||
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
|
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
|
||||||
},
|
},
|
||||||
std::{ops::Deref, rc::Rc},
|
std::{cell::Cell, ops::Deref, rc::Rc},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct DisplayNode {
|
pub struct DisplayNode {
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
|
pub extents: Cell<Rect>,
|
||||||
pub outputs: CopyHashMap<ConnectorId, Rc<OutputNode>>,
|
pub outputs: CopyHashMap<ConnectorId, Rc<OutputNode>>,
|
||||||
pub stacked: LinkedList<Rc<dyn Node>>,
|
pub stacked: LinkedList<Rc<dyn Node>>,
|
||||||
pub seat_state: NodeSeatState,
|
pub seat_state: NodeSeatState,
|
||||||
|
|
@ -25,11 +28,36 @@ impl DisplayNode {
|
||||||
pub fn new(id: NodeId) -> Self {
|
pub fn new(id: NodeId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
|
extents: Default::default(),
|
||||||
outputs: Default::default(),
|
outputs: Default::default(),
|
||||||
stacked: Default::default(),
|
stacked: Default::default(),
|
||||||
seat_state: Default::default(),
|
seat_state: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_extents(&self) {
|
||||||
|
let outputs = self.outputs.lock();
|
||||||
|
let mut x1 = i32::MAX;
|
||||||
|
let mut y1 = i32::MAX;
|
||||||
|
let mut x2 = i32::MIN;
|
||||||
|
let mut y2 = i32::MIN;
|
||||||
|
for output in outputs.values() {
|
||||||
|
let pos = output.global.pos.get();
|
||||||
|
x1 = x1.min(pos.x1());
|
||||||
|
y1 = y1.min(pos.y1());
|
||||||
|
x2 = x2.max(pos.x2());
|
||||||
|
y2 = y2.max(pos.y2());
|
||||||
|
}
|
||||||
|
if x1 >= x2 {
|
||||||
|
x1 = 0;
|
||||||
|
x2 = 0;
|
||||||
|
}
|
||||||
|
if y1 >= y2 {
|
||||||
|
y1 = 0;
|
||||||
|
y2 = 0;
|
||||||
|
}
|
||||||
|
self.extents.set(Rect::new(x1, y1, x2, y2).unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SizedNode for DisplayNode {
|
impl SizedNode for DisplayNode {
|
||||||
|
|
@ -132,4 +160,8 @@ impl SizedNode for DisplayNode {
|
||||||
fn pointer_focus(&self, seat: &Rc<WlSeatGlobal>) {
|
fn pointer_focus(&self, seat: &Rc<WlSeatGlobal>) {
|
||||||
seat.set_known_cursor(KnownCursor::Default);
|
seat.set_known_cursor(KnownCursor::Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render(&self, renderer: &mut Renderer, x: i32, y: i32) {
|
||||||
|
renderer.render_display(self, x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,7 @@ impl OutputNode {
|
||||||
|
|
||||||
fn change_extents_(&self, rect: &Rect) {
|
fn change_extents_(&self, rect: &Rect) {
|
||||||
self.global.pos.set(*rect);
|
self.global.pos.set(*rect);
|
||||||
|
self.state.root.update_extents();
|
||||||
self.update_render_data();
|
self.update_render_data();
|
||||||
if let Some(c) = self.workspace.get() {
|
if let Some(c) = self.workspace.get() {
|
||||||
c.node_change_extents(&self.workspace_rect());
|
c.node_change_extents(&self.workspace_rect());
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,12 @@ impl From<std::io::Error> for OsError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for OsError {
|
||||||
|
fn default() -> Self {
|
||||||
|
Errno::default().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Error for OsError {}
|
impl Error for OsError {}
|
||||||
|
|
||||||
impl Display for OsError {
|
impl Display for OsError {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pub trait WindowsExt<T> {
|
||||||
T: 'a;
|
T: 'a;
|
||||||
|
|
||||||
fn array_windows_ext<'a, const N: usize>(&'a self) -> Self::Windows<'a, N>;
|
fn array_windows_ext<'a, const N: usize>(&'a self) -> Self::Windows<'a, N>;
|
||||||
|
fn array_chunks_ext<'a, const N: usize>(&'a self) -> &'a [[T; N]];
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> WindowsExt<T> for [T] {
|
impl<T> WindowsExt<T> for [T] {
|
||||||
|
|
@ -18,6 +19,11 @@ impl<T> WindowsExt<T> for [T] {
|
||||||
fn array_windows_ext<'a, const N: usize>(&'a self) -> Self::Windows<'a, N> {
|
fn array_windows_ext<'a, const N: usize>(&'a self) -> Self::Windows<'a, N> {
|
||||||
WindowsIter { slice: self }
|
WindowsIter { slice: self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn array_chunks_ext<'a, const N: usize>(&'a self) -> &'a [[T; N]] {
|
||||||
|
let len = self.len() / N;
|
||||||
|
unsafe { std::slice::from_raw_parts(self.as_ptr() as _, len) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WindowsIter<'a, T, const N: usize> {
|
pub struct WindowsIter<'a, T, const N: usize> {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ pub mod gbm;
|
||||||
pub type Modifier = u64;
|
pub type Modifier = u64;
|
||||||
|
|
||||||
pub const INVALID_MODIFIER: Modifier = 0x00ff_ffff_ffff_ffff;
|
pub const INVALID_MODIFIER: Modifier = 0x00ff_ffff_ffff_ffff;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const LINEAR_MODIFIER: Modifier = 0;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct ModifiedFormat {
|
pub struct ModifiedFormat {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use {crate::format::Format, std::rc::Rc, uapi::OwnedFd};
|
use {crate::format::Format, std::rc::Rc, uapi::OwnedFd};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct DmaBufPlane {
|
pub struct DmaBufPlane {
|
||||||
pub offset: u32,
|
pub offset: u32,
|
||||||
pub stride: u32,
|
pub stride: u32,
|
||||||
pub fd: Rc<OwnedFd>,
|
pub fd: Rc<OwnedFd>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct DmaBuf {
|
pub struct DmaBuf {
|
||||||
pub width: i32,
|
pub width: i32,
|
||||||
pub height: i32,
|
pub height: i32,
|
||||||
|
|
|
||||||
142
src/video/gbm.rs
142
src/video/gbm.rs
|
|
@ -1,6 +1,9 @@
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
format::formats,
|
format::formats,
|
||||||
|
utils::oserror::OsError,
|
||||||
video::{
|
video::{
|
||||||
dmabuf::{DmaBuf, DmaBufPlane},
|
dmabuf::{DmaBuf, DmaBufPlane},
|
||||||
drm::{Drm, DrmError},
|
drm::{Drm, DrmError},
|
||||||
|
|
@ -11,6 +14,7 @@ use {
|
||||||
fmt::{Debug, Formatter},
|
fmt::{Debug, Formatter},
|
||||||
ptr,
|
ptr,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
slice,
|
||||||
},
|
},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
uapi::{c, OwnedFd},
|
uapi::{c, OwnedFd},
|
||||||
|
|
@ -28,6 +32,8 @@ pub enum GbmError {
|
||||||
UnknownFormat,
|
UnknownFormat,
|
||||||
#[error("Could not retrieve a drm-buf fd")]
|
#[error("Could not retrieve a drm-buf fd")]
|
||||||
DrmFd,
|
DrmFd,
|
||||||
|
#[error("Could not map bo")]
|
||||||
|
MapBo(#[source] OsError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Device = u8;
|
pub type Device = u8;
|
||||||
|
|
@ -39,16 +45,42 @@ pub const GBM_BO_USE_CURSOR: u32 = 1 << 1;
|
||||||
pub const GBM_BO_USE_RENDERING: u32 = 1 << 2;
|
pub const GBM_BO_USE_RENDERING: u32 = 1 << 2;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const GBM_BO_USE_WRITE: u32 = 1 << 3;
|
pub const GBM_BO_USE_WRITE: u32 = 1 << 3;
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const GBM_BO_USE_LINEAR: u32 = 1 << 4;
|
pub const GBM_BO_USE_LINEAR: u32 = 1 << 4;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const GBM_BO_USE_PROTECTED: u32 = 1 << 5;
|
pub const GBM_BO_USE_PROTECTED: u32 = 1 << 5;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const GBM_BO_IMPORT_WL_BUFFER: u32 = 0x5501;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const GBM_BO_IMPORT_EGL_IMAGE: u32 = 0x5502;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const GBM_BO_IMPORT_FD: u32 = 0x5503;
|
||||||
|
const GBM_BO_IMPORT_FD_MODIFIER: u32 = 0x5504;
|
||||||
|
|
||||||
|
const GBM_BO_TRANSFER_READ: u32 = 1 << 0;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const GBM_BO_TRANSFER_WRITE: u32 = 1 << 1;
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const GBM_BO_TRANSFER_READ_WRITE: u32 = GBM_BO_TRANSFER_READ | GBM_BO_TRANSFER_WRITE;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct gbm_import_fd_modifier_data {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
format: u32,
|
||||||
|
num_fds: u32,
|
||||||
|
fds: [c::c_int; 4],
|
||||||
|
strides: [c::c_int; 4],
|
||||||
|
offsets: [c::c_int; 4],
|
||||||
|
modifier: u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[link(name = "gbm")]
|
#[link(name = "gbm")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn gbm_create_device(fd: c::c_int) -> *mut Device;
|
fn gbm_create_device(fd: c::c_int) -> *mut Device;
|
||||||
fn gbm_device_destroy(dev: *mut Device);
|
fn gbm_device_destroy(dev: *mut Device);
|
||||||
|
|
||||||
|
fn gbm_bo_import(dev: *mut Device, ty: u32, buffer: *mut u8, flags: u32) -> *mut Bo;
|
||||||
fn gbm_bo_create_with_modifiers2(
|
fn gbm_bo_create_with_modifiers2(
|
||||||
dev: *mut Device,
|
dev: *mut Device,
|
||||||
width: u32,
|
width: u32,
|
||||||
|
|
@ -71,10 +103,21 @@ extern "C" {
|
||||||
fn gbm_bo_get_format(bo: *mut Bo) -> u32;
|
fn gbm_bo_get_format(bo: *mut Bo) -> u32;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn gbm_bo_get_bpp(bo: *mut Bo) -> u32;
|
fn gbm_bo_get_bpp(bo: *mut Bo) -> u32;
|
||||||
|
fn gbm_bo_map(
|
||||||
|
bo: *mut Bo,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
flags: u32,
|
||||||
|
strid: *mut u32,
|
||||||
|
map_data: *mut *mut u8,
|
||||||
|
) -> *mut u8;
|
||||||
|
fn gbm_bo_unmap(bo: *mut Bo, map_data: *mut u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GbmDevice {
|
pub struct GbmDevice {
|
||||||
_drm: Drm,
|
pub drm: Drm,
|
||||||
dev: *mut Device,
|
dev: *mut Device,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,8 +132,20 @@ struct BoHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GbmBo {
|
pub struct GbmBo {
|
||||||
_bo: BoHolder,
|
bo: BoHolder,
|
||||||
dma: DmaBuf,
|
dmabuf: DmaBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GbmBoMap {
|
||||||
|
bo: Rc<GbmBo>,
|
||||||
|
data: *mut [u8],
|
||||||
|
opaque: *mut u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GbmBoMap {
|
||||||
|
pub unsafe fn data(&self) -> &[u8] {
|
||||||
|
&*self.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn export_bo(bo: *mut Bo) -> Result<DmaBuf, GbmError> {
|
unsafe fn export_bo(bo: *mut Bo) -> Result<DmaBuf, GbmError> {
|
||||||
|
|
@ -132,7 +187,7 @@ impl GbmDevice {
|
||||||
if dev.is_null() {
|
if dev.is_null() {
|
||||||
Err(GbmError::CreateDevice)
|
Err(GbmError::CreateDevice)
|
||||||
} else {
|
} else {
|
||||||
Ok(Self { _drm: drm, dev })
|
Ok(Self { drm: drm, dev })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,7 +222,44 @@ impl GbmDevice {
|
||||||
}
|
}
|
||||||
let bo = BoHolder { bo };
|
let bo = BoHolder { bo };
|
||||||
let dma = export_bo(bo.bo)?;
|
let dma = export_bo(bo.bo)?;
|
||||||
Ok(GbmBo { _bo: bo, dma })
|
Ok(GbmBo {
|
||||||
|
bo: bo,
|
||||||
|
dmabuf: dma,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_dmabuf(&self, dmabuf: &DmaBuf, usage: u32) -> Result<GbmBo, GbmError> {
|
||||||
|
let mut import = gbm_import_fd_modifier_data {
|
||||||
|
width: dmabuf.width as _,
|
||||||
|
height: dmabuf.height as _,
|
||||||
|
format: dmabuf.format.drm as _,
|
||||||
|
num_fds: dmabuf.planes.len() as _,
|
||||||
|
fds: [0; 4],
|
||||||
|
strides: [0; 4],
|
||||||
|
offsets: [0; 4],
|
||||||
|
modifier: dmabuf.modifier,
|
||||||
|
};
|
||||||
|
for (i, plane) in dmabuf.planes.iter().enumerate() {
|
||||||
|
import.fds[i] = plane.fd.raw();
|
||||||
|
import.strides[i] = plane.stride as _;
|
||||||
|
import.offsets[i] = plane.offset as _;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let bo = gbm_bo_import(
|
||||||
|
self.dev,
|
||||||
|
GBM_BO_IMPORT_FD_MODIFIER,
|
||||||
|
&mut import as *const _ as _,
|
||||||
|
usage,
|
||||||
|
);
|
||||||
|
if bo.is_null() {
|
||||||
|
return Err(GbmError::CreateBo);
|
||||||
|
}
|
||||||
|
let bo = BoHolder { bo };
|
||||||
|
Ok(GbmBo {
|
||||||
|
bo: bo,
|
||||||
|
dmabuf: dmabuf.clone(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,8 +273,42 @@ impl Drop for GbmDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GbmBo {
|
impl GbmBo {
|
||||||
pub fn dma(&self) -> &DmaBuf {
|
pub fn dmabuf(&self) -> &DmaBuf {
|
||||||
&self.dma
|
&self.dmabuf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(self: &Rc<Self>) -> Result<GbmBoMap, GbmError> {
|
||||||
|
let mut stride = 0;
|
||||||
|
let mut map_data = ptr::null_mut();
|
||||||
|
unsafe {
|
||||||
|
let map = gbm_bo_map(
|
||||||
|
self.bo.bo,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
self.dmabuf.width as _,
|
||||||
|
self.dmabuf.height as _,
|
||||||
|
GBM_BO_TRANSFER_READ,
|
||||||
|
&mut stride,
|
||||||
|
&mut map_data,
|
||||||
|
);
|
||||||
|
if map.is_null() {
|
||||||
|
return Err(GbmError::MapBo(OsError::default()));
|
||||||
|
}
|
||||||
|
let map = slice::from_raw_parts_mut(map, (stride * self.dmabuf.height as u32) as usize);
|
||||||
|
Ok(GbmBoMap {
|
||||||
|
bo: self.clone(),
|
||||||
|
data: map,
|
||||||
|
opaque: map_data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for GbmBoMap {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
gbm_bo_unmap(self.bo.bo.bo, self.opaque);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,7 @@ msg quit = 2 {
|
||||||
msg set_log_level = 3 {
|
msg set_log_level = 3 {
|
||||||
level: u32,
|
level: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg take_screenshot = 4 {
|
||||||
|
id: id(jay_screenshot),
|
||||||
|
}
|
||||||
|
|
|
||||||
14
wire/jay_screenshot.txt
Normal file
14
wire/jay_screenshot.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# events
|
||||||
|
|
||||||
|
msg dmabuf = 0 {
|
||||||
|
drm_dev: fd,
|
||||||
|
fd: fd,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
offset: u32,
|
||||||
|
stride: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
msg error = 1 {
|
||||||
|
msg: str,
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue