mod shell;
mod ui;
use clap::{Parser, ValueEnum};
use std::sync::{Arc, Mutex, mpsc};
use ui::Mode;
use wlr_capture::tr;
use wlr_capture::{i18n, theme, wl};
#[derive(Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
enum Layout {
#[default]
Full,
Compact,
}
#[derive(Parser)]
#[command(name = "wlr-chooser", version, about)]
struct Cli {
#[arg(short = 'w', long, group = "what")]
windows: bool,
#[arg(short = 'o', long, visible_alias = "screens", group = "what")]
outputs: bool,
#[arg(long, group = "what")]
both: bool,
#[arg(long)]
include_system: bool,
#[arg(long, value_name = "COLSxROWS", value_parser = parse_grid)]
grid: Option<(u32, u32)>,
#[arg(long)]
switch: bool,
#[arg(long, value_enum, default_value_t = Layout::Full)]
layout: Layout,
#[arg(long, value_name = "SECS", hide = true)]
bench_capture: Option<u64>,
}
fn acquire_switch_lock() -> Option<std::fs::File> {
use rustix::fs::{FlockOperation, flock};
let dir = std::env::var_os("XDG_RUNTIME_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(std::env::temp_dir);
let f = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(dir.join("wlr-chooser-switch.lock"))
.ok()?;
flock(&f, FlockOperation::NonBlockingLockExclusive).ok()?;
Some(f)
}
fn parse_grid(s: &str) -> Result<(u32, u32), String> {
let (c, r) = s
.split_once(['x', 'X', '×'])
.ok_or("expected COLSxROWS, e.g. 4x3")?;
let n = |v: &str, what: &str| {
v.trim()
.parse::<u32>()
.ok()
.filter(|&n| n >= 1)
.ok_or(format!("{what} must be a positive integer"))
};
Ok((n(c, "columns")?, n(r, "rows")?))
}
fn main() {
let cli = Cli::parse();
i18n::init();
if let Some(secs) = cli.bench_capture {
ui::bench_capture(secs);
return;
}
let switch = cli.switch; let expose = switch && cli.layout == Layout::Full;
let mode = if cli.windows || switch {
Mode::Windows
} else if cli.outputs {
Mode::Outputs
} else {
Mode::All
};
let _instance_lock = if switch {
match acquire_switch_lock() {
Some(lock) => Some(lock),
None => return, }
} else {
None
};
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || ui::capture_thread(tx));
let out: ui::Outcome = Arc::new(Mutex::new(None));
let out_for_app = out.clone();
let theme = theme::Theme::load();
let app = ui::App::new(
rx,
out_for_app,
mode,
cli.include_system,
cli.grid,
expose,
theme,
);
if let Err(e) = shell::run(app) {
eprintln!("{}", tr!("error", error = format!("{e:#}")));
std::process::exit(2);
}
let selection = out.lock().unwrap().take();
match selection {
Some(sel) if switch => {
if sel.is_window {
if let Err(e) = wl::activate_window(&sel.app_id, &sel.title, sel.dup_index) {
eprintln!("{}", tr!("error", error = format!("{e:#}")));
std::process::exit(2);
}
}
std::process::exit(0);
}
Some(sel) => {
println!("{}", sel.token);
std::process::exit(0);
}
None => std::process::exit(1), }
}