use crate::image_utils::png_to_rgba_image;
use anyhow::{anyhow, Result};
use dbus::{
arg::{AppendAll, Iter, IterAppend, PropMap, ReadAll, RefArg, TypeMismatchError, Variant},
blocking::Connection,
message::{MatchRule, SignalArgs},
};
use image::RgbaImage;
use libwayshot::{CaptureRegion, WayshotConnection};
use percent_encoding::percent_decode;
use std::{
collections::HashMap,
env::temp_dir,
fs::{self},
sync::{Arc, Mutex},
time::{Duration, SystemTime, UNIX_EPOCH},
};
#[derive(Debug)]
pub struct OrgFreedesktopPortalRequestResponse {
pub status: u32,
pub results: PropMap,
}
impl AppendAll for OrgFreedesktopPortalRequestResponse {
fn append(&self, i: &mut IterAppend) {
RefArg::append(&self.status, i);
RefArg::append(&self.results, i);
}
}
impl ReadAll for OrgFreedesktopPortalRequestResponse {
fn read(i: &mut Iter) -> Result<Self, TypeMismatchError> {
Ok(OrgFreedesktopPortalRequestResponse {
status: i.read()?,
results: i.read()?,
})
}
}
impl SignalArgs for OrgFreedesktopPortalRequestResponse {
const NAME: &'static str = "Response";
const INTERFACE: &'static str = "org.freedesktop.portal.Request";
}
fn org_gnome_shell_screenshot(
conn: &Connection,
x: i32,
y: i32,
width: i32,
height: i32,
) -> Result<RgbaImage> {
let proxy = conn.with_proxy(
"org.gnome.Shell.Screenshot",
"/org/gnome/Shell/Screenshot",
Duration::from_secs(10),
);
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?;
let dirname = temp_dir().join("screenshot");
fs::create_dir_all(&dirname)?;
let mut path = dirname.join(timestamp.as_micros().to_string());
path.set_extension("png");
let filename = path.to_string_lossy().to_string();
proxy.method_call(
"org.gnome.Shell.Screenshot",
"ScreenshotArea",
(x, y, width, height, false, &filename),
)?;
let rgba_image = png_to_rgba_image(&filename, 0, 0, width, height)?;
fs::remove_file(&filename)?;
Ok(rgba_image)
}
fn org_freedesktop_portal_screenshot(
conn: &Connection,
x: i32,
y: i32,
width: i32,
height: i32,
) -> Result<RgbaImage> {
let status: Arc<Mutex<Option<u32>>> = Arc::new(Mutex::new(None));
let status_res = status.clone();
let path: Arc<Mutex<String>> = Arc::new(Mutex::new(String::new()));
let path_res = path.clone();
let match_rule = MatchRule::new_signal("org.freedesktop.portal.Request", "Response");
conn.add_match(
match_rule,
move |response: OrgFreedesktopPortalRequestResponse, _conn, _msg| {
if let Ok(mut status) = status.lock() {
*status = Some(response.status);
}
let uri = response.results.get("uri").and_then(|str| str.as_str());
if let (Some(uri_str), Ok(mut path)) = (uri, path.lock()) {
*path = uri_str[7..].to_string();
}
true
},
)?;
let proxy = conn.with_proxy(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
Duration::from_millis(10000),
);
let mut options: PropMap = HashMap::new();
options.insert(
String::from("handle_token"),
Variant(Box::new(String::from("1234"))),
);
options.insert(String::from("modal"), Variant(Box::new(true)));
options.insert(String::from("interactive"), Variant(Box::new(false)));
proxy.method_call(
"org.freedesktop.portal.Screenshot",
"Screenshot",
("", options),
)?;
for _ in 0..60 {
let result = conn.process(Duration::from_millis(1000))?;
let status = status_res
.lock()
.map_err(|_| anyhow!("Get status lock failed"))?;
if result && status.is_some() {
break;
}
}
let status = status_res
.lock()
.map_err(|_| anyhow!("Get status lock failed"))?;
let status = *status;
let path = path_res
.lock()
.map_err(|_| anyhow!("Get path lock failed"))?;
let path = &*path;
if status.ne(&Some(0)) || path.is_empty() {
if !path.is_empty() {
fs::remove_file(path)?;
}
return Err(anyhow!("Screenshot failed or canceled",));
}
let filename = percent_decode(path.as_bytes()).decode_utf8()?.to_string();
let rgba_image = png_to_rgba_image(&filename, x, y, width, height)?;
fs::remove_file(&filename)?;
Ok(rgba_image)
}
fn wlr_screenshot(
x_coordinate: i32,
y_coordinate: i32,
width: i32,
height: i32,
) -> Result<RgbaImage> {
let wayshot_connection = WayshotConnection::new()?;
let capture_region = CaptureRegion {
x_coordinate,
y_coordinate,
width,
height,
};
let rgba_image = wayshot_connection.screenshot(capture_region, false)?;
Ok(rgba_image)
}
pub fn wayland_screenshot(x: i32, y: i32, width: i32, height: i32) -> Result<RgbaImage> {
let conn = Connection::new_session()?;
org_gnome_shell_screenshot(&conn, x, y, width, height)
.or_else(|_| org_freedesktop_portal_screenshot(&conn, x, y, width, height))
.or_else(|_| wlr_screenshot(x, y, width, height))
}