Merge pull request #25 from aarmea/u/aarmea/4/steam-type
Add "steam"-specific type
This commit is contained in:
commit
6e64e8e69d
13 changed files with 267 additions and 33 deletions
|
|
@ -180,9 +180,8 @@ label = "Celeste"
|
|||
icon = "~/Games/Icons/Celeste.png"
|
||||
|
||||
[entries.kind]
|
||||
type = "snap"
|
||||
snap_name = "steam"
|
||||
args = ["steam://rungameid/504230"] # Steam App ID (passed to 'snap run steam')
|
||||
type = "steam"
|
||||
app_id = 504230 # Steam App ID
|
||||
|
||||
[entries.availability]
|
||||
[[entries.availability.windows]]
|
||||
|
|
@ -206,9 +205,8 @@ label = "A Short Hike"
|
|||
icon = "~/Games/Icons/A_Short_Hike.png"
|
||||
|
||||
[entries.kind]
|
||||
type = "snap"
|
||||
snap_name = "steam"
|
||||
args = ["steam://rungameid/1055540"] # Steam App ID (passed to 'snap run steam')
|
||||
type = "steam"
|
||||
app_id = 1055540 # Steam App ID
|
||||
|
||||
[entries.availability]
|
||||
[[entries.availability.windows]]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use std::time::Duration;
|
|||
pub enum EntryKindTag {
|
||||
Process,
|
||||
Snap,
|
||||
Steam,
|
||||
Flatpak,
|
||||
Vm,
|
||||
Media,
|
||||
|
|
@ -47,6 +48,17 @@ pub enum EntryKind {
|
|||
#[serde(default)]
|
||||
env: HashMap<String, String>,
|
||||
},
|
||||
/// Steam game launched via the Steam snap (Linux)
|
||||
Steam {
|
||||
/// Steam App ID (e.g., 504230 for Celeste)
|
||||
app_id: u32,
|
||||
/// Additional command-line arguments passed to Steam
|
||||
#[serde(default)]
|
||||
args: Vec<String>,
|
||||
/// Additional environment variables
|
||||
#[serde(default)]
|
||||
env: HashMap<String, String>,
|
||||
},
|
||||
/// Flatpak application - uses systemd scope-based process management
|
||||
Flatpak {
|
||||
/// The Flatpak application ID (e.g., "org.prismlauncher.PrismLauncher")
|
||||
|
|
@ -79,6 +91,7 @@ impl EntryKind {
|
|||
match self {
|
||||
EntryKind::Process { .. } => EntryKindTag::Process,
|
||||
EntryKind::Snap { .. } => EntryKindTag::Snap,
|
||||
EntryKind::Steam { .. } => EntryKindTag::Steam,
|
||||
EntryKind::Flatpak { .. } => EntryKindTag::Flatpak,
|
||||
EntryKind::Vm { .. } => EntryKindTag::Vm,
|
||||
EntryKind::Media { .. } => EntryKindTag::Media,
|
||||
|
|
|
|||
|
|
@ -110,6 +110,9 @@ kind = { type = "process", command = "/usr/bin/game", args = ["--fullscreen"] }
|
|||
# Snap application
|
||||
kind = { type = "snap", snap_name = "mc-installer" }
|
||||
|
||||
# Steam game (via Steam snap)
|
||||
kind = { type = "steam", app_id = 504230 }
|
||||
|
||||
# Virtual machine (future)
|
||||
kind = { type = "vm", driver = "qemu", args = { disk = "game.qcow2" } }
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ fn main() -> ExitCode {
|
|||
EntryKind::Snap { snap_name, .. } => {
|
||||
format!("snap ({})", snap_name)
|
||||
}
|
||||
EntryKind::Steam { app_id, .. } => {
|
||||
format!("steam ({})", app_id)
|
||||
}
|
||||
EntryKind::Flatpak { app_id, .. } => {
|
||||
format!("flatpak ({})", app_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ fn convert_entry_kind(raw: RawEntryKind) -> EntryKind {
|
|||
match raw {
|
||||
RawEntryKind::Process { command, args, env, cwd } => EntryKind::Process { command, args, env, cwd },
|
||||
RawEntryKind::Snap { snap_name, command, args, env } => EntryKind::Snap { snap_name, command, args, env },
|
||||
RawEntryKind::Steam { app_id, args, env } => EntryKind::Steam { app_id, args, env },
|
||||
RawEntryKind::Flatpak { app_id, args, env } => EntryKind::Flatpak { app_id, args, env },
|
||||
RawEntryKind::Vm { driver, args } => EntryKind::Vm { driver, args },
|
||||
RawEntryKind::Media { library_id, args } => EntryKind::Media { library_id, args },
|
||||
|
|
|
|||
|
|
@ -116,6 +116,17 @@ pub enum RawEntryKind {
|
|||
#[serde(default)]
|
||||
env: HashMap<String, String>,
|
||||
},
|
||||
/// Steam game launched via the Steam snap (Linux)
|
||||
Steam {
|
||||
/// Steam App ID (e.g., 504230 for Celeste)
|
||||
app_id: u32,
|
||||
/// Additional command-line arguments passed to Steam
|
||||
#[serde(default)]
|
||||
args: Vec<String>,
|
||||
/// Additional environment variables
|
||||
#[serde(default)]
|
||||
env: HashMap<String, String>,
|
||||
},
|
||||
/// Flatpak application - uses systemd scope-based process management
|
||||
Flatpak {
|
||||
/// The Flatpak application ID (e.g., "org.prismlauncher.PrismLauncher")
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ fn validate_entry(entry: &RawEntry, config: &RawConfig) -> Vec<ValidationError>
|
|||
});
|
||||
}
|
||||
}
|
||||
RawEntryKind::Steam { app_id, .. } => {
|
||||
if *app_id == 0 {
|
||||
errors.push(ValidationError::EntryError {
|
||||
entry_id: entry.id.clone(),
|
||||
message: "app_id must be > 0".into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
RawEntryKind::Flatpak { app_id, .. } => {
|
||||
if app_id.is_empty() {
|
||||
errors.push(ValidationError::EntryError {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ let caps = host.capabilities();
|
|||
// Check supported entry kinds
|
||||
if caps.supports_kind(EntryKindTag::Process) { /* ... */ }
|
||||
if caps.supports_kind(EntryKindTag::Snap) { /* ... */ }
|
||||
if caps.supports_kind(EntryKindTag::Steam) { /* ... */ }
|
||||
|
||||
// Check enforcement capabilities
|
||||
if caps.can_kill_forcefully { /* Can use SIGKILL/TerminateProcess */ }
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ impl HostCapabilities {
|
|||
let mut spawn_kinds = HashSet::new();
|
||||
spawn_kinds.insert(EntryKindTag::Process);
|
||||
spawn_kinds.insert(EntryKindTag::Snap);
|
||||
spawn_kinds.insert(EntryKindTag::Steam);
|
||||
spawn_kinds.insert(EntryKindTag::Flatpak);
|
||||
spawn_kinds.insert(EntryKindTag::Vm);
|
||||
spawn_kinds.insert(EntryKindTag::Media);
|
||||
|
|
|
|||
|
|
@ -90,6 +90,21 @@ let entry_kind = EntryKind::Snap {
|
|||
let handle = host.spawn(session_id, &entry_kind, options).await?;
|
||||
```
|
||||
|
||||
### Spawning Steam Games
|
||||
|
||||
Steam games are launched via the Steam snap:
|
||||
|
||||
```rust
|
||||
let entry_kind = EntryKind::Steam {
|
||||
app_id: 504230,
|
||||
args: vec![],
|
||||
env: Default::default(),
|
||||
};
|
||||
|
||||
// Spawns via: snap run steam steam://rungameid/504230
|
||||
let handle = host.spawn(session_id, &entry_kind, options).await?;
|
||||
```
|
||||
|
||||
### Stopping Sessions
|
||||
|
||||
```rust
|
||||
|
|
|
|||
|
|
@ -3,17 +3,20 @@
|
|||
use async_trait::async_trait;
|
||||
use shepherd_api::EntryKind;
|
||||
use shepherd_host_api::{
|
||||
HostAdapter, HostCapabilities, HostError, HostEvent, HostHandlePayload,
|
||||
ExitStatus, HostAdapter, HostCapabilities, HostError, HostEvent, HostHandlePayload,
|
||||
HostResult, HostSessionHandle, SpawnOptions, StopMode,
|
||||
};
|
||||
use shepherd_util::SessionId;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::process::{init, kill_by_command, kill_flatpak_cgroup, kill_snap_cgroup, ManagedProcess};
|
||||
use crate::process::{
|
||||
find_steam_game_pids, init, kill_by_command, kill_flatpak_cgroup, kill_snap_cgroup,
|
||||
kill_steam_game_processes, ManagedProcess,
|
||||
};
|
||||
|
||||
/// Expand `~` at the beginning of a path to the user's home directory
|
||||
fn expand_tilde(path: &str) -> String {
|
||||
|
|
@ -40,6 +43,15 @@ struct SessionInfo {
|
|||
command_name: String,
|
||||
snap_name: Option<String>,
|
||||
flatpak_app_id: Option<String>,
|
||||
steam_app_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SteamSession {
|
||||
pid: u32,
|
||||
pgid: u32,
|
||||
app_id: u32,
|
||||
seen_game: bool,
|
||||
}
|
||||
|
||||
/// Linux host adapter
|
||||
|
|
@ -48,6 +60,7 @@ pub struct LinuxHost {
|
|||
processes: Arc<Mutex<HashMap<u32, ManagedProcess>>>,
|
||||
/// Track session info for killing
|
||||
session_info: Arc<Mutex<HashMap<SessionId, SessionInfo>>>,
|
||||
steam_sessions: Arc<Mutex<HashMap<u32, SteamSession>>>,
|
||||
event_tx: mpsc::UnboundedSender<HostEvent>,
|
||||
event_rx: Arc<Mutex<Option<mpsc::UnboundedReceiver<HostEvent>>>>,
|
||||
}
|
||||
|
|
@ -63,6 +76,7 @@ impl LinuxHost {
|
|||
capabilities: HostCapabilities::linux_full(),
|
||||
processes: Arc::new(Mutex::new(HashMap::new())),
|
||||
session_info: Arc::new(Mutex::new(HashMap::new())),
|
||||
steam_sessions: Arc::new(Mutex::new(HashMap::new())),
|
||||
event_tx: tx,
|
||||
event_rx: Arc::new(Mutex::new(Some(rx))),
|
||||
}
|
||||
|
|
@ -71,6 +85,7 @@ impl LinuxHost {
|
|||
/// Start the background process monitor
|
||||
pub fn start_monitor(&self) -> tokio::task::JoinHandle<()> {
|
||||
let processes = self.processes.clone();
|
||||
let steam_sessions = self.steam_sessions.clone();
|
||||
let event_tx = self.event_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -78,13 +93,22 @@ impl LinuxHost {
|
|||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
|
||||
let mut exited = Vec::new();
|
||||
let steam_pids: HashSet<u32> = {
|
||||
steam_sessions
|
||||
.lock()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
|
||||
{
|
||||
let mut procs = processes.lock().unwrap();
|
||||
for (pid, proc) in procs.iter_mut() {
|
||||
match proc.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
exited.push((*pid, proc.pgid, status));
|
||||
let is_steam = steam_pids.contains(pid);
|
||||
exited.push((*pid, proc.pgid, status, is_steam));
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
|
|
@ -93,12 +117,16 @@ impl LinuxHost {
|
|||
}
|
||||
}
|
||||
|
||||
for (pid, _, _) in &exited {
|
||||
for (pid, _, _, _) in &exited {
|
||||
procs.remove(pid);
|
||||
}
|
||||
}
|
||||
|
||||
for (pid, pgid, status) in exited {
|
||||
for (pid, pgid, status, is_steam) in exited {
|
||||
if is_steam {
|
||||
info!(pid = pid, pgid = pgid, status = ?status, "Steam launch process exited");
|
||||
continue;
|
||||
}
|
||||
info!(pid = pid, pgid = pgid, status = ?status, "Process exited - sending HostEvent::Exited");
|
||||
|
||||
// We don't have the session_id here, so we use a placeholder
|
||||
|
|
@ -110,6 +138,49 @@ impl LinuxHost {
|
|||
|
||||
let _ = event_tx.send(HostEvent::Exited { handle, status });
|
||||
}
|
||||
|
||||
// Track Steam sessions by Steam App ID instead of process exit
|
||||
let steam_snapshot: Vec<SteamSession> = {
|
||||
steam_sessions
|
||||
.lock()
|
||||
.unwrap()
|
||||
.values()
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut ended = Vec::new();
|
||||
|
||||
for session in &steam_snapshot {
|
||||
let has_game = !find_steam_game_pids(session.app_id).is_empty();
|
||||
if has_game {
|
||||
if let Ok(mut map) = steam_sessions.lock() {
|
||||
map.entry(session.pid)
|
||||
.and_modify(|entry| entry.seen_game = true);
|
||||
}
|
||||
} else if session.seen_game {
|
||||
ended.push((session.pid, session.pgid));
|
||||
}
|
||||
}
|
||||
|
||||
if !ended.is_empty() {
|
||||
let mut map = steam_sessions.lock().unwrap();
|
||||
let mut procs = processes.lock().unwrap();
|
||||
|
||||
for (pid, pgid) in ended {
|
||||
map.remove(&pid);
|
||||
procs.remove(&pid);
|
||||
|
||||
let handle = HostSessionHandle::new(
|
||||
SessionId::new(),
|
||||
HostHandlePayload::Linux { pid, pgid },
|
||||
);
|
||||
let _ = event_tx.send(HostEvent::Exited {
|
||||
handle,
|
||||
status: ExitStatus::success(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -133,15 +204,15 @@ impl HostAdapter for LinuxHost {
|
|||
entry_kind: &EntryKind,
|
||||
options: SpawnOptions,
|
||||
) -> HostResult<HostSessionHandle> {
|
||||
// Extract argv, env, cwd, snap_name, and flatpak_app_id based on entry kind
|
||||
let (argv, env, cwd, snap_name, flatpak_app_id) = match entry_kind {
|
||||
// Extract argv, env, cwd, snap_name, flatpak_app_id, and steam_app_id based on entry kind
|
||||
let (argv, env, cwd, snap_name, flatpak_app_id, steam_app_id) = match entry_kind {
|
||||
EntryKind::Process { command, args, env, cwd } => {
|
||||
let mut argv = vec![expand_tilde(command)];
|
||||
argv.extend(expand_args(args));
|
||||
let expanded_cwd = cwd.as_ref().map(|c| {
|
||||
std::path::PathBuf::from(expand_tilde(&c.to_string_lossy()))
|
||||
});
|
||||
(argv, env.clone(), expanded_cwd, None, None)
|
||||
(argv, env.clone(), expanded_cwd, None, None, None)
|
||||
}
|
||||
EntryKind::Snap { snap_name, command, args, env } => {
|
||||
// For snap apps, we need to use 'snap run <snap_name>' to launch them.
|
||||
|
|
@ -154,13 +225,24 @@ impl HostAdapter for LinuxHost {
|
|||
argv.push(cmd.clone());
|
||||
}
|
||||
argv.extend(expand_args(args));
|
||||
(argv, env.clone(), None, Some(snap_name.clone()), None)
|
||||
(argv, env.clone(), None, Some(snap_name.clone()), None, None)
|
||||
}
|
||||
EntryKind::Steam { app_id, args, env } => {
|
||||
// Steam games are launched via the Steam snap: snap run steam steam://rungameid/<app_id>
|
||||
let mut argv = vec![
|
||||
"snap".to_string(),
|
||||
"run".to_string(),
|
||||
"steam".to_string(),
|
||||
format!("steam://rungameid/{}", app_id),
|
||||
];
|
||||
argv.extend(expand_args(args));
|
||||
(argv, env.clone(), None, None, None, Some(*app_id))
|
||||
}
|
||||
EntryKind::Flatpak { app_id, args, env } => {
|
||||
// For Flatpak apps, we use 'flatpak run <app_id>' to launch them.
|
||||
let mut argv = vec!["flatpak".to_string(), "run".to_string(), app_id.clone()];
|
||||
argv.extend(expand_args(args));
|
||||
(argv, env.clone(), None, None, Some(app_id.clone()))
|
||||
(argv, env.clone(), None, None, Some(app_id.clone()), None)
|
||||
}
|
||||
EntryKind::Vm { driver, args } => {
|
||||
// Construct command line from VM driver
|
||||
|
|
@ -173,13 +255,13 @@ impl HostAdapter for LinuxHost {
|
|||
argv.push(value.to_string());
|
||||
}
|
||||
}
|
||||
(argv, HashMap::new(), None, None, None)
|
||||
(argv, HashMap::new(), None, None, None, None)
|
||||
}
|
||||
EntryKind::Media { library_id, args: _ } => {
|
||||
// For media, we'd typically launch a media player
|
||||
// This is a placeholder - real implementation would integrate with a player
|
||||
let argv = vec!["xdg-open".to_string(), expand_tilde(library_id)];
|
||||
(argv, HashMap::new(), None, None, None)
|
||||
(argv, HashMap::new(), None, None, None, None)
|
||||
}
|
||||
EntryKind::Custom { type_name: _, payload: _ } => {
|
||||
return Err(HostError::UnsupportedKind);
|
||||
|
|
@ -190,6 +272,8 @@ impl HostAdapter for LinuxHost {
|
|||
// For snap/flatpak apps, use the app name (not "snap"/"flatpak") to avoid killing unrelated processes
|
||||
let command_name = if let Some(ref snap) = snap_name {
|
||||
snap.clone()
|
||||
} else if steam_app_id.is_some() {
|
||||
"steam".to_string()
|
||||
} else if let Some(ref app_id) = flatpak_app_id {
|
||||
app_id.clone()
|
||||
} else {
|
||||
|
|
@ -215,6 +299,7 @@ impl HostAdapter for LinuxHost {
|
|||
command_name: command_name.clone(),
|
||||
snap_name: snap_name.clone(),
|
||||
flatpak_app_id: flatpak_app_id.clone(),
|
||||
steam_app_id,
|
||||
};
|
||||
self.session_info.lock().unwrap().insert(session_id.clone(), session_info_entry);
|
||||
info!(session_id = %session_id, command = %command_name, snap = ?snap_name, flatpak = ?flatpak_app_id, "Tracking session info");
|
||||
|
|
@ -226,6 +311,18 @@ impl HostAdapter for LinuxHost {
|
|||
|
||||
self.processes.lock().unwrap().insert(pid, proc);
|
||||
|
||||
if let Some(app_id) = steam_app_id {
|
||||
self.steam_sessions.lock().unwrap().insert(
|
||||
pid,
|
||||
SteamSession {
|
||||
pid,
|
||||
pgid,
|
||||
app_id,
|
||||
seen_game: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
info!(pid = pid, pgid = pgid, "Spawned process");
|
||||
|
||||
Ok(handle)
|
||||
|
|
@ -256,6 +353,12 @@ impl HostAdapter for LinuxHost {
|
|||
if let Some(ref snap) = info.snap_name {
|
||||
kill_snap_cgroup(snap, nix::sys::signal::Signal::SIGTERM);
|
||||
info!(snap = %snap, "Sent SIGTERM via snap cgroup");
|
||||
} else if let Some(app_id) = info.steam_app_id {
|
||||
let _ = kill_steam_game_processes(app_id, nix::sys::signal::Signal::SIGTERM);
|
||||
if let Ok(mut map) = self.steam_sessions.lock() {
|
||||
map.entry(pid).and_modify(|entry| entry.seen_game = true);
|
||||
}
|
||||
info!(steam_app_id = app_id, "Sent SIGTERM to Steam game processes");
|
||||
} else if let Some(ref app_id) = info.flatpak_app_id {
|
||||
kill_flatpak_cgroup(app_id, nix::sys::signal::Signal::SIGTERM);
|
||||
info!(flatpak = %app_id, "Sent SIGTERM via flatpak cgroup");
|
||||
|
|
@ -265,9 +368,10 @@ impl HostAdapter for LinuxHost {
|
|||
info!(command = %info.command_name, "Sent SIGTERM via command name");
|
||||
}
|
||||
}
|
||||
|
||||
// Also send SIGTERM via process handle
|
||||
{
|
||||
|
||||
// Also send SIGTERM via process handle (skip for Steam sessions)
|
||||
let is_steam = session_info.as_ref().and_then(|info| info.steam_app_id).is_some();
|
||||
if !is_steam {
|
||||
let procs = self.processes.lock().unwrap();
|
||||
if let Some(p) = procs.get(&pid) {
|
||||
let _ = p.terminate();
|
||||
|
|
@ -283,6 +387,9 @@ impl HostAdapter for LinuxHost {
|
|||
if let Some(ref snap) = info.snap_name {
|
||||
kill_snap_cgroup(snap, nix::sys::signal::Signal::SIGKILL);
|
||||
info!(snap = %snap, "Sent SIGKILL via snap cgroup (timeout)");
|
||||
} else if let Some(app_id) = info.steam_app_id {
|
||||
let _ = kill_steam_game_processes(app_id, nix::sys::signal::Signal::SIGKILL);
|
||||
info!(steam_app_id = app_id, "Sent SIGKILL to Steam game processes (timeout)");
|
||||
} else if let Some(ref app_id) = info.flatpak_app_id {
|
||||
kill_flatpak_cgroup(app_id, nix::sys::signal::Signal::SIGKILL);
|
||||
info!(flatpak = %app_id, "Sent SIGKILL via flatpak cgroup (timeout)");
|
||||
|
|
@ -291,17 +398,26 @@ impl HostAdapter for LinuxHost {
|
|||
info!(command = %info.command_name, "Sent SIGKILL via command name (timeout)");
|
||||
}
|
||||
}
|
||||
|
||||
// Also force kill via process handle
|
||||
let procs = self.processes.lock().unwrap();
|
||||
if let Some(p) = procs.get(&pid) {
|
||||
let _ = p.kill();
|
||||
|
||||
// Also force kill via process handle (skip for Steam sessions)
|
||||
if !is_steam {
|
||||
let procs = self.processes.lock().unwrap();
|
||||
if let Some(p) = procs.get(&pid) {
|
||||
let _ = p.kill();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if process is still running
|
||||
let still_running = self.processes.lock().unwrap().contains_key(&pid);
|
||||
let still_running = if is_steam {
|
||||
let app_id = session_info.as_ref().and_then(|info| info.steam_app_id);
|
||||
app_id
|
||||
.map(|id| !find_steam_game_pids(id).is_empty())
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
self.processes.lock().unwrap().contains_key(&pid)
|
||||
};
|
||||
|
||||
if !still_running {
|
||||
break;
|
||||
|
|
@ -316,6 +432,12 @@ impl HostAdapter for LinuxHost {
|
|||
if let Some(ref snap) = info.snap_name {
|
||||
kill_snap_cgroup(snap, nix::sys::signal::Signal::SIGKILL);
|
||||
info!(snap = %snap, "Sent SIGKILL via snap cgroup");
|
||||
} else if let Some(app_id) = info.steam_app_id {
|
||||
let _ = kill_steam_game_processes(app_id, nix::sys::signal::Signal::SIGKILL);
|
||||
if let Ok(mut map) = self.steam_sessions.lock() {
|
||||
map.entry(pid).and_modify(|entry| entry.seen_game = true);
|
||||
}
|
||||
info!(steam_app_id = app_id, "Sent SIGKILL to Steam game processes");
|
||||
} else if let Some(ref app_id) = info.flatpak_app_id {
|
||||
kill_flatpak_cgroup(app_id, nix::sys::signal::Signal::SIGKILL);
|
||||
info!(flatpak = %app_id, "Sent SIGKILL via flatpak cgroup");
|
||||
|
|
@ -324,11 +446,14 @@ impl HostAdapter for LinuxHost {
|
|||
info!(command = %info.command_name, "Sent SIGKILL via command name");
|
||||
}
|
||||
}
|
||||
|
||||
// Also force kill via process handle
|
||||
let procs = self.processes.lock().unwrap();
|
||||
if let Some(p) = procs.get(&pid) {
|
||||
let _ = p.kill();
|
||||
|
||||
// Also force kill via process handle (skip for Steam sessions)
|
||||
let is_steam = session_info.as_ref().and_then(|info| info.steam_app_id).is_some();
|
||||
if !is_steam {
|
||||
let procs = self.processes.lock().unwrap();
|
||||
if let Some(p) = procs.get(&pid) {
|
||||
let _ = p.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,6 +155,60 @@ pub fn kill_flatpak_cgroup(app_id: &str, _signal: Signal) -> bool {
|
|||
stopped_any
|
||||
}
|
||||
|
||||
/// Find Steam game process IDs by Steam App ID (from environment variables)
|
||||
pub fn find_steam_game_pids(app_id: u32) -> Vec<i32> {
|
||||
let mut pids = Vec::new();
|
||||
let target = app_id.to_string();
|
||||
let keys = ["SteamAppId", "SteamAppID", "STEAM_APP_ID"];
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir("/proc") {
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name_str = name.to_string_lossy();
|
||||
if let Ok(pid) = name_str.parse::<i32>() {
|
||||
let env_path = format!("/proc/{}/environ", pid);
|
||||
let Ok(env_bytes) = std::fs::read(&env_path) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for var in env_bytes.split(|b| *b == 0) {
|
||||
if var.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let Ok(var_str) = std::str::from_utf8(var) else {
|
||||
continue;
|
||||
};
|
||||
for key in &keys {
|
||||
let prefix = format!("{}=", key);
|
||||
if var_str
|
||||
.strip_prefix(&prefix)
|
||||
.is_some_and(|val| val == target)
|
||||
{
|
||||
pids.push(pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pids
|
||||
}
|
||||
|
||||
/// Kill Steam game processes by Steam App ID
|
||||
pub fn kill_steam_game_processes(app_id: u32, signal: Signal) -> bool {
|
||||
let pids = find_steam_game_pids(app_id);
|
||||
if pids.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for pid in pids {
|
||||
let _ = signal::kill(Pid::from_raw(pid), signal);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Kill processes by command name using pkill
|
||||
pub fn kill_by_command(command_name: &str, signal: Signal) -> bool {
|
||||
let signal_name = match signal {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ impl LauncherTile {
|
|||
let fallback_icon = match entry.kind_tag {
|
||||
shepherd_api::EntryKindTag::Process => "application-x-executable",
|
||||
shepherd_api::EntryKindTag::Snap => "application-x-executable",
|
||||
shepherd_api::EntryKindTag::Steam => "application-x-executable",
|
||||
shepherd_api::EntryKindTag::Flatpak => "application-x-executable",
|
||||
shepherd_api::EntryKindTag::Vm => "computer",
|
||||
shepherd_api::EntryKindTag::Media => "video-x-generic",
|
||||
|
|
|
|||
Loading…
Reference in a new issue