Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ path = "src/main.rs"

[dependencies]
anyhow = "1"
x11rb = { version = "0.13", features = ["allow-unsafe-code"] }
image = { version = "0.25", default-features = false }
image = { version = "0.25", default-features = false, features = ["png"] }
rqrr = "0.8"

[target.'cfg(not(target_os = "macos"))'.dependencies]
x11rb = { version = "0.13", features = ["allow-unsafe-code"] }

[package.metadata.release]
push = true
publish = true
Expand Down
4 changes: 2 additions & 2 deletions Cross.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[target.x86_64-unknown-linux-gnu]
pre-build = ["apt-get update && apt-get install -y libxcb-dev"]
pre-build = ["apt-get update && apt-get install -y libxcb1-dev"]

[target.aarch64-unknown-linux-gnu]
pre-build = [
"dpkg --add-architecture arm64",
"apt-get update && apt-get install -y libxcb-dev:arm64"
"apt-get update && apt-get install -y libxcb1-dev:arm64"
]
6 changes: 1 addition & 5 deletions ci.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ pkgdesc = "CLI tool to capture a screen region, decode any QR code found, an
# nur_build_inputs = "openssl"

# Extra apt packages needed to compile on Ubuntu (CI build machines)
extra_apt_packages = "libxcb1-dev"

# macOS: extra Homebrew packages and env vars needed to compile
# macos_extra_brew = "--cask xquartz"
# macos_extra_env = "LIBRARY_PATH=/opt/X11/lib PKG_CONFIG_PATH=/opt/X11/lib/pkgconfig"
extra_apt_packages = "libxcb1-dev libwayland-dev"

# Shell completions: set to true if the binary exposes a `completion <shell>` subcommand
# has_shell_completions = "true"
Expand Down
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pkgs.mkShell {
pkgs.pkg-config
pkgs.xorg.libxcb
pkgs.xorg.libX11
pkgs.wayland
];
shellHook = ''
export PATH="$PWD/scripts:$PATH"
Expand Down
30 changes: 30 additions & 0 deletions src/backend/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::io::Read;
use std::process::{Command, Stdio};

use image::GrayImage;

pub struct MacOsBackend;

impl super::Backend for MacOsBackend {
fn capture(&self) -> anyhow::Result<Option<GrayImage>> {
let mut child = Command::new("screencapture")
.args(["-i", "-s", "-"])
.stdout(Stdio::piped())
.spawn()?;

let mut png = Vec::new();
child
.stdout
.as_mut()
.expect("stdout piped")
.read_to_end(&mut png)?;

let status = child.wait()?;
if !status.success() || png.is_empty() {
return Ok(None);
}

let img = image::load_from_memory(&png)?.into_luma8();
Ok(Some(img))
}
}
47 changes: 47 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pub mod x11;

#[cfg(not(target_os = "macos"))]
pub mod wayland;

#[cfg(target_os = "macos")]
pub mod macos;

use image::GrayImage;

pub trait Backend {
fn capture(&self) -> anyhow::Result<Option<GrayImage>>;
}

pub enum DisplayServer {
#[cfg(not(target_os = "macos"))]
Wayland,
X11,
#[cfg(target_os = "macos")]
MacOs,
}

pub fn detect() -> anyhow::Result<DisplayServer> {
#[cfg(target_os = "macos")]
return Ok(DisplayServer::MacOs);

#[cfg(not(target_os = "macos"))]
if std::env::var("WAYLAND_DISPLAY").is_ok() {
return Ok(DisplayServer::Wayland);
}

if std::env::var("DISPLAY").is_ok() {
return Ok(DisplayServer::X11);
}

anyhow::bail!("no display found: neither WAYLAND_DISPLAY nor DISPLAY is set")
}

pub fn build() -> anyhow::Result<Box<dyn Backend>> {
match detect()? {
#[cfg(not(target_os = "macos"))]
DisplayServer::Wayland => Ok(Box::new(wayland::WaylandBackend)),
DisplayServer::X11 => Ok(Box::new(x11::X11Backend::new()?)),
#[cfg(target_os = "macos")]
DisplayServer::MacOs => Ok(Box::new(macos::MacOsBackend)),
}
}
52 changes: 52 additions & 0 deletions src/backend/wayland.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::io::Read;
use std::process::{Command, Stdio};

use image::GrayImage;

pub struct WaylandBackend;

impl super::Backend for WaylandBackend {
fn capture(&self) -> anyhow::Result<Option<GrayImage>> {
let geom = slurp()?;
let geom = match geom {
Some(g) => g,
None => return Ok(None),
};

let png = grim(&geom)?;
let img = image::load_from_memory(&png)?.into_luma8();
Ok(Some(img))
}
}

fn slurp() -> anyhow::Result<Option<String>> {
let output = Command::new("slurp").output()?;
if !output.status.success() {
return Ok(None);
}
let geom = String::from_utf8(output.stdout)?.trim().to_string();
if geom.is_empty() {
return Ok(None);
}
Ok(Some(geom))
}

fn grim(geom: &str) -> anyhow::Result<Vec<u8>> {
let mut child = Command::new("grim")
.args(["-g", geom, "-"])
.stdout(Stdio::piped())
.spawn()?;

let mut png = Vec::new();
child
.stdout
.as_mut()
.expect("stdout piped")
.read_to_end(&mut png)?;

let status = child.wait()?;
if !status.success() {
anyhow::bail!("grim exited with status {status}");
}
Ok(png)
}
Loading