forked from michael/leanshot
kinda works
This commit is contained in:
parent
3536c3f262
commit
36d6192d4f
4 changed files with 264 additions and 52 deletions
58
Cargo.lock
generated
58
Cargo.lock
generated
|
@ -380,19 +380,43 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro-error"
|
||||||
version = "0.4.30"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "0.6.13"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -470,21 +494,23 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "structopt"
|
name = "structopt"
|
||||||
version = "0.2.18"
|
version = "0.3.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7"
|
checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"lazy_static 1.4.0",
|
||||||
"structopt-derive",
|
"structopt-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "structopt-derive"
|
name = "structopt-derive"
|
||||||
version = "0.2.18"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107"
|
checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
|
@ -492,9 +518,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "0.15.44"
|
version = "1.0.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -577,9 +603,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.1.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unreachable"
|
name = "unreachable"
|
||||||
|
@ -596,6 +622,12 @@ version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "void"
|
name = "void"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
|
@ -12,6 +12,6 @@ anyhow = "1.0.36"
|
||||||
image = "0.23.12"
|
image = "0.23.12"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
stderrlog = "0.4.3"
|
stderrlog = "0.4.3"
|
||||||
structopt = "0.2"
|
|
||||||
xcb-util = { version = "0.3.0", features = ["image", "cursor"] }
|
xcb-util = { version = "0.3.0", features = ["image", "cursor"] }
|
||||||
xcb = { version = "0.9" }
|
xcb = { version = "0.9" }
|
||||||
|
structopt = "0.3.21"
|
||||||
|
|
200
src/gui.rs
200
src/gui.rs
|
@ -2,8 +2,12 @@ use std::path::Path;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use image::ColorType;
|
use image::{Bgra, ColorType, DynamicImage, ImageBuffer};
|
||||||
use xcb::{base::Connection, xproto, Screen};
|
use xcb::{
|
||||||
|
base::Connection,
|
||||||
|
xproto::{self, Rectangle},
|
||||||
|
Screen,
|
||||||
|
};
|
||||||
use xcb_util::{ffi::image::*, image as xcb_image};
|
use xcb_util::{ffi::image::*, image as xcb_image};
|
||||||
|
|
||||||
pub struct Gui {
|
pub struct Gui {
|
||||||
|
@ -48,7 +52,7 @@ impl Gui {
|
||||||
Ok(ScreenCapture { image })
|
Ok(ScreenCapture { image })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interactive_select(&self, image: &ScreenCapture) -> Result<()> {
|
pub fn interactive_select(&self, image: &ScreenCapture) -> Result<Rectangle> {
|
||||||
let id = self.conn.generate_id();
|
let 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();
|
||||||
|
@ -67,58 +71,168 @@ impl Gui {
|
||||||
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
|
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
|
||||||
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_BACK_PIXEL, screen.white_pixel()),
|
||||||
(xcb::CW_EVENT_MASK, u32::MAX),
|
// (xcb::CW_EVENT_MASK, u32::MAX),
|
||||||
],
|
],
|
||||||
).request_check()?;
|
)
|
||||||
|
.request_check()?;
|
||||||
|
|
||||||
xproto::map_window(&self.conn, id).request_check()?;
|
xproto::map_window(&self.conn, id).request_check()?;
|
||||||
self.conn.flush();
|
self.conn.flush();
|
||||||
|
|
||||||
|
let wm_state = xproto::intern_atom(&self.conn, true, "_NET_WM_STATE")
|
||||||
|
.get_reply()?
|
||||||
|
.atom();
|
||||||
|
let wm_fullscreen = xproto::intern_atom(&self.conn, true, "_NET_WM_STATE_FULLSCREEN")
|
||||||
|
.get_reply()?
|
||||||
|
.atom();
|
||||||
|
xproto::change_property(
|
||||||
|
&self.conn,
|
||||||
|
xcb::PROP_MODE_REPLACE as u8,
|
||||||
|
id,
|
||||||
|
wm_state,
|
||||||
|
4,
|
||||||
|
32,
|
||||||
|
&[wm_fullscreen],
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("Setting input focus...");
|
||||||
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,
|
id,
|
||||||
xcb::CURRENT_TIME,
|
xcb::CURRENT_TIME,
|
||||||
);
|
)
|
||||||
xproto::grab_pointer(
|
.request_check()?;
|
||||||
|
|
||||||
|
info!("Grabbing keyboard...");
|
||||||
|
let result = xproto::grab_keyboard(
|
||||||
&self.conn,
|
&self.conn,
|
||||||
true,
|
false,
|
||||||
id,
|
id,
|
||||||
u16::MAX,
|
xcb::CURRENT_TIME,
|
||||||
|
xcb::GRAB_MODE_ASYNC as u8,
|
||||||
|
xcb::GRAB_MODE_ASYNC as u8,
|
||||||
|
)
|
||||||
|
.get_reply()?;
|
||||||
|
println!("result: {:?}", result.status());
|
||||||
|
|
||||||
|
info!("Grabbing pointer...");
|
||||||
|
let result = xproto::grab_pointer(
|
||||||
|
&self.conn,
|
||||||
|
false,
|
||||||
|
id,
|
||||||
|
(xcb::EVENT_MASK_BUTTON_PRESS
|
||||||
|
| xcb::EVENT_MASK_BUTTON_RELEASE
|
||||||
|
| xcb::EVENT_MASK_POINTER_MOTION) 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,
|
||||||
xcb::NONE,
|
xcb::NONE,
|
||||||
xcb::NONE,
|
xcb::NONE,
|
||||||
xcb::CURRENT_TIME,
|
xcb::CURRENT_TIME,
|
||||||
);
|
)
|
||||||
|
.get_reply()?;
|
||||||
|
println!("result: {:?}", result.status());
|
||||||
|
|
||||||
let gc = self.conn.generate_id();
|
let gc = self.conn.generate_id();
|
||||||
xproto::create_gc(&self.conn, gc, id, &[]);
|
xproto::create_gc(
|
||||||
|
&self.conn,
|
||||||
|
gc,
|
||||||
|
id,
|
||||||
|
&[
|
||||||
|
(xcb::GC_FOREGROUND, screen.white_pixel()),
|
||||||
|
(xcb::GC_BACKGROUND, screen.white_pixel()),
|
||||||
|
(xcb::GC_LINE_WIDTH, 3),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
// drag start
|
#[derive(Default)]
|
||||||
let mut dragging = false;
|
struct State {
|
||||||
let mut dx = -1.0f64;
|
dragging: bool,
|
||||||
let mut dy = -1.0f64;
|
dx: i16,
|
||||||
|
dy: i16,
|
||||||
|
|
||||||
while let Some(evt) = self.conn.wait_for_event() {
|
mx: i16,
|
||||||
xcb_image::put(&self.conn, id, gc, &image.image, 0, 0);
|
my: i16,
|
||||||
|
}
|
||||||
|
|
||||||
match evt.response_type() {
|
impl State {
|
||||||
xcb::EXPOSE => {
|
pub fn rect(&self) -> Rectangle {
|
||||||
println!("EXPOSE");
|
let tlx = self.dx.min(self.mx);
|
||||||
}
|
let tly = self.dy.min(self.my);
|
||||||
xcb::BUTTON_PRESS => {
|
|
||||||
println!("PRESSED BUTTON");
|
let brx = self.dx.max(self.mx);
|
||||||
}
|
let bry = self.dy.max(self.my);
|
||||||
v => {
|
|
||||||
println!("value: {:?}", v);
|
let rw = (brx - tlx) as u16;
|
||||||
}
|
let rh = (bry - tly) as u16;
|
||||||
|
|
||||||
|
Rectangle::new(tlx, tly, rw, rh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
todo!();
|
let mut state = State::default();
|
||||||
|
|
||||||
|
let redraw = |state: &State| {
|
||||||
|
xcb_image::put(&self.conn, id, gc, &image.image, 0, 0);
|
||||||
|
|
||||||
|
if state.dragging {
|
||||||
|
let rect = state.rect();
|
||||||
|
xproto::poly_rectangle(&self.conn, id, gc, &[rect]);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.conn.flush();
|
||||||
|
};
|
||||||
|
|
||||||
|
redraw(&state);
|
||||||
|
|
||||||
|
while let Some(evt) = self.conn.wait_for_event() {
|
||||||
|
match evt.response_type() {
|
||||||
|
xcb::KEY_RELEASE => {
|
||||||
|
let key_evt = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&evt) };
|
||||||
|
if key_evt.detail() == 9 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xcb::KEY_PRESS => {}
|
||||||
|
xcb::BUTTON_PRESS => {
|
||||||
|
let button_evt = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&evt) };
|
||||||
|
info!("pressed {:?}", button_evt.detail());
|
||||||
|
state.dx = button_evt.root_x();
|
||||||
|
state.dy = button_evt.root_y();
|
||||||
|
|
||||||
|
// left mouse button
|
||||||
|
if button_evt.detail() == 1 {
|
||||||
|
state.dragging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xcb::BUTTON_RELEASE => {
|
||||||
|
let button_evt = unsafe { xcb::cast_event::<xcb::ButtonReleaseEvent>(&evt) };
|
||||||
|
info!("released {:?}", button_evt.detail());
|
||||||
|
|
||||||
|
// left mouse button
|
||||||
|
if state.dragging && button_evt.detail() == 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xcb::MOTION_NOTIFY => {
|
||||||
|
let motion_evt = unsafe { xcb::cast_event::<xcb::MotionNotifyEvent>(&evt) };
|
||||||
|
state.mx = motion_evt.root_x();
|
||||||
|
state.my = motion_evt.root_y();
|
||||||
|
}
|
||||||
|
v => {
|
||||||
|
println!("event type: {:?}", v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redraw(&state);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Loop exited, cleaning up...");
|
||||||
|
xproto::ungrab_keyboard(&self.conn, xcb::CURRENT_TIME).request_check()?;
|
||||||
|
|
||||||
|
Ok(state.rect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,14 +242,30 @@ 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, section)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_cropped_to(&self, to: impl AsRef<Path>, section: Rectangle) -> Result<()> {
|
||||||
let to = to.as_ref();
|
let to = to.as_ref();
|
||||||
image::save_buffer(
|
let image = ImageBuffer::<Bgra<u8>, _>::from_raw(
|
||||||
to,
|
|
||||||
self.image.data(),
|
|
||||||
self.image.width() as u32,
|
self.image.width() as u32,
|
||||||
self.image.height() as u32,
|
self.image.height() as u32,
|
||||||
ColorType::Bgra8,
|
self.image.data().to_vec(),
|
||||||
)?;
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut image = DynamicImage::ImageBgra8(image);
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
|
||||||
|
image.save(to)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
56
src/main.rs
56
src/main.rs
|
@ -1,20 +1,70 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate anyhow;
|
||||||
|
|
||||||
mod gui;
|
mod gui;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use crate::gui::Gui;
|
use crate::gui::Gui;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
stderrlog::new().module(module_path!()).init().unwrap();
|
let opt = Options::from_args();
|
||||||
|
|
||||||
|
stderrlog::new()
|
||||||
|
.module(module_path!())
|
||||||
|
.verbosity(5)
|
||||||
|
.init()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let gui = Gui::new()?;
|
let gui = Gui::new()?;
|
||||||
let capture = gui.capture_entire_screen()?;
|
let capture = gui.capture_entire_screen()?;
|
||||||
capture.save_to("shiet.jpg")?;
|
|
||||||
|
|
||||||
gui.interactive_select(&capture)?;
|
match opt.region {
|
||||||
|
Region::Fullscreen => {
|
||||||
|
capture.save_to(&opt.outfile)?;
|
||||||
|
}
|
||||||
|
Region::Selection => {
|
||||||
|
let rectangle = gui.interactive_select(&capture)?;
|
||||||
|
capture.save_cropped_to(&opt.outfile, rectangle);
|
||||||
|
}
|
||||||
|
_ => todo!("TODO"),
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Options for screenshot
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
pub struct Options {
|
||||||
|
/// The region to select (fullscreen | window | select)
|
||||||
|
#[structopt(parse(try_from_str = Region::from_str))]
|
||||||
|
pub region: Region,
|
||||||
|
|
||||||
|
/// The file to save the screenshot to
|
||||||
|
#[structopt(short = "o", long = "out", parse(from_os_str))]
|
||||||
|
pub outfile: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A region option
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum Region {
|
||||||
|
Fullscreen,
|
||||||
|
ActiveWindow,
|
||||||
|
Selection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Region {
|
||||||
|
pub fn from_str(x: &str) -> Result<Self> {
|
||||||
|
match x {
|
||||||
|
"fullscreen" => Ok(Region::Fullscreen),
|
||||||
|
"window" => Ok(Region::ActiveWindow),
|
||||||
|
"select" | "selection" => Ok(Region::Selection),
|
||||||
|
_ => bail!("expected {fullscreen|window|selection}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue