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]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.30"
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
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 = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.13"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -470,21 +494,23 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
|||
|
||||
[[package]]
|
||||
name = "structopt"
|
||||
version = "0.2.18"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7"
|
||||
checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"lazy_static 1.4.0",
|
||||
"structopt-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "structopt-derive"
|
||||
version = "0.2.18"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107"
|
||||
checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
|
@ -492,9 +518,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.44"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||
checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -577,9 +603,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
|
@ -596,6 +622,12 @@ version = "0.8.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
|
|
|
@ -12,6 +12,6 @@ anyhow = "1.0.36"
|
|||
image = "0.23.12"
|
||||
log = "0.4.8"
|
||||
stderrlog = "0.4.3"
|
||||
structopt = "0.2"
|
||||
xcb-util = { version = "0.3.0", features = ["image", "cursor"] }
|
||||
xcb = { version = "0.9" }
|
||||
structopt = "0.3.21"
|
||||
|
|
190
src/gui.rs
190
src/gui.rs
|
@ -2,8 +2,12 @@ use std::path::Path;
|
|||
use std::ptr;
|
||||
|
||||
use anyhow::Result;
|
||||
use image::ColorType;
|
||||
use xcb::{base::Connection, xproto, Screen};
|
||||
use image::{Bgra, ColorType, DynamicImage, ImageBuffer};
|
||||
use xcb::{
|
||||
base::Connection,
|
||||
xproto::{self, Rectangle},
|
||||
Screen,
|
||||
};
|
||||
use xcb_util::{ffi::image::*, image as xcb_image};
|
||||
|
||||
pub struct Gui {
|
||||
|
@ -48,7 +52,7 @@ impl Gui {
|
|||
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 screen = self.get_default_screen();
|
||||
let root_window = screen.root();
|
||||
|
@ -67,58 +71,168 @@ impl Gui {
|
|||
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
|
||||
screen.root_visual(),
|
||||
&[
|
||||
// (xcb::CW_OVERRIDE_REDIRECT, 1),
|
||||
(xcb::CW_BACK_PIXEL, screen.white_pixel()),
|
||||
(xcb::CW_EVENT_MASK, u32::MAX),
|
||||
(xcb::CW_OVERRIDE_REDIRECT, 1),
|
||||
// (xcb::CW_BACK_PIXEL, screen.white_pixel()),
|
||||
// (xcb::CW_EVENT_MASK, u32::MAX),
|
||||
],
|
||||
).request_check()?;
|
||||
)
|
||||
.request_check()?;
|
||||
|
||||
xproto::map_window(&self.conn, id).request_check()?;
|
||||
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(
|
||||
&self.conn,
|
||||
xcb::INPUT_FOCUS_POINTER_ROOT as u8,
|
||||
id,
|
||||
xcb::CURRENT_TIME,
|
||||
);
|
||||
xproto::grab_pointer(
|
||||
)
|
||||
.request_check()?;
|
||||
|
||||
info!("Grabbing keyboard...");
|
||||
let result = xproto::grab_keyboard(
|
||||
&self.conn,
|
||||
true,
|
||||
false,
|
||||
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::NONE,
|
||||
xcb::NONE,
|
||||
xcb::CURRENT_TIME,
|
||||
);
|
||||
)
|
||||
.get_reply()?;
|
||||
println!("result: {:?}", result.status());
|
||||
|
||||
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
|
||||
let mut dragging = false;
|
||||
let mut dx = -1.0f64;
|
||||
let mut dy = -1.0f64;
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
dragging: bool,
|
||||
dx: i16,
|
||||
dy: i16,
|
||||
|
||||
while let Some(evt) = self.conn.wait_for_event() {
|
||||
mx: i16,
|
||||
my: i16,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn rect(&self) -> Rectangle {
|
||||
let tlx = self.dx.min(self.mx);
|
||||
let tly = self.dy.min(self.my);
|
||||
|
||||
let brx = self.dx.max(self.mx);
|
||||
let bry = self.dy.max(self.my);
|
||||
|
||||
let rw = (brx - tlx) as u16;
|
||||
let rh = (bry - tly) as u16;
|
||||
|
||||
Rectangle::new(tlx, tly, rw, rh)
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = State::default();
|
||||
|
||||
let redraw = |state: &State| {
|
||||
xcb_image::put(&self.conn, id, gc, &image.image, 0, 0);
|
||||
|
||||
match evt.response_type() {
|
||||
xcb::EXPOSE => {
|
||||
println!("EXPOSE");
|
||||
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 => {
|
||||
println!("PRESSED BUTTON");
|
||||
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!("value: {:?}", v);
|
||||
}
|
||||
println!("event type: {:?}", v);
|
||||
}
|
||||
}
|
||||
|
||||
todo!();
|
||||
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 {
|
||||
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();
|
||||
image::save_buffer(
|
||||
to,
|
||||
self.image.data(),
|
||||
let image = ImageBuffer::<Bgra<u8>, _>::from_raw(
|
||||
self.image.width() 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(())
|
||||
}
|
||||
}
|
||||
|
|
56
src/main.rs
56
src/main.rs
|
@ -1,20 +1,70 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
|
||||
mod gui;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use crate::gui::Gui;
|
||||
|
||||
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 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(())
|
||||
}
|
||||
|
||||
/// 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