Compare commits

..

3 commits

Author SHA1 Message Date
1851311684
cargo.lock 2020-07-13 12:51:52 -05:00
1897deb33b
bump version to 0.3.4 2020-07-13 12:48:41 -05:00
ee9f00284d
use DISPLAY environment variable 2020-07-13 12:48:23 -05:00
35 changed files with 2457 additions and 732 deletions

View file

@ -1,9 +0,0 @@
root = true
[*]
end_of_file = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
indent_size = 4

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
/target /target
**/*.rs.bk **/*.rs.bk
shiet.* shiet.png

44
.travis.yml Normal file
View file

@ -0,0 +1,44 @@
language: rust
sudo: false
cache: cargo
dist: trusty
addons:
apt:
packages:
- libx11-dev
- libimlib2-dev
- build-essential
- mesa-common-dev
env:
global:
- PROJECT_NAME=leanshot
matrix:
include:
- os: linux
rust: stable
env: TARGET=x86_64-unknown-linux-gnu
fast_finish: true
before_script: rustup target add $TARGET || echo ok
script:
- if [ -z "$TRAVIS_TAG" ]; then cargo check --all --target $TARGET; fi
before_deploy: ./ci/package $TRAVIS_TAG $TARGET
deploy:
- provider: releases
api_key:
secure: ZCA3+LjTzliSC3m9DjcEYZOsRhaI5r9LAHfuw5HuaLYbprbEVwqLCTV1ioe7F9dLPembqGoGEsVnWAsrAUP0C9BRUuOrZMYvTOS3qTwSX7pPc/fsNUzP9uc6XPIMQ7/QeakmpgVYvObt9/1Wxui715nPmj9h+YIB4P3BUT8VsufzG0LogNSTwkhicM19O9oFhoCg39qQBueBe2SrS35dqvofSoJlEk2wSBBesQ9xuw9goP8ZkTKD3w/ZmPUS9RuWi1FSzsUcnLGCEMwYxLk9CBnG9I0G8kvDF194UYq7YnlaK4sn4AG5UxkMYcWN573vb+KX56VoHa57Z7qNIoUy1Kk0MatIjPIxd2LQJSTl7I5sckfoq61wihRYYj8R1qI4V4mqBX+MHFwyfs7/XT2pVfPHeflvJ4MMZW5t+OYWv8LfwFgufbLyYS79ZkBIjVnk2ligSv8kSPcuTtnSzp6UJlHsEbRDlWcciAYA92d2bggz7BrSG+X1eDAVto+bGdAeAp5zgRPVKwbpA9aD52y2GmlMh4Z+GcjhF6U7z+2AYRalH9X8d4mSdQ7KuhFfigoR/84tO+kC5QeKnf3FfgXPzOimt8xCsd2MFDrg2sPvgFkQGyM0ihB9NnNBfiFrQrRP3+DyTCI3TqOfkz8QKe4h7DxefR1t+YvheNZjGtC8C+E=
file_glob: true
file:
- leanshot-*
on:
condition: $TRAVIS_RUST_VERSION = stable
tags: true
skip_cleanup: true
notifications:
email: false

1304
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,22 @@
[package] [package]
name = "leanshot" name = "leanshot"
description = "Screenshot capture for Linux." description = "Screenshot capture for Linux."
version = "0.5.0" version = "0.3.4"
repository = "https://git.mzhang.io/michael/leanshot" repository = "https://github.com/iptq/leanshot"
license = "MIT" license-file = "LICENSE"
edition = "2018" authors = ["Michael Zhang <iptq@protonmail.com>"]
authors = ["Michael Zhang <mail@mzhang.io>"]
[workspace]
members = [".", "imlib2", "imlib2/imlib2-sys", "xlib"]
[dependencies] [dependencies]
anyhow = "1.0.38" failure = "0.1"
image = { version = "0.23.13", default-features = false, features = ["jpeg", "png"] } gl = "0.11"
log = "0.4.14" glutin = "0.19"
stderrlog = "0.5.1" imlib2 = { version = "0.1", path = "./imlib2" }
xcb-util = { version = "0.3.0", features = ["image", "cursor"] } leanshot_xlib = { version = "0.1", path = "./xlib" }
xcb = "0.9.0" nanovg = { version = "1.0.2", features = ["gl3"] }
structopt = "0.3.21" png = "0.14"
libc = "0.2.86" structopt = "0.2"
time = "0.1"
x11 = { version = "2.18", features = ["xlib"] }

20
LICENSE
View file

@ -1,19 +1,7 @@
Copyright 2018-2020 Michael Zhang Copyright 2018 Michael Zhang
Permission is hereby granted, free of charge, to any person obtaining a copy of 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:
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 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 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.
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.

View file

