This commit is contained in:
Michael Zhang 2020-12-24 12:28:13 -06:00
parent 8bcd9d6385
commit dcdf13594a
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
3 changed files with 48 additions and 70 deletions

39
Cargo.lock generated
View file

@ -29,12 +29,12 @@ checksum = "68803225a7b13e47191bab76f2687382b60d259e8cf37f6e1893658b84bb9479"
[[package]]
name = "atty"
version = "0.2.11"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"termion",
"winapi",
]
@ -355,12 +355,6 @@ dependencies = [
"libc",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "png"
version = "0.16.8"
@ -440,21 +434,6 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
dependencies = [
"redox_syscall",
]
[[package]]
name = "scoped_threadpool"
version = "0.1.9"
@ -530,18 +509,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "termion"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]]
name = "textwrap"
version = "0.11.0"

View file

@ -4,8 +4,7 @@ use anyhow::Result;
use image::{Bgra, DynamicImage, ImageBuffer};
use xcb::{
base::Connection,
xproto::{self, Rectangle},
Screen,
xproto::{self, Rectangle, Screen},
};
use xcb_util::image as xcb_image;
@ -47,7 +46,7 @@ impl Gui {
}
pub fn interactive_select(&self, image: &ScreenCapture) -> Result<Option<Rectangle>> {
let id = self.conn.generate_id();
let window_id = self.conn.generate_id();
let screen = self.get_default_screen();
let root_window = screen.root();
let (width, height) = (screen.width_in_pixels(), screen.height_in_pixels());
@ -60,7 +59,7 @@ impl Gui {
xproto::create_window(
&self.conn,
xcb::COPY_FROM_PARENT as u8,
id,
window_id,
root_window,
0,
0,
@ -71,14 +70,13 @@ impl Gui {
screen.root_visual(),
&[
(xcb::CW_OVERRIDE_REDIRECT, 1),
// (xcb::CW_BACK_PIXEL, screen.white_pixel()),
(xcb::CW_EVENT_MASK, evt_mask),
(xcb::CW_CURSOR, cursor),
],
)
.request_check()?;
xproto::map_window(&self.conn, id).request_check()?;
xproto::map_window(&self.conn, window_id).request_check()?;
self.conn.flush();
let wm_state = xproto::intern_atom(&self.conn, true, "_NET_WM_STATE")
@ -90,7 +88,7 @@ impl Gui {
xproto::change_property(
&self.conn,
xcb::PROP_MODE_REPLACE as u8,
id,
window_id,
wm_state,
4,
32,
@ -101,7 +99,7 @@ impl Gui {
xproto::set_input_focus(
&self.conn,
xcb::INPUT_FOCUS_POINTER_ROOT as u8,
id,
window_id,
xcb::CURRENT_TIME,
)
.request_check()?;
@ -109,8 +107,8 @@ impl Gui {
info!("Grabbing keyboard...");
xproto::grab_keyboard(
&self.conn,
false,
id,
true,
window_id,
xcb::CURRENT_TIME,
xcb::GRAB_MODE_ASYNC as u8,
xcb::GRAB_MODE_ASYNC as u8,
@ -121,7 +119,7 @@ impl Gui {
xproto::grab_pointer(
&self.conn,
false,
id,
window_id,
evt_mask as u16,
xcb::GRAB_MODE_ASYNC as u8,
xcb::GRAB_MODE_ASYNC as u8,
@ -131,11 +129,11 @@ impl Gui {
)
.get_reply()?;
let gc = self.conn.generate_id();
let window_gc = self.conn.generate_id();
xproto::create_gc(
&self.conn,
gc,
id,
window_gc,
window_id,
&[
(xcb::GC_FOREGROUND, screen.white_pixel()),
(xcb::GC_BACKGROUND, screen.white_pixel()),
@ -146,9 +144,12 @@ impl Gui {
#[derive(Default)]
struct State {
dragging: bool,
// where did we start dragging from
dx: i16,
dy: i16,
// where is the mouse right now
mx: i16,
my: i16,
}
@ -172,11 +173,11 @@ impl Gui {
let mut cancelled = false;
let redraw = |state: &State| {
xcb_image::put(&self.conn, id, gc, &image.image, 0, 0);
xcb_image::put(&self.conn, window_id, window_gc, &image.image, 0, 0);
if state.dragging {
let rect = state.rect();
xproto::poly_rectangle(&self.conn, id, gc, &[rect]);
xproto::poly_rectangle(&self.conn, window_id, window_gc, &[rect]);
}
self.conn.flush();
@ -188,15 +189,21 @@ impl Gui {
match evt.response_type() {
xcb::KEY_RELEASE => {
let key_evt = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&evt) };
info!("released key {:?}", key_evt.detail());
if key_evt.detail() == 9 {
cancelled = true;
break;
if state.dragging {
state.dragging = false;
} else {
cancelled = true;
break;
}
}
}
xcb::KEY_PRESS => {}
xcb::BUTTON_PRESS => {
let button_evt = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&evt) };
info!("pressed {:?}", button_evt.detail());
info!("pressed mouse button {:?}", button_evt.detail());
state.dx = button_evt.root_x();
state.dy = button_evt.root_y();
@ -207,7 +214,7 @@ impl Gui {
}
xcb::BUTTON_RELEASE => {
let button_evt = unsafe { xcb::cast_event::<xcb::ButtonReleaseEvent>(&evt) };
info!("released {:?}", button_evt.detail());
info!("released mouse button {:?}", button_evt.detail());
// left mouse button
if state.dragging && button_evt.detail() == 1 {
@ -241,11 +248,10 @@ pub struct ScreenCapture {
impl ScreenCapture {
pub fn save_to(&self, to: impl AsRef<Path>) -> Result<()> {
let section = Rectangle::new(0, 0, self.image.width(), self.image.height());
self.save_cropped_to(to, section)
self.save_cropped_to(to, None)
}
pub fn save_cropped_to(&self, to: impl AsRef<Path>, section: Rectangle) -> Result<()> {
pub fn save_cropped_to(&self, to: impl AsRef<Path>, section: Option<Rectangle>) -> Result<()> {
let to = to.as_ref();
let image = ImageBuffer::<Bgra<u8>, _>::from_raw(
self.image.width() as u32,
@ -256,14 +262,17 @@ impl ScreenCapture {
let image = DynamicImage::ImageBgra8(image);
let mut image = DynamicImage::ImageRgb8(image.into_rgb8());
// TODO: compare the rectangles to see if we can skip the crop
// crop the image
let image = image.crop(
section.x() as u32,
section.y() as u32,
section.width() as u32,
section.height() as u32,
);
let image = if let Some(section) = section {
// crop the image
image.crop(
section.x() as u32,
section.y() as u32,
section.width() as u32,
section.height() as u32,
)
} else {
image
};
image.save(to)?;
Ok(())

View file

@ -7,6 +7,7 @@ mod gui;
use std::path::PathBuf;
use std::process;
use std::str::FromStr;
use anyhow::Result;
use structopt::StructOpt;
@ -31,7 +32,7 @@ fn main() -> Result<()> {
}
Region::Selection => {
if let Some(rectangle) = gui.interactive_select(&capture)? {
capture.save_cropped_to(&opt.outfile, rectangle)?;
capture.save_cropped_to(&opt.outfile, Some(rectangle))?;
} else {
info!("Cancelled by user.");
process::exit(1);
@ -65,8 +66,9 @@ pub enum Region {
Selection,
}
impl Region {
pub fn from_str(x: &str) -> Result<Self> {
impl FromStr for Region {
type Err = anyhow::Error;
fn from_str(x: &str) -> Result<Self> {
match x {
"fullscreen" => Ok(Region::Fullscreen),
"select" | "selection" => Ok(Region::Selection),