use chrono::Local;
use handlers::keyboard::KeyPattern;
use handlers::store::{Store, StoreKey};
use image::{DynamicImage, ImageFormat, RgbaImage};
use slog_scope;
use std::fs::{self, File};
use std::path::PathBuf;
use std::process::{Command, Stdio};
use wlc::{Callback, Geometry, Key, KeyState, Modifiers, Output, Point, View};
use wlc::render::{GLES2PixelFormat, RenderInstance, RenderOutput, Renderer};
pub struct QueuedScreenshots;
impl StoreKey for QueuedScreenshots {
type Value = Vec<Geometry>;
}
#[derive(Default)]
pub struct ScreenshotHandler {
config: ScreenshotConfig,
}
impl ScreenshotHandler {
pub fn new(config: ScreenshotConfig) -> ScreenshotHandler {
ScreenshotHandler { config: config }
}
}
#[derive(Default, Clone, PartialEq, Eq, Hash, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ScreenshotConfig {
#[serde(default = "::handlers::render::screenshot::default_path")]
location: PathBuf,
#[serde(default)]
keys: KeyPatterns,
}
fn default_path() -> PathBuf {
let child = Command::new("xdg-user-dir")
.arg("DESKTOP")
.stdout(Stdio::piped())
.spawn()
.expect("Failed to execute xdg-user-dir. Could not find default path for screenshots");
let output = child.wait_with_output()
.expect("xdg-user-dir did terminate in an unusual way");
PathBuf::from(String::from_utf8_lossy(&*output.stdout).into_owned().trim())
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Deserialize)]
pub struct KeyPatterns {
#[serde(default)]
pub output: Option<KeyPattern>,
#[serde(default)]
pub view: Option<KeyPattern>,
}
impl Callback for ScreenshotHandler {
fn output_created(&mut self, output: &Output) -> bool {
output.insert::<QueuedScreenshots>(Vec::new());
true
}
fn output_render_post(&mut self, output: &mut RenderOutput) {
if let Some(queued) = output.get::<QueuedScreenshots>() {
let mut lock = queued.write().unwrap();
for mut geometry in lock.drain(..) {
if let Some(image) = make_screenshot(output, &mut geometry) {
let filename = Local::now().format("screenshot_%Y-%m-%dT%H:%M:%S%.f%:z.png");
let mut path = self.config.location.clone();
fs::create_dir_all(path.clone()).expect("Could not create screenshots folder");
path.push(format!("{}", filename));
image.save(&mut File::create(path).expect("Failed to create screenshot file"),
ImageFormat::PNG)
.expect("Failed to encode screenshot");
}
}
}
}
fn keyboard_key(&mut self, view: Option<&View>, _time: u32, modifiers: Modifiers, key: Key,
state: KeyState)
-> bool {
match Some(KeyPattern::new(state, modifiers.mods, key)) {
x if x == self.config.keys.output => {
Output::with_focused_output(|output| {
let delayed = output.get::<QueuedScreenshots>().unwrap();
let mut lock = delayed.write().unwrap();
lock.push(Geometry {
origin: Point { x: 0, y: 0 },
size: output.resolution(),
});
});
true
}
x if x == self.config.keys.view => {
if let Some(view) = view {
let output = view.output();
let queued = output.get::<QueuedScreenshots>().unwrap();
let mut lock = queued.write().unwrap();
lock.push(view.visible_geometry());
true
} else {
false
}
}
_ => false,
}
}
}
pub fn make_screenshot(output: &mut RenderOutput, geo: &mut Geometry) -> Option<DynamicImage> {
match output.get_renderer() {
RenderInstance::GLES2(renderer) => {
let pixels = renderer.pixels_read(GLES2PixelFormat::RGBA8888, geo);
let image = RgbaImage::from_raw(geo.size.w, geo.size.h, pixels).expect("Invalid sizes");
Some(DynamicImage::ImageRgba8(image).flipv())
}
_ => {
error!(slog_scope::logger(), "Unsupported renderer for screenshots");
None
}
}
}