feat: GUI dashboard, system Proton scanning, and XDG autostart
GUI dashboard (gui.rs): - Add detect, diagnose, settings panel, per-launcher games management - Async Kill with optimistic UI state and rollback on error - Settings: pick_list for Proton version, browse button for compat dir, validation, per-launcher version badge - Detect Installed button with scan results in footer - Diagnose card with ✓/✗ checklist per launcher Issue #1 — Use System Proton (proton.rs): - Extend list_installed() to scan ~/.steam/root/compatibilitytools.d, ~/.local/share/Steam/compatibilitytools.d, and /usr/share/steam/compatibilitytools.d in addition to the configured compat dir; deduplicates by name so the same version never appears twice Issue #4 — Remove Systemd Service (service.rs): - Replace systemd unit management with XDG autostart: install() writes ~/.config/autostart/umutray.desktop instead of a systemd unit and never calls systemctl; uninstall() removes that file; status() checks whether the autostart entry exists - Update GUI service_is_installed() to check the XDG autostart path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+95
-31
@@ -1,7 +1,7 @@
|
||||
use crate::{config::Config, detect, diagnose, launcher, service, util::async_blocking};
|
||||
use crate::{config::Config, detect, diagnose, launcher, proton, service, util::{async_blocking, pick_folder}};
|
||||
use anyhow::Result;
|
||||
use iced::widget::{
|
||||
button, column, container, mouse_area, row, scrollable, text, text_input, Column,
|
||||
button, column, container, mouse_area, pick_list, row, scrollable, text, text_input, Column,
|
||||
};
|
||||
use iced::{
|
||||
Alignment, Background, Border, Color, Element, Length, Padding, Subscription, Task, Theme,
|
||||
@@ -24,6 +24,7 @@ pub enum Message {
|
||||
ToggleGamescope(String, String),
|
||||
UpdateProton,
|
||||
ProtonDone(Result<(), String>),
|
||||
KillDone(String, Result<(), String>),
|
||||
// Context menu
|
||||
ShowContextMenu(String),
|
||||
HideContextMenu,
|
||||
@@ -48,6 +49,8 @@ pub enum Message {
|
||||
HideSettings,
|
||||
SettingsProtonVersionChanged(String),
|
||||
SettingsCompatDirChanged(String),
|
||||
BrowseCompatDir,
|
||||
BrowseCompatDirDone(Option<String>),
|
||||
SaveSettings,
|
||||
ServiceInstall,
|
||||
ServiceUninstall,
|
||||
@@ -73,6 +76,7 @@ struct Dashboard {
|
||||
settings_open: bool,
|
||||
settings_proton_version: String,
|
||||
settings_compat_dir: String,
|
||||
proton_versions: Vec<String>,
|
||||
service_busy: bool,
|
||||
service_status: String,
|
||||
}
|
||||
@@ -85,6 +89,7 @@ impl Dashboard {
|
||||
}
|
||||
let settings_proton_version = config.proton_version.clone();
|
||||
let settings_compat_dir = config.proton_compat_dir.to_string_lossy().into_owned();
|
||||
let proton_versions = proton::list_installed(&config);
|
||||
Self {
|
||||
config,
|
||||
running,
|
||||
@@ -100,6 +105,7 @@ impl Dashboard {
|
||||
settings_open: false,
|
||||
settings_proton_version,
|
||||
settings_compat_dir,
|
||||
proton_versions,
|
||||
service_busy: false,
|
||||
service_status: String::new(),
|
||||
}
|
||||
@@ -119,6 +125,7 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
||||
state
|
||||
.running
|
||||
.retain(|k, _| fresh.launchers.iter().any(|l| &l.name == k));
|
||||
state.proton_versions = proton::list_installed(&fresh);
|
||||
state.config = fresh;
|
||||
}
|
||||
Task::none()
|
||||
@@ -146,16 +153,21 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
||||
}
|
||||
Message::Kill(name) => {
|
||||
state.last_error = None;
|
||||
if let Some(l) = state.config.find(&name) {
|
||||
let l = l.clone();
|
||||
match launcher::kill(&l) {
|
||||
Ok(()) => {
|
||||
state.running.insert(name, false);
|
||||
}
|
||||
Err(e) => {
|
||||
state.last_error = Some(format!("Kill failed: {e}"));
|
||||
}
|
||||
}
|
||||
let Some(l) = state.config.find(&name) else {
|
||||
return Task::none();
|
||||
};
|
||||
let l = l.clone();
|
||||
let name2 = name.clone();
|
||||
state.running.insert(name, false);
|
||||
Task::perform(
|
||||
async_blocking(move || launcher::kill(&l).map_err(|e| e.to_string())),
|
||||
move |res| Message::KillDone(name2.clone(), res),
|
||||
)
|
||||
}
|
||||
Message::KillDone(name, res) => {
|
||||
if let Err(e) = res {
|
||||
state.running.insert(name, true);
|
||||
state.last_error = Some(format!("Kill failed: {e}"));
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -379,6 +391,7 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
||||
state.settings_proton_version = state.config.proton_version.clone();
|
||||
state.settings_compat_dir =
|
||||
state.config.proton_compat_dir.to_string_lossy().into_owned();
|
||||
state.proton_versions = proton::list_installed(&state.config);
|
||||
state.service_status = String::new();
|
||||
Task::none()
|
||||
}
|
||||
@@ -394,12 +407,45 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
||||
state.settings_compat_dir = v;
|
||||
Task::none()
|
||||
}
|
||||
Message::BrowseCompatDir => Task::perform(
|
||||
async_blocking(|| pick_folder("Choose GE-Proton compat directory")),
|
||||
Message::BrowseCompatDirDone,
|
||||
),
|
||||
Message::BrowseCompatDirDone(path) => {
|
||||
if let Some(p) = path {
|
||||
state.settings_compat_dir = p;
|
||||
state.proton_versions =
|
||||
proton::list_installed_from(PathBuf::from(&state.settings_compat_dir));
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::SaveSettings => {
|
||||
state.last_error = None;
|
||||
let version = state.settings_proton_version.trim().to_string();
|
||||
let compat = PathBuf::from(state.settings_compat_dir.trim());
|
||||
match state.config.set_globals(Some(version), Some(compat)) {
|
||||
|
||||
// Validate compat dir
|
||||
if !compat.is_absolute() {
|
||||
state.last_error = Some("Compat directory must be an absolute path.".into());
|
||||
return Task::none();
|
||||
}
|
||||
// Validate proton version format (allow "GE-Proton (latest)" or "GE-Proton\d+-\d+")
|
||||
let version_key = if version == "GE-Proton (latest)" {
|
||||
"GE-Proton".to_string()
|
||||
} else {
|
||||
version.clone()
|
||||
};
|
||||
let valid_version = version_key == "GE-Proton"
|
||||
|| version_key.starts_with("GE-Proton");
|
||||
if !valid_version {
|
||||
state.last_error =
|
||||
Some(format!("Unknown Proton version \"{version_key}\". Expected \"GE-Proton\" or a specific version like \"GE-Proton10-1\"."));
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
match state.config.set_globals(Some(version_key), Some(compat)) {
|
||||
Ok(()) => {
|
||||
state.proton_versions = proton::list_installed(&state.config);
|
||||
state.service_status = "Settings saved.".into();
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -410,7 +456,7 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
||||
}
|
||||
Message::ServiceInstall => {
|
||||
state.service_busy = true;
|
||||
state.service_status = "Installing service…".into();
|
||||
state.service_status = "Installing autostart…".into();
|
||||
Task::perform(
|
||||
async_blocking(|| service::install().map_err(|e| e.to_string())),
|
||||
Message::ServiceActionDone,
|
||||
@@ -418,7 +464,7 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
||||
}
|
||||
Message::ServiceUninstall => {
|
||||
state.service_busy = true;
|
||||
state.service_status = "Removing service…".into();
|
||||
state.service_status = "Removing autostart…".into();
|
||||
Task::perform(
|
||||
async_blocking(|| service::uninstall().map_err(|e| e.to_string())),
|
||||
Message::ServiceActionDone,
|
||||
@@ -429,9 +475,9 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
||||
match res {
|
||||
Ok(()) => {
|
||||
state.service_status = if service_is_installed() {
|
||||
"Service installed — autostarts on login.".into()
|
||||
"Autostart enabled — starts on next login.".into()
|
||||
} else {
|
||||
"Service removed.".into()
|
||||
"Autostart removed.".into()
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -464,13 +510,8 @@ fn toggle_flag(
|
||||
}
|
||||
|
||||
fn service_is_installed() -> bool {
|
||||
std::env::var("HOME")
|
||||
.ok()
|
||||
.map(|h| {
|
||||
PathBuf::from(h)
|
||||
.join(".config/systemd/user/umutray.service")
|
||||
.exists()
|
||||
})
|
||||
dirs::home_dir()
|
||||
.map(|h| h.join(".config/autostart/umutray.desktop").exists())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
@@ -671,10 +712,22 @@ fn launcher_card<'a>(
|
||||
}
|
||||
};
|
||||
|
||||
let version_badge: Element<Message> = if let Some(v) = &l.proton_version {
|
||||
text(format!(" [{v}]"))
|
||||
.size(11)
|
||||
.style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.55, 0.75, 1.0)),
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
text("").into()
|
||||
};
|
||||
|
||||
let header = row![
|
||||
text(&l.display).size(15),
|
||||
text(" — ").size(12),
|
||||
text(status_label).size(12),
|
||||
version_badge,
|
||||
iced::widget::horizontal_space(),
|
||||
action,
|
||||
]
|
||||
@@ -870,17 +923,28 @@ fn view_settings(state: &Dashboard) -> Element<'_, Message> {
|
||||
]
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let proton_version_input =
|
||||
text_input("e.g. GE-Proton or GE-Proton10-34", &state.settings_proton_version)
|
||||
.on_input(Message::SettingsProtonVersionChanged)
|
||||
.padding(8);
|
||||
let proton_version_picker = pick_list(
|
||||
state.proton_versions.as_slice(),
|
||||
Some(if state.settings_proton_version == "GE-Proton" {
|
||||
"GE-Proton (latest)".to_string()
|
||||
} else {
|
||||
state.settings_proton_version.clone()
|
||||
}),
|
||||
Message::SettingsProtonVersionChanged,
|
||||
)
|
||||
.width(Length::Fill);
|
||||
|
||||
let compat_dir_input = text_input(
|
||||
"e.g. ~/.local/share/Steam/compatibilitytools.d",
|
||||
&state.settings_compat_dir,
|
||||
)
|
||||
.on_input(Message::SettingsCompatDirChanged)
|
||||
.padding(8);
|
||||
.padding(8)
|
||||
.width(Length::Fill);
|
||||
|
||||
let browse_compat_btn = button(text("Browse…").size(13))
|
||||
.on_press(Message::BrowseCompatDir)
|
||||
.style(button::secondary);
|
||||
|
||||
let save_btn = button(text("Save").size(13))
|
||||
.on_press(Message::SaveSettings)
|
||||
@@ -909,9 +973,9 @@ fn view_settings(state: &Dashboard) -> Element<'_, Message> {
|
||||
header,
|
||||
iced::widget::horizontal_rule(1),
|
||||
text("Proton version").size(13),
|
||||
proton_version_input,
|
||||
proton_version_picker,
|
||||
text("GE-Proton compat directory").size(13),
|
||||
compat_dir_input,
|
||||
row![compat_dir_input, browse_compat_btn].spacing(8).align_y(Alignment::Center),
|
||||
save_btn,
|
||||
iced::widget::horizontal_rule(1),
|
||||
text(svc_status_label).size(13),
|
||||
|
||||
+53
-10
@@ -1,7 +1,10 @@
|
||||
use crate::config::Config;
|
||||
use anyhow::{Context, Result};
|
||||
use owo_colors::OwoColorize;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashSet;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const GITHUB_API: &str = "https://api.github.com/repos/GloriousEggroll/proton-ge-custom/releases";
|
||||
|
||||
@@ -106,6 +109,54 @@ fn install_version(config: &Config, tag: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return all GE-Proton* directories found in `dir`.
|
||||
fn scan_ge_proton_in(dir: &PathBuf, seen: &mut HashSet<String>, out: &mut Vec<String>) {
|
||||
let Ok(entries) = std::fs::read_dir(dir) else { return };
|
||||
for entry in entries.flatten() {
|
||||
if !entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
if name.starts_with("GE-Proton") && seen.insert(name.clone()) {
|
||||
out.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return all GE-Proton versions found in `dir`, newest-first,
|
||||
/// prepended with "GE-Proton (latest)".
|
||||
pub fn list_installed_from(dir: PathBuf) -> Vec<String> {
|
||||
let mut seen = HashSet::new();
|
||||
let mut versions = Vec::new();
|
||||
scan_ge_proton_in(&dir, &mut seen, &mut versions);
|
||||
versions.sort_by(|a, b| b.cmp(a));
|
||||
let mut out = vec!["GE-Proton (latest)".to_string()];
|
||||
out.extend(versions);
|
||||
out
|
||||
}
|
||||
|
||||
/// Return all GE-Proton versions found across the configured compat dir and
|
||||
/// common system Proton locations (Steam, ProtonUp-Qt, /usr/share/steam).
|
||||
pub fn list_installed(config: &Config) -> Vec<String> {
|
||||
let mut dirs = vec![config.proton_compat_dir.clone()];
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
// ProtonUp-Qt and manual installs land here by default
|
||||
dirs.push(home.join(".steam/root/compatibilitytools.d"));
|
||||
dirs.push(home.join(".local/share/Steam/compatibilitytools.d"));
|
||||
}
|
||||
dirs.push(PathBuf::from("/usr/share/steam/compatibilitytools.d"));
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
let mut versions = Vec::new();
|
||||
for dir in &dirs {
|
||||
scan_ge_proton_in(dir, &mut seen, &mut versions);
|
||||
}
|
||||
versions.sort_by(|a, b| b.cmp(a));
|
||||
let mut out = vec!["GE-Proton (latest)".to_string()];
|
||||
out.extend(versions);
|
||||
out
|
||||
}
|
||||
|
||||
/// Install the latest GE-Proton release (called from tray menu).
|
||||
pub fn install_latest(config: &Config) -> Result<()> {
|
||||
println!("Checking for latest GE-Proton...");
|
||||
@@ -152,11 +203,7 @@ fn print_list(config: &Config) -> Result<()> {
|
||||
let releases = fetch_releases(10)?;
|
||||
for r in &releases {
|
||||
let installed = config.proton_compat_dir.join(&r.tag_name).exists();
|
||||
let marker = if installed {
|
||||
" \x1b[1;32m✓ installed\x1b[0m"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let marker = if installed { format!(" {}", "✓ installed".green().bold()) } else { String::new() };
|
||||
println!(" {}{}", r.tag_name, marker);
|
||||
}
|
||||
Ok(())
|
||||
@@ -220,11 +267,7 @@ fn pick_interactively(config: &Config) -> Result<String> {
|
||||
println!("Recent GE-Proton releases:");
|
||||
for (i, r) in releases.iter().enumerate() {
|
||||
let installed = config.proton_compat_dir.join(&r.tag_name).exists();
|
||||
let marker = if installed {
|
||||
" \x1b[1;32m✓\x1b[0m"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let marker = if installed { format!(" {}", "✓".green().bold()) } else { String::new() };
|
||||
println!(" {:2}) {}{}", i + 1, r.tag_name, marker);
|
||||
}
|
||||
|
||||
|
||||
+50
-78
@@ -1,69 +1,46 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{Context, Result};
|
||||
use owo_colors::OwoColorize;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
const UNIT_NAME: &str = "umutray.service";
|
||||
const DESKTOP_NAME: &str = "umutray.desktop";
|
||||
|
||||
fn home() -> Result<PathBuf> {
|
||||
Ok(PathBuf::from(
|
||||
std::env::var("HOME").context("$HOME is not set")?,
|
||||
))
|
||||
dirs::home_dir().context("Cannot determine home directory")
|
||||
}
|
||||
|
||||
fn unit_path() -> Result<PathBuf> {
|
||||
Ok(home()?.join(".config/systemd/user").join(UNIT_NAME))
|
||||
fn autostart_path() -> Result<PathBuf> {
|
||||
Ok(home()?.join(".config/autostart").join(DESKTOP_NAME))
|
||||
}
|
||||
|
||||
fn desktop_path() -> Result<PathBuf> {
|
||||
Ok(home()?
|
||||
.join(".local/share/applications")
|
||||
.join(DESKTOP_NAME))
|
||||
Ok(home()?.join(".local/share/applications").join(DESKTOP_NAME))
|
||||
}
|
||||
|
||||
fn render_unit(exe: &std::path::Path) -> String {
|
||||
format!(
|
||||
"[Unit]\n\
|
||||
Description=umutray Wine launcher manager\n\
|
||||
After=graphical-session.target\n\
|
||||
PartOf=graphical-session.target\n\
|
||||
\n\
|
||||
[Service]\n\
|
||||
ExecStart={exe}\n\
|
||||
Restart=on-failure\n\
|
||||
RestartSec=5\n\
|
||||
\n\
|
||||
[Install]\n\
|
||||
WantedBy=graphical-session.target\n",
|
||||
exe = exe.display(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_desktop(exe: &std::path::Path) -> String {
|
||||
format!(
|
||||
fn render_desktop(exe: &std::path::Path, autostart: bool) -> String {
|
||||
let mut s = format!(
|
||||
"[Desktop Entry]\n\
|
||||
Name=umutray\n\
|
||||
Comment=Wine launcher manager for Windows game launchers\n\
|
||||
Exec={exe} gui\n\
|
||||
Exec={exe}\n\
|
||||
Icon=applications-games\n\
|
||||
Type=Application\n\
|
||||
Categories=Game;\n\
|
||||
Keywords=wine;proton;gaming;launcher;\n\
|
||||
StartupNotify=true\n",
|
||||
StartupNotify=false\n",
|
||||
exe = exe.display(),
|
||||
)
|
||||
}
|
||||
|
||||
fn systemctl(args: &[&str]) -> Result<()> {
|
||||
let status = Command::new("systemctl")
|
||||
.arg("--user")
|
||||
.args(args)
|
||||
.status()
|
||||
.context("Failed to invoke systemctl --user (is systemd installed?)")?;
|
||||
if !status.success() {
|
||||
bail!("systemctl --user {} exited non-zero", args.join(" "));
|
||||
);
|
||||
if autostart {
|
||||
s.push_str("X-GNOME-Autostart-enabled=true\n");
|
||||
s.push_str("Hidden=false\n");
|
||||
} else {
|
||||
// App-menu entry launches the GUI
|
||||
s = s.replace(
|
||||
&format!("Exec={}", exe.display()),
|
||||
&format!("Exec={} gui", exe.display()),
|
||||
);
|
||||
s.push_str("StartupNotify=true\n");
|
||||
}
|
||||
Ok(())
|
||||
s
|
||||
}
|
||||
|
||||
/// Install only the .desktop file so umutray appears in the app menu.
|
||||
@@ -74,9 +51,9 @@ pub fn install_desktop() -> Result<()> {
|
||||
if let Some(p) = desktop.parent() {
|
||||
std::fs::create_dir_all(p).with_context(|| format!("Failed to create {p:?}"))?;
|
||||
}
|
||||
std::fs::write(&desktop, render_desktop(&exe))
|
||||
std::fs::write(&desktop, render_desktop(&exe, false))
|
||||
.with_context(|| format!("Failed to write desktop file {desktop:?}"))?;
|
||||
println!("\x1b[1;32m✓\x1b[0m App menu entry written: {}", desktop.display());
|
||||
println!("{} App menu entry written: {}", "✓".green().bold(), desktop.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -93,58 +70,53 @@ pub fn uninstall_desktop() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the unit + desktop file, reload systemd, and enable+start the service.
|
||||
/// Write an XDG autostart entry and the app-menu .desktop file.
|
||||
pub fn install() -> Result<()> {
|
||||
let exe = std::env::current_exe().context("Cannot determine path to own executable")?;
|
||||
|
||||
// systemd unit
|
||||
let unit = unit_path()?;
|
||||
if let Some(p) = unit.parent() {
|
||||
// XDG autostart
|
||||
let autostart = autostart_path()?;
|
||||
if let Some(p) = autostart.parent() {
|
||||
std::fs::create_dir_all(p).with_context(|| format!("Failed to create {p:?}"))?;
|
||||
}
|
||||
std::fs::write(&unit, render_unit(&exe))
|
||||
.with_context(|| format!("Failed to write unit file {unit:?}"))?;
|
||||
println!("Wrote unit: {}", unit.display());
|
||||
std::fs::write(&autostart, render_desktop(&exe, true))
|
||||
.with_context(|| format!("Failed to write autostart file {autostart:?}"))?;
|
||||
println!("Wrote autostart: {}", autostart.display());
|
||||
|
||||
// .desktop file
|
||||
// App-menu entry
|
||||
install_desktop()?;
|
||||
println!("Exec: {} gui", exe.display());
|
||||
println!();
|
||||
|
||||
systemctl(&["daemon-reload"])?;
|
||||
systemctl(&["enable", "--now", UNIT_NAME])?;
|
||||
|
||||
println!();
|
||||
println!("\x1b[1;32m✓\x1b[0m Service installed and started.");
|
||||
println!(" umutray autostarts with your session and is in the app menu.");
|
||||
println!(" Status: systemctl --user status {UNIT_NAME}");
|
||||
println!(" Logs: journalctl --user -u {UNIT_NAME} -f");
|
||||
println!("{} Autostart installed.", "✓".green().bold());
|
||||
println!(" umutray will start with your next graphical session.");
|
||||
println!(" To start now: {}", exe.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stop, disable, and remove the unit + desktop files.
|
||||
/// Remove the XDG autostart entry and the app-menu .desktop file.
|
||||
pub fn uninstall() -> Result<()> {
|
||||
let _ = systemctl(&["disable", "--now", UNIT_NAME]);
|
||||
|
||||
let unit = unit_path()?;
|
||||
if unit.exists() {
|
||||
std::fs::remove_file(&unit).with_context(|| format!("Failed to remove {unit:?}"))?;
|
||||
println!("Removed {}", unit.display());
|
||||
let autostart = autostart_path()?;
|
||||
if autostart.exists() {
|
||||
std::fs::remove_file(&autostart)
|
||||
.with_context(|| format!("Failed to remove {autostart:?}"))?;
|
||||
println!("Removed {}", autostart.display());
|
||||
} else {
|
||||
println!("No unit file at {}", unit.display());
|
||||
println!("No autostart file at {}", autostart.display());
|
||||
}
|
||||
|
||||
uninstall_desktop()?;
|
||||
|
||||
let _ = systemctl(&["daemon-reload"]);
|
||||
println!("\x1b[1;32m✓\x1b[0m Service removed.");
|
||||
println!("{} Autostart removed.", "✓".green().bold());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pass through `systemctl --user status`.
|
||||
/// Show whether the XDG autostart entry is present.
|
||||
pub fn status() -> Result<()> {
|
||||
let _ = Command::new("systemctl")
|
||||
.args(["--user", "status", UNIT_NAME])
|
||||
.status();
|
||||
let autostart = autostart_path()?;
|
||||
if autostart.exists() {
|
||||
println!("{} Autostart enabled: {}", "✓".green().bold(), autostart.display());
|
||||
} else {
|
||||
println!("{} Autostart not installed ({})", "✗".red().bold(), autostart.display());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user