WIP: process spawn API
This commit is contained in:
parent
bdc1083b04
commit
f3e62c43ea
9 changed files with 614 additions and 1 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
|
@ -369,6 +369,12 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.83"
|
||||
|
|
@ -542,6 +548,16 @@ version = "1.0.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
|
|
@ -562,6 +578,19 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.4"
|
||||
|
|
@ -577,6 +606,8 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"cairo-rs",
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smithay-client-toolkit",
|
||||
"wayland-client",
|
||||
]
|
||||
|
|
@ -1052,3 +1083,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
|||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0095ecd462946aa3927d9297b63ef82fb9a5316d7a37d134eeb36e58228615a"
|
||||
|
|
|
|||
|
|
@ -8,3 +8,5 @@ smithay-client-toolkit = "0.19"
|
|||
wayland-client = "0.31"
|
||||
cairo-rs = { version = "0.20", features = ["v1_16"] }
|
||||
chrono = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
|
|
|||
123
src/daemon/PROCESS_SPAWN_API.md
Normal file
123
src/daemon/PROCESS_SPAWN_API.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Process Spawning API
|
||||
|
||||
The daemon now supports spawning graphical processes within the current session.
|
||||
|
||||
## API Messages
|
||||
|
||||
### SpawnProcess
|
||||
Spawns a new process with the specified command and arguments.
|
||||
|
||||
```rust
|
||||
use crate::daemon::{IpcClient, IpcMessage, IpcResponse};
|
||||
|
||||
// Spawn a process with arguments
|
||||
let message = IpcMessage::SpawnProcess {
|
||||
command: "firefox".to_string(),
|
||||
args: vec!["--new-window".to_string(), "https://example.com".to_string()],
|
||||
};
|
||||
|
||||
match IpcClient::send_message(&message) {
|
||||
Ok(IpcResponse::ProcessSpawned { success, pid, message }) => {
|
||||
if success {
|
||||
println!("Process spawned with PID: {:?}", pid);
|
||||
} else {
|
||||
eprintln!("Failed to spawn: {}", message);
|
||||
}
|
||||
}
|
||||
Ok(other) => eprintln!("Unexpected response: {:?}", other),
|
||||
Err(e) => eprintln!("IPC error: {}", e),
|
||||
}
|
||||
```
|
||||
|
||||
### LaunchApp (Legacy)
|
||||
Spawns a process from a command string (command and args in one string).
|
||||
|
||||
```rust
|
||||
let message = IpcMessage::LaunchApp {
|
||||
name: "Terminal".to_string(),
|
||||
command: "alacritty".to_string(),
|
||||
};
|
||||
|
||||
match IpcClient::send_message(&message) {
|
||||
Ok(IpcResponse::ProcessSpawned { success, pid, message }) => {
|
||||
println!("Launch result: {} (PID: {:?})", message, pid);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
```
|
||||
|
||||
## Process Management
|
||||
|
||||
### Automatic Cleanup
|
||||
The daemon automatically tracks spawned processes and cleans up when they exit:
|
||||
- Each spawned process is tracked by PID
|
||||
- The daemon periodically checks for finished processes
|
||||
- Exited processes are automatically removed from tracking
|
||||
|
||||
### Status Query
|
||||
Get the number of currently running processes:
|
||||
|
||||
```rust
|
||||
match IpcClient::send_message(&IpcMessage::GetStatus) {
|
||||
Ok(IpcResponse::Status { uptime_secs, apps_running }) => {
|
||||
println!("Daemon uptime: {}s, Processes running: {}",
|
||||
uptime_secs, apps_running);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Inheritance
|
||||
|
||||
Spawned processes inherit the daemon's environment, which includes:
|
||||
- `WAYLAND_DISPLAY` - for Wayland session access
|
||||
- `XDG_RUNTIME_DIR` - runtime directory
|
||||
- `DISPLAY` - for X11 fallback (if available)
|
||||
- All other environment variables from the daemon
|
||||
|
||||
This ensures graphical applications can connect to the display server.
|
||||
|
||||
## Examples
|
||||
|
||||
### Spawn a terminal emulator
|
||||
```rust
|
||||
IpcClient::send_message(&IpcMessage::SpawnProcess {
|
||||
command: "alacritty".to_string(),
|
||||
args: vec![],
|
||||
})
|
||||
```
|
||||
|
||||
### Spawn a browser with URL
|
||||
```rust
|
||||
IpcClient::send_message(&IpcMessage::SpawnProcess {
|
||||
command: "firefox".to_string(),
|
||||
args: vec!["https://github.com".to_string()],
|
||||
})
|
||||
```
|
||||
|
||||
### Spawn with working directory (using sh wrapper)
|
||||
```rust
|
||||
IpcClient::send_message(&IpcMessage::SpawnProcess {
|
||||
command: "sh".to_string(),
|
||||
args: vec![
|
||||
"-c".to_string(),
|
||||
"cd /path/to/project && code .".to_string()
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
`ProcessSpawned` response contains:
|
||||
- `success: bool` - Whether the spawn was successful
|
||||
- `pid: Option<u32>` - Process ID if successful, None on failure
|
||||
- `message: String` - Human-readable status message
|
||||
|
||||
## Error Handling
|
||||
|
||||
Common errors:
|
||||
- Command not found: Returns `success: false` with error message
|
||||
- Permission denied: Returns `success: false` with permission error
|
||||
- Invalid arguments: Returns `success: false` with argument error
|
||||
|
||||
Always check the `success` field before assuming the process started.
|
||||
86
src/daemon/README.md
Normal file
86
src/daemon/README.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Daemon and IPC Implementation
|
||||
|
||||
This directory contains the daemon process and IPC (Inter-Process Communication) implementation for shepherd-launcher.
|
||||
|
||||
## Architecture
|
||||
|
||||
The application uses a multi-process architecture:
|
||||
- **Main Process**: Spawns the daemon and runs the UI
|
||||
- **Daemon Process**: Background service that handles application launching and state management
|
||||
- **IPC**: Unix domain sockets for communication between processes
|
||||
|
||||
## Files
|
||||
|
||||
- `mod.rs`: Module exports
|
||||
- `daemon.rs`: Daemon process implementation
|
||||
- `ipc.rs`: IPC protocol, message types, client and server implementations
|
||||
|
||||
## IPC Protocol
|
||||
|
||||
Communication uses JSON-serialized messages over Unix domain sockets.
|
||||
|
||||
### Message Types (UI → Daemon)
|
||||
- `Ping`: Simple health check
|
||||
- `GetStatus`: Request daemon status (uptime, running apps)
|
||||
- `LaunchApp { name, command }`: Request to launch an application
|
||||
- `Shutdown`: Request daemon shutdown
|
||||
|
||||
### Response Types (Daemon → UI)
|
||||
- `Pong`: Response to Ping
|
||||
- `Status { uptime_secs, apps_running }`: Daemon status information
|
||||
- `AppLaunched { success, message }`: Result of app launch request
|
||||
- `ShuttingDown`: Acknowledgment of shutdown request
|
||||
- `Error { message }`: Error response
|
||||
|
||||
## Socket Location
|
||||
|
||||
The IPC socket is created at: `$XDG_RUNTIME_DIR/shepherd-launcher.sock` (typically `/run/user/1000/shepherd-launcher.sock`)
|
||||
|
||||
## Usage Example
|
||||
|
||||
```rust
|
||||
use crate::daemon::{IpcClient, IpcMessage, IpcResponse};
|
||||
|
||||
// Send a ping
|
||||
match IpcClient::send_message(&IpcMessage::Ping) {
|
||||
Ok(IpcResponse::Pong) => println!("Daemon is alive!"),
|
||||
Ok(other) => println!("Unexpected response: {:?}", other),
|
||||
Err(e) => eprintln!("IPC error: {}", e),
|
||||
}
|
||||
|
||||
// Get daemon status
|
||||
match IpcClient::send_message(&IpcMessage::GetStatus) {
|
||||
Ok(IpcResponse::Status { uptime_secs, apps_running }) => {
|
||||
println!("Uptime: {}s, Apps: {}", uptime_secs, apps_running);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Launch an app
|
||||
let msg = IpcMessage::LaunchApp {
|
||||
name: "Firefox".to_string(),
|
||||
command: "firefox".to_string(),
|
||||
};
|
||||
match IpcClient::send_message(&msg) {
|
||||
Ok(IpcResponse::AppLaunched { success, message }) => {
|
||||
println!("Launch {}: {}", if success { "succeeded" } else { "failed" }, message);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
```
|
||||
|
||||
## Current Functionality
|
||||
|
||||
Currently this is a dummy implementation demonstrating the IPC pattern:
|
||||
- The daemon process runs in the background
|
||||
- The UI periodically queries the daemon status (every 5 seconds)
|
||||
- Messages are printed to stdout for debugging
|
||||
- App launching is simulated (doesn't actually launch apps yet)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Actual application launching logic
|
||||
- App state tracking
|
||||
- Bi-directional notifications (daemon → UI events)
|
||||
- Multiple concurrent IPC connections
|
||||
- Authentication/security
|
||||
29
src/daemon/daemon.rs
Normal file
29
src/daemon/daemon.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use super::ipc::IpcServer;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Start the daemon process
|
||||
pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("[Daemon] Starting shepherd-launcher daemon...");
|
||||
|
||||
let mut ipc_server = IpcServer::new()?;
|
||||
println!("[Daemon] IPC server listening on socket");
|
||||
|
||||
loop {
|
||||
// Handle incoming IPC connections
|
||||
match ipc_server.accept_and_handle() {
|
||||
Ok(should_shutdown) => {
|
||||
if should_shutdown {
|
||||
println!("[Daemon] Shutdown requested, exiting...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("[Daemon] Error handling client: {}", e),
|
||||
}
|
||||
|
||||
// Sleep briefly to avoid busy-waiting
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
println!("[Daemon] Daemon shut down cleanly");
|
||||
Ok(())
|
||||
}
|
||||
207
src/daemon/ipc.rs
Normal file
207
src/daemon/ipc.rs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Child, Command};
|
||||
|
||||
/// Messages that can be sent from the UI to the daemon
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum IpcMessage {
|
||||
Ping,
|
||||
GetStatus,
|
||||
LaunchApp { name: String, command: String },
|
||||
SpawnProcess { command: String, args: Vec<String> },
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
/// Responses sent from the daemon to the UI
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum IpcResponse {
|
||||
Pong,
|
||||
Status { uptime_secs: u64, apps_running: usize },
|
||||
AppLaunched { success: bool, message: String },
|
||||
ProcessSpawned { success: bool, pid: Option<u32>, message: String },
|
||||
ShuttingDown,
|
||||
Error { message: String },
|
||||
}
|
||||
|
||||
/// Get the IPC socket path
|
||||
pub fn get_socket_path() -> PathBuf {
|
||||
let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
|
||||
.unwrap_or_else(|_| "/tmp".to_string());
|
||||
PathBuf::from(runtime_dir).join("shepherd-launcher.sock")
|
||||
}
|
||||
|
||||
/// Server-side IPC handler for the daemon
|
||||
pub struct IpcServer {
|
||||
listener: UnixListener,
|
||||
start_time: std::time::Instant,
|
||||
processes: HashMap<u32, Child>,
|
||||
}
|
||||
|
||||
impl IpcServer {
|
||||
pub fn new() -> std::io::Result<Self> {
|
||||
let socket_path = get_socket_path();
|
||||
|
||||
// Remove old socket if it exists
|
||||
let _ = std::fs::remove_file(&socket_path);
|
||||
|
||||
let listener = UnixListener::bind(&socket_path)?;
|
||||
listener.set_nonblocking(true)?;
|
||||
|
||||
Ok(Self {
|
||||
listener,
|
||||
start_time: std::time::Instant::now(),
|
||||
processes: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn accept_and_handle(&mut self) -> std::io::Result<bool> {
|
||||
// Clean up finished processes
|
||||
self.cleanup_processes();
|
||||
|
||||
match self.listener.accept() {
|
||||
Ok((stream, _)) => {
|
||||
let should_shutdown = self.handle_client(stream)?;
|
||||
Ok(should_shutdown)
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
Ok(false)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_client(&mut self, mut stream: UnixStream) -> std::io::Result<bool> {
|
||||
let mut reader = BufReader::new(stream.try_clone()?);
|
||||
let mut line = String::new();
|
||||
|
||||
reader.read_line(&mut line)?;
|
||||
|
||||
let message: IpcMessage = match serde_json::from_str(&line) {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
let response = IpcResponse::Error {
|
||||
message: format!("Failed to parse message: {}", e),
|
||||
};
|
||||
let response_json = serde_json::to_string(&response)?;
|
||||
writeln!(stream, "{}", response_json)?;
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
let should_shutdown = matches!(message, IpcMessage::Shutdown);
|
||||
let response = self.process_message(message);
|
||||
let response_json = serde_json::to_string(&response)?;
|
||||
writeln!(stream, "{}", response_json)?;
|
||||
|
||||
Ok(should_shutdown)
|
||||
}
|
||||
|
||||
fn process_message(&mut self, message: IpcMessage) -> IpcResponse {
|
||||
match message {
|
||||
IpcMessage::Ping => IpcResponse::Pong,
|
||||
IpcMessage::GetStatus => {
|
||||
let uptime_secs = self.start_time.elapsed().as_secs();
|
||||
IpcResponse::Status {
|
||||
uptime_secs,
|
||||
apps_running: self.processes.len(),
|
||||
}
|
||||
}
|
||||
IpcMessage::LaunchApp { name, command } => {
|
||||
println!("[Daemon] Launching app: {} ({})", name, command);
|
||||
self.spawn_graphical_process(&command, &[])
|
||||
}
|
||||
IpcMessage::SpawnProcess { command, args } => {
|
||||
println!("[Daemon] Spawning process: {} {:?}", command, args);
|
||||
self.spawn_graphical_process(&command, &args)
|
||||
}
|
||||
IpcMessage::Shutdown => IpcResponse::ShuttingDown,
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_graphical_process(&mut self, command: &str, args: &[String]) -> IpcResponse {
|
||||
// Parse command if it contains arguments and args is empty
|
||||
let (cmd, cmd_args) = if args.is_empty() {
|
||||
let parts: Vec<&str> = command.split_whitespace().collect();
|
||||
if parts.is_empty() {
|
||||
return IpcResponse::ProcessSpawned {
|
||||
success: false,
|
||||
pid: None,
|
||||
message: "Empty command".to_string(),
|
||||
};
|
||||
}
|
||||
(parts[0], parts[1..].iter().map(|s| s.to_string()).collect())
|
||||
} else {
|
||||
(command, args.to_vec())
|
||||
};
|
||||
|
||||
match Command::new(cmd)
|
||||
.args(&cmd_args)
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => {
|
||||
let pid = child.id();
|
||||
println!("[Daemon] Successfully spawned process PID: {}", pid);
|
||||
self.processes.insert(pid, child);
|
||||
IpcResponse::ProcessSpawned {
|
||||
success: true,
|
||||
pid: Some(pid),
|
||||
message: format!("Process spawned with PID {}", pid),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[Daemon] Failed to spawn process '{}': {}", cmd, e);
|
||||
IpcResponse::ProcessSpawned {
|
||||
success: false,
|
||||
pid: None,
|
||||
message: format!("Failed to spawn: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup_processes(&mut self) {
|
||||
// Check for finished processes and remove them
|
||||
let mut finished = Vec::new();
|
||||
for (pid, child) in self.processes.iter_mut() {
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
println!("[Daemon] Process {} exited with status: {}", pid, status);
|
||||
finished.push(*pid);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
eprintln!("[Daemon] Error checking process {}: {}", pid, e);
|
||||
finished.push(*pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
for pid in finished {
|
||||
self.processes.remove(&pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Client-side IPC handler for the UI
|
||||
pub struct IpcClient;
|
||||
|
||||
impl IpcClient {
|
||||
pub fn send_message(message: &IpcMessage) -> std::io::Result<IpcResponse> {
|
||||
let socket_path = get_socket_path();
|
||||
let mut stream = UnixStream::connect(&socket_path)?;
|
||||
|
||||
let message_json = serde_json::to_string(message)?;
|
||||
writeln!(stream, "{}", message_json)?;
|
||||
|
||||
let mut reader = BufReader::new(stream);
|
||||
let mut response_line = String::new();
|
||||
reader.read_line(&mut response_line)?;
|
||||
|
||||
let response: IpcResponse = serde_json::from_str(&response_line)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
5
src/daemon/mod.rs
Normal file
5
src/daemon/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod daemon;
|
||||
mod ipc;
|
||||
|
||||
pub use daemon::start_daemon;
|
||||
pub use ipc::{IpcClient, IpcMessage, IpcResponse};
|
||||
76
src/main.rs
76
src/main.rs
|
|
@ -1,5 +1,79 @@
|
|||
mod daemon;
|
||||
mod ui;
|
||||
|
||||
use std::env;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
ui::run()
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
// Check if we're running as the daemon
|
||||
if args.len() > 1 && args[1] == "--daemon" {
|
||||
return daemon::start_daemon();
|
||||
}
|
||||
|
||||
// Spawn the daemon process
|
||||
println!("[Main] Spawning daemon process...");
|
||||
let mut daemon_child = Command::new(&args[0])
|
||||
.arg("--daemon")
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()?;
|
||||
|
||||
let daemon_pid = daemon_child.id();
|
||||
println!("[Main] Daemon spawned with PID: {}", daemon_pid);
|
||||
|
||||
// Give the daemon a moment to start up
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Test the IPC connection
|
||||
println!("[Main] Testing IPC connection...");
|
||||
match daemon::IpcClient::send_message(&daemon::IpcMessage::Ping) {
|
||||
Ok(daemon::IpcResponse::Pong) => println!("[Main] IPC connection successful!"),
|
||||
Ok(response) => println!("[Main] Unexpected response: {:?}", response),
|
||||
Err(e) => println!("[Main] IPC connection failed: {}", e),
|
||||
}
|
||||
|
||||
// Start the UI
|
||||
println!("[Main] Starting UI...");
|
||||
let ui_result = ui::run();
|
||||
|
||||
// UI has exited, shut down the daemon
|
||||
println!("[Main] UI exited, shutting down daemon...");
|
||||
match daemon::IpcClient::send_message(&daemon::IpcMessage::Shutdown) {
|
||||
Ok(daemon::IpcResponse::ShuttingDown) => {
|
||||
println!("[Main] Daemon acknowledged shutdown");
|
||||
}
|
||||
Ok(response) => {
|
||||
println!("[Main] Unexpected shutdown response: {:?}", response);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[Main] Failed to send shutdown to daemon: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for daemon to exit (with timeout)
|
||||
let wait_start = std::time::Instant::now();
|
||||
loop {
|
||||
match daemon_child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
println!("[Main] Daemon exited with status: {}", status);
|
||||
break;
|
||||
}
|
||||
Ok(None) => {
|
||||
if wait_start.elapsed().as_secs() > 5 {
|
||||
eprintln!("[Main] Daemon did not exit in time, killing it");
|
||||
let _ = daemon_child.kill();
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[Main] Error waiting for daemon: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui_result
|
||||
}
|
||||
|
|
|
|||
50
src/ui/ui.rs
50
src/ui/ui.rs
|
|
@ -1,4 +1,5 @@
|
|||
use super::clock::ClockApp;
|
||||
use crate::daemon::{IpcClient, IpcMessage, IpcResponse};
|
||||
use smithay_client_toolkit::{
|
||||
compositor::CompositorState,
|
||||
output::OutputState,
|
||||
|
|
@ -49,11 +50,60 @@ pub fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
app.layer_surface = Some(layer_surface);
|
||||
|
||||
// Periodically query daemon status via IPC
|
||||
let mut counter = 0;
|
||||
|
||||
// Example: Spawn a test process after 2 seconds
|
||||
let mut test_spawned = false;
|
||||
|
||||
loop {
|
||||
event_queue.blocking_dispatch(&mut app)?;
|
||||
|
||||
if app.configured {
|
||||
app.draw(&qh)?;
|
||||
|
||||
// Example: Spawn a simple graphical process after 2 seconds
|
||||
if counter == 4 && !test_spawned {
|
||||
println!("[UI] Testing process spawn API...");
|
||||
match IpcClient::send_message(&IpcMessage::SpawnProcess {
|
||||
command: "echo".to_string(),
|
||||
args: vec!["Hello from spawned process!".to_string()],
|
||||
}) {
|
||||
Ok(IpcResponse::ProcessSpawned { success, pid, message }) => {
|
||||
if success {
|
||||
println!("[UI] Process spawned successfully! PID: {:?}, Message: {}",
|
||||
pid, message);
|
||||
} else {
|
||||
println!("[UI] Process spawn failed: {}", message);
|
||||
}
|
||||
}
|
||||
Ok(response) => {
|
||||
println!("[UI] Unexpected response: {:?}", response);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[UI] Failed to spawn process: {}", e);
|
||||
}
|
||||
}
|
||||
test_spawned = true;
|
||||
}
|
||||
|
||||
// Every 10 iterations (5 seconds), query the daemon
|
||||
if counter % 10 == 0 {
|
||||
match IpcClient::send_message(&IpcMessage::GetStatus) {
|
||||
Ok(IpcResponse::Status { uptime_secs, apps_running }) => {
|
||||
println!("[UI] Daemon status - Uptime: {}s, Apps running: {}",
|
||||
uptime_secs, apps_running);
|
||||
}
|
||||
Ok(response) => {
|
||||
println!("[UI] Unexpected daemon response: {:?}", response);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[UI] Failed to communicate with daemon: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
counter += 1;
|
||||
|
||||
// Sleep briefly to reduce CPU usage
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue