kinda works

This commit is contained in:
Michael Zhang 2020-12-23 22:42:45 -06:00
parent 3536c3f262
commit 36d6192d4f
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
4 changed files with 264 additions and 52 deletions

58
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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(())
} }
} }

View file

@ -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}"),
}
}
}