forked from michael/leanshot
ouais
This commit is contained in:
parent
8bcd9d6385
commit
dcdf13594a
3 changed files with 48 additions and 70 deletions
39
Cargo.lock
generated
39
Cargo.lock
generated
|
@ -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"
|
||||
|
|
71
src/gui.rs
71
src/gui.rs
|
@ -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(())
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue