forked from michael/leanshot
Compare commits
32 commits
Author | SHA1 | Date | |
---|---|---|---|
4ab3d97838 | |||
8830797d5f | |||
59493c5ce4 | |||
f66d70836e | |||
b245c362c4 | |||
b75eeae98c | |||
58adb0680f | |||
3e10e781aa | |||
9f98ab10d6 | |||
8d863206b3 | |||
1edac3a62a | |||
24ef0f8d99 | |||
00d66f9712 | |||
f7b5288d03 | |||
b7a390fd49 | |||
8555dc0c37 | |||
3af06e9665 | |||
dcdf13594a | |||
8bcd9d6385 | |||
c681ad2648 | |||
39d80bddf7 | |||
eca99edb41 | |||
2372cbb963 | |||
36d6192d4f | |||
3536c3f262 | |||
942d959b53 | |||
6be5db5e57 | |||
505f56766a | |||
8dc13a1ad6 | |||
77393ca68c | |||
15220bed4b | |||
f6a1d572f5 |
35 changed files with 728 additions and 2453 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal 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
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
shiet.png
|
shiet.*
|
||||||
|
|
44
.travis.yml
44
.travis.yml
|
@ -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
1298
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
30
Cargo.toml
30
Cargo.toml
|
@ -1,22 +1,18 @@
|
||||||
[package]
|
[package]
|
||||||
name = "leanshot"
|
name = "leanshot"
|
||||||
description = "Screenshot capture for Linux."
|
description = "Screenshot capture for Linux."
|
||||||
version = "0.3.4"
|
version = "0.5.0"
|
||||||
repository = "https://github.com/iptq/leanshot"
|
repository = "https://git.mzhang.io/michael/leanshot"
|
||||||
license-file = "LICENSE"
|
license = "MIT"
|
||||||
authors = ["Michael Zhang <iptq@protonmail.com>"]
|
edition = "2018"
|
||||||
|
authors = ["Michael Zhang <mail@mzhang.io>"]
|
||||||
[workspace]
|
|
||||||
members = [".", "imlib2", "imlib2/imlib2-sys", "xlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1"
|
anyhow = "1.0.38"
|
||||||
gl = "0.11"
|
image = { version = "0.23.13", default-features = false, features = ["jpeg", "png"] }
|
||||||
glutin = "0.19"
|
log = "0.4.14"
|
||||||
imlib2 = { version = "0.1", path = "./imlib2" }
|
stderrlog = "0.5.1"
|
||||||
leanshot_xlib = { version = "0.1", path = "./xlib" }
|
xcb-util = { version = "0.3.0", features = ["image", "cursor"] }
|
||||||
nanovg = { version = "1.0.2", features = ["gl3"] }
|
xcb = "0.9.0"
|
||||||
png = "0.14"
|
structopt = "0.3.21"
|
||||||
structopt = "0.2"
|
libc = "0.2.86"
|
||||||
time = "0.1"
|
|
||||||
x11 = { version = "2.18", features = ["xlib"] }
|
|
||||||
|
|
20
LICENSE
20
LICENSE
|
@ -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.
|
||||||
|
|
25
README.md
25
README.md
|
@ -1,20 +1,23 @@
|
||||||
leanshot
|
leanshot
|
||||||
========
|
========
|
||||||
|
|
||||||
[![](https://api.travis-ci.org/iptq/leanshot.svg?branch=develop)](https://travis-ci.org/iptq/leanshot)
|
Screenshot-capturing utility for X11.
|
||||||
[![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 imlib2 and OpenGL installed. Fortunately, these are relatively common libraries.
|
You must have:
|
||||||
|
|
||||||
|
- python3 (build-time)
|
||||||
|
- libxcb
|
||||||
|
- xcb-util-image
|
||||||
|
|
||||||
Installation
|
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:
|
To install from crates.io, use:
|
||||||
|
|
||||||
|
@ -22,6 +25,14 @@ 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
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -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.
|
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
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
outdir=~/Pictures/Screenshots
|
outdir=$HOME/Pictures/Screenshots
|
||||||
mkdir -p "${outdir}"
|
mkdir -p "${outdir}"
|
||||||
|
|
||||||
file=$(mktemp --suffix .png)
|
file=$(mktemp --suffix .png)
|
||||||
|
|
|
@ -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" }
|
|
|
@ -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"
|
|
|
@ -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");
|
|
||||||
}
|
|
|
@ -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"));
|
|
|
@ -1 +0,0 @@
|
||||||
#include "Imlib2.h"
|
|
|
@ -1,7 +0,0 @@
|
||||||
/// Enumerated error type.
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
#[derive(Debug, Fail)]
|
|
||||||
pub enum Error {
|
|
||||||
#[fail(display = "error")]
|
|
||||||
Error,
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 })
|
|
||||||
}
|
|
|
@ -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(())
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
636
src/gui.rs
636
src/gui.rs
|
@ -1,348 +1,326 @@
|
||||||
use std::env;
|
use std::path::Path;
|
||||||
|
|
||||||
use imlib2::{self, Image as Image2};
|
use anyhow::Result;
|
||||||
use xlib::{Display, Visual, Window};
|
use image::{Bgra, DynamicImage, ImageBuffer};
|
||||||
|
use xcb::{
|
||||||
use errors::ScreenshotError;
|
base::Connection,
|
||||||
use Options;
|
xproto::{self, Rectangle, Screen},
|
||||||
use Rectangle;
|
|
||||||
use Region;
|
|
||||||
|
|
||||||
pub struct GUI {
|
|
||||||
pub(crate) display: Display,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GUI {
|
|
||||||
pub fn new() -> Result<Self, ScreenshotError> {
|
|
||||||
let display = Display::connect(env::var("DISPLAY").unwrap_or_else(|_| ":0".to_owned()))?;
|
|
||||||
Ok(GUI { display })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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()?;
|
|
||||||
|
|
||||||
// let root_im = root.get_image();
|
|
||||||
|
|
||||||
// let mut done = 0;
|
|
||||||
// let mut button_press = false;
|
|
||||||
// while done == 0 && self.display.pending()? > 0 {
|
|
||||||
// let ev = self.display.next_event()?;
|
|
||||||
// match ev.kind() {
|
|
||||||
// EventKind::ButtonPress => {
|
|
||||||
// button_press = true;
|
|
||||||
// }
|
|
||||||
// EventKind::ButtonRelease => {
|
|
||||||
// if button_press {
|
|
||||||
// done = 1;
|
|
||||||
// }
|
|
||||||
// button_press = false;
|
|
||||||
// }
|
|
||||||
// _ => (),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
use 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 xcb_util::image as xcb_image;
|
||||||
use x11;
|
|
||||||
use std::{f32::consts, mem, slice};
|
|
||||||
|
|
||||||
// let attr = window.get_attributes()?;
|
pub struct Gui {
|
||||||
// let width = attr.get_width();
|
conn: Connection,
|
||||||
// let height = attr.get_height();
|
|
||||||
// let root = attr.get_root();
|
|
||||||
// let (x, y, _) = self.display.translate_coordinates(window, 0, 0, root)?;
|
|
||||||
let width = capture.get_width();
|
|
||||||
let height = capture.get_height();
|
|
||||||
|
|
||||||
let mut evl = EventsLoop::new();
|
|
||||||
let mon = evl.get_primary_monitor();
|
|
||||||
|
|
||||||
// TODO: handle error
|
|
||||||
let wb = WindowBuilder::new()
|
|
||||||
.with_x11_window_type(XWindowType::Splash)
|
|
||||||
.with_decorations(false)
|
|
||||||
.with_visibility(false)
|
|
||||||
.with_always_on_top(true)
|
|
||||||
.with_dimensions(
|
|
||||||
PhysicalSize::new(width.into(), height.into()).to_logical(mon.get_hidpi_factor()),
|
|
||||||
).with_fullscreen(Some(mon));
|
|
||||||
let ctx = glutin::ContextBuilder::new()
|
|
||||||
.with_vsync(false)
|
|
||||||
.with_multisampling(4)
|
|
||||||
.with_double_buffer(Some(true))
|
|
||||||
.with_srgb(true);
|
|
||||||
let win = GlWindow::new(wb, ctx, &evl).expect("couldn't make window");
|
|
||||||
win.set_position((0.0, 0.0).into());
|
|
||||||
let f = win.get_hidpi_factor() as f64;
|
|
||||||
|
|
||||||
// crosshair
|
|
||||||
win.set_cursor(MouseCursor::Crosshair);
|
|
||||||
// win.set_inner_size((width, height).into());
|
|
||||||
// println!("size={:?} pos={:?} outer={:?}", win.get_inner_size(), win.get_inner_position(), win.get_outer_size());
|
|
||||||
// println!("{:?}", win.get_hidpi_factor());
|
|
||||||
|
|
||||||
let x = Display::from_handle(win.get_xlib_display().unwrap() as u64);
|
|
||||||
let len;
|
|
||||||
let raw_data;
|
|
||||||
{
|
|
||||||
let _g = x.grab();
|
|
||||||
// let img = Image2::create_from_drawable(window, 0, 0, 0, width as i32, height as i32, true)?;
|
|
||||||
imlib2::context_set_image(&capture);
|
|
||||||
len = (width * height) as usize;
|
|
||||||
// println!("{}", window.as_raw());
|
|
||||||
raw_data = unsafe { slice::from_raw_parts(imlib2::image_get_data(), len) };
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
win.make_current().expect("couldn't make window");
|
|
||||||
gl::load_with(|sym| win.get_proc_address(sym) as *const _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mem::forget(x);
|
|
||||||
|
|
||||||
// convert ARGB to RGBA
|
|
||||||
let mut data = vec![0u32; raw_data.len()];
|
|
||||||
data.copy_from_slice(raw_data);
|
|
||||||
for i in &mut data {
|
|
||||||
// fix the colors
|
|
||||||
*i = (*i & 0xff00ff00) | ((*i & 0xff) << 16) | ((*i >> 16) & 0xff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// invert image
|
impl Gui {
|
||||||
let mut inverted = vec![0u32; raw_data.len()];
|
pub fn new() -> Result<Self> {
|
||||||
inverted.copy_from_slice(raw_data);
|
let (conn, _) = Connection::connect(None)?;
|
||||||
for i in &mut inverted {
|
Ok(Gui { conn })
|
||||||
// fix the colors
|
|
||||||
*i = (*i & 0xff000000) | !(*i & 0xffffff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = nanovg::ContextBuilder::new()
|
fn get_default_screen(&self) -> Screen {
|
||||||
.build()
|
// TODO: multiple screens
|
||||||
.expect("couldn't init nanovg");
|
let setup = self.conn.get_setup();
|
||||||
|
let mut iter = setup.roots();
|
||||||
let image = Image::new(&ctx)
|
iter.next().unwrap()
|
||||||
.build_from_rgba(width as usize, height as usize, data.as_slice())
|
|
||||||
.expect("couldn't create image");
|
|
||||||
|
|
||||||
let inverted_image = Image::new(&ctx)
|
|
||||||
.build_from_rgba(width as usize, height as usize, inverted.as_slice())
|
|
||||||
.expect("couldn't create image");
|
|
||||||
|
|
||||||
let mut running = true;
|
|
||||||
let mut down = false;
|
|
||||||
// drag start
|
|
||||||
let mut dx = -1.0f64;
|
|
||||||
let mut dy = -1.0f64;
|
|
||||||
// curr mouse
|
|
||||||
let mut mx = -1.0f64;
|
|
||||||
let mut my = -1.0f64;
|
|
||||||
// rect
|
|
||||||
let mut rectw = 0.0f64;
|
|
||||||
let mut recth = 0.0f64;
|
|
||||||
let mut delayed_down = false;
|
|
||||||
let mut redraw = true;
|
|
||||||
|
|
||||||
win.show();
|
|
||||||
win.set_position(PhysicalPosition::new(0.0, 0.0).to_logical(f));
|
|
||||||
while running {
|
|
||||||
if redraw {
|
|
||||||
// let size = win.get_inner_size().unwrap();
|
|
||||||
// let (width, height) = (size.width as i32, size.height as i32);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
gl::Viewport(0, 0, width as i32, height as i32);
|
|
||||||
gl::ClearColor(0.3, 0.3, 0.32, 1.0);
|
|
||||||
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (width, height) = (width as f64, height as f64);
|
pub fn capture_entire_screen(&self) -> Result<ScreenCapture> {
|
||||||
ctx.frame((width as f32, height as f32), f as f32, |frame| {
|
// get the dimensions of the screen
|
||||||
let path_opts = PathOptions::default();
|
let screen = self.get_default_screen();
|
||||||
frame.path(
|
let (width, height) = (screen.width_in_pixels(), screen.height_in_pixels());
|
||||||
|path| {
|
|
||||||
path.rect((0.0, 0.0), ((width * f) as f32, (height * f) as f32));
|
let image = xcb_image::get(
|
||||||
// path.fill(Color::from_rgba(200, 200, 200, 255), Default::default());
|
&self.conn,
|
||||||
path.fill(
|
screen.root(),
|
||||||
ImagePattern {
|
0,
|
||||||
image: &image,
|
0,
|
||||||
origin: (0.0, 0.0),
|
width,
|
||||||
size: (width as f32, height as f32),
|
height,
|
||||||
angle: 0.0 / 180.0 * consts::PI,
|
u32::MAX,
|
||||||
alpha: 1.0,
|
xcb::IMAGE_FORMAT_Z_PIXMAP,
|
||||||
},
|
|
||||||
Default::default(),
|
|
||||||
)
|
)
|
||||||
},
|
.unwrap();
|
||||||
path_opts,
|
|
||||||
);
|
Ok(ScreenCapture { image })
|
||||||
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 {
|
pub fn interactive_select(&self, image: &ScreenCapture) -> Result<Option<Rectangle>> {
|
||||||
Event::WindowEvent { event, .. } => match event {
|
let window_id = self.conn.generate_id();
|
||||||
WindowEvent::Destroyed => running = false,
|
let screen = self.get_default_screen();
|
||||||
WindowEvent::KeyboardInput {
|
let root_window = screen.root();
|
||||||
input:
|
let (width, height) = (screen.width_in_pixels(), screen.height_in_pixels());
|
||||||
KeyboardInput {
|
|
||||||
virtual_keycode,
|
let evt_mask = xcb::EVENT_MASK_BUTTON_PRESS
|
||||||
state,
|
| xcb::EVENT_MASK_BUTTON_RELEASE
|
||||||
..
|
| xcb::EVENT_MASK_POINTER_MOTION;
|
||||||
},
|
let cursor = xcb_util::cursor::create_font_cursor(&self.conn, xcb_util::cursor::CROSSHAIR);
|
||||||
..
|
|
||||||
} => match (virtual_keycode, state) {
|
xproto::create_window(
|
||||||
(Some(VirtualKeyCode::Escape), ElementState::Released) => {
|
&self.conn,
|
||||||
if down {
|
xcb::COPY_FROM_PARENT as u8,
|
||||||
down = false;
|
window_id,
|
||||||
rectw = 0.0;
|
root_window,
|
||||||
recth = 0.0;
|
0,
|
||||||
} else {
|
0,
|
||||||
unsafe {
|
width,
|
||||||
x11::xlib::XDestroyWindow(
|
height,
|
||||||
self.display.as_raw(),
|
0,
|
||||||
win.get_xlib_window().unwrap(),
|
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()?;
|
||||||
running = false;
|
|
||||||
}
|
xproto::map_window(&self.conn, window_id).request_check()?;
|
||||||
}
|
self.conn.flush();
|
||||||
_ => (),
|
|
||||||
},
|
let wm_state = xproto::intern_atom(&self.conn, true, "_NET_WM_STATE")
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
.get_reply()?
|
||||||
mx = position.x;
|
.atom();
|
||||||
my = position.y;
|
let wm_fullscreen = xproto::intern_atom(&self.conn, true, "_NET_WM_STATE_FULLSCREEN")
|
||||||
if down {
|
.get_reply()?
|
||||||
if delayed_down {
|
.atom();
|
||||||
dx = mx;
|
xproto::change_property(
|
||||||
dy = my;
|
&self.conn,
|
||||||
delayed_down = false;
|
xcb::PROP_MODE_REPLACE as u8,
|
||||||
} else {
|
window_id,
|
||||||
redraw = true;
|
wm_state,
|
||||||
}
|
4,
|
||||||
rectw = mx - dx;
|
32,
|
||||||
recth = my - dy;
|
&[wm_fullscreen],
|
||||||
}
|
);
|
||||||
}
|
|
||||||
WindowEvent::MouseInput { button, state, .. } => match button {
|
info!("Setting input focus...");
|
||||||
MouseButton::Left => {
|
xproto::set_input_focus(
|
||||||
down = match state {
|
&self.conn,
|
||||||
ElementState::Pressed => {
|
xcb::INPUT_FOCUS_POINTER_ROOT as u8,
|
||||||
delayed_down = true;
|
window_id,
|
||||||
if mx < 0.0 || my < 0.0 {
|
xcb::CURRENT_TIME,
|
||||||
} 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(),
|
|
||||||
)
|
)
|
||||||
|
.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 {
|
||||||
|
pub fn rect(&self) -> Option<Rectangle> {
|
||||||
|
if self.dx < 0 || self.dy < 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
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())],
|
||||||
|
);
|
||||||
|
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(())
|
||||||
};
|
};
|
||||||
running = false;
|
|
||||||
}
|
redraw(&state)?;
|
||||||
false
|
|
||||||
}
|
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()
|
||||||
win.swap_buffers().expect("couldn't swap buffers");
|
);
|
||||||
}
|
|
||||||
if rectw.abs() > 0.0 && recth.abs() > 0.0 {
|
if evt.detail() == 9 {
|
||||||
let mut x = dx;
|
if state.dragging {
|
||||||
let mut y = dy;
|
state.dragging = false;
|
||||||
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 {
|
||||||
Err(ScreenshotError::Error)
|
cancelled = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
99
src/main.rs
99
src/main.rs
|
@ -1,36 +1,83 @@
|
||||||
//! Screenshot capturing utility.
|
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate failure;
|
extern crate log;
|
||||||
extern crate gl;
|
|
||||||
extern crate glutin;
|
|
||||||
extern crate imlib2;
|
|
||||||
extern crate nanovg;
|
|
||||||
extern crate png;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate structopt;
|
extern crate anyhow;
|
||||||
extern crate leanshot_xlib as xlib;
|
|
||||||
extern crate time;
|
|
||||||
extern crate x11;
|
|
||||||
|
|
||||||
mod capture;
|
|
||||||
mod errors;
|
|
||||||
mod gui;
|
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 structopt::StructOpt;
|
||||||
use xlib::Rectangle;
|
|
||||||
|
|
||||||
pub use capture::capture;
|
use crate::gui::Gui;
|
||||||
pub use options::{Options, Region};
|
|
||||||
|
|
||||||
use failure::Error;
|
fn main() -> Result<()> {
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub fn main() -> Result<(), Error> {
|
|
||||||
let opt = Options::from_args();
|
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}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
77
src/singleton.rs
Normal 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");
|
||||||
|
}
|
|
@ -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"] }
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) };
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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) };
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 })
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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) };
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 })
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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};
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) };
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue