wip
This commit is contained in:
parent
942d959b53
commit
3536c3f262
11 changed files with 442 additions and 1011 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
shiet.png
|
||||
shiet.*
|
||||
|
|
641
Cargo.lock
generated
641
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
19
Cargo.toml
19
Cargo.toml
|
@ -2,21 +2,16 @@
|
|||
name = "leanshot"
|
||||
description = "Screenshot capture for Linux."
|
||||
version = "0.4.0"
|
||||
repository = "https://git.sr.ht/~iptq/leanshot"
|
||||
repository = "https://git.mzhang.io/michael/leanshot"
|
||||
license-file = "LICENSE"
|
||||
edition = "2018"
|
||||
authors = ["Michael Zhang <iptq@protonmail.com>"]
|
||||
|
||||
[features]
|
||||
default = ["x11"]
|
||||
x11 = ["safex11"]
|
||||
authors = ["Michael Zhang <mail@mzhang.io>"]
|
||||
|
||||
[dependencies]
|
||||
png = "0.14"
|
||||
structopt = "0.2"
|
||||
anyhow = "1.0.31"
|
||||
image = "0.23.5"
|
||||
anyhow = "1.0.36"
|
||||
image = "0.23.12"
|
||||
log = "0.4.8"
|
||||
stderrlog = "0.4.3"
|
||||
|
||||
safex11 = { version = "0.0", path = "../safex11", optional = true, features = ["xlib", "xinerama"] }
|
||||
structopt = "0.2"
|
||||
xcb-util = { version = "0.3.0", features = ["image", "cursor"] }
|
||||
xcb = { version = "0.9" }
|
||||
|
|
18
LICENSE
18
LICENSE
|
@ -1,7 +1,19 @@
|
|||
Copyright 2018-2020 Michael Zhang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
leanshot
|
||||
========
|
||||
|
||||
[![](https://api.travis-ci.org/iptq/leanshot.svg?branch=develop)](https://travis-ci.org/iptq/leanshot)
|
||||
[![dependency status](https://deps.rs/repo/github/iptq/leanshot/status.svg)](https://deps.rs/repo/github/iptq/leanshot)
|
||||
|
||||
Screenshot-capturing utility.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
You must have imlib2 and OpenGL installed. Fortunately, these are relatively common libraries.
|
||||
You must have imlib2 and OpenGL installed. Fortunately, these are relatively
|
||||
common libraries.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
|
180
src/gui.rs
180
src/gui.rs
|
@ -1,67 +1,141 @@
|
|||
use std::path::Path;
|
||||
use std::ptr;
|
||||
|
||||
use anyhow::Result;
|
||||
use image::RgbaImage;
|
||||
use x11::{
|
||||
xlib::{Display, Window},
|
||||
Rectangle,
|
||||
};
|
||||
use image::ColorType;
|
||||
use xcb::{base::Connection, xproto, Screen};
|
||||
use xcb_util::{ffi::image::*, image as xcb_image};
|
||||
|
||||
use crate::platform::{x11::X11, Platform};
|
||||
use crate::{Options, Region};
|
||||
|
||||
pub struct GUI<P: Platform> {
|
||||
inner: P,
|
||||
pub struct Gui {
|
||||
conn: Connection,
|
||||
default_screen: i32,
|
||||
}
|
||||
|
||||
impl GUI<X11> {
|
||||
impl Gui {
|
||||
pub fn new() -> Result<Self> {
|
||||
let x11 = X11::new()?;
|
||||
Ok(GUI { inner: x11 })
|
||||
let (conn, default_screen) = Connection::connect(None)?;
|
||||
Ok(Gui {
|
||||
conn,
|
||||
default_screen,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Platform> GUI<P> {
|
||||
/// Captures the window and produces an Image.
|
||||
pub fn capture_window(&self, opt: &Options, window: Window) -> Result<RgbaImage> {
|
||||
let attr = window.get_attributes()?;
|
||||
let width = attr.get_width();
|
||||
let height = attr.get_height();
|
||||
let root = attr.get_root();
|
||||
fn get_default_screen(&self) -> Screen {
|
||||
// TODO: multiple screens
|
||||
let setup = self.conn.get_setup();
|
||||
let mut iter = setup.roots();
|
||||
iter.next().unwrap()
|
||||
}
|
||||
|
||||
// let (x, y, _) = self.display.translate_coordinates(window, 0, 0, root)?;
|
||||
pub fn capture_entire_screen(&self) -> Result<ScreenCapture> {
|
||||
// get the dimensions of the screen
|
||||
let screen = self.get_default_screen();
|
||||
let (width, height) = (screen.width_in_pixels(), screen.height_in_pixels());
|
||||
println!("width, height = {:?}", (width, height));
|
||||
|
||||
// imlib2::context_set_display(&self.display);
|
||||
// let visual = Visual::default(&self.display, 0);
|
||||
// imlib2::context_set_visual(&visual);
|
||||
let image = xcb_image::get(
|
||||
&self.conn,
|
||||
screen.root(),
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
u32::MAX,
|
||||
xcb::IMAGE_FORMAT_Z_PIXMAP,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Region::Selection = opt.region {
|
||||
let image = self.inner.capture_full_screen()?;
|
||||
let rect = self.inner.show_interactive_selection(image)?;
|
||||
unimplemented!("rect: {:?}", rect);
|
||||
// let capture =
|
||||
// Image2::create_from_drawable(window, 0, x, y, width as i32, height as i32, true)?;
|
||||
// let region = self.interactive_select(&capture)?;
|
||||
// imlib2::context_set_image(&capture);
|
||||
// return imlib2::create_scaled_cropped_image(
|
||||
// region.x,
|
||||
// region.y,
|
||||
// region.width,
|
||||
// region.height,
|
||||
// )
|
||||
// .map_err(|err| err.into());
|
||||
Ok(ScreenCapture { image })
|
||||
}
|
||||
|
||||
pub fn interactive_select(&self, image: &ScreenCapture) -> Result<()> {
|
||||
let 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());
|
||||
|
||||
let window = xproto::create_window(
|
||||
&self.conn,
|
||||
xcb::COPY_FROM_PARENT as u8,
|
||||
id,
|
||||
root_window,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
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),
|
||||
],
|
||||
).request_check()?;
|
||||
|
||||
xproto::map_window(&self.conn, id).request_check()?;
|
||||
self.conn.flush();
|
||||
|
||||
xproto::set_input_focus(
|
||||
&self.conn,
|
||||
xcb::INPUT_FOCUS_POINTER_ROOT as u8,
|
||||
id,
|
||||
xcb::CURRENT_TIME,
|
||||
);
|
||||
xproto::grab_pointer(
|
||||
&self.conn,
|
||||
true,
|
||||
id,
|
||||
u16::MAX,
|
||||
xcb::GRAB_MODE_ASYNC as u8,
|
||||
xcb::GRAB_MODE_ASYNC as u8,
|
||||
xcb::NONE,
|
||||
xcb::NONE,
|
||||
xcb::CURRENT_TIME,
|
||||
);
|
||||
|
||||
let gc = self.conn.generate_id();
|
||||
xproto::create_gc(&self.conn, gc, id, &[]);
|
||||
|
||||
// drag start
|
||||
let mut dragging = false;
|
||||
let mut dx = -1.0f64;
|
||||
let mut dy = -1.0f64;
|
||||
|
||||
while let Some(evt) = self.conn.wait_for_event() {
|
||||
xcb_image::put(&self.conn, id, gc, &image.image, 0, 0);
|
||||
|
||||
match evt.response_type() {
|
||||
xcb::EXPOSE => {
|
||||
println!("EXPOSE");
|
||||
}
|
||||
xcb::BUTTON_PRESS => {
|
||||
println!("PRESSED BUTTON");
|
||||
}
|
||||
v => {
|
||||
println!("value: {:?}", v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unimplemented!()
|
||||
// Image2::create_from_drawable(window, 0, x, y, width as i32, height as i32, true)
|
||||
// .map_err(|err| err.into())
|
||||
}
|
||||
|
||||
/// Get the active window.
|
||||
pub fn get_active_window(&self) -> Result<P::Window> {
|
||||
self.inner.get_active_window()
|
||||
}
|
||||
|
||||
/// Get the active window.
|
||||
pub fn capture_full_screen(&self) -> Result<P::Image> {
|
||||
self.inner.capture_full_screen()
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScreenCapture {
|
||||
image: xcb_image::Image,
|
||||
}
|
||||
|
||||
impl ScreenCapture {
|
||||
pub fn save_to(&self, to: impl AsRef<Path>) -> Result<()> {
|
||||
let to = to.as_ref();
|
||||
image::save_buffer(
|
||||
to,
|
||||
self.image.data(),
|
||||
self.image.width() as u32,
|
||||
self.image.height() as u32,
|
||||
ColorType::Bgra8,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
38
src/lib.rs
38
src/lib.rs
|
@ -1,38 +0,0 @@
|
|||
//! Screenshot capturing utility.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate safex11 as x11;
|
||||
|
||||
mod platform;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub use crate::platform::{x11::X11, Image, Platform};
|
||||
|
||||
type Rectangle = x11::Rectangle;
|
||||
|
||||
/// A region option
|
||||
#[allow(missing_docs)]
|
||||
pub enum Region {
|
||||
Fullscreen,
|
||||
ActiveWindow,
|
||||
Selection,
|
||||
}
|
||||
|
||||
impl Region {
|
||||
/// Convert string to Region enum
|
||||
// TODO: use FromStr trait
|
||||
pub fn from_str(x: &str) -> Result<Self> {
|
||||
match x {
|
||||
"fullscreen" => Ok(Region::Fullscreen),
|
||||
"window" => Ok(Region::ActiveWindow),
|
||||
"select" | "selection" => Ok(Region::Selection),
|
||||
_ => bail!("please choose a region from 'fullscreen', 'window', or 'select'"),
|
||||
}
|
||||
}
|
||||
}
|
103
src/main.rs
103
src/main.rs
|
@ -1,107 +1,20 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::path::PathBuf;
|
||||
mod gui;
|
||||
|
||||
use anyhow::Result;
|
||||
use image::{imageops, RgbImage};
|
||||
use leanshot::{Image, Platform, Region, X11};
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn main() -> Result<()> {
|
||||
let opt = Options::from_args();
|
||||
stderrlog::new()
|
||||
.module(module_path!())
|
||||
.module("safex11")
|
||||
.verbosity(opt.verbose)
|
||||
.init()
|
||||
.unwrap();
|
||||
use crate::gui::Gui;
|
||||
|
||||
let gui = X11::new()?;
|
||||
fn main() -> Result<()> {
|
||||
stderrlog::new().module(module_path!()).init().unwrap();
|
||||
|
||||
let screen_layout = gui.get_screen_layout()?;
|
||||
debug!("screen layout: {:?}", screen_layout);
|
||||
let gui = Gui::new()?;
|
||||
let capture = gui.capture_entire_screen()?;
|
||||
capture.save_to("shiet.jpg")?;
|
||||
|
||||
let _image = match opt.region {
|
||||
Region::ActiveWindow => {
|
||||
let window = gui.get_active_window()?;
|
||||
let _image = gui.capture_window(window);
|
||||
}
|
||||
Region::Fullscreen | Region::Selection => {
|
||||
let _image = gui.capture_full_screen()?;
|
||||
|
||||
// calculate the full size of the image
|
||||
let mut min_x = 0;
|
||||
let mut min_y = 0;
|
||||
let mut max_x = 0;
|
||||
let mut max_y = 0;
|
||||
for (i, (_, screen)) in screen_layout.iter().enumerate() {
|
||||
let left = screen.x;
|
||||
let right = screen.x + screen.width as i16;
|
||||
let top = screen.y;
|
||||
let bottom = screen.y + screen.height as i16;
|
||||
if i == 0 {
|
||||
min_x = left;
|
||||
min_y = top;
|
||||
max_x = right;
|
||||
max_y = bottom;
|
||||
} else {
|
||||
min_x = min_x.min(left);
|
||||
min_y = min_y.min(top);
|
||||
max_x = max_x.max(right);
|
||||
max_y = max_y.max(bottom);
|
||||
}
|
||||
}
|
||||
|
||||
// make a new image
|
||||
let width = (max_x - min_x) as u32;
|
||||
let height = (max_y - min_y) as u32;
|
||||
let mut base_image = RgbImage::new(width, height);
|
||||
|
||||
// copy all of the images into it
|
||||
let images = gui.capture_full_screen()?;
|
||||
for (id, image) in images.iter() {
|
||||
let screen = screen_layout.get(&id).expect("shouldn't fail");
|
||||
let x = (screen.x + min_x) as u32;
|
||||
let y = (screen.y + min_y) as u32;
|
||||
let image = image.to_rgb_image();
|
||||
imageops::overlay(&mut base_image, &image, x, y);
|
||||
}
|
||||
|
||||
if let Region::Selection = opt.region {
|
||||
// bring up the interactive selection
|
||||
let _rect = gui.show_interactive_selection(&images)?;
|
||||
}
|
||||
|
||||
// save the image
|
||||
base_image.save(&opt.outfile)?;
|
||||
}
|
||||
};
|
||||
gui.interactive_select(&capture)?;
|
||||
|
||||
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,
|
||||
|
||||
/// Whether or not to also copy it to the clipboard
|
||||
#[structopt(short = "c")]
|
||||
pub clipboard: bool,
|
||||
|
||||
/// Verbosity of output
|
||||
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
|
||||
pub verbose: usize,
|
||||
|
||||
/// X display connection string to use
|
||||
#[structopt(long = "x-display", default_value = ":0")]
|
||||
pub x_display: String,
|
||||
}
|
||||
|
|
279
src/old.rs
279
src/old.rs
|
@ -1,279 +0,0 @@
|
|||
|
||||
|
||||
/// Brings up an interactive selection GUI.
|
||||
pub fn interactive_select(&self, capture: &Image2) -> Result<Rectangle> {
|
||||
// let window = SelectWindow::new(&self.display);
|
||||
// let root = self.display.get_default_root_window()?;
|
||||
|
||||
// let root_im = root.get_image();
|
||||
|
||||
// let mut done = 0;
|
||||
// let mut button_press = false;
|
||||
// while done == 0 && self.display.pending()? > 0 {
|
||||
// let ev = self.display.next_event()?;
|
||||
// match ev.kind() {
|
||||
// EventKind::ButtonPress => {
|
||||
// button_press = true;
|
||||
// }
|
||||
// EventKind::ButtonRelease => {
|
||||
// if button_press {
|
||||
// done = 1;
|
||||
// }
|
||||
// button_press = false;
|
||||
// }
|
||||
// _ => (),
|
||||
// }
|
||||
// }
|
||||
|
||||
use glutin::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
os::unix::{WindowBuilderExt, WindowExt, XWindowType},
|
||||
ElementState, Event, EventsLoop, GlContext, GlWindow, KeyboardInput, MouseButton,
|
||||
MouseCursor, VirtualKeyCode, WindowBuilder, WindowEvent,
|
||||
};
|
||||
use nanovg::{Image, ImagePattern, PathOptions, StrokeOptions};
|
||||
use std::{f32::consts, mem, slice};
|
||||
|
||||
// let attr = window.get_attributes()?;
|
||||
// let width = attr.get_width();
|
||||
// let height = attr.get_height();
|
||||
// let root = attr.get_root();
|
||||
// let (x, y, _) = self.display.translate_coordinates(window, 0, 0, root)?;
|
||||
let width = capture.get_width();
|
||||
let height = capture.get_height();
|
||||
|
||||
let mut evl = EventsLoop::new();
|
||||
let mon = evl.get_primary_monitor();
|
||||
|
||||
// TODO: handle error
|
||||
let wb = WindowBuilder::new()
|
||||
.with_x11_window_type(XWindowType::Splash)
|
||||
.with_decorations(false)
|
||||
.with_visibility(false)
|
||||
.with_always_on_top(true)
|
||||
.with_dimensions(
|
||||
PhysicalSize::new(width.into(), height.into()).to_logical(mon.get_hidpi_factor()),
|
||||
)
|
||||
.with_fullscreen(Some(mon));
|
||||
let ctx = glutin::ContextBuilder::new()
|
||||
.with_vsync(false)
|
||||
.with_multisampling(4)
|
||||
.with_double_buffer(Some(true))
|
||||
.with_srgb(true);
|
||||
let win = GlWindow::new(wb, ctx, &evl).expect("couldn't make window");
|
||||
win.set_position((0.0, 0.0).into());
|
||||
let f = win.get_hidpi_factor() as f64;
|
||||
|
||||
// crosshair
|
||||
win.set_cursor(MouseCursor::Crosshair);
|
||||
// win.set_inner_size((width, height).into());
|
||||
// println!("size={:?} pos={:?} outer={:?}", win.get_inner_size(), win.get_inner_position(), win.get_outer_size());
|
||||
// println!("{:?}", win.get_hidpi_factor());
|
||||
|
||||
let x = Display::from_handle(win.get_xlib_display().unwrap() as u64);
|
||||
let len;
|
||||
let raw_data;
|
||||
{
|
||||
let _g = x.grab();
|
||||
// let img = Image2::create_from_drawable(window, 0, 0, 0, width as i32, height as i32, true)?;
|
||||
imlib2::context_set_image(&capture);
|
||||
len = (width * height) as usize;
|
||||
// println!("{}", window.as_raw());
|
||||
raw_data = unsafe { slice::from_raw_parts(imlib2::image_get_data(), len) };
|
||||
|
||||
unsafe {
|
||||
win.make_current().expect("couldn't make window");
|
||||
gl::load_with(|sym| win.get_proc_address(sym) as *const _);
|
||||
}
|
||||
}
|
||||
mem::forget(x);
|
||||
|
||||
// convert ARGB to RGBA
|
||||
let mut data = vec![0u32; raw_data.len()];
|
||||
data.copy_from_slice(raw_data);
|
||||
for i in &mut data {
|
||||
// fix the colors
|
||||
*i = (*i & 0xff00ff00) | ((*i & 0xff) << 16) | ((*i >> 16) & 0xff);
|
||||
}
|
||||
|
||||
// invert image
|
||||
let mut inverted = vec![0u32; raw_data.len()];
|
||||
inverted.copy_from_slice(raw_data);
|
||||
for i in &mut inverted {
|
||||
// fix the colors
|
||||
*i = (*i & 0xff000000) | !(*i & 0xffffff);
|
||||
}
|
||||
|
||||
let ctx = nanovg::ContextBuilder::new()
|
||||
.build()
|
||||
.expect("couldn't init nanovg");
|
||||
|
||||
let image = Image::new(&ctx)
|
||||
.build_from_rgba(width as usize, height as usize, data.as_slice())
|
||||
.expect("couldn't create image");
|
||||
|
||||
let inverted_image = Image::new(&ctx)
|
||||
.build_from_rgba(width as usize, height as usize, inverted.as_slice())
|
||||
.expect("couldn't create image");
|
||||
|
||||
let mut running = true;
|
||||
let mut down = false;
|
||||
// drag start
|
||||
let mut dx = -1.0f64;
|
||||
let mut dy = -1.0f64;
|
||||
// curr mouse
|
||||
let mut mx = -1.0f64;
|
||||
let mut my = -1.0f64;
|
||||
// rect
|
||||
let mut rectw = 0.0f64;
|
||||
let mut recth = 0.0f64;
|
||||
let mut delayed_down = false;
|
||||
let mut redraw = true;
|
||||
|
||||
win.show();
|
||||
win.set_position(PhysicalPosition::new(0.0, 0.0).to_logical(f));
|
||||
while running {
|
||||
if redraw {
|
||||
// let size = win.get_inner_size().unwrap();
|
||||
// let (width, height) = (size.width as i32, size.height as i32);
|
||||
|
||||
unsafe {
|
||||
gl::Viewport(0, 0, width as i32, height as i32);
|
||||
gl::ClearColor(0.3, 0.3, 0.32, 1.0);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);
|
||||
}
|
||||
|
||||
let (width, height) = (width as f64, height as f64);
|
||||
ctx.frame((width as f32, height as f32), f as f32, |frame| {
|
||||
let path_opts = PathOptions::default();
|
||||
frame.path(
|
||||
|path| {
|
||||
path.rect((0.0, 0.0), ((width * f) as f32, (height * f) as f32));
|
||||
// path.fill(Color::from_rgba(200, 200, 200, 255), Default::default());
|
||||
path.fill(
|
||||
ImagePattern {
|
||||
image: &image,
|
||||
origin: (0.0, 0.0),
|
||||
size: (width as f32, height as f32),
|
||||
angle: 0.0 / 180.0 * consts::PI,
|
||||
alpha: 1.0,
|
||||
},
|
||||
Default::default(),
|
||||
)
|
||||
},
|
||||
path_opts,
|
||||
);
|
||||
if down && rectw.abs() > 0.0 && recth.abs() > 0.0 {
|
||||
frame.path(
|
||||
|path| {
|
||||
path.rect(
|
||||
((dx * f) as f32, (dy * f) as f32),
|
||||
((rectw * f) as f32, (recth * f) as f32),
|
||||
);
|
||||
path.stroke(
|
||||
// Color::from_rgba(0, 0, 0, 255),
|
||||
ImagePattern {
|
||||
image: &inverted_image,
|
||||
origin: (0.0, 0.0),
|
||||
size: (width as f32, height as f32),
|
||||
angle: 0.0 / 180.0 * consts::PI,
|
||||
alpha: 1.0,
|
||||
},
|
||||
StrokeOptions {
|
||||
width: 1.0,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
},
|
||||
path_opts,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
evl.poll_events(|event| {
|
||||
if let Event::WindowEvent { event, .. } = event {
|
||||
match event {
|
||||
WindowEvent::Destroyed => running = false,
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
virtual_keycode,
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
if let (Some(VirtualKeyCode::Escape), ElementState::Released) =
|
||||
(virtual_keycode, state)
|
||||
{
|
||||
if down {
|
||||
down = false;
|
||||
rectw = 0.0;
|
||||
recth = 0.0;
|
||||
} else {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
mx = position.x;
|
||||
my = position.y;
|
||||
if down {
|
||||
if delayed_down {
|
||||
dx = mx;
|
||||
dy = my;
|
||||
delayed_down = false;
|
||||
} else {
|
||||
redraw = true;
|
||||
}
|
||||
rectw = mx - dx;
|
||||
recth = my - dy;
|
||||
}
|
||||
}
|
||||
WindowEvent::MouseInput { button, state, .. } => {
|
||||
if let MouseButton::Left = button {
|
||||
down = match state {
|
||||
ElementState::Pressed => {
|
||||
delayed_down = true;
|
||||
if mx < 0.0 || my < 0.0 {
|
||||
} else {
|
||||
dx = mx;
|
||||
dy = my;
|
||||
}
|
||||
true
|
||||
}
|
||||
ElementState::Released => {
|
||||
if down && rectw.abs() > 0.0 && recth.abs() > 0.0 {
|
||||
running = false;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
});
|
||||
win.swap_buffers().expect("couldn't swap buffers");
|
||||
}
|
||||
if rectw.abs() > 0.0 && recth.abs() > 0.0 {
|
||||
let mut x = dx;
|
||||
let mut y = dy;
|
||||
if rectw < 0.0 {
|
||||
x += rectw;
|
||||
}
|
||||
if recth < 0.0 {
|
||||
y += recth;
|
||||
}
|
||||
Ok(Rectangle::new(
|
||||
(x * f) as i32,
|
||||
(y * f) as i32,
|
||||
(rectw.abs() * f) as u32,
|
||||
(recth.abs() * f) as u32,
|
||||
))
|
||||
} else {
|
||||
Err(ScreenshotError::Error)
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
#[cfg(feature = "x11")]
|
||||
pub mod x11;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
use anyhow::Result;
|
||||
use image::RgbImage;
|
||||
|
||||
use crate::Rectangle;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScreenInfo {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub x: i16,
|
||||
pub y: i16,
|
||||
}
|
||||
|
||||
/// Set of functions that all platforms must implement
|
||||
pub trait Platform {
|
||||
/// Type of window handles
|
||||
type Window;
|
||||
|
||||
/// Type of images
|
||||
type Image: Image;
|
||||
|
||||
/// Type of screen handles (must be hashable and uniquely identifying)
|
||||
type ScreenId: Hash;
|
||||
|
||||
/// Get a handle to the currently active window
|
||||
fn get_active_window(&self) -> Result<Self::Window>;
|
||||
|
||||
/// Capture a specific window by handle
|
||||
fn capture_window(&self, window: Self::Window) -> Result<Self::Image>;
|
||||
|
||||
/// Get the screen layout (list of screens, where they are)
|
||||
fn get_screen_layout(&self) -> Result<HashMap<Self::ScreenId, ScreenInfo>>;
|
||||
|
||||
/// Capture full screen
|
||||
fn capture_full_screen(&self) -> Result<HashMap<Self::ScreenId, Self::Image>>;
|
||||
|
||||
/// Open the interactive selection interface
|
||||
fn show_interactive_selection(
|
||||
&self,
|
||||
image: &HashMap<Self::ScreenId, Self::Image>,
|
||||
) -> Result<Rectangle>;
|
||||
}
|
||||
|
||||
/// Set of functions platform-specific images must implement
|
||||
pub trait Image {
|
||||
/// Convert the image into an image::RgbImage
|
||||
fn to_rgb_image(&self) -> RgbImage;
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use image::{Rgb, RgbImage};
|
||||
use x11::{
|
||||
xinerama::ScreensInfo,
|
||||
xlib::{Display, ImageByteOrder, Window},
|
||||
};
|
||||
|
||||
use crate::platform::{Image as ImageT, Platform, ScreenInfo};
|
||||
use crate::Rectangle;
|
||||
|
||||
/// Interface to x11
|
||||
pub struct X11 {
|
||||
inner: Display,
|
||||
}
|
||||
|
||||
impl X11 {
|
||||
/// Create a new x11 instace
|
||||
pub fn new() -> Result<Self> {
|
||||
// TODO: configure connection string
|
||||
let display = Display::connect(":0")?;
|
||||
|
||||
Ok(X11 { inner: display })
|
||||
}
|
||||
}
|
||||
|
||||
impl Platform for X11 {
|
||||
type Window = x11::x11::xlib::Window;
|
||||
type Image = Image;
|
||||
type ScreenId = i32;
|
||||
|
||||
fn get_active_window(&self) -> Result<Self::Window> {
|
||||
let (window, _) = self.inner.get_input_focus()?;
|
||||
Ok(window.inner)
|
||||
}
|
||||
|
||||
fn capture_window(&self, window: Self::Window) -> Result<Self::Image> {
|
||||
let window = Window {
|
||||
display: &self.inner,
|
||||
inner: window,
|
||||
};
|
||||
let image = window.get_image()?;
|
||||
Ok(Image(image))
|
||||
}
|
||||
|
||||
fn capture_full_screen(&self) -> Result<HashMap<Self::ScreenId, Self::Image>> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
// wait what?
|
||||
// x11 doesn't have a root window for each screen apparently
|
||||
let window = self.inner.get_default_root_window()?;
|
||||
result.insert(0, Image(window.get_image()?));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn get_screen_layout(&self) -> Result<HashMap<Self::ScreenId, ScreenInfo>> {
|
||||
let screens_info = ScreensInfo::query(&self.inner)?;
|
||||
Ok(screens_info
|
||||
.iter()
|
||||
.map(|(num, screen)| {
|
||||
(
|
||||
num,
|
||||
ScreenInfo {
|
||||
x: screen.x,
|
||||
y: screen.y,
|
||||
width: screen.width as u16,
|
||||
height: screen.height as u16,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn show_interactive_selection(
|
||||
&self,
|
||||
image: &HashMap<Self::ScreenId, Self::Image>,
|
||||
) -> Result<Rectangle> {
|
||||
let root_window = self.inner.get_default_root_window()?;
|
||||
let window = Window::create(
|
||||
&self.inner,
|
||||
Some(root_window),
|
||||
Rectangle::new(0, 0, 500, 500),
|
||||
Default::default(),
|
||||
)?;
|
||||
window.map();
|
||||
|
||||
loop {
|
||||
let event = self.inner.next_event()?;
|
||||
debug!("event: {:?}", event);
|
||||
|
||||
// quit
|
||||
}
|
||||
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Image(x11::xlib::Image);
|
||||
|
||||
impl ImageT for Image {
|
||||
fn to_rgb_image(&self) -> RgbImage {
|
||||
let width = self.0.get_width();
|
||||
let height = self.0.get_height();
|
||||
let pixbuf = self.0.buffer();
|
||||
let byte_order = self.0.get_byte_order().unwrap();
|
||||
RgbImage::from_fn(width, height, |x, y| {
|
||||
let (r, g, b) = pixbuf.get_pixel(x, y).unwrap();
|
||||
[r, g, b].into()
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue