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"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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"
|
name = "leanshot"
|
||||||
description = "Screenshot capture for Linux."
|
description = "Screenshot capture for Linux."
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
repository = "https://github.com/iptq/leanshot"
|
repository = "https://git.sr.ht/~iptq/leanshot"
|
||||||
license-file = "LICENSE"
|
license-file = "LICENSE"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Michael Zhang <failed.down@gmail.com>"]
|
authors = ["Michael Zhang <iptq@protonmail.com>"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "x11"]
|
members = [".", "x11"]
|
||||||
|
@ -15,15 +15,11 @@ default = ["x11"]
|
||||||
x11 = ["leanshot_x11"]
|
x11 = ["leanshot_x11"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# gl = "0.11"
|
|
||||||
# glutin = "0.19"
|
|
||||||
# nanovg = { version = "1.0.2", features = ["gl3"] }
|
|
||||||
png = "0.14"
|
png = "0.14"
|
||||||
structopt = "0.2"
|
structopt = "0.2"
|
||||||
# time = "0.1"
|
|
||||||
anyhow = "1.0.31"
|
anyhow = "1.0.31"
|
||||||
image = "0.23.5"
|
image = "0.23.5"
|
||||||
|
|
||||||
leanshot_x11 = { version = "0.2", path = "x11", optional = true, features = ["xlib", "xinerama"] }
|
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
stderrlog = "0.4.3"
|
stderrlog = "0.4.3"
|
||||||
|
|
||||||
|
leanshot_x11 = { version = "0.2", path = "x11", optional = true, features = ["xlib", "xinerama"] }
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate anyhow;
|
extern crate anyhow;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
extern crate leanshot_x11 as x11;
|
extern crate leanshot_x11 as x11;
|
||||||
extern crate structopt;
|
|
||||||
|
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
pub use crate::platform::{x11::X11, Platform};
|
pub use crate::platform::{x11::X11, Image, Platform};
|
||||||
|
|
||||||
type Rectangle = x11::Rectangle;
|
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 std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use leanshot::{Platform, Region, X11};
|
use image::{imageops, RgbImage};
|
||||||
|
use leanshot::{Image, Platform, Region, X11};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub fn main() -> Result<()> {
|
pub fn main() -> Result<()> {
|
||||||
stderrlog::new().module(module_path!()).init().unwrap();
|
|
||||||
|
|
||||||
let opt = Options::from_args();
|
let opt = Options::from_args();
|
||||||
|
stderrlog::new()
|
||||||
|
.module(module_path!())
|
||||||
|
.module("leanshot_x11")
|
||||||
|
.verbosity(opt.verbose)
|
||||||
|
.init()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let gui = X11::new()?;
|
let gui = X11::new()?;
|
||||||
|
|
||||||
let screen_layout = gui.get_screen_layout()?;
|
let screen_layout = gui.get_screen_layout()?;
|
||||||
println!("{:?}", screen_layout);
|
debug!("screen layout: {:?}", screen_layout);
|
||||||
|
|
||||||
let image = match opt.region {
|
let image = match opt.region {
|
||||||
Region::ActiveWindow => {
|
Region::ActiveWindow => {
|
||||||
|
@ -22,14 +30,57 @@ pub fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
Region::Fullscreen | Region::Selection => {
|
Region::Fullscreen | Region::Selection => {
|
||||||
let mut image = gui.capture_full_screen()?;
|
let mut image = gui.capture_full_screen()?;
|
||||||
|
|
||||||
if let Region::Fullscreen = opt.region {
|
if let Region::Fullscreen = opt.region {
|
||||||
let rect = gui.show_interactive_selection(image)?;
|
// calculate the full size of the image
|
||||||
// TODO: crop 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
|
/// Options for screenshot
|
||||||
|
@ -46,4 +97,8 @@ pub struct Options {
|
||||||
/// Whether or not to also copy it to the clipboard
|
/// Whether or not to also copy it to the clipboard
|
||||||
#[structopt(short = "c")]
|
#[structopt(short = "c")]
|
||||||
pub clipboard: bool,
|
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 std::hash::Hash;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use image::RgbaImage;
|
use image::RgbImage;
|
||||||
|
|
||||||
use crate::Rectangle;
|
use crate::Rectangle;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ pub trait Platform {
|
||||||
/// Type of images
|
/// Type of images
|
||||||
type Image: Image;
|
type Image: Image;
|
||||||
|
|
||||||
/// Type of screen handles
|
/// Type of screen handles (must be hashable and uniquely identifying)
|
||||||
type ScreenId: Hash;
|
type ScreenId: Hash;
|
||||||
|
|
||||||
/// Get a handle to the currently active window
|
/// 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>>;
|
fn get_screen_layout(&self) -> Result<HashMap<Self::ScreenId, ScreenInfo>>;
|
||||||
|
|
||||||
/// Capture full screen
|
/// 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
|
/// Open the interactive selection interface
|
||||||
fn show_interactive_selection(&self, image: Self::Image) -> Result<Rectangle>;
|
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
|
/// Set of functions platform-specific images must implement
|
||||||
pub trait Image {
|
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 std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use image::RgbaImage;
|
use image::{Rgb, RgbImage};
|
||||||
use x11::{
|
use x11::{
|
||||||
xinerama::ScreensInfo,
|
xinerama::ScreensInfo,
|
||||||
xlib::{Display, Window},
|
xlib::{Display, ImageByteOrder, Window},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::platform::{Image as ImageT, Platform, ScreenInfo};
|
use crate::platform::{Image as ImageT, Platform, ScreenInfo};
|
||||||
|
@ -40,9 +40,15 @@ impl Platform for X11 {
|
||||||
Ok(Image(image))
|
Ok(Image(image))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capture_full_screen(&self) -> Result<Self::Image> {
|
fn capture_full_screen(&self) -> Result<HashMap<Self::ScreenId, Self::Image>> {
|
||||||
let window = self.inner.get_root_window(0);
|
let mut result = HashMap::new();
|
||||||
unimplemented!()
|
|
||||||
|
// 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>> {
|
fn get_screen_layout(&self) -> Result<HashMap<Self::ScreenId, ScreenInfo>> {
|
||||||
|
@ -77,7 +83,14 @@ impl Platform for X11 {
|
||||||
pub struct Image(x11::xlib::Image);
|
pub struct Image(x11::xlib::Image);
|
||||||
|
|
||||||
impl ImageT for Image {
|
impl ImageT for Image {
|
||||||
fn into_rgba_image(self) -> RgbaImage {
|
fn into_rgb_image(self) -> RgbImage {
|
||||||
todo!()
|
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"
|
libc = "0.2"
|
||||||
x11 = { version = "2.18" }
|
x11 = { version = "2.18" }
|
||||||
thiserror = "1.0.20"
|
thiserror = "1.0.20"
|
||||||
|
log = "0.4.8"
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate thiserror;
|
extern crate thiserror;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
pub extern crate x11;
|
pub extern crate x11;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::os::raw::c_ulong;
|
||||||
|
|
||||||
use x11::xlib;
|
use x11::xlib;
|
||||||
|
|
||||||
use crate::errors::{Error, Result};
|
use crate::errors::{Error, Result};
|
||||||
|
@ -16,11 +18,17 @@ pub enum ImageByteOrder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The buffer pointed to by an XImage.
|
/// The buffer pointed to by an XImage.
|
||||||
pub struct PixBuffer {
|
pub struct PixBuffer<'a> {
|
||||||
/// The raw pointer to the buffer
|
/// The raw pointer to the buffer
|
||||||
pub buf: *mut u8,
|
pub buf: *mut u8,
|
||||||
|
|
||||||
/// The size of the buffer
|
/// The size of the buffer
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
|
|
||||||
|
/// Rgb masks
|
||||||
|
pub masks: (u64, u64, u64),
|
||||||
|
|
||||||
|
pub image: &'a Image,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
/// Produces a PixBuffer
|
||||||
pub fn buffer(&self) -> PixBuffer {
|
pub fn buffer(&self) -> PixBuffer {
|
||||||
let size = self.get_size();
|
let size = self.get_size();
|
||||||
let buf = unsafe { (*self.inner).data as *mut u8 };
|
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.
|
/// Gets the byte at the index of the data buffer.
|
||||||
pub fn get_byte(&self, index: usize) -> Option<u8> {
|
pub fn get_byte(&self, index: usize) -> Option<u8> {
|
||||||
if index > self.size {
|
if index > self.size {
|
||||||
|
@ -76,4 +97,26 @@ impl PixBuffer {
|
||||||
}
|
}
|
||||||
Some(unsafe { *self.buf.offset(index as isize) as u8 })
|
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::display::{Display, GetDisplay};
|
||||||
pub use self::drawable::Drawable;
|
pub use self::drawable::Drawable;
|
||||||
pub use self::event::{Event, EventKind};
|
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::visual::Visual;
|
||||||
pub use self::window::Window;
|
pub use self::window::Window;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue