mod backend;
mod config;
mod displays;
#[cfg(target_os = "macos")]
mod macos_live_lockscreen;
mod plan;
mod scale;
mod wallpaper;
#[cfg(target_os = "macos")]
use std::path::PathBuf;
#[cfg(target_os = "linux")]
use anyhow::Context;
use backend::{Backend, PauseMode, RunOptions};
use clap::Parser;
use clap::Subcommand;
use scale::ScaleMode;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
#[command(subcommand_negates_reqs = true)]
struct Args {
#[command(subcommand)]
command: Option<Command>,
#[arg(conflicts_with_all = ["rand", "display", "display_rand"])]
path: Option<String>,
#[arg(long, conflicts_with_all = ["path", "display", "display_rand"])]
rand: bool,
#[arg(
long,
value_names = ["ID", "PATH"],
num_args = 2,
action = clap::ArgAction::Append,
conflicts_with_all = ["path", "rand"],
)]
display: Vec<String>,
#[arg(
long = "display-rand",
value_name = "ID",
action = clap::ArgAction::Append,
conflicts_with_all = ["path", "rand"],
)]
display_rand: Vec<String>,
#[arg(long, value_enum, default_value_t = ScaleMode::Fill)]
scale: ScaleMode,
#[cfg(target_os = "linux")]
#[arg(long, value_enum, default_value_t = backend::wayland::LayerMode::Background)]
layer: backend::wayland::LayerMode,
#[cfg(target_os = "linux")]
#[arg(long, value_name = "PATH")]
shader: Option<String>,
#[arg(long, conflicts_with = "pause_below")]
pause_on_battery: bool,
#[arg(long, value_name = "PERCENT", value_parser = clap::value_parser!(u8).range(1..=100))]
pause_below: Option<u8>,
#[arg(long, value_name = "BROWSER")]
cookies_from_browser: Option<String>,
#[arg(long, value_name = "FORMAT", default_value = None)]
yt_dlp_format: Option<String>,
#[arg(long, value_name = "ARGS")]
yt_dlp_args: Option<String>,
}
#[derive(Subcommand, Debug)]
enum Command {
Displays,
#[cfg(target_os = "macos")]
InstallLiveLockscreen {
video: PathBuf,
#[arg(long)]
name: Option<String>,
#[arg(long)]
remove: bool,
},
}
fn main() -> anyhow::Result<()> {
env_logger::init();
let args = Args::parse();
if let Some(cmd) = args.command {
match cmd {
Command::Displays => {
let detected = displays::list()?;
displays::print(&detected);
return Ok(());
}
#[cfg(target_os = "macos")]
Command::InstallLiveLockscreen {
video,
name,
remove,
} => return macos_live_lockscreen::install::run(video, name, remove),
}
}
let config = config::load()?;
let cli_per_display = plan::CliPerDisplay {
pinned: args
.display
.chunks(2)
.map(|c| (c[0].clone(), c[1].clone()))
.collect(),
random: args.display_rand.clone(),
};
let plan = plan::build(args.path, args.rand, cli_per_display, &config)?;
let yt_dlp = plan::YtDlpOpts {
format: args.yt_dlp_format,
cookies_from_browser: args.cookies_from_browser,
extra_args: args
.yt_dlp_args
.unwrap_or_default()
.split_whitespace()
.map(|s| s.to_string())
.collect(),
};
let playback = plan::resolve(plan, &config.search_paths, &yt_dlp)?;
if let plan::Playback::Mirror(ref path) = playback
&& let Ok(home) = std::env::var("HOME")
{
let cache_dir = std::path::Path::new(&home).join(".cache/phonto");
if std::fs::create_dir_all(&cache_dir).is_ok() {
let _ = std::fs::write(cache_dir.join("current"), path);
}
}
let pause = match (args.pause_on_battery, args.pause_below) {
(true, _) => PauseMode::OnBattery,
(false, Some(pct)) => PauseMode::BelowPercent(pct),
(false, None) => PauseMode::Never,
};
let options = RunOptions {
pause,
scale: args.scale,
};
#[cfg(target_os = "linux")]
{
let shader = args
.shader
.as_deref()
.map(|p| {
std::fs::read_to_string(p)
.with_context(|| format!("failed to read shader file: {p}"))
})
.transpose()?;
backend::wayland::WaylandBackend::new(args.layer, shader)?.run(playback, options)
}
#[cfg(target_os = "macos")]
return backend::macos::MacosBackend::new()?.run(playback, options);
}