use clap::command;
use leftwm_core::child_process::{self, Nanny};
use std::env;
use std::path::Path;
use std::process::{Child, Command, ExitStatus, exit};
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
type Subcommand<'a> = &'a str;
type SubcommandArgs = Vec<String>;
type LeftwmArgs = Vec<String>;
const SUBCOMMAND_PREFIX: &str = "leftwm-";
const SUBCOMMAND_NAME_INDEX: usize = 0;
const SUBCOMMAND_DESCRIPTION_INDEX: usize = 1;
const AVAILABLE_SUBCOMMANDS: [[&str; 2]; 6] = [
["check", "Check syntax of the configuration file"],
["command", "Send external commands to LeftWM"],
["state", "Print the current state of LeftWM"],
["theme", "Manage LeftWM themes"],
["config", "Manage LeftWM configuration file"],
["log", "Retrieves information logged by leftwm-worker"],
];
fn main() {
let args: LeftwmArgs = env::args().collect();
let has_subcommands = args.len() > 1;
if has_subcommands {
parse_subcommands(&args);
}
start_leftwm();
}
fn execute_subcommand(subcommand: Subcommand, subcommand_args: SubcommandArgs) -> ! {
let subcommand_file = format!("{SUBCOMMAND_PREFIX}{subcommand}");
match &mut Command::new(subcommand_file).args(subcommand_args).spawn() {
Ok(child) => {
let status = child.wait().expect("Failed to wait for child.");
exit(status.code().unwrap_or(0));
}
Err(e) => {
eprintln!("Failed to execute {subcommand}. {e}");
exit(1);
}
};
}
fn print_help_page() {
let subcommands = {
let mut subcommands = Vec::new();
for entry in AVAILABLE_SUBCOMMANDS {
let subcommand_name = entry[SUBCOMMAND_NAME_INDEX];
let subcommand_description = entry[SUBCOMMAND_DESCRIPTION_INDEX];
subcommands.push(clap::Command::new(subcommand_name).about(subcommand_description));
}
subcommands
};
command!()
.about(
"Starts LeftWM if no arguments are supplied. If a subcommand is given, executes the \
the corresponding leftwm program, e.g. 'leftwm theme' will execute 'leftwm-theme', if \
it is installed.",
)
.subcommands(subcommands)
.help_template(leftwm::utils::get_help_template())
.print_help()
.unwrap();
}
fn is_subcommand(subcommand: &str) -> bool {
AVAILABLE_SUBCOMMANDS
.into_iter()
.any(|entry| entry[SUBCOMMAND_NAME_INDEX] == subcommand)
}
fn parse_subcommands(args: &LeftwmArgs) -> ! {
const SUBCOMMAND_INDEX: usize = 1;
const SUBCOMMAND_ARGS_INDEX: usize = 2;
let subcommand = &args[SUBCOMMAND_INDEX];
let subcommand_args = args[SUBCOMMAND_ARGS_INDEX..].to_vec();
if is_subcommand(subcommand) {
execute_subcommand(subcommand, subcommand_args);
} else if subcommand == "help" {
if subcommand_args.is_empty() {
print_help_page();
} else if is_subcommand(&subcommand_args[0]) {
execute_subcommand(&subcommand_args[0], vec!["--help".to_string()]);
} else {
println!("No such subcommand. Try 'leftwm --help' to find valid subcommands.");
}
} else if subcommand == "--version" || subcommand == "-v" {
println!("leftwm {}", env!("CARGO_PKG_VERSION"));
} else {
print_help_page();
}
exit(0);
}
#[allow(unsafe_code)]
fn set_env_vars() {
unsafe { env::set_var("XDG_CURRENT_DESKTOP", "LeftWM") };
unsafe { env::set_var("_JAVA_AWT_WM_NONREPARENTING", "1") };
}
fn get_current_exe() -> std::path::PathBuf {
#[cfg(not(target_os = "openbsd"))]
{
std::env::current_exe().expect("can't get path to leftwm-binary")
}
#[cfg(target_os = "openbsd")]
{
let arg0 = std::env::args()
.next()
.expect("Cannot get args[0] to compute leftwm executable path");
std::path::PathBuf::from(arg0)
}
}
fn start_leftwm() {
let current_exe = get_current_exe();
set_env_vars();
let mut children = Nanny::autostart();
let flag = get_sigchld_flag();
let mut error_occured = false;
let mut session_exit_status: Option<ExitStatus> = None;
while !error_occured {
let mut leftwm_session = start_leftwm_session(¤t_exe);
#[cfg(feature = "lefthk")]
let mut lefthk_session = start_lefthk_session(¤t_exe);
while session_is_running(&mut leftwm_session) {
children.remove_finished_children();
while is_suspending(&flag) {
nix::unistd::pause();
}
}
#[cfg(feature = "lefthk")]
kill_lefthk_session(&mut lefthk_session);
session_exit_status = get_exit_status(&mut leftwm_session);
error_occured = check_error_occured(session_exit_status);
#[cfg(feature = "slow-dm-fix")]
{
let delay = std::time::Duration::from_secs(2);
std::thread::sleep(delay);
}
}
if error_occured {
print_crash_message();
}
match session_exit_status {
Some(exit_status) => std::process::exit(exit_status.code().unwrap_or(0)),
None => std::process::exit(1),
};
}
fn session_is_running(leftwm_session: &mut Child) -> bool {
leftwm_session
.try_wait()
.expect("failed to wait on worker")
.is_none()
}
fn start_leftwm_session(current_exe: &Path) -> Child {
let worker_file = current_exe.with_file_name("leftwm-worker");
Command::new(worker_file)
.spawn()
.expect("failed to start leftwm")
}
#[cfg(feature = "lefthk")]
fn start_lefthk_session(current_exe: &Path) -> Child {
let worker_file = current_exe.with_file_name("lefthk-worker");
Command::new(worker_file)
.spawn()
.expect("failed to start lefthk")
}
#[cfg(feature = "lefthk")]
fn kill_lefthk_session(lefthk_session: &mut Child) {
if lefthk_session.kill().is_ok() {
while lefthk_session
.try_wait()
.expect("failed to reap lefthk")
.is_none()
{}
}
}
fn get_sigchld_flag() -> Arc<AtomicBool> {
let flag = Arc::new(AtomicBool::new(false));
child_process::register_child_hook(flag.clone());
flag
}
fn is_suspending(flag: &Arc<AtomicBool>) -> bool {
!flag.swap(false, Ordering::SeqCst)
}
fn get_exit_status(leftwm_session: &mut Child) -> Option<ExitStatus> {
leftwm_session.wait().ok()
}
fn check_error_occured(session_exit_status: Option<ExitStatus>) -> bool {
if let Some(exit_status) = session_exit_status {
!exit_status.success()
} else {
true
}
}
fn print_crash_message() {
println!(concat!(
"Leftwm crashed due to an unexpected error.\n",
"Please create a new issue and post its log if possible.\n",
"\n",
"NOTE: You can restart leftwm with `startx`."
));
}