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]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.11"
|
version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
"termion",
|
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -355,12 +355,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "numtoa"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.16.8"
|
version = "0.16.8"
|
||||||
|
@ -440,21 +434,6 @@ dependencies = [
|
||||||
"num_cpus",
|
"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]]
|
[[package]]
|
||||||
name = "scoped_threadpool"
|
name = "scoped_threadpool"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
|
@ -530,18 +509,6 @@ dependencies = [
|
||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
57
src/gui.rs
57
src/gui.rs
|
@ -4,8 +4,7 @@ use anyhow::Result;
|
||||||
use image::{Bgra, DynamicImage, ImageBuffer};
|
use image::{Bgra, DynamicImage, ImageBuffer};
|
||||||
use xcb::{
|
use xcb::{
|
||||||
base::Connection,
|
base::Connection,
|
||||||
xproto::{self, Rectangle},
|
xproto::{self, Rectangle, Screen},
|
||||||
Screen,
|
|
||||||
};
|
};
|
||||||
use xcb_util::image as xcb_image;
|
use xcb_util::image as xcb_image;
|
||||||
|
|
||||||
|
@ -47,7 +46,7 @@ impl Gui {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interactive_select(&self, image: &ScreenCapture) -> Result<Option<Rectangle>> {
|
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 screen = self.get_default_screen();
|
||||||
let root_window = screen.root();
|
let root_window = screen.root();
|
||||||
let (width, height) = (screen.width_in_pixels(), screen.height_in_pixels());
|
let (width, height) = (screen.width_in_pixels(), screen.height_in_pixels());
|
||||||
|
@ -60,7 +59,7 @@ impl Gui {
|
||||||
xproto::create_window(
|
xproto::create_window(
|
||||||
&self.conn,
|
&self.conn,
|
||||||
xcb::COPY_FROM_PARENT as u8,
|
xcb::COPY_FROM_PARENT as u8,
|
||||||
id,
|
window_id,
|
||||||
root_window,
|
root_window,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -71,14 +70,13 @@ impl Gui {
|
||||||
screen.root_visual(),
|
screen.root_visual(),
|
||||||
&[
|
&[
|
||||||
(xcb::CW_OVERRIDE_REDIRECT, 1),
|
(xcb::CW_OVERRIDE_REDIRECT, 1),
|
||||||
// (xcb::CW_BACK_PIXEL, screen.white_pixel()),
|
|
||||||
(xcb::CW_EVENT_MASK, evt_mask),
|
(xcb::CW_EVENT_MASK, evt_mask),
|
||||||
(xcb::CW_CURSOR, cursor),
|
(xcb::CW_CURSOR, cursor),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.request_check()?;
|
.request_check()?;
|
||||||
|
|
||||||
xproto::map_window(&self.conn, id).request_check()?;
|
xproto::map_window(&self.conn, window_id).request_check()?;
|
||||||
self.conn.flush();
|
self.conn.flush();
|
||||||
|
|
||||||
let wm_state = xproto::intern_atom(&self.conn, true, "_NET_WM_STATE")
|
let wm_state = xproto::intern_atom(&self.conn, true, "_NET_WM_STATE")
|
||||||
|
@ -90,7 +88,7 @@ impl Gui {
|
||||||
xproto::change_property(
|
xproto::change_property(
|
||||||
&self.conn,
|
&self.conn,
|
||||||
xcb::PROP_MODE_REPLACE as u8,
|
xcb::PROP_MODE_REPLACE as u8,
|
||||||
id,
|
window_id,
|
||||||
wm_state,
|
wm_state,
|
||||||
4,
|
4,
|
||||||
32,
|
32,
|
||||||
|
@ -101,7 +99,7 @@ impl Gui {
|
||||||
xproto::set_input_focus(
|
xproto::set_input_focus(
|
||||||
&self.conn,
|
&self.conn,
|
||||||
xcb::INPUT_FOCUS_POINTER_ROOT as u8,
|
xcb::INPUT_FOCUS_POINTER_ROOT as u8,
|
||||||
id,
|
window_id,
|
||||||
xcb::CURRENT_TIME,
|
xcb::CURRENT_TIME,
|
||||||
)
|
)
|
||||||
.request_check()?;
|
.request_check()?;
|
||||||
|
@ -109,8 +107,8 @@ impl Gui {
|
||||||
info!("Grabbing keyboard...");
|
info!("Grabbing keyboard...");
|
||||||
xproto::grab_keyboard(
|
xproto::grab_keyboard(
|
||||||
&self.conn,
|
&self.conn,
|
||||||
false,
|
true,
|
||||||
id,
|
window_id,
|
||||||
xcb::CURRENT_TIME,
|
xcb::CURRENT_TIME,
|
||||||
xcb::GRAB_MODE_ASYNC as u8,
|
xcb::GRAB_MODE_ASYNC as u8,
|
||||||
xcb::GRAB_MODE_ASYNC as u8,
|
xcb::GRAB_MODE_ASYNC as u8,
|
||||||
|
@ -121,7 +119,7 @@ impl Gui {
|
||||||
xproto::grab_pointer(
|
xproto::grab_pointer(
|
||||||
&self.conn,
|
&self.conn,
|
||||||
false,
|
false,
|
||||||
id,
|
window_id,
|
||||||
evt_mask as u16,
|
evt_mask as u16,
|
||||||
xcb::GRAB_MODE_ASYNC as u8,
|
xcb::GRAB_MODE_ASYNC as u8,
|
||||||
xcb::GRAB_MODE_ASYNC as u8,
|
xcb::GRAB_MODE_ASYNC as u8,
|
||||||
|
@ -131,11 +129,11 @@ impl Gui {
|
||||||
)
|
)
|
||||||
.get_reply()?;
|
.get_reply()?;
|
||||||
|
|
||||||
let gc = self.conn.generate_id();
|
let window_gc = self.conn.generate_id();
|
||||||
xproto::create_gc(
|
xproto::create_gc(
|
||||||
&self.conn,
|
&self.conn,
|
||||||
gc,
|
window_gc,
|
||||||
id,
|
window_id,
|
||||||
&[
|
&[
|
||||||
(xcb::GC_FOREGROUND, screen.white_pixel()),
|
(xcb::GC_FOREGROUND, screen.white_pixel()),
|
||||||
(xcb::GC_BACKGROUND, screen.white_pixel()),
|
(xcb::GC_BACKGROUND, screen.white_pixel()),
|
||||||
|
@ -146,9 +144,12 @@ impl Gui {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct State {
|
struct State {
|
||||||
dragging: bool,
|
dragging: bool,
|
||||||
|
|
||||||
|
// where did we start dragging from
|
||||||
dx: i16,
|
dx: i16,
|
||||||
dy: i16,
|
dy: i16,
|
||||||
|
|
||||||
|
// where is the mouse right now
|
||||||
mx: i16,
|
mx: i16,
|
||||||
my: i16,
|
my: i16,
|
||||||
}
|
}
|
||||||
|
@ -172,11 +173,11 @@ impl Gui {
|
||||||
let mut cancelled = false;
|
let mut cancelled = false;
|
||||||
|
|
||||||
let redraw = |state: &State| {
|
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 {
|
if state.dragging {
|
||||||
let rect = state.rect();
|
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();
|
self.conn.flush();
|
||||||
|
@ -188,15 +189,21 @@ impl Gui {
|
||||||
match evt.response_type() {
|
match evt.response_type() {
|
||||||
xcb::KEY_RELEASE => {
|
xcb::KEY_RELEASE => {
|
||||||
let key_evt = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&evt) };
|
let key_evt = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&evt) };
|
||||||
|
info!("released key {:?}", key_evt.detail());
|
||||||
|
|
||||||
if key_evt.detail() == 9 {
|
if key_evt.detail() == 9 {
|
||||||
|
if state.dragging {
|
||||||
|
state.dragging = false;
|
||||||
|
} else {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
xcb::KEY_PRESS => {}
|
xcb::KEY_PRESS => {}
|
||||||
xcb::BUTTON_PRESS => {
|
xcb::BUTTON_PRESS => {
|
||||||
let button_evt = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&evt) };
|
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.dx = button_evt.root_x();
|
||||||
state.dy = button_evt.root_y();
|
state.dy = button_evt.root_y();
|
||||||
|
|
||||||
|
@ -207,7 +214,7 @@ impl Gui {
|
||||||
}
|
}
|
||||||
xcb::BUTTON_RELEASE => {
|
xcb::BUTTON_RELEASE => {
|
||||||
let button_evt = unsafe { xcb::cast_event::<xcb::ButtonReleaseEvent>(&evt) };
|
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
|
// left mouse button
|
||||||
if state.dragging && button_evt.detail() == 1 {
|
if state.dragging && button_evt.detail() == 1 {
|
||||||
|
@ -241,11 +248,10 @@ pub struct ScreenCapture {
|
||||||
|
|
||||||
impl ScreenCapture {
|
impl ScreenCapture {
|
||||||
pub fn save_to(&self, to: impl AsRef<Path>) -> Result<()> {
|
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, None)
|
||||||
self.save_cropped_to(to, section)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 to = to.as_ref();
|
||||||
let image = ImageBuffer::<Bgra<u8>, _>::from_raw(
|
let image = ImageBuffer::<Bgra<u8>, _>::from_raw(
|
||||||
self.image.width() as u32,
|
self.image.width() as u32,
|
||||||
|
@ -256,14 +262,17 @@ impl ScreenCapture {
|
||||||
let image = DynamicImage::ImageBgra8(image);
|
let image = DynamicImage::ImageBgra8(image);
|
||||||
let mut image = DynamicImage::ImageRgb8(image.into_rgb8());
|
let mut image = DynamicImage::ImageRgb8(image.into_rgb8());
|
||||||
|
|
||||||
// TODO: compare the rectangles to see if we can skip the crop
|
let image = if let Some(section) = section {
|
||||||
// crop the image
|
// crop the image
|
||||||
let image = image.crop(
|
image.crop(
|
||||||
section.x() as u32,
|
section.x() as u32,
|
||||||
section.y() as u32,
|
section.y() as u32,
|
||||||
section.width() as u32,
|
section.width() as u32,
|
||||||
section.height() as u32,
|
section.height() as u32,
|
||||||
);
|
)
|
||||||
|
} else {
|
||||||
|
image
|
||||||
|
};
|
||||||
|
|
||||||
image.save(to)?;
|
image.save(to)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -7,6 +7,7 @@ mod gui;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
@ -31,7 +32,7 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
Region::Selection => {
|
Region::Selection => {
|
||||||
if let Some(rectangle) = gui.interactive_select(&capture)? {
|
if let Some(rectangle) = gui.interactive_select(&capture)? {
|
||||||
capture.save_cropped_to(&opt.outfile, rectangle)?;
|
capture.save_cropped_to(&opt.outfile, Some(rectangle))?;
|
||||||
} else {
|
} else {
|
||||||
info!("Cancelled by user.");
|
info!("Cancelled by user.");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
|
@ -65,8 +66,9 @@ pub enum Region {
|
||||||
Selection,
|
Selection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Region {
|
impl FromStr for Region {
|
||||||
pub fn from_str(x: &str) -> Result<Self> {
|
type Err = anyhow::Error;
|
||||||
|
fn from_str(x: &str) -> Result<Self> {
|
||||||
match x {
|
match x {
|
||||||
"fullscreen" => Ok(Region::Fullscreen),
|
"fullscreen" => Ok(Region::Fullscreen),
|
||||||
"select" | "selection" => Ok(Region::Selection),
|
"select" | "selection" => Ok(Region::Selection),
|
||||||
|
|
Loading…
Reference in a new issue