Compare commits

..

32 commits

Author SHA1 Message Date
4ab3d97838 Corrects the dependency list. 2021-07-13 11:18:18 -05:00
8830797d5f
bump version to 0.5 2021-07-13 11:03:02 -05:00
59493c5ce4
Switch to using pid checking instead 2021-02-27 01:02:22 -06:00
f66d70836e
Update dependencies 2021-02-21 06:54:28 -06:00
b245c362c4
Don't allow 2 processes of leanshot to run at the same time, using a
lockfile.
2021-02-21 06:52:09 -06:00
b75eeae98c
add traces 2021-01-13 13:02:13 -06:00
58adb0680f
add package name 2021-01-06 14:32:50 -06:00
3e10e781aa
Add link to AUR 2021-01-06 14:32:23 -06:00
9f98ab10d6
bump up version 2021-01-06 13:30:53 -06:00
8d863206b3
don't show rectangle when it's too small 2021-01-06 13:30:23 -06:00
1edac3a62a
add usage to readme 2021-01-06 13:20:33 -06:00
24ef0f8d99
remove unnecessary image features 2021-01-06 13:19:34 -06:00
00d66f9712
draw 2 rectangles to prevent the white rectangle from being invisible on
white backgrounds
2021-01-06 13:16:13 -06:00
f7b5288d03
bump deps 2021-01-06 13:09:03 -06:00
b7a390fd49
update readme 2020-12-24 12:49:39 -06:00
8555dc0c37
Add key events to the window event mask 2020-12-24 12:47:00 -06:00
3af06e9665
remove window option 2020-12-24 12:29:40 -06:00
dcdf13594a
ouais 2020-12-24 12:28:13 -06:00
8bcd9d6385
fix event mask and cursor 2020-12-24 01:17:39 -06:00
c681ad2648
cancelled by user case 2020-12-23 23:43:03 -06:00
39d80bddf7
re-encode image to work with png 2020-12-23 22:58:39 -06:00
eca99edb41
cursor and verbosity 2020-12-23 22:55:06 -06:00
2372cbb963
fix lints and update deps 2020-12-23 22:46:13 -06:00
36d6192d4f
kinda works 2020-12-23 22:42:45 -06:00
3536c3f262
wip 2020-12-23 18:30:32 -06:00
942d959b53
Depend on safex11 2020-10-20 21:11:41 -05:00
6be5db5e57
update 2020-06-28 00:34:50 -05:00
505f56766a
screen capture full screen 2020-06-26 21:14:32 -05:00
8dc13a1ad6
screen layout detection 2020-06-26 14:53:06 -05:00
77393ca68c
push some restructure 2020-06-26 13:51:31 -05:00
15220bed4b
cargo clippy + cargo fmt 2020-06-15 21:25:28 -05:00
f6a1d572f5
cargo fix 2020-06-15 21:10:08 -05:00
35 changed files with 728 additions and 2453 deletions

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
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
**/*.rs.bk
shiet.png
shiet.*

View file

@ -1,44 +0,0 @@
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

1298
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

20
LICENSE
View file

@ -1,7 +1,19 @@
Copyright 2018 Michael Zhang
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.

View file

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

View file

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

View file

@ -1,11 +0,0 @@
[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

@ -1,13 +0,0 @@
[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

@ -1,20 +0,0 @@
// 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");
}

View file

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

View file

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

View file

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

View file

@ -1,77 +0,0 @@
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();
}
}
}

View file

@ -1,70 +0,0 @@
//! 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 })
}

View file

@ -1,22 +0,0 @@
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(())
}

View file

@ -1,41 +0,0 @@
#[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,348 +1,326 @@
use std::env;
use std::path::Path;
use imlib2::{self, Image as Image2};
use xlib::{Display, Visual, Window};
use anyhow::Result;
use image::{Bgra, DynamicImage, ImageBuffer};
use xcb::{
base::Connection,
xproto::{self, Rectangle, Screen},
};
use xcb_util::image as xcb_image;
use errors::ScreenshotError;
use Options;
use Rectangle;
use Region;
pub struct GUI {
pub(crate) display: Display,
pub struct Gui {
conn: Connection,
}
impl GUI {
pub fn new() -> Result<Self, ScreenshotError> {
let display = Display::connect(env::var("DISPLAY").unwrap_or_else(|_| ":0".to_owned()))?;
Ok(GUI { display })
impl Gui {
pub fn new() -> Result<Self> {
let (conn, _) = Connection::connect(None)?;
Ok(Gui { conn })
}
/// Captures the window and produces an Image.
pub fn capture_window(&self, opt: &Options, window: Window) -> Result<Image2, ScreenshotError> {
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)?;
imlib2::context_set_display(&self.display);
let visual = Visual::default(&self.display, 0);
imlib2::context_set_visual(&visual);
if let Region::Selection = opt.region {
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());
}
Image2::create_from_drawable(window, 0, x, y, width as i32, height as i32, true)
.map_err(|err| err.into())
fn get_default_screen(&self) -> Screen {
// TODO: multiple screens
let setup = self.conn.get_setup();
let mut iter = setup.roots();
iter.next().unwrap()
}
/// Get the active window.
pub fn get_active_window(&self) -> Result<Window, ScreenshotError> {
self.display
.get_input_focus()
.map(|(window, _)| window)
.map_err(|err| err.into())
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());
let image = xcb_image::get(
&self.conn,
screen.root(),
0,
0,
width,
height,
u32::MAX,
xcb::IMAGE_FORMAT_Z_PIXMAP,
)
.unwrap();
Ok(ScreenCapture { image })
}
/// Brings up an interactive selection GUI.
pub fn interactive_select(&self, capture: &Image2) -> Result<Rectangle, ScreenshotError> {
// let window = SelectWindow::new(&self.display);
// let root = self.display.get_default_root_window()?;
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 root_im = root.get_image();
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);
// 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;
// }
// _ => (),
// }
// }
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()?;
use gl;
use glutin::{
self,
dpi::{PhysicalPosition, PhysicalSize},
os::unix::{WindowBuilderExt, WindowExt, XWindowType},
ElementState, Event, EventsLoop, GlContext, GlWindow, KeyboardInput, MouseButton,
MouseCursor, VirtualKeyCode, WindowBuilder, WindowEvent,
};
use nanovg::{self, Image, ImagePattern, PathOptions, StrokeOptions};
use x11;
use std::{f32::consts, mem, slice};
xproto::map_window(&self.conn, window_id).request_check()?;
self.conn.flush();
// 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 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],
);
let mut evl = EventsLoop::new();
let mon = evl.get_primary_monitor();
info!("Setting input focus...");
xproto::set_input_focus(
&self.conn,
xcb::INPUT_FOCUS_POINTER_ROOT as u8,
window_id,
xcb::CURRENT_TIME,
)
.request_check()?;
// 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;
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()?;
// 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());
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 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) };
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),
],
);
unsafe {
win.make_current().expect("couldn't make window");
gl::load_with(|sym| win.get_proc_address(sym) as *const _);
}
}
mem::forget(x);
#[derive(Default)]
struct State {
dragging: bool,
// 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);
// where did we start dragging from
dx: i16,
dy: i16,
// where is the mouse right now
mx: i16,
my: i16,
}
// 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);
impl State {
pub fn rect(&self) -> Option<Rectangle> {
if self.dx < 0 || self.dy < 0 {
return None;
}
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,
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;
if rw <= 2 || rh <= 2 {
return None;
}
Some(Rectangle::new(tlx, tly, rw, rh))
}
}
let mut state = State::default();
state.dx = -1;
state.dy = -1;
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())],
);
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,
);
}
});
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]);
}
}
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;
self.conn.flush();
Ok(())
};
redraw(&state)?;
while let Some(evt) = self.conn.wait_for_event() {
match evt.response_type() {
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 {
if state.dragging {
state.dragging = false;
} else {
cancelled = true;
break;
}
}
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
}
};
}
xcb::KEY_PRESS => {}
xcb::BUTTON_PRESS => {
let evt = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&evt) };
trace!("button pressed: button={}", evt.detail());
state.mx = evt.root_x();
state.my = evt.root_y();
state.dx = evt.root_x();
state.dy = evt.root_y();
// left mouse button
if evt.detail() == 1 {
state.dragging = true;
}
}
xcb::BUTTON_RELEASE => {
let evt = unsafe { xcb::cast_event::<xcb::ButtonReleaseEvent>(&evt) };
trace!("button released: button={}", evt.detail());
// left mouse button
if state.dragging && evt.detail() == 1 {
state.dragging = false;
if let Some(_) = state.rect() {
break;
}
_ => (),
},
_ => (),
},
_ => (),
});
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;
}
}
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);
}
}
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)
redraw(&state)?;
}
info!("Loop exited, cleaning up...");
xproto::ungrab_keyboard(&self.conn, xcb::CURRENT_TIME).request_check()?;
xproto::ungrab_pointer(&self.conn, xcb::CURRENT_TIME).request_check()?;
Ok(if cancelled { None } else { state.rect() })
}
}
pub struct ScreenCapture {
image: xcb_image::Image,
}
impl ScreenCapture {
pub fn save_to(&self, to: impl AsRef<Path>) -> Result<()> {
self.save_cropped_to(to, None)
}
pub fn save_cropped_to(&self, to: impl AsRef<Path>, section: Option<Rectangle>) -> Result<()> {
let to = to.as_ref();
let image = ImageBuffer::<Bgra<u8>, _>::from_raw(
self.image.width() as u32,
self.image.height() as u32,
self.image.data().to_vec(),
)
.unwrap();
let image = DynamicImage::ImageBgra8(image);
let mut image = DynamicImage::ImageRgb8(image.into_rgb8());
let image = if let Some(section) = section {
// crop the image
image.crop(
section.x() as u32,
section.y() as u32,
section.width() as u32,
section.height() as u32,
)
} else {
image
};
image.save(to)?;
Ok(())
}
}

View file

@ -1,36 +1,83 @@
//! Screenshot capturing utility.
#![deny(missing_docs)]
#[macro_use]
extern crate failure;
extern crate gl;
extern crate glutin;
extern crate imlib2;
extern crate nanovg;
extern crate png;
extern crate log;
#[macro_use]
extern crate structopt;
extern crate leanshot_xlib as xlib;
extern crate time;
extern crate x11;
extern crate anyhow;
mod capture;
mod errors;
mod gui;
mod options;
mod singleton;
use errors::ScreenshotError;
use std::path::PathBuf;
use std::process;
use std::str::FromStr;
use anyhow::Result;
use structopt::StructOpt;
use xlib::Rectangle;
pub use capture::capture;
pub use options::{Options, Region};
use crate::gui::Gui;
use failure::Error;
#[allow(missing_docs)]
pub fn main() -> Result<(), Error> {
fn main() -> Result<()> {
let opt = Options::from_args();
capture(&opt).map(|_| ()).map_err(|err| err.into())
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)]
pub enum Region {
Fullscreen,
Selection,
}
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}"),
}
}
}

View file

@ -1,38 +0,0 @@
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),
}
}
}

77
src/singleton.rs Normal file
View file

@ -0,0 +1,77 @@
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");
}

View file

@ -1,11 +0,0 @@
[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"] }

View file

@ -1,38 +0,0 @@
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
}
}

View file

@ -1,13 +0,0 @@
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) };
}
}

View file

@ -1,5 +0,0 @@
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;

View file

@ -1,175 +0,0 @@
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) };
}
}

View file

@ -1,26 +0,0 @@
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 })
}
}

View file

@ -1,25 +0,0 @@
/// 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,
}

View file

@ -1,42 +0,0 @@
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) };
}
}

View file

@ -1,79 +0,0 @@
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 })
}
}

View file

@ -1,35 +0,0 @@
//! 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};

View file

@ -1,24 +0,0 @@
/// 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,
}
}
}

View file

@ -1,21 +0,0 @@
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 }
}
}

View file

@ -1,170 +0,0 @@
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) };
}
}