pub mod error;
pub mod geometry;
mod wayland_capture;
pub use error::{Error, Result};
pub use geometry::Box;
use wayland_capture::WaylandCapture as PlatformCapture;
#[derive(Debug, Clone)]
pub struct CaptureResult {
data: Vec<u8>,
width: u32,
height: u32,
}
impl CaptureResult {
pub fn new(data: Vec<u8>, width: u32, height: u32) -> Self {
Self {
data,
width,
height,
}
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn into_data(self) -> Vec<u8> {
self.data
}
}
#[derive(Debug, Clone)]
pub struct Output {
name: String,
geometry: Box,
scale: i32,
description: Option<String>,
}
impl Output {
pub fn name(&self) -> &str {
&self.name
}
pub fn geometry(&self) -> &Box {
&self.geometry
}
pub fn scale(&self) -> i32 {
self.scale
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
}
#[derive(Debug, Clone)]
pub struct CaptureParameters {
output_name: String,
region: Option<Box>,
overlay_cursor: bool,
scale: Option<f64>,
}
impl CaptureParameters {
pub fn new(output_name: impl Into<String>) -> Self {
Self {
output_name: output_name.into(),
region: None,
overlay_cursor: false,
scale: None,
}
}
pub fn region(mut self, region: Box) -> Self {
self.region = Some(region);
self
}
pub fn overlay_cursor(mut self, overlay_cursor: bool) -> Self {
self.overlay_cursor = overlay_cursor;
self
}
pub fn scale(mut self, scale: f64) -> Self {
self.scale = Some(scale);
self
}
pub fn output_name(&self) -> &str {
&self.output_name
}
pub fn region_ref(&self) -> Option<&Box> {
self.region.as_ref()
}
pub fn overlay_cursor_enabled(&self) -> bool {
self.overlay_cursor
}
pub fn scale_factor(&self) -> Option<f64> {
self.scale
}
}
#[derive(Debug, Clone)]
pub struct MultiOutputCaptureResult {
outputs: std::collections::HashMap<String, CaptureResult>,
}
impl MultiOutputCaptureResult {
pub fn new(outputs: std::collections::HashMap<String, CaptureResult>) -> Self {
Self { outputs }
}
pub fn get(&self, output_name: &str) -> Option<&CaptureResult> {
self.outputs.get(output_name)
}
pub fn outputs(&self) -> &std::collections::HashMap<String, CaptureResult> {
&self.outputs
}
pub fn into_outputs(self) -> std::collections::HashMap<String, CaptureResult> {
self.outputs
}
}
pub struct Grim {
platform_capture: PlatformCapture,
}
impl Grim {
pub fn new() -> Result<Self> {
let platform_capture = PlatformCapture::new()?;
Ok(Self { platform_capture })
}
pub fn get_outputs(&mut self) -> Result<Vec<Output>> {
self.platform_capture.get_outputs()
}
pub fn capture_all(&mut self) -> Result<CaptureResult> {
self.platform_capture.capture_all()
}
pub fn capture_all_with_scale(&mut self, scale: f64) -> Result<CaptureResult> {
self.platform_capture.capture_all_with_scale(scale)
}
pub fn capture_output(&mut self, output_name: &str) -> Result<CaptureResult> {
self.platform_capture.capture_output(output_name)
}
pub fn capture_output_with_scale(
&mut self,
output_name: &str,
scale: f64,
) -> Result<CaptureResult> {
self.platform_capture
.capture_output_with_scale(output_name, scale)
}
pub fn capture_region(&mut self, region: Box) -> Result<CaptureResult> {
self.platform_capture.capture_region(region)
}
pub fn capture_region_with_scale(&mut self, region: Box, scale: f64) -> Result<CaptureResult> {
self.platform_capture
.capture_region_with_scale(region, scale)
}
pub fn capture_outputs(
&mut self,
parameters: Vec<CaptureParameters>,
) -> Result<MultiOutputCaptureResult> {
self.platform_capture.capture_outputs(parameters)
}
pub fn capture_outputs_with_scale(
&mut self,
parameters: Vec<CaptureParameters>,
default_scale: f64,
) -> Result<MultiOutputCaptureResult> {
self.platform_capture
.capture_outputs_with_scale(parameters, default_scale)
}
pub fn save_png<P: AsRef<std::path::Path>>(
&self,
data: &[u8],
width: u32,
height: u32,
path: P,
) -> Result<()> {
self.save_png_with_compression(data, width, height, path, 6) }
pub fn save_png_with_compression<P: AsRef<std::path::Path>>(
&self,
data: &[u8],
width: u32,
height: u32,
path: P,
compression: u8,
) -> Result<()> {
use std::io::BufWriter;
let pixels = u64::from(width) * u64::from(height);
let expected = pixels.checked_mul(4).ok_or_else(|| {
Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
))
})?;
if expected > usize::MAX as u64 {
return Err(Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
)));
}
if data.len() != expected as usize {
return Err(Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
)));
}
let file = std::fs::File::create(&path).map_err(|e| Error::IoWithContext {
operation: format!("creating output file '{}'", path.as_ref().display()),
source: e,
})?;
let writer = BufWriter::new(file);
let mut encoder = png::Encoder::new(writer, width, height);
let compression_level = match compression {
0 => png::Compression::Fast,
1..=3 => png::Compression::Best,
4..=6 => png::Compression::Default,
7..=9 => png::Compression::Best,
_ => png::Compression::Default,
};
encoder.set_compression(compression_level);
encoder.set_color(png::ColorType::Rgba);
encoder.set_filter(png::FilterType::NoFilter);
let mut writer = encoder
.write_header()
.map_err(|e| Error::Io(std::io::Error::other(format!("PNG encoding error: {}", e))))?;
writer
.write_image_data(data)
.map_err(|e| Error::Io(std::io::Error::other(format!("PNG encoding error: {}", e))))?;
writer
.finish()
.map_err(|e| Error::Io(std::io::Error::other(format!("PNG encoding error: {}", e))))?;
Ok(())
}
#[cfg(feature = "jpeg")]
pub fn save_jpeg<P: AsRef<std::path::Path>>(
&self,
data: &[u8],
width: u32,
height: u32,
path: P,
) -> Result<()> {
self.save_jpeg_with_quality(data, width, height, path, 80)
}
#[cfg(feature = "jpeg")]
pub fn save_jpeg_with_quality<P: AsRef<std::path::Path>>(
&self,
data: &[u8],
width: u32,
height: u32,
path: P,
quality: u8,
) -> Result<()> {
let pixels = u64::from(width) * u64::from(height);
let expected = pixels.checked_mul(4).ok_or_else(|| {
Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
))
})?;
if expected > usize::MAX as u64 {
return Err(Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
)));
}
if data.len() != expected as usize {
return Err(Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
)));
}
let pixels = expected as usize / 4;
let rgb_len = pixels.checked_mul(3).ok_or_else(|| {
Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
))
})?;
let mut rgb_data = vec![0u8; rgb_len];
for (i, rgba) in data.chunks_exact(4).enumerate() {
let base = i * 3;
rgb_data[base] = rgba[0];
rgb_data[base + 1] = rgba[1];
rgb_data[base + 2] = rgba[2];
}
let mut output_file = std::fs::File::create(&path).map_err(|e| Error::IoWithContext {
operation: format!("creating output file '{}'", path.as_ref().display()),
source: e,
})?;
let mut _encoder = jpeg_encoder::Encoder::new(&mut output_file, quality);
_encoder
.encode(
&rgb_data,
width as u16,
height as u16,
jpeg_encoder::ColorType::Rgb,
)
.map_err(|e| Error::Io(std::io::Error::other(format!("JPEG encoding error: {}", e))))?;
Ok(())
}
#[cfg(not(feature = "jpeg"))]
pub fn save_jpeg<P: AsRef<std::path::Path>>(
&self,
_data: &[u8],
_width: u32,
_height: u32,
_path: P,
) -> Result<()> {
Err(Error::ImageProcessing(image::ImageError::Unsupported(
image::error::UnsupportedError::from_format_and_kind(
image::error::ImageFormatHint::Name("JPEG".to_string()),
image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
),
)))
}
#[cfg(not(feature = "jpeg"))]
pub fn save_jpeg_with_quality<P: AsRef<std::path::Path>>(
&self,
_data: &[u8],
_width: u32,
_height: u32,
_path: P,
_quality: u8,
) -> Result<()> {
Err(Error::ImageProcessing(image::ImageError::Unsupported(
image::error::UnsupportedError::from_format_and_kind(
image::error::ImageFormatHint::Name("JPEG".to_string()),
image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
),
)))
}
#[cfg(feature = "jpeg")]
pub fn to_jpeg(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
self.to_jpeg_with_quality(data, width, height, 80)
}
#[cfg(feature = "jpeg")]
pub fn to_jpeg_with_quality(
&self,
data: &[u8],
width: u32,
height: u32,
quality: u8,
) -> Result<Vec<u8>> {
let pixels = u64::from(width) * u64::from(height);
let expected = pixels.checked_mul(4).ok_or_else(|| {
Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
))
})?;
if expected > usize::MAX as u64 {
return Err(Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
)));
}
if data.len() != expected as usize {
return Err(Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
)));
}
let pixels = expected as usize / 4;
let rgb_len = pixels.checked_mul(3).ok_or_else(|| {
Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
))
})?;
let mut rgb_data = vec![0u8; rgb_len];
for (i, rgba) in data.chunks_exact(4).enumerate() {
let base = i * 3;
rgb_data[base] = rgba[0];
rgb_data[base + 1] = rgba[1];
rgb_data[base + 2] = rgba[2];
}
let mut jpeg_data = Vec::new();
let mut _encoder = jpeg_encoder::Encoder::new(&mut jpeg_data, quality);
_encoder
.encode(
&rgb_data,
width as u16,
height as u16,
jpeg_encoder::ColorType::Rgb,
)
.map_err(|e| Error::Io(std::io::Error::other(format!("JPEG encoding error: {}", e))))?;
Ok(jpeg_data)
}
#[cfg(not(feature = "jpeg"))]
pub fn to_jpeg(&self, _data: &[u8], _width: u32, _height: u32) -> Result<Vec<u8>> {
Err(Error::ImageProcessing(image::ImageError::Unsupported(
image::error::UnsupportedError::from_format_and_kind(
image::error::ImageFormatHint::Name("JPEG".to_string()),
image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
),
)))
}
#[cfg(not(feature = "jpeg"))]
pub fn to_jpeg_with_quality(
&self,
_data: &[u8],
_width: u32,
_height: u32,
_quality: u8,
) -> Result<Vec<u8>> {
Err(Error::ImageProcessing(image::ImageError::Unsupported(
image::error::UnsupportedError::from_format_and_kind(
image::error::ImageFormatHint::Name("JPEG".to_string()),
image::error::UnsupportedErrorKind::Format(image::ImageFormat::Jpeg.into()),
),
)))
}
pub fn to_png(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
self.to_png_with_compression(data, width, height, 6)
}
pub fn to_png_with_compression(
&self,
data: &[u8],
width: u32,
height: u32,
compression: u8,
) -> Result<Vec<u8>> {
use std::io::Cursor;
let pixels = u64::from(width) * u64::from(height);
let expected = pixels.checked_mul(4).ok_or_else(|| {
Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
))
})?;
if expected > usize::MAX as u64 {
return Err(Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
)));
}
if data.len() != expected as usize {
return Err(Error::ImageProcessing(image::ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
)));
}
let mut output = Vec::new();
{
let writer = Cursor::new(&mut output);
let mut encoder = png::Encoder::new(writer, width, height);
let compression_level = match compression {
0 => png::Compression::Fast,
1..=3 => png::Compression::Best,
4..=6 => png::Compression::Default,
7..=9 => png::Compression::Best,
_ => png::Compression::Default,
};
encoder.set_compression(compression_level);
encoder.set_color(png::ColorType::Rgba);
encoder.set_filter(png::FilterType::NoFilter);
let mut writer = encoder.write_header().map_err(|e| {
Error::Io(std::io::Error::other(format!("PNG encoding error: {}", e)))
})?;
writer.write_image_data(data).map_err(|e| {
Error::Io(std::io::Error::other(format!("PNG encoding error: {}", e)))
})?;
writer.finish().map_err(|e| {
Error::Io(std::io::Error::other(format!("PNG encoding error: {}", e)))
})?;
}
Ok(output)
}
pub fn save_ppm<P: AsRef<std::path::Path>>(
&self,
data: &[u8],
width: u32,
height: u32,
path: P,
) -> Result<()> {
let ppm_data = self.to_ppm(data, width, height)?;
std::fs::write(&path, ppm_data).map_err(|e| Error::IoWithContext {
operation: format!("writing PPM data to file '{}'", path.as_ref().display()),
source: e,
})?;
Ok(())
}
pub fn to_ppm(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
let header = format!("P6\n{} {}\n255\n", width, height);
let mut ppm_data = header.into_bytes();
for chunk in data.chunks_exact(4) {
ppm_data.push(chunk[0]); ppm_data.push(chunk[1]); ppm_data.push(chunk[2]); }
Ok(ppm_data)
}
pub fn read_region_from_stdin() -> Result<Box> {
use std::io::{self, BufRead};
let stdin = io::stdin();
let mut handle = stdin.lock();
let mut line = String::new();
handle.read_line(&mut line)?;
line = line.trim_end().to_string();
line.parse()
}
pub fn write_png_to_stdout(&self, data: &[u8], width: u32, height: u32) -> Result<()> {
let png_data = self.to_png(data, width, height)?;
use std::io::Write;
let stdout = std::io::stdout();
let mut handle = stdout.lock();
handle.write_all(&png_data)?;
handle.flush()?;
Ok(())
}
pub fn write_png_to_stdout_with_compression(
&self,
data: &[u8],
width: u32,
height: u32,
compression: u8,
) -> Result<()> {
let png_data = self.to_png_with_compression(data, width, height, compression)?;
use std::io::Write;
let stdout = std::io::stdout();
let mut handle = stdout.lock();
handle.write_all(&png_data)?;
handle.flush()?;
Ok(())
}
#[cfg(feature = "jpeg")]
pub fn write_jpeg_to_stdout(&self, data: &[u8], width: u32, height: u32) -> Result<()> {
self.write_jpeg_to_stdout_with_quality(data, width, height, 80)
}
#[cfg(feature = "jpeg")]
pub fn write_jpeg_to_stdout_with_quality(
&self,
data: &[u8],
width: u32,
height: u32,
quality: u8,
) -> Result<()> {
let jpeg_data = self.to_jpeg_with_quality(data, width, height, quality)?;
use std::io::Write;
let stdout = std::io::stdout();
let mut handle = stdout.lock();
handle.write_all(&jpeg_data)?;
handle.flush()?;
Ok(())
}
pub fn write_ppm_to_stdout(&self, data: &[u8], width: u32, height: u32) -> Result<()> {
let ppm_data = self.to_ppm(data, width, height)?;
use std::io::Write;
let stdout = std::io::stdout();
let mut handle = stdout.lock();
handle.write_all(&ppm_data)?;
handle.flush()?;
Ok(())
}
}