use image::RgbaImage;
use webp::{Encoder, WebPConfig};
use xcap::{Monitor, Window};
use crate::model::WindowInfo;
use crate::writer::Pixels;
pub fn focused_window() -> Option<Window> {
Window::all()
.ok()?
.into_iter()
.find(|w| w.is_focused().unwrap_or(false))
}
pub fn is_own_window(window: &Window) -> bool {
window.pid().ok() == Some(std::process::id())
}
pub fn window_info(window: &Window) -> WindowInfo {
WindowInfo {
title: window.title().unwrap_or_default(),
app_name: window.app_name().unwrap_or_default(),
pid: window.pid().unwrap_or_default(),
x: window.x().unwrap_or_default(),
y: window.y().unwrap_or_default(),
width: window.width().unwrap_or_default(),
height: window.height().unwrap_or_default(),
}
}
pub fn capture_window(window: &Window) -> Option<RgbaImage> {
window.capture_image().ok()
}
pub fn encode_webp(image: &RgbaImage, quality: f32) -> Vec<u8> {
let encoder = Encoder::from_rgba(image.as_raw(), image.width(), image.height());
let Ok(mut config) = WebPConfig::new() else {
return encoder.encode(quality).to_vec(); };
config.lossless = 0;
config.alpha_compression = 1;
config.quality = quality;
config.method = 1;
match encoder.encode_advanced(&config) {
Ok(mem) => mem.to_vec(),
Err(_) => encoder.encode(quality).to_vec(),
}
}
pub fn draw_click_marker(image: &mut RgbaImage, cx: i32, cy: i32) {
const RADIUS: i32 = 16;
const THICKNESS: i32 = 3;
let red = image::Rgba([235, 30, 30, 255]);
let (w, h) = (image.width() as i32, image.height() as i32);
let inner = (RADIUS - THICKNESS).pow(2);
let outer = RADIUS.pow(2);
for dy in -RADIUS..=RADIUS {
for dx in -RADIUS..=RADIUS {
let dist = dx * dx + dy * dy;
if dist <= outer && dist >= inner {
let (px, py) = (cx + dx, cy + dy);
if px >= 0 && py >= 0 && px < w && py < h {
image.put_pixel(px as u32, py as u32, red);
}
}
}
}
}
pub enum Screen {
Xcap,
#[cfg(target_os = "linux")]
Portal(crate::screencast::PortalCapture),
}
impl Screen {
pub fn new() -> Self {
#[cfg(target_os = "linux")]
{
if crate::input::is_wayland() {
match crate::screencast::PortalCapture::start() {
Ok(p) => return Screen::Portal(p),
Err(e) => eprintln!("ssr: portail indisponible ({e}) — repli xcap"),
}
}
}
Screen::Xcap
}
pub fn capture_for_click(
&self,
pos: Option<(i32, i32)>,
) -> Option<(Pixels, Option<WindowInfo>)> {
match self {
Screen::Xcap => {
let (image, info) = capture_for_click_native(pos)?;
Some((Pixels::Rgba(image), info))
}
#[cfg(target_os = "linux")]
Screen::Portal(p) => {
let frame = p.latest_frame()?;
Some((Pixels::Portal(frame), None))
}
}
}
}
impl Default for Screen {
fn default() -> Self {
Self::new()
}
}
fn capture_for_click_native(pos: Option<(i32, i32)>) -> Option<(RgbaImage, Option<WindowInfo>)> {
#[cfg(target_os = "windows")]
if let Some(result) = crate::capture_windows::capture_for_click(pos) {
return Some(result);
}
#[cfg(target_os = "macos")]
if let Some(result) = crate::capture_macos::capture_for_click(pos) {
return Some(result);
}
capture_for_click_xcap(pos)
}
pub fn capture_for_click_xcap(pos: Option<(i32, i32)>) -> Option<(RgbaImage, Option<WindowInfo>)> {
if let Some(window) = focused_window() {
if is_own_window(&window) {
return None;
}
let info = window_info(&window);
let mut image = capture_window(&window)?;
if let Some((px, py)) = pos {
draw_click_marker(&mut image, px - info.x, py - info.y);
}
return Some((image, Some(info)));
}
let monitor = match pos {
Some((x, y)) => Monitor::from_point(x, y).ok().or_else(first_monitor)?,
None => first_monitor()?,
};
let mut image = monitor.capture_image().ok()?;
if let (Some((px, py)), Ok(mx), Ok(my)) = (pos, monitor.x(), monitor.y()) {
draw_click_marker(&mut image, px - mx, py - my);
}
Some((image, None))
}
fn first_monitor() -> Option<Monitor> {
Monitor::all().ok()?.into_iter().next()
}