forked from michael/leanshot
screen capture full screen
This commit is contained in:
parent
8dc13a1ad6
commit
505f56766a
10 changed files with 145 additions and 32 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -244,6 +244,7 @@ name = "leanshot_x11"
|
|||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"x11 2.18.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -2,10 +2,10 @@
|
|||
name = "leanshot"
|
||||
description = "Screenshot capture for Linux."
|
||||
version = "0.4.0"
|
||||
repository = "https://github.com/iptq/leanshot"
|
||||
repository = "https://git.sr.ht/~iptq/leanshot"
|
||||
license-file = "LICENSE"
|
||||
edition = "2018"
|
||||
authors = ["Michael Zhang <failed.down@gmail.com>"]
|
||||
authors = ["Michael Zhang <iptq@protonmail.com>"]
|
||||
|
||||
[workspace]
|
||||
members = [".", "x11"]
|
||||
|
@ -15,15 +15,11 @@ default = ["x11"]
|
|||
x11 = ["leanshot_x11"]
|
||||
|
||||
[dependencies]
|
||||
# gl = "0.11"
|
||||
# glutin = "0.19"
|
||||
# nanovg = { version = "1.0.2", features = ["gl3"] }
|
||||
png = "0.14"
|
||||
structopt = "0.2"
|
||||
# time = "0.1"
|
||||
anyhow = "1.0.31"
|
||||
image = "0.23.5"
|
||||
|
||||
leanshot_x11 = { version = "0.2", path = "x11", optional = true, features = ["xlib", "xinerama"] }
|
||||
log = "0.4.8"
|
||||
stderrlog = "0.4.3"
|
||||
|
||||
leanshot_x11 = { version = "0.2", path = "x11", optional = true, features = ["xlib", "xinerama"] }
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate leanshot_x11 as x11;
|
||||
extern crate structopt;
|
||||
|
||||
mod platform;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub use crate::platform::{x11::X11, Platform};
|
||||
pub use crate::platform::{x11::X11, Image, Platform};
|
||||
|
||||
type Rectangle = x11::Rectangle;
|
||||
|
||||
|
|
69
src/main.rs
69
src/main.rs
|
@ -1,19 +1,27 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use leanshot::{Platform, Region, X11};
|
||||
use image::{imageops, RgbImage};
|
||||
use leanshot::{Image, Platform, Region, X11};
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn main() -> Result<()> {
|
||||
stderrlog::new().module(module_path!()).init().unwrap();
|
||||
|
||||
let opt = Options::from_args();
|
||||
stderrlog::new()
|
||||
.module(module_path!())
|
||||
.module("leanshot_x11")
|
||||
.verbosity(opt.verbose)
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
let gui = X11::new()?;
|
||||
|
||||
let screen_layout = gui.get_screen_layout()?;
|
||||
println!("{:?}", screen_layout);
|
||||
debug!("screen layout: {:?}", screen_layout);
|
||||
|
||||
let image = match opt.region {
|
||||
Region::ActiveWindow => {
|
||||
|
@ -22,14 +30,57 @@ pub fn main() -> Result<()> {
|
|||
}
|
||||
Region::Fullscreen | Region::Selection => {
|
||||
let mut image = gui.capture_full_screen()?;
|
||||
|
||||
if let Region::Fullscreen = opt.region {
|
||||
let rect = gui.show_interactive_selection(image)?;
|
||||
// TODO: crop the image
|
||||
// calculate the full size of the image
|
||||
let mut min_x = 0;
|
||||
let mut min_y = 0;
|
||||
let mut max_x = 0;
|
||||
let mut max_y = 0;
|
||||
for (i, (_, screen)) in screen_layout.iter().enumerate() {
|
||||
let left = screen.x;
|
||||
let right = screen.x + screen.width as i16;
|
||||
let top = screen.y;
|
||||
let bottom = screen.y + screen.height as i16;
|
||||
if i == 0 {
|
||||
min_x = left;
|
||||
min_y = top;
|
||||
max_x = right;
|
||||
max_y = bottom;
|
||||
} else {
|
||||
min_x = min_x.min(left);
|
||||
min_y = min_y.min(top);
|
||||
max_x = max_x.max(right);
|
||||
max_y = max_y.max(bottom);
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"full virtual screen rect : ({}, {}) to ({}, {})",
|
||||
min_x, min_y, max_x, max_y
|
||||
);
|
||||
|
||||
// make a new image
|
||||
let width = (max_x - min_x) as u32;
|
||||
let height = (max_y - min_y) as u32;
|
||||
let mut base_image = RgbImage::new(width, height);
|
||||
|
||||
// copy all of the images into it
|
||||
for (id, image) in gui.capture_full_screen()? {
|
||||
let screen = screen_layout.get(&id).expect("shouldn't fail");
|
||||
let x = (screen.x + min_x) as u32;
|
||||
let y = (screen.y + min_y) as u32;
|
||||
let image = image.into_rgb_image();
|
||||
imageops::overlay(&mut base_image, &image, x, y);
|
||||
}
|
||||
|
||||
// save the image
|
||||
base_image.save(&opt.outfile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unimplemented!("leanshot::capture")
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Options for screenshot
|
||||
|
@ -46,4 +97,8 @@ pub struct Options {
|
|||
/// Whether or not to also copy it to the clipboard
|
||||
#[structopt(short = "c")]
|
||||
pub clipboard: bool,
|
||||
|
||||
/// Verbosity of output
|
||||
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
|
||||
verbose: usize,
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::collections::HashMap;
|
|||
use std::hash::Hash;
|
||||
|
||||
use anyhow::Result;
|
||||
use image::RgbaImage;
|
||||
use image::RgbImage;
|
||||
|
||||
use crate::Rectangle;
|
||||
|
||||
|
@ -25,7 +25,7 @@ pub trait Platform {
|
|||
/// Type of images
|
||||
type Image: Image;
|
||||
|
||||
/// Type of screen handles
|
||||
/// Type of screen handles (must be hashable and uniquely identifying)
|
||||
type ScreenId: Hash;
|
||||
|
||||
/// Get a handle to the currently active window
|
||||
|
@ -38,7 +38,7 @@ pub trait Platform {
|
|||
fn get_screen_layout(&self) -> Result<HashMap<Self::ScreenId, ScreenInfo>>;
|
||||
|
||||
/// Capture full screen
|
||||
fn capture_full_screen(&self) -> Result<Self::Image>;
|
||||
fn capture_full_screen(&self) -> Result<HashMap<Self::ScreenId, Self::Image>>;
|
||||
|
||||
/// Open the interactive selection interface
|
||||
fn show_interactive_selection(&self, image: Self::Image) -> Result<Rectangle>;
|
||||
|
@ -46,5 +46,6 @@ pub trait Platform {
|
|||
|
||||
/// Set of functions platform-specific images must implement
|
||||
pub trait Image {
|
||||
fn into_rgba_image(self) -> RgbaImage;
|
||||
/// Convert the image into an image::RgbImage
|
||||
fn into_rgb_image(self) -> RgbImage;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use image::RgbaImage;
|
||||
use image::{Rgb, RgbImage};
|
||||
use x11::{
|
||||
xinerama::ScreensInfo,
|
||||
xlib::{Display, Window},
|
||||
xlib::{Display, ImageByteOrder, Window},
|
||||
};
|
||||
|
||||
use crate::platform::{Image as ImageT, Platform, ScreenInfo};
|
||||
|
@ -40,9 +40,15 @@ impl Platform for X11 {
|
|||
Ok(Image(image))
|
||||
}
|
||||
|
||||
fn capture_full_screen(&self) -> Result<Self::Image> {
|
||||
let window = self.inner.get_root_window(0);
|
||||
unimplemented!()
|
||||
fn capture_full_screen(&self) -> Result<HashMap<Self::ScreenId, Self::Image>> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
// wait what?
|
||||
// x11 doesn't have a root window for each screen apparently
|
||||
let window = self.inner.get_default_root_window()?;
|
||||
result.insert(0, Image(window.get_image()?));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn get_screen_layout(&self) -> Result<HashMap<Self::ScreenId, ScreenInfo>> {
|
||||
|
@ -77,7 +83,14 @@ impl Platform for X11 {
|
|||
pub struct Image(x11::xlib::Image);
|
||||
|
||||
impl ImageT for Image {
|
||||
fn into_rgba_image(self) -> RgbaImage {
|
||||
todo!()
|
||||
fn into_rgb_image(self) -> RgbImage {
|
||||
let width = self.0.get_width();
|
||||
let height = self.0.get_height();
|
||||
let pixbuf = self.0.buffer();
|
||||
let byte_order = self.0.get_byte_order().unwrap();
|
||||
RgbImage::from_fn(width, height, |x, y| {
|
||||
let (r, g, b) = pixbuf.get_pixel(x, y).unwrap();
|
||||
[r, g, b].into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,3 +15,4 @@ xinerama = ["x11/xlib", "x11/xinerama"]
|
|||
libc = "0.2"
|
||||
x11 = { version = "2.18" }
|
||||
thiserror = "1.0.20"
|
||||
log = "0.4.8"
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#[macro_use]
|
||||
extern crate thiserror;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub extern crate x11;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::os::raw::c_ulong;
|
||||
|
||||
use x11::xlib;
|
||||
|
||||
use crate::errors::{Error, Result};
|
||||
|
@ -16,11 +18,17 @@ pub enum ImageByteOrder {
|
|||
}
|
||||
|
||||
/// The buffer pointed to by an XImage.
|
||||
pub struct PixBuffer {
|
||||
pub struct PixBuffer<'a> {
|
||||
/// The raw pointer to the buffer
|
||||
pub buf: *mut u8,
|
||||
|
||||
/// The size of the buffer
|
||||
pub size: usize,
|
||||
|
||||
/// Rgb masks
|
||||
pub masks: (u64, u64, u64),
|
||||
|
||||
pub image: &'a Image,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
|
@ -54,11 +62,24 @@ impl Image {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_rgb_masks(&self) -> (u64, u64, u64) {
|
||||
let red_mask = unsafe { (*self.inner).red_mask };
|
||||
let green_mask = unsafe { (*self.inner).green_mask };
|
||||
let blue_mask = unsafe { (*self.inner).blue_mask };
|
||||
(red_mask, blue_mask, green_mask)
|
||||
}
|
||||
|
||||
/// Produces a PixBuffer
|
||||
pub fn buffer(&self) -> PixBuffer {
|
||||
let size = self.get_size();
|
||||
let buf = unsafe { (*self.inner).data as *mut u8 };
|
||||
PixBuffer { buf, size }
|
||||
let masks = self.get_rgb_masks();
|
||||
PixBuffer {
|
||||
buf,
|
||||
size,
|
||||
masks,
|
||||
image: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +89,7 @@ impl Drop for Image {
|
|||
}
|
||||
}
|
||||
|
||||
impl PixBuffer {
|
||||
impl<'a> PixBuffer<'a> {
|
||||
/// Gets the byte at the index of the data buffer.
|
||||
pub fn get_byte(&self, index: usize) -> Option<u8> {
|
||||
if index > self.size {
|
||||
|
@ -76,4 +97,26 @@ impl PixBuffer {
|
|||
}
|
||||
Some(unsafe { *self.buf.offset(index as isize) as u8 })
|
||||
}
|
||||
|
||||
/// Gets the rgb value of a particular pixel
|
||||
pub fn get_pixel(&self, x: u32, y: u32) -> Option<(u8, u8, u8)> {
|
||||
// let bytes_per_line = unsafe{(*self.image.inner).bytes_per_line} as u32;
|
||||
// let bits_per_pixel=unsafe{(*self.image.inner).bits_per_pixel} as u32;
|
||||
// let pixel_ptr = (y * bytes_per_line + x * bits_per_pixel) / 8;
|
||||
// let pixel = unsafe{*((*self.image.inner).data.offset(pixel_ptr as isize) as *mut c_ulong)};
|
||||
|
||||
let get_pixel = unsafe { (*self.image.inner).funcs.get_pixel }.unwrap();
|
||||
let pixel = unsafe { get_pixel(self.image.inner, x as i32, y as i32) };
|
||||
|
||||
// warn!("pixel: {:?} {:#b}", pixel, pixel);
|
||||
let red_mask = unsafe { (*self.image.inner).red_mask };
|
||||
let green_mask = unsafe { (*self.image.inner).green_mask };
|
||||
let blue_mask = unsafe { (*self.image.inner).blue_mask };
|
||||
// warn!("masks: {:b} {:b} {:b}", red_mask, green_mask, blue_mask);
|
||||
let red = ((pixel & unsafe { (*self.image.inner).red_mask }) >> 16) as u8;
|
||||
let green = ((pixel & unsafe { (*self.image.inner).green_mask }) >> 8) as u8;
|
||||
let blue = ((pixel & unsafe { (*self.image.inner).blue_mask }) >> 0) as u8;
|
||||
// warn!("rgb: {:?}", (red, green, blue));
|
||||
Some((red, green, blue))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ pub use self::cursor::Cursor;
|
|||
pub use self::display::{Display, GetDisplay};
|
||||
pub use self::drawable::Drawable;
|
||||
pub use self::event::{Event, EventKind};
|
||||
pub use self::image::Image;
|
||||
pub use self::image::{Image, ImageByteOrder};
|
||||
pub use self::visual::Visual;
|
||||
pub use self::window::Window;
|
||||
|
||||
|
|
Loading…
Reference in a new issue