mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-14 21:04:04 +01:00
refactor(komorebic-core): the Launcher skeleton for start, and the stop message
start is the same dance in all three komorebic: look whether komorebi is running, start it, wait three seconds and look again, give up after three attempts by running it in front of the user so its stderr can be read. launcher::start runs that loop over a Launcher (daemon_name, spawn, is_running, diagnose) that each platform implements by hand; what follows a successful start (companions, the licence text) stays in each komorebic, as does stop's handling of companions, whose order differs per platform; only stop_message(ignore_restore) is shared. The loop flushes standard output after 'Waiting for ... to start...' (KDE did, macOS and Windows did not) and puts the same space after it; both are invisible in the text printed. Nothing uses it yet. Verified: native cargo test -p komorebic-core: 31 passed, 0 failed (4 new launcher tests with a fake launcher: already running, second try, three tries then diagnose, stop message).
This commit is contained in:
1 parent
0345fccba2
commit
be3d1cd6b6
4 files changed
+156
No files matched your search
Generated
+1
@@ -3586,6 +3586,7 @@ version = "0.1.42"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"color-eyre",
|
||||
"komorebi-layouts",
|
||||
"komorebi-protocol",
|
||||
]
|
||||
|
||||
@@ -7,5 +7,6 @@ edition = "2024"
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4"
|
||||
color-eyre = { workspace = true }
|
||||
komorebi-layouts = { path = "../../komorebi-layouts" }
|
||||
komorebi-protocol = { path = "../komorebi-protocol", default-features = false }
|
||||
@@ -0,0 +1,152 @@
|
||||
//! `komorebic start` and `komorebic stop`: the part of them that is the same on every platform.
|
||||
//!
|
||||
//! Starting the daemon is the same dance everywhere: look whether it is running, and if it is
|
||||
//! not, start it, wait a few seconds, look again, and give up after three attempts by running it
|
||||
//! in front of the user so that what it says on its standard error can be read. What differs is
|
||||
//! how the daemon is started (a `Command`, a PowerShell script), how its process is found, and
|
||||
//! what is done once it is up (companions such as whkd and the bar, the licence text), which each
|
||||
//! platform writes by hand around [`start`].
|
||||
|
||||
use color_eyre::eyre;
|
||||
use komorebi_protocol::message::SocketMessage;
|
||||
use std::io::Write;
|
||||
use std::time::Duration;
|
||||
|
||||
/// How many times the daemon is started before `start` gives up.
|
||||
pub const ATTEMPTS: usize = 3;
|
||||
|
||||
/// How long to wait after each start before looking whether the daemon is running.
|
||||
pub const WAIT: Duration = Duration::from_secs(3);
|
||||
|
||||
/// What `start` needs of a platform.
|
||||
pub trait Launcher {
|
||||
/// What the daemon is called in what is printed (`komorebi`, `komorebi.exe`).
|
||||
fn daemon_name(&self) -> &str;
|
||||
|
||||
/// Start the daemon in the background.
|
||||
fn spawn(&mut self) -> eyre::Result<()>;
|
||||
|
||||
/// Look at the processes and say whether the daemon is among them.
|
||||
fn is_running(&mut self) -> eyre::Result<bool>;
|
||||
|
||||
/// Run the daemon in front of the user and print what it says on its standard error.
|
||||
fn diagnose(&mut self) -> eyre::Result<()>;
|
||||
}
|
||||
|
||||
/// Start the daemon unless it is running, waiting [`WAIT`] after each of at most [`ATTEMPTS`]
|
||||
/// starts. Returns whether it is running; if it is not, it has been run in front of the user
|
||||
/// ([`Launcher::diagnose`]) and the caller has nothing more to do.
|
||||
pub fn start(launcher: &mut impl Launcher) -> eyre::Result<bool> {
|
||||
start_waiting(launcher, WAIT)
|
||||
}
|
||||
|
||||
/// [`start`] with another waiting time.
|
||||
pub fn start_waiting(launcher: &mut impl Launcher, wait: Duration) -> eyre::Result<bool> {
|
||||
let name = launcher.daemon_name().to_string();
|
||||
let mut attempts = 0;
|
||||
let mut running = launcher.is_running()?;
|
||||
|
||||
while !running && attempts < ATTEMPTS {
|
||||
launcher.spawn()?;
|
||||
|
||||
print!("Waiting for {name} to start... ");
|
||||
std::io::stdout().flush()?;
|
||||
std::thread::sleep(wait);
|
||||
|
||||
if launcher.is_running()? {
|
||||
println!("Started!");
|
||||
running = true;
|
||||
} else {
|
||||
println!("{name} did not start... Trying again");
|
||||
attempts += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !running {
|
||||
println!("\nRunning {name} directly for detailed error output\n");
|
||||
launcher.diagnose()?;
|
||||
}
|
||||
|
||||
Ok(running)
|
||||
}
|
||||
|
||||
/// The message that stops the daemon, with or without restoring the windows it hid.
|
||||
pub const fn stop_message(ignore_restore: bool) -> SocketMessage {
|
||||
if ignore_restore {
|
||||
SocketMessage::StopIgnoreRestore
|
||||
} else {
|
||||
SocketMessage::Stop
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A daemon that is up after it has been started `starts_needed` times.
|
||||
struct Fake {
|
||||
starts_needed: usize,
|
||||
started: usize,
|
||||
diagnosed: bool,
|
||||
}
|
||||
|
||||
impl Launcher for Fake {
|
||||
fn daemon_name(&self) -> &str {
|
||||
"komorebi"
|
||||
}
|
||||
|
||||
fn spawn(&mut self) -> eyre::Result<()> {
|
||||
self.started += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_running(&mut self) -> eyre::Result<bool> {
|
||||
Ok(self.started >= self.starts_needed)
|
||||
}
|
||||
|
||||
fn diagnose(&mut self) -> eyre::Result<()> {
|
||||
self.diagnosed = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn fake(starts_needed: usize) -> Fake {
|
||||
Fake {
|
||||
starts_needed,
|
||||
started: 0,
|
||||
diagnosed: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_daemon_that_runs_is_not_started() {
|
||||
let mut launcher = fake(0);
|
||||
assert!(start_waiting(&mut launcher, Duration::ZERO).unwrap());
|
||||
assert_eq!(launcher.started, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_daemon_that_needs_a_second_try_is_started_twice() {
|
||||
let mut launcher = fake(2);
|
||||
assert!(start_waiting(&mut launcher, Duration::ZERO).unwrap());
|
||||
assert_eq!(launcher.started, 2);
|
||||
assert!(!launcher.diagnosed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_daemon_that_never_starts_is_tried_three_times_and_diagnosed() {
|
||||
let mut launcher = fake(usize::MAX);
|
||||
assert!(!start_waiting(&mut launcher, Duration::ZERO).unwrap());
|
||||
assert_eq!(launcher.started, ATTEMPTS);
|
||||
assert!(launcher.diagnosed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stop_says_whether_to_restore() {
|
||||
assert!(matches!(stop_message(false), SocketMessage::Stop));
|
||||
assert!(matches!(
|
||||
stop_message(true),
|
||||
SocketMessage::StopIgnoreRestore
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
//!
|
||||
//! - [`args`] has the argument blocks the subcommands are built from.
|
||||
//! - [`cli`] has [`cli::Opts`], the command line of a platform, generic over its own subcommands.
|
||||
//! - [`launcher`] has the part of `start` and `stop` that is the same on every platform.
|
||||
//! - [`messages`] has the subcommands that are one message to the daemon.
|
||||
//! - [`pan`] reads the stream of touchpad gestures that `komorebic pan --stream` is given.
|
||||
//! - [`platform`] has the subcommands of each platform that are not in [`messages`].
|
||||
@@ -13,6 +14,7 @@
|
||||
pub mod args;
|
||||
pub mod cli;
|
||||
pub mod docgen;
|
||||
pub mod launcher;
|
||||
pub mod messages;
|
||||
pub mod pan;
|
||||
pub mod platform;
|
||||
Reference in new issue
Block a user