lios-bar 0.1.9

A native GTK4/VTE sidebar workspace for navigating live Lios terminal sessions.
Documentation
mod cli;
mod compat;
mod config;
mod desktop;
mod paste;
mod search;
mod workspace;

use gtk::prelude::*;
use gtk::{gio, glib};
use std::env;
use std::path::PathBuf;

const APP_ID: &str = "dev.lios.Bar";

fn main() -> glib::ExitCode {
    match run() {
        Ok(code) => code,
        Err(message) => {
            eprintln!("lios-bar: {message}");
            glib::ExitCode::FAILURE
        }
    }
}

fn run() -> Result<glib::ExitCode, String> {
    match cli::parse(env::args().skip(1))? {
        cli::Action::Help => {
            print!("{}", cli::help());
            Ok(glib::ExitCode::SUCCESS)
        }
        cli::Action::Version => {
            println!("lios-bar {}", env!("CARGO_PKG_VERSION"));
            Ok(glib::ExitCode::SUCCESS)
        }
        cli::Action::Check => {
            let report = compat::compatibility_report();
            print!("{}", report.text);
            Ok(if report.compatible {
                glib::ExitCode::SUCCESS
            } else {
                glib::ExitCode::FAILURE
            })
        }
        cli::Action::Install => {
            println!("{}", desktop::install()?);
            Ok(glib::ExitCode::SUCCESS)
        }
        cli::Action::Uninstall => {
            println!("{}", desktop::uninstall()?);
            Ok(glib::ExitCode::SUCCESS)
        }
        cli::Action::Run {
            sidebar,
            working_directory,
            no_gpu,
        } => run_application(sidebar, working_directory, no_gpu),
    }
}

fn run_application(
    sidebar: Option<config::SidebarSide>,
    working_directory: Option<PathBuf>,
    no_gpu: bool,
) -> Result<glib::ExitCode, String> {
    let working_directory = validate_working_directory(working_directory)?;
    let config_path = config::default_path()?;
    let mut config = config::Config::load(&config_path)?;
    config.sidebar = sidebar.unwrap_or(config.sidebar);

    if no_gpu {
        // SAFETY: run() reaches here on the main thread before GTK, GApplication,
        // worker threads, or shells are started. This changes only this process.
        unsafe { env::set_var("GSK_RENDERER", "cairo") };
    }
    gtk::init().map_err(|error| {
        format!(
            "cannot initialize GTK4: {error}. Run `lios-bar --check` from your \
             graphical session and check DISPLAY/WAYLAND_DISPLAY and GDK_BACKEND."
        )
    })?;

    let application = gtk::Application::builder()
        .application_id(APP_ID)
        .flags(gio::ApplicationFlags::NON_UNIQUE)
        .build();
    application.connect_activate(move |application| {
        let window = workspace::build(
            application,
            config.clone(),
            config_path.clone(),
            working_directory.clone(),
        );
        window.present();
    });
    // CLI arguments were already parsed above; do not let GApplication
    // reinterpret LiosBar's options as its own.
    Ok(application.run_with_args(&[env!("CARGO_PKG_NAME")]))
}

fn validate_working_directory(path: Option<PathBuf>) -> Result<Option<PathBuf>, String> {
    let Some(path) = path else {
        return Ok(None);
    };
    if !path.is_dir() {
        return Err(format!(
            "working directory '{}' is not a directory",
            path.display()
        ));
    }
    path.canonicalize()
        .map(Some)
        .map_err(|error| format!("could not resolve '{}': {error}", path.display()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn missing_working_directory_is_rejected() {
        assert!(
            validate_working_directory(Some(PathBuf::from("/definitely/not/a/liosbar/directory")))
                .is_err()
        );
    }

    #[test]
    fn absent_working_directory_is_allowed() {
        assert_eq!(validate_working_directory(None).unwrap(), None);
    }
}