#![cfg_attr(feature = "app", windows_subsystem = "windows")]
mod gui;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(windows)]
mod windows;
use std::path::PathBuf;
#[allow(unused_imports)]
use anyhow::{Result, anyhow};
use clap::Parser;
use gg_lib::config::{self, read_config};
use gg_lib::web;
use gg_lib::{RunOptions, askpass};
use jj_lib::settings::UserSettings;
#[derive(clap::Subcommand, Debug)]
enum Subcommand {
Gui {
workspace: Option<PathBuf>,
},
Web {
workspace: Option<PathBuf>,
#[arg(short, long)]
port: Option<u16>,
#[arg(long, conflicts_with = "no_launch")]
launch: bool,
#[arg(long, conflicts_with = "launch")]
no_launch: bool,
},
}
#[derive(Parser, Debug)]
#[command(version, author, args_conflicts_with_subcommands = true)]
struct Args {
#[command(subcommand)]
command: Option<Subcommand>,
#[arg(index(1))]
workspace: Option<PathBuf>,
#[arg(short, long, global = true)]
debug: bool,
#[arg(long, global = true)]
ignore_immutable: bool,
#[arg(
long,
global = true,
help = "Run in foreground (don't spawn a background process).",
hide = true
)]
foreground: bool,
}
impl Args {
fn mode(&self) -> Option<LaunchMode> {
match &self.command {
Some(Subcommand::Gui { .. }) => Some(LaunchMode::Gui),
Some(Subcommand::Web { .. }) => Some(LaunchMode::Web),
None => None,
}
}
fn workspace(&self) -> Option<PathBuf> {
match &self.command {
Some(Subcommand::Gui { workspace }) | Some(Subcommand::Web { workspace, .. }) => {
workspace.clone()
}
None => self.workspace.clone(),
}
}
fn web_options(&self) -> web::WebOptions {
match &self.command {
Some(Subcommand::Web {
port,
launch,
no_launch,
..
}) => web::WebOptions {
port: *port,
launch: *launch,
no_launch: *no_launch,
},
_ => web::WebOptions::default(),
}
}
}
enum LaunchMode {
Gui,
Web,
}
fn default_mode(settings: &UserSettings) -> LaunchMode {
match settings.get_string("gg.default-mode").ok().as_deref() {
Some("web") => LaunchMode::Web,
_ => LaunchMode::Gui,
}
}
fn main() -> Result<()> {
if let Some(result) = askpass::run_askpass() {
return result;
}
#[cfg(all(windows, feature = "app"))]
{
windows::reattach_console();
}
let args = Args::parse();
if !args.foreground && should_spawn() {
spawn_app()
} else {
run_app(args)
}
}
fn should_spawn() -> bool {
#[cfg(not(feature = "app"))]
return true;
#[cfg(all(feature = "app", target_os = "macos"))]
{
use std::io::IsTerminal;
return std::io::stderr().is_terminal();
}
#[cfg(all(feature = "app", not(target_os = "macos")))]
return false;
}
fn spawn_app() -> Result<()> {
use std::io::{BufRead, BufReader};
use std::process::{Command, Stdio, exit};
let exe = std::env::current_exe()?;
let mut cmd = Command::new(&exe);
cmd.args(std::env::args().skip(1)); cmd.arg("--foreground");
cmd.env("GG_SPAWNED", "1");
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::inherit());
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
cmd.process_group(0);
}
#[cfg(windows)]
{
use ::windows::Win32::System::Threading::{CREATE_NEW_PROCESS_GROUP, DETACHED_PROCESS};
use std::os::windows::process::CommandExt;
cmd.creation_flags(CREATE_NEW_PROCESS_GROUP.0 | DETACHED_PROCESS.0);
}
match cmd.spawn() {
Err(err) => Err(anyhow!("Startup error: {}", err)),
Ok(mut child) => {
if let Some(stdout) = child.stdout.take() {
let reader = BufReader::new(stdout);
let _ = reader.lines().next();
}
exit(0)
}
}
}
fn run_app(args: Args) -> Result<()> {
let repo_path = config::resolve_repo_path(args.workspace().as_deref());
let (settings, _, _, _) = read_config(repo_path.as_deref())?;
let mode = args.mode().unwrap_or_else(|| default_mode(&settings));
let context = tauri::generate_context!();
let is_child = std::env::var_os("GG_SPAWNED").is_some();
let options = RunOptions {
context,
settings,
workspace: args.workspace(),
debug: args.debug,
is_child,
ignore_immutable: args.ignore_immutable,
enable_askpass: true,
};
match mode {
LaunchMode::Gui => gui::run_gui(options),
LaunchMode::Web => web::run_web(options, args.web_options()),
}
}