@ -1,23 +1,20 @@
leanshot leanshot
======== ========
Screenshot-capturing utility for X11. [![](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 Requirements
------------ ------------
You must have: You must have imlib2 and OpenGL installed. Fortunately, these are relatively common libraries.
- python3 (build-time)
- libxcb
- xcb-util-image
Installation Installation
------------ ------------
Find it in package repositories: Binary distributions are available on the [releases](https://github.com/iptq/leanshot/releases) page.
- [`leanshot` on Arch Linux (AUR)](https://aur.archlinux.org/packages/leanshot/)
To install from crates.io, use: To install from crates.io, use:
@ -25,14 +22,6 @@ To install from crates.io, use:
cargo install leanshot cargo install leanshot
``` ```
Usage
-----
```
leanshot -o hello.png select
leanshot -o hello.png fullscreen
```
Example Integration Example Integration
------------------- -------------------
@ -51,8 +40,6 @@ $XCLIP -selection clipboard -t image/png -i $FILE
Then, you can bind this script to the keybinds of your choice using your window manager's config. Then, you can bind this script to the keybinds of your choice using your window manager's config.
You can find other scripts in the `contrib/` directory.
Contact Contact
------- -------

View file

@ -2,7 +2,7 @@
set -eu set -eu
outdir=$HOME/Pictures/Screenshots outdir=~/Pictures/Screenshots
mkdir -p "${outdir}" mkdir -p "${outdir}"
file=$(mktemp --suffix .png) file=$(mktemp --suffix .png)

11
imlib2/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "imlib2"
version = "0.1.1"
description = "imlib2 bindings"
license-file = "../LICENSE"
authors = ["Michael Zhang <failed.down@gmail.com>"]
[dependencies]
failure = "0.1"
imlib2-sys = { version = "0.1", path = "imlib2-sys" }
leanshot_xlib = { version = "0.1", path = "../xlib" }

View file

@ -0,0 +1,13 @@
[package]
name = "imlib2-sys"
version = "0.1.0"
description = "ffi for imlib2"
license-file = "../../LICENSE"
authors = ["Michael Zhang <failed.down@gmail.com>"]
build = "build.rs"
[lib]
path = "lib.rs"
[build-dependencies]
bindgen = "0.47"

View file

@ -0,0 +1,20 @@
// bindgen build script by remexre
extern crate bindgen;
use std::env;
use std::path::PathBuf;
use bindgen::Builder;
fn main() {
println!("cargo:rustc-link-lib=Imlib2");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
Builder::default()
.header("wrapper.h")
.blacklist_type("max_align_t")
.generate()
.expect("Unable to generate bindings")
.write_to_file(out_path.join("bindings.rs"))
.expect("Unable to write bindings");
}

5
imlib2/imlib2-sys/lib.rs Normal file
View file

@ -0,0 +1,5 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

View file

@ -0,0 +1 @@
#include "Imlib2.h"

7
imlib2/src/errors.rs Normal file
View file

@ -0,0 +1,7 @@
/// Enumerated error type.
#[allow(missing_docs)]
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "error")]
Error,
}

77
imlib2/src/image.rs Normal file
View file

@ -0,0 +1,77 @@
use std::ffi::CString;
use std::path::Path;
use imlib2_sys as im;
use xlib::Drawable;
use Error;
/// A simple wrapper around Imlib_Image
pub struct Image {
pub(crate) inner: im::Imlib_Image,
}
impl Image {
/// Creates an Image from a pixmap.
pub fn create_from_drawable(
drawable: impl Drawable,
pixmap: im::Pixmap,
x: i32,
y: i32,
width: i32,
height: i32,
grab_x: bool,
) -> Result<Self, Error> {
unsafe { im::imlib_context_set_drawable(drawable.as_drawable()) };
let image = unsafe {
im::imlib_create_image_from_drawable(
pixmap,
x,
y,
width,
height,
if grab_x { 1 } else { 0 },
) as im::Imlib_Image
};
unsafe { im::imlib_context_set_image(image) };
Ok(Image { inner: image })
}
/// Get width
pub fn get_width(&self) -> i32 {
unsafe {
im::imlib_context_set_image(self.inner);
im::imlib_image_get_width()
}
}
/// Get height
pub fn get_height(&self) -> i32 {
unsafe {
im::imlib_context_set_image(self.inner);
im::imlib_image_get_height()
}
}
/// Save this image
pub fn save_image(&self, file: impl AsRef<Path>) -> Result<(), Error> {
let mut error = 0;
let path = CString::new(file.as_ref().to_str().unwrap()).unwrap();
unsafe { im::imlib_save_image_with_error_return(path.as_ptr(), &mut error) };
Ok(())
}
/// Get raw image
pub fn as_raw(&self) -> im::Imlib_Image {
self.inner
}
}
impl Drop for Image {
fn drop(&mut self) {
unsafe {
im::imlib_context_set_image(self.inner);
im::imlib_free_image();
}
}
}

70
imlib2/src/lib.rs Normal file
View file

@ -0,0 +1,70 @@
//! Safe-ish bindings to imlib2 (at least the only parts I need).
#[macro_use]
extern crate failure;
extern crate imlib2_sys;
extern crate leanshot_xlib as xlib;
mod errors;
mod image;
pub use errors::Error;
pub use image::Image;
pub use imlib2_sys::{Drawable, Pixmap};
use xlib::{Display, Visual};
/// Set the display for the imlib context.
pub fn context_set_display(display: &Display) {
unsafe {
imlib2_sys::imlib_context_set_display(display.as_raw() as *mut imlib2_sys::_XDisplay)
};
}
/// Set the visual for the imlib context.
pub fn context_set_visual(visual: &Visual) {
unsafe { imlib2_sys::imlib_context_set_visual(visual.as_raw() as *mut imlib2_sys::Visual) };
}
/// Set the visual for the imlib context.
pub fn context_set_image(image: &Image) {
unsafe { imlib2_sys::imlib_context_set_image(image.as_raw() as imlib2_sys::Imlib_Image) };
}
/// Get a pointer to the raw image data for the current image.
pub fn image_get_data() -> *mut u32 {
unsafe { imlib2_sys::imlib_image_get_data_for_reading_only() }
}
/// Create cropped image
pub fn create_cropped_image(x: i32, y: i32, width: u32, height: u32) -> Result<Image, Error> {
let inner =
unsafe { imlib2_sys::imlib_create_cropped_image(x, y, width as i32, height as i32) };
if inner.is_null() {
return Err(Error::Error);
}
Ok(Image { inner })
}
/// Create scaled cropped image
pub fn create_scaled_cropped_image(
x: i32,
y: i32,
width: u32,
height: u32,
) -> Result<Image, Error> {
let inner = unsafe {
imlib2_sys::imlib_create_cropped_scaled_image(
x,
y,
width as i32,
height as i32,
width as i32,
height as i32,
)
};
if inner.is_null() {
return Err(Error::Error);
}
Ok(Image { inner })
}

22
src/capture.rs Normal file
View file

@ -0,0 +1,22 @@
use errors::ScreenshotError;
use gui::GUI;
use imlib2;
use options::{Options, Region};
/// The main capture routine.
pub fn capture(opt: &Options) -> Result<(), ScreenshotError> {
let gui = GUI::new()?;
let window_to_capture = match opt.region {
Region::ActiveWindow => gui.get_active_window()?,
_ => gui.display.get_default_root_window()?,
};
let capture = gui.capture_window(&opt, window_to_capture)?;
imlib2::context_set_image(&capture);
capture.save_image(&opt.outfile)?;
Ok(())
}

41
src/errors.rs Normal file
View file

@ -0,0 +1,41 @@
#[derive(Debug, Fail)]
pub enum ScreenshotError {
#[fail(display = "x11 error")]
X11Error(#[cause] ::xlib::X11Error),
#[fail(display = "imlib2 error")]
ImlibError(#[cause] ::imlib2::Error),
#[fail(display = "io error")]
IOError(#[cause] ::std::io::Error),
#[fail(display = "png encoding error")]
PngEncodingError(#[cause] ::png::EncodingError),
#[fail(display = "error")]
Error,
}
impl From<::std::io::Error> for ScreenshotError {
fn from(err: ::std::io::Error) -> Self {
ScreenshotError::IOError(err)
}
}
impl From<::xlib::X11Error> for ScreenshotError {
fn from(err: ::xlib::X11Error) -> Self {
ScreenshotError::X11Error(err)
}
}
impl From<::imlib2::Error> for ScreenshotError {
fn from(err: ::imlib2::Error) -> Self {
ScreenshotError::ImlibError(err)
}
}
impl From<::png::EncodingError> for ScreenshotError {
fn from(err: ::png::EncodingError) -> Self {
ScreenshotError::PngEncodingError(err)
}
}

View file

@ -1,326 +1,348 @@
use std::path::Path; use std::env;
use anyhow::Result; use imlib2::{self, Image as Image2};
use image::{Bgra, DynamicImage, ImageBuffer}; use xlib::{Display, Visual, Window};
use xcb::{
base::Connection,
xproto::{self, Rectangle, Screen},
};
use xcb_util::image as xcb_image;
pub struct Gui { use errors::ScreenshotError;
conn: Connection, use Options;
use Rectangle;
use Region;
pub struct GUI {
pub(crate) display: Display,
} }
impl Gui { impl GUI {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self, ScreenshotError> {
let (conn, _) = Connection::connect(None)?; let display = Display::connect(env::var("DISPLAY").unwrap_or_else(|_| ":0".to_owned()))?;
Ok(Gui { conn }) Ok(GUI { display })
} }
fn get_default_screen(&self) -> Screen { /// Captures the window and produces an Image.
// TODO: multiple screens pub fn capture_window(&self, opt: &Options, window: Window) -> Result<Image2, ScreenshotError> {
let setup = self.conn.get_setup(); let attr = window.get_attributes()?;
let mut iter = setup.roots(); let width = attr.get_width();
iter.next().unwrap() let height = attr.get_height();
} let root = attr.get_root();
let (x, y, _) = self.display.translate_coordinates(window, 0, 0, root)?;
pub fn capture_entire_screen(&self) -> Result<ScreenCapture> { imlib2::context_set_display(&self.display);
// get the dimensions of the screen let visual = Visual::default(&self.display, 0);
let screen = self.get_default_screen(); imlib2::context_set_visual(&visual);
let (width, height) = (screen.width_in_pixels(), screen.height_in_pixels());
let image = xcb_image::get( if let Region::Selection = opt.region {
&self.conn, let capture =
screen.root(), Image2::create_from_drawable(window, 0, x, y, width as i32, height as i32, true)?;
0, let region = self.interactive_select(&capture)?;
0, imlib2::context_set_image(&capture);
width, return imlib2::create_scaled_cropped_image(
height, region.x,
u32::MAX, region.y,
xcb::IMAGE_FORMAT_Z_PIXMAP, region.width,
) region.height,
.unwrap(); ).map_err(|err| err.into());
Ok(ScreenCapture { image })
}
pub fn interactive_select(&self, image: &ScreenCapture) -> Result<Option<Rectangle>> {
let window_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 evt_mask = xcb::EVENT_MASK_BUTTON_PRESS
| xcb::EVENT_MASK_BUTTON_RELEASE
| xcb::EVENT_MASK_POINTER_MOTION;
let cursor = xcb_util::cursor::create_font_cursor(&self.conn, xcb_util::cursor::CROSSHAIR);
xproto::create_window(
&self.conn,
xcb::COPY_FROM_PARENT as u8,
window_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_EVENT_MASK,
evt_mask | xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_KEY_RELEASE,
),
(xcb::CW_CURSOR, cursor),
],
)
.request_check()?;
xproto::map_window(&self.conn, window_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,
window_id,
wm_state,
4,
32,
&[wm_fullscreen],
);
info!("Setting input focus...");
xproto::set_input_focus(
&self.conn,
xcb::INPUT_FOCUS_POINTER_ROOT as u8,
window_id,
xcb::CURRENT_TIME,
)
.request_check()?;
info!("Grabbing keyboard...");
xproto::grab_keyboard(
&self.conn,
true,
window_id,
xcb::CURRENT_TIME,
xcb::GRAB_MODE_ASYNC as u8,
xcb::GRAB_MODE_ASYNC as u8,
)
.get_reply()?;
info!("Grabbing pointer...");
xproto::grab_pointer(
&self.conn,
false,
window_id,
evt_mask as u16,
xcb::GRAB_MODE_ASYNC as u8,
xcb::GRAB_MODE_ASYNC as u8,
xcb::NONE,
cursor,
xcb::CURRENT_TIME,
)
.get_reply()?;
let window_gc = self.conn.generate_id();
xproto::create_gc(
&self.conn,
window_gc,
window_id,
&[
(xcb::GC_FOREGROUND, screen.white_pixel()),
(xcb::GC_BACKGROUND, screen.white_pixel()),
(xcb::GC_LINE_WIDTH, 1),
],
);
#[derive(Default)]
struct State {
dragging: bool,
// where did we start dragging from
dx: i16,
dy: i16,
// where is the mouse right now
mx: i16,
my: i16,
} }
impl State { Image2::create_from_drawable(window, 0, x, y, width as i32, height as i32, true)
pub fn rect(&self) -> Option<Rectangle> { .map_err(|err| err.into())
if self.dx < 0 || self.dy < 0 { }
return None;
}
let tlx = self.dx.min(self.mx); /// Get the active window.
let tly = self.dy.min(self.my); pub fn get_active_window(&self) -> Result<Window, ScreenshotError> {
self.display
.get_input_focus()
.map(|(window, _)| window)
.map_err(|err| err.into())
}
let brx = self.dx.max(self.mx); /// Brings up an interactive selection GUI.
let bry = self.dy.max(self.my); pub fn interactive_select(&self, capture: &Image2) -> Result<Rectangle, ScreenshotError> {
// let window = SelectWindow::new(&self.display);
// let root = self.display.get_default_root_window()?;
let rw = (brx - tlx) as u16; // let root_im = root.get_image();
let rh = (bry - tly) as u16;
if rw <= 2 || rh <= 2 { // let mut done = 0;
return None; // 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;
// }
// _ => (),
// }
// }
Some(Rectangle::new(tlx, tly, rw, rh)) use gl;
} use glutin::{
} self,
dpi::{PhysicalPosition, PhysicalSize},
let mut state = State::default(); os::unix::{WindowBuilderExt, WindowExt, XWindowType},
state.dx = -1; ElementState, Event, EventsLoop, GlContext, GlWindow, KeyboardInput, MouseButton,
state.dy = -1; MouseCursor, VirtualKeyCode, WindowBuilder, WindowEvent,
let mut cancelled = false;
let redraw = |state: &State| -> Result<()> {
xcb_image::put(&self.conn, window_id, window_gc, &image.image, 0, 0);
if let Some(rect) = state.rect() {
if state.dragging {
xproto::change_gc(
&self.conn,
window_gc,
&[(xcb::GC_FOREGROUND, screen.black_pixel())],
);
xproto::poly_rectangle(&self.conn, window_id, window_gc, &[rect]);
let rect2 = Rectangle::new(
rect.x() + 1,
rect.y() + 1,
rect.width() - 2,
rect.height() - 2,
);
xproto::change_gc(
&self.conn,
window_gc,
&[(xcb::GC_FOREGROUND, screen.white_pixel())],
);
xproto::poly_rectangle(&self.conn, window_id, window_gc, &[rect2]);
}
}
self.conn.flush();
Ok(())
}; };
use nanovg::{self, Image, ImagePattern, PathOptions, StrokeOptions};
use x11;
use std::{f32::consts, mem, slice};
redraw(&state)?; // 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();
while let Some(evt) = self.conn.wait_for_event() { let mut evl = EventsLoop::new();
match evt.response_type() { let mon = evl.get_primary_monitor();
xcb::KEY_RELEASE => {
let evt = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&evt) };
trace!(
"key released: root={} key={} state={}",
evt.root(),
evt.detail(),
evt.state()
);
if evt.detail() == 9 { // TODO: handle error
if state.dragging { let wb = WindowBuilder::new()
state.dragging = false; .with_x11_window_type(XWindowType::Splash)
} else { .with_decorations(false)
cancelled = true; .with_visibility(false)
break; .with_always_on_top(true)
} .with_dimensions(
} PhysicalSize::new(width.into(), height.into()).to_logical(mon.get_hidpi_factor()),
} ).with_fullscreen(Some(mon));
xcb::KEY_PRESS => {} let ctx = glutin::ContextBuilder::new()
xcb::BUTTON_PRESS => { .with_vsync(false)
let evt = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&evt) }; .with_multisampling(4)
trace!("button pressed: button={}", evt.detail()); .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;
state.mx = evt.root_x(); // crosshair
state.my = evt.root_y(); win.set_cursor(MouseCursor::Crosshair);
state.dx = evt.root_x(); // win.set_inner_size((width, height).into());
state.dy = evt.root_y(); // println!("size={:?} pos={:?} outer={:?}", win.get_inner_size(), win.get_inner_position(), win.get_outer_size());
// println!("{:?}", win.get_hidpi_factor());
// left mouse button let x = Display::from_handle(win.get_xlib_display().unwrap() as u64);
if evt.detail() == 1 { let len;
state.dragging = true; let raw_data;
} {
} let _g = x.grab();
xcb::BUTTON_RELEASE => { // let img = Image2::create_from_drawable(window, 0, 0, 0, width as i32, height as i32, true)?;
let evt = unsafe { xcb::cast_event::<xcb::ButtonReleaseEvent>(&evt) }; imlib2::context_set_image(&capture);
trace!("button released: button={}", evt.detail()); len = (width * height) as usize;
// println!("{}", window.as_raw());
raw_data = unsafe { slice::from_raw_parts(imlib2::image_get_data(), len) };
// left mouse button unsafe {
if state.dragging && evt.detail() == 1 { win.make_current().expect("couldn't make window");
state.dragging = false; gl::load_with(|sym| win.get_proc_address(sym) as *const _);
if let Some(_) = state.rect() {
break;
}
}
}
xcb::MOTION_NOTIFY => {
trace!("mouse movement event");
let motion_evt = unsafe { xcb::cast_event::<xcb::MotionNotifyEvent>(&evt) };
state.mx = motion_evt.root_x();
state.my = motion_evt.root_y();
}
v => {
trace!("unknown event of type: {:?}", v);
}
} }
}
mem::forget(x);
redraw(&state)?; // 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);
} }
info!("Loop exited, cleaning up..."); // invert image
xproto::ungrab_keyboard(&self.conn, xcb::CURRENT_TIME).request_check()?; let mut inverted = vec![0u32; raw_data.len()];
xproto::ungrab_pointer(&self.conn, xcb::CURRENT_TIME).request_check()?; inverted.copy_from_slice(raw_data);
for i in &mut inverted {
// fix the colors
*i = (*i & 0xff000000) | !(*i & 0xffffff);
}
Ok(if cancelled { None } else { state.rect() }) let ctx = nanovg::ContextBuilder::new()
} .build()
} .expect("couldn't init nanovg");
pub struct ScreenCapture { let image = Image::new(&ctx)
image: xcb_image::Image, .build_from_rgba(width as usize, height as usize, data.as_slice())
} .expect("couldn't create image");
impl ScreenCapture { let inverted_image = Image::new(&ctx)
pub fn save_to(&self, to: impl AsRef<Path>) -> Result<()> { .build_from_rgba(width as usize, height as usize, inverted.as_slice())
self.save_cropped_to(to, None) .expect("couldn't create image");
}
pub fn save_cropped_to(&self, to: impl AsRef<Path>, section: Option<Rectangle>) -> Result<()> { let mut running = true;
let to = to.as_ref(); let mut down = false;
let image = ImageBuffer::<Bgra<u8>, _>::from_raw( // drag start
self.image.width() as u32, let mut dx = -1.0f64;
self.image.height() as u32, let mut dy = -1.0f64;
self.image.data().to_vec(), // curr mouse
) let mut mx = -1.0f64;
.unwrap(); let mut my = -1.0f64;
let image = DynamicImage::ImageBgra8(image); // rect
let mut image = DynamicImage::ImageRgb8(image.into_rgb8()); let mut rectw = 0.0f64;
let mut recth = 0.0f64;
let mut delayed_down = false;
let mut redraw = true;
let image = if let Some(section) = section { win.show();
// crop the image win.set_position(PhysicalPosition::new(0.0, 0.0).to_logical(f));
image.crop( while running {
section.x() as u32, if redraw {
section.y() as u32, // let size = win.get_inner_size().unwrap();
section.width() as u32, // let (width, height) = (size.width as i32, size.height as i32);
section.height() as u32,
) 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| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::Destroyed => running = false,
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode,
state,
..
},
..
} => match (virtual_keycode, state) {
(Some(VirtualKeyCode::Escape), ElementState::Released) => {
if down {
down = false;
rectw = 0.0;
recth = 0.0;
} else {
unsafe {
x11::xlib::XDestroyWindow(
self.display.as_raw(),
win.get_xlib_window().unwrap(),
)
};
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, .. } => match button {
MouseButton::Left => {
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 {
unsafe {
x11::xlib::XDestroyWindow(
self.display.as_raw(),
win.get_xlib_window().unwrap(),
)
};
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 { } else {
image Err(ScreenshotError::Error)
}; }
image.save(to)?;
Ok(())
} }
} }

View file

@ -1,83 +1,36 @@
#[macro_use] //! Screenshot capturing utility.
extern crate log;
#[macro_use]
extern crate anyhow;
#![deny(missing_docs)]
#[macro_use]
extern crate failure;
extern crate gl;
extern crate glutin;
extern crate imlib2;
extern crate nanovg;
extern crate png;
#[macro_use]
extern crate structopt;
extern crate leanshot_xlib as xlib;
extern crate time;
extern crate x11;
mod capture;
mod errors;
mod gui; mod gui;
mod singleton; mod options;
use std::path::PathBuf; use errors::ScreenshotError;
use std::process;
use std::str::FromStr;
use anyhow::Result;
use structopt::StructOpt; use structopt::StructOpt;
use xlib::Rectangle;
use crate::gui::Gui; pub use capture::capture;
pub use options::{Options, Region};
fn main() -> Result<()> { use failure::Error;
let opt = Options::from_args();
stderrlog::new()
.module(module_path!())
.verbosity(opt.verbose)
.init()
.unwrap();
let gui = Gui::new()?;
let capture = gui.capture_entire_screen()?;
match opt.region {
Region::Fullscreen => {
capture.save_to(&opt.outfile)?;
}
Region::Selection => {
info!("Creating lockfile...");
let _lockfile = singleton::check_singleton()?;
info!("Lockfile: {:?}", _lockfile);
if let Some(rectangle) = gui.interactive_select(&capture)? {
capture.save_cropped_to(&opt.outfile, Some(rectangle))?;
} else {
info!("Cancelled by user.");
process::exit(1);
}
}
}
Ok(())
}
/// Options for screenshot
#[derive(StructOpt)]
pub struct Options {
/// The region to select (fullscreen | 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,
/// Verbose mode (-v, -vv, -vvv, etc)
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
verbose: usize,
}
/// A region option
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum Region { pub fn main() -> Result<(), Error> {
Fullscreen, let opt = Options::from_args();
Selection, capture(&opt).map(|_| ()).map_err(|err| err.into())
}
impl FromStr for Region {
type Err = anyhow::Error;
fn from_str(x: &str) -> Result<Self> {
match x {
"fullscreen" => Ok(Region::Fullscreen),
"select" | "selection" => Ok(Region::Selection),
_ => bail!("expected {fullscreen|selection}"),
}
}
} }

38
src/options.rs Normal file
View file

@ -0,0 +1,38 @@
use std::path::PathBuf;
use ScreenshotError;
/// A region option
#[allow(missing_docs)]
pub enum Region {
Fullscreen,
ActiveWindow,
Selection,
}
/// Optiosn 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,
}
impl Region {
pub(self) fn from_str(x: &str) -> Result<Self, ScreenshotError> {
match x {
"fullscreen" => Ok(Region::Fullscreen),
"window" => Ok(Region::ActiveWindow),
"select" | "selection" => Ok(Region::Selection),
_ => Err(ScreenshotError::Error),
}
}
}

View file

@ -1,77 +0,0 @@
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::PathBuf;
use std::process;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use anyhow::{Error, Result};
const PATHS: &[&str] = &["/var/run", "/tmp"];
pub fn check_singleton() -> Result<Lockfile> {
let (mut file, path) = get_lock_file()?;
let pid = process::id();
let contents = format!("{}", pid).as_bytes().to_vec();
file.write_all(&contents)?;
Ok(Lockfile(path))
}
#[derive(Debug)]
pub struct Lockfile(PathBuf);
impl Drop for Lockfile {
fn drop(&mut self) {
info!("Dropping lockfile...");
fs::remove_file(&self.0).unwrap();
}
}
fn get_lock_file() -> Result<(File, PathBuf)> {
for dir_path in PATHS.iter() {
use std::io::ErrorKind::*;
let dir_path = PathBuf::from(dir_path);
let lockfile_path = dir_path.join("leanshot.pid");
// check if it already exists
match File::open(&lockfile_path) {
Ok(mut f) => {
let mut contents = String::new();
f.read_to_string(&mut contents)?;
let pid = contents.parse::<i32>()?;
// let mut parts = contents.split(":");
// let timestamp = parts.next().unwrap().parse::<u64>()?;
// let pid = parts.next().unwrap();
// let time = UNIX_EPOCH + Duration::from_millis(timestamp);
// if the process that has the lockfile open is still running, bail
let status = unsafe {libc::kill(pid, 0)};
// 0 means the signal was successfully sent, which means it's still running
// if it's 0 then we bail
if status == 0 {
bail!("existing lockfile for pid {}", pid);
}
}
// ignore errors
Err(e) => match e.kind() {
NotFound => {}
_ => return Err(Error::from(e).context("could not open lockfile for reading")),
},
};
let file = match File::create(&lockfile_path) {
Ok(f) => f,
Err(e) => match e.kind() {
PermissionDenied => continue,
_ => return Err(Error::from(e).context("could not open lockfile for writing")),
},
};
return Ok((file, lockfile_path));
}
bail!("could not find a suitable place to place lockfile");
}

11
xlib/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "leanshot_xlib"
version = "0.1.1"
description = "xlib bindings"
license-file = "../LICENSE"
authors = ["Michael Zhang <failed.down@gmail.com>"]
[dependencies]
failure = "0.1"
libc = "0.2"
x11 = { version = "2.18", features = ["xlib"] }

38
xlib/src/atom.rs Normal file
View file

@ -0,0 +1,38 @@
use std::ffi::CString;
use x11::xlib as x;
use Display;
use X11Error;
/// A unique string or intger
pub struct Atom {
inner: x::Atom,
}
impl Atom {
/// Create a new atom using a string
pub fn new(
display: &Display,
val: impl AsRef<str>,
only_if_exists: bool,
) -> Result<Self, X11Error> {
let val = {
let v = val.as_ref();
let s = CString::new(v).unwrap();
s.as_ptr()
};
let inner =
unsafe { x::XInternAtom(display.as_raw(), val, if only_if_exists { 1 } else { 0 }) };
Ok(Atom { inner })
}
/// Create a new Atom object from an existing handle
pub fn from(handle: x::Atom) -> Self {
Atom { inner: handle }
}
/// Get the handle
pub fn as_raw(&self) -> x::Atom {
self.inner
}
}

13
xlib/src/cursor.rs Normal file
View file

@ -0,0 +1,13 @@
use x11::xlib as x;
/// Mouse pointer
pub struct Cursor {
pub(crate) display: *mut x::Display,
pub(crate) inner: x::Cursor,
}
impl Drop for Cursor {
fn drop(&mut self) {
unsafe { x::XFreeCursor(self.display, self.inner) };
}
}

5
xlib/src/cursorfont.rs Normal file
View file

@ -0,0 +1,5 @@
pub const XC_crosshair: u32 = 34;
pub const XC_ll_angle: u32 = 76;
pub const XC_lr_angle: u32 = 78;
pub const XC_ul_angle: u32 = 144;
pub const XC_ur_angle: u32 = 148;

175
xlib/src/display.rs Normal file
View file

@ -0,0 +1,175 @@
use std::ffi::CString;
use libc;
use x11::xlib as x;
use {Cursor, Event, Visual, Window, X11Error};
/// A connection to an X server.
pub struct Display {
inner: *mut x::Display,
}
pub struct Grab(pub(crate) *mut x::Display);
/// Something that's part of a display.
pub trait GetDisplay {
/// Get the current display
fn get_display(&self) -> *mut x::Display;
}
impl Display {
/// Opens a new connection to an X server.
///
/// On POSIX-conformant systems, the display name or DISPLAY environment variable can be a string in the format:
/// hostname:number.screen_number
pub fn connect(display_name: impl AsRef<str>) -> Result<Display, X11Error> {
let display_name = CString::new(display_name.as_ref()).unwrap();
let inner = unsafe { x::XOpenDisplay(display_name.as_ptr()) };
if inner.is_null() {
return Err(X11Error::DisplayOpenError);
}
Ok(Display { inner })
}
/// Create a Display for an existing connection
pub fn from_handle(handle: u64) -> Self {
Display {
inner: handle as *mut x::Display,
}
}
/// Grab
pub fn grab(&self) -> Grab {
unsafe { x::XGrabServer(self.inner) };
Grab(self.inner)
}
/// Wrapper around XCreateFontCursor
pub fn create_font_cursor(&self, shape: u32) -> Result<Cursor, X11Error> {
let cursor = unsafe { x::XCreateFontCursor(self.inner, shape) as x::Cursor };
if cursor == 0 {
return Err(X11Error::CreateCursorError);
}
Ok(Cursor {
display: self.inner,
inner: cursor,
})
}
/// Get the next event, blocks until an event is reached.
pub fn next_event(&self) -> Result<Event, X11Error> {
let event =
unsafe { libc::malloc(::std::mem::size_of::<x::XAnyEvent>()) as *mut x::XAnyEvent };
Event::from_raw(event)
}
/// Returns the number of events that are still pending
pub fn pending(&self) -> Result<i32, X11Error> {
Ok(unsafe { x::XPending(self.inner) })
}
/// Gets the raw X Display handle
pub fn as_raw(&self) -> *mut x::Display {
self.inner
}
/// Gets the default visual
pub fn default_visual(&self, screen: i32) -> Visual {
let visual = unsafe { x::XDefaultVisual(self.inner, screen) };
Visual { inner: visual }
}
/// Returns the root window for the given screen.
pub fn get_root_window(&self, screen: i32) -> Result<Window, X11Error> {
let inner = unsafe { x::XRootWindow(self.inner, screen) };
if inner == 0 {
return Err(X11Error::GetWindowError);
}
let window = Window {
display: self.inner,
inner,
};
Ok(window)
}
/// Returns the root window for the default screen.
pub fn get_default_root_window(&self) -> Result<Window, X11Error> {
let inner = unsafe { x::XDefaultRootWindow(self.inner) };
if inner == 0 {
return Err(X11Error::GetWindowError);
}
let window = Window {
display: self.inner,
inner,
};
Ok(window)
}
/// Translate coordinates relative to w1 to coordinates relative to w2.
/// If the coordinates are contained in a mapped child of the destination window, the third return
/// value will hold that child window.
pub fn translate_coordinates(
&self,
w1: Window,
x: i32,
y: i32,
w2: Window,
) -> Result<(i32, i32, Option<Window>), X11Error> {
let mut rx = 0;
let mut ry = 0;
let mut child_return: x::Window = 0;
let status = unsafe {
x::XTranslateCoordinates(
self.inner,
w1.inner,
w2.inner,
x,
y,
&mut rx,
&mut ry,
&mut child_return,
)
};
if status == 0 {
return Err(X11Error::TranslateCoordinatesError);
}
let child = match child_return {
0 => None,
val => Some(Window {
display: self.inner,
inner: val,
}),
};
Ok((rx, ry, child))
}
/// Sync
pub fn sync(&self, discard: bool) {
unsafe { x::XSync(self.inner, if discard { 1 } else { 0 }) };
}
/// eturns the focus window and the current focus state.
pub fn get_input_focus(&self) -> Result<(Window, i32), X11Error> {
let mut focus_return: x::Window = 0;
let mut revert_to_return = 0;
unsafe { x::XGetInputFocus(self.inner, &mut focus_return, &mut revert_to_return) };
let window = Window {
display: self.inner,
inner: focus_return,
};
return Ok((window, revert_to_return));
}
}
impl Drop for Display {
fn drop(&mut self) {
unsafe { x::XCloseDisplay(self.inner) };
}
}
impl Drop for Grab {
fn drop(&mut self) {
unsafe { x::XUngrabServer(self.0) };
}
}

26
xlib/src/drawable.rs Normal file
View file

@ -0,0 +1,26 @@
use x11::xlib as x;
use {GetDisplay, Image, Rectangle, X11Error};
/// Anything that's drawable
pub trait Drawable: GetDisplay {
/// Get drawable handle
fn as_drawable(&self) -> x::Drawable;
/// Capture a snapshot of this drawable, clipped by rect.
fn get_image(&self, rect: Rectangle) -> Result<Image, X11Error> {
let image = unsafe {
x::XGetImage(
self.get_display(),
self.as_drawable(),
rect.x as i32,
rect.y as i32,
rect.width,
rect.height,
0xffffffff,
x::ZPixmap,
)
};
Ok(Image { inner: image })
}
}

25
xlib/src/errors.rs Normal file
View file

@ -0,0 +1,25 @@
/// Any error that can be raised when using this library.
#[allow(missing_docs)]
#[derive(Debug, Fail)]
pub enum X11Error {
#[fail(display = "failed to create cursor")]
CreateCursorError,
#[fail(display = "failed to open display")]
DisplayOpenError,
#[fail(display = "failed to get attributes")]
GetAttributesError,
#[fail(display = "failed to get window")]
GetWindowError,
#[fail(display = "invalid byte order")]
InvalidByteOrder,
#[fail(display = "failed to translate coordinates")]
TranslateCoordinatesError,
#[fail(display = "error")]
Error,
}

42
xlib/src/event.rs Normal file
View file

@ -0,0 +1,42 @@
use libc;
use x11::xlib as x;
use X11Error;
/// An x11 Event
pub struct Event {
inner: *mut x::XAnyEvent,
kind: EventKind,
}
/// Type of event
pub enum EventKind {
/// A mouse button was pressed
ButtonPress,
/// A mouse button was released
ButtonRelease,
/// None event
None,
}
impl Event {
/// Returns the EventKind of this event
pub fn kind(&self) -> &EventKind {
&self.kind
}
pub(super) fn from_raw(event: *mut x::XAnyEvent) -> Result<Self, X11Error> {
Ok(Event {
inner: event,
kind: EventKind::None,
})
}
}
impl Drop for Event {
fn drop(&mut self) {
unsafe { libc::free(self.inner as *mut libc::c_void) };
}
}

79
xlib/src/image.rs Normal file
View file

@ -0,0 +1,79 @@
use x11::xlib as x;
use X11Error;
/// A handle to an XImage.
pub struct Image {
pub(super) inner: *mut x::XImage,
}
/// Image byte order
pub enum ImageByteOrder {
/// Least significant byte first
LSBFirst,
/// Most significant byte first
MSBFirst,
}
/// The buffer pointed to by an XImage.
pub struct PixBuffer {
/// The raw pointer to the buffer
pub buf: *mut u8,
/// The size of the buffer
pub size: usize,
}
impl Image {
/// Get the size (in bytes) of the data buffer.
pub fn get_size(&self) -> usize {
4 * self.get_width() as usize * self.get_height() as usize
}
/// Get the image width
pub fn get_width(&self) -> u32 {
unsafe { (*self.inner).width as u32 }
}
/// Get the image height
pub fn get_height(&self) -> u32 {
unsafe { (*self.inner).height as u32 }
}
/// Get the image depth
pub fn get_depth(&self) -> u32 {
unsafe { (*self.inner).depth as u32 }
}
/// Get byte order
pub fn get_byte_order(&self) -> Result<ImageByteOrder, X11Error> {
let byte_order = unsafe { (*self.inner).byte_order };
match byte_order {
x::LSBFirst => Ok(ImageByteOrder::LSBFirst),
x::MSBFirst => Ok(ImageByteOrder::MSBFirst),
_ => Err(X11Error::InvalidByteOrder),
}
}
/// Produces a PixBuffer
pub fn buffer(&self) -> PixBuffer {
let size = self.get_size();
let buf = unsafe { (*self.inner).data as *mut u8 };
PixBuffer { buf, size }
}
}
impl Drop for Image {
fn drop(&mut self) {
unsafe { x::XDestroyImage(self.inner) };
}
}
impl PixBuffer {
/// Gets the byte at the index of the data buffer.
pub fn get_byte(&self, index: usize) -> Option<u8> {
if index > self.size {
return None;
}
Some(unsafe { *self.buf.offset(index as isize) as u8 })
}
}

35
xlib/src/lib.rs Normal file
View file

@ -0,0 +1,35 @@
//! Safe-ish bindings to parts of x11's xlib module.
//!
//! I need this for my project.
#[macro_use]
extern crate failure;
extern crate libc;
pub extern crate x11;
mod atom;
mod cursor;
mod display;
mod drawable;
mod errors;
mod event;
mod image;
mod rect;
mod visual;
mod window;
#[allow(non_upper_case_globals)]
#[allow(missing_docs)]
mod cursorfont;
pub use atom::Atom;
pub use cursor::Cursor;
pub use cursorfont::*;
pub use display::{Display, GetDisplay, Grab};
pub use drawable::Drawable;
pub use errors::X11Error;
pub use event::{Event, EventKind};
pub use image::{Image, ImageByteOrder, PixBuffer};
pub use rect::Rectangle;
pub use visual::Visual;
pub use window::{Window, WindowAttributes};

24
xlib/src/rect.rs Normal file
View file

@ -0,0 +1,24 @@
/// A rectangle.
#[derive(Debug)]
pub struct Rectangle {
/// x
pub x: i32,
/// y
pub y: i32,
/// width
pub width: u32,
/// height
pub height: u32,
}
impl Rectangle {
/// Create a new Rectangle from u32s
pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
Rectangle {
x,
y,
width,
height,
}
}
}

21
xlib/src/visual.rs Normal file
View file

@ -0,0 +1,21 @@
use x11::xlib as x;
use Display;
/// A wrapper around a Visual
pub struct Visual {
pub(super) inner: *mut x::Visual,
}
impl Visual {
/// Gets the raw handle to the x11 Visual
pub fn as_raw(&self) -> *mut x::Visual {
self.inner
}
/// Gets the default visual
pub fn default(display: &Display, screen: i32) -> Self {
let inner = unsafe { x::XDefaultVisual(display.as_raw(), screen) };
Visual { inner }
}
}

170
xlib/src/window.rs Normal file
View file

@ -0,0 +1,170 @@
use std::mem;
// use imlib2::Drawable;
use libc;
use x11::xlib as x;
use {Atom, Display, Drawable, GetDisplay, Image, Rectangle, X11Error};
/// A wrapper around a window handle.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Window {
pub(super) display: *mut x::Display,
pub(super) inner: x::Window,
}
/// Window Attributes
pub struct WindowAttributes {
pub(super) display: *mut x::Display,
pub(self) inner: *mut x::XWindowAttributes,
}
impl Window {
/// Create a new window
pub fn create(
display: &Display,
parent: Option<Window>,
location: Rectangle,
) -> Result<Window, X11Error> {
let parent = match parent {
Some(parent) => parent,
None => display.get_default_root_window()?,
};
let visual = display.default_visual(0);
let window = unsafe {
x::XCreateWindow(
display.as_raw(),
parent.as_raw(),
location.x as i32,
location.y as i32,
location.width,
location.height,
0,
0,
0,
visual.as_raw(),
0,
0 as *mut x::XSetWindowAttributes,
)
};
Ok(Window {
display: display.as_raw(),
inner: window,
})
}
/// Create a new Window instance from an existing ID
pub fn create_from_handle(display: &Display, id: u64) -> Result<Window, X11Error> {
Ok(Window {
display: display.as_raw(),
inner: id,
})
}
/// Get window attributes.
pub fn get_attributes(&self) -> Result<WindowAttributes, X11Error> {
let attr = unsafe {
libc::malloc(mem::size_of::<x::XWindowAttributes>()) as *mut x::XWindowAttributes
};
let result = unsafe { x::XGetWindowAttributes(self.display, self.inner, attr) };
match result {
0 => Err(X11Error::GetAttributesError),
_ => Ok(WindowAttributes {
display: self.display,
inner: attr,
}),
}
}
/// Get the raw window handle
pub fn as_raw(&self) -> x::Window {
self.inner
}
/// Get image
pub fn get_image(&self) -> Result<Image, X11Error> {
let attr = self.get_attributes()?;
Drawable::get_image(
self,
Rectangle::new(
attr.get_x(),
attr.get_y(),
attr.get_width(),
attr.get_height(),
),
)
}
/// Change window property
// TODO: make it more general
pub fn change_property(&self, key: &Atom, val: &Atom) {
use std::mem::transmute;
let v = val.as_raw();
unsafe {
x::XChangeProperty(
self.display,
self.inner,
key.as_raw(),
x::XA_ATOM,
32,
x::PropModeReplace,
transmute(&v),
1,
);
}
}
}
impl GetDisplay for Window {
fn get_display(&self) -> *mut x::Display {
self.display
}
}
impl Drawable for Window {
fn as_drawable(&self) -> x::Drawable {
self.inner
}
}
// impl AsRef<Drawable> for Window {
// fn as_ref(&self) -> &Drawable {
// &self.inner
// }
// }
impl WindowAttributes {
/// Gets the width of the window
pub fn get_x(&self) -> i32 {
unsafe { (*self.inner).x as i32 }
}
/// Gets the height of the window
pub fn get_y(&self) -> i32 {
unsafe { (*self.inner).y as i32 }
}
/// Gets the width of the window
pub fn get_width(&self) -> u32 {
unsafe { (*self.inner).width as u32 }
}
/// Gets the height of the window
pub fn get_height(&self) -> u32 {
unsafe { (*self.inner).height as u32 }
}
/// Get the root window of this window
pub fn get_root(&self) -> Window {
Window {
display: self.display,
inner: unsafe { (*self.inner).root },
}
}
}
impl Drop for WindowAttributes {
fn drop(&mut self) {
unsafe { libc::free(self.inner as *mut libc::c_void) };
}
}