use std::fs;
use std::io;
use std::path::PathBuf;
pub fn path() -> Option<PathBuf> {
let dir = std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config")))?;
Some(dir.join("autostart").join("hyprcorrect.desktop"))
}
pub fn is_enabled() -> bool {
path().is_some_and(|p| p.exists())
}
pub fn enable(exec_path: &str) -> io::Result<()> {
let path = path().ok_or_else(|| {
io::Error::other(
"couldn't resolve $XDG_CONFIG_HOME / $HOME — autostart needs one of them set",
)
})?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let _ = ensure_user_icon()?;
let contents = format!(
"[Desktop Entry]\n\
Type=Application\n\
Name=hyprcorrect\n\
GenericName=Spelling corrector\n\
Comment=Keyboard-driven desktop spelling and typo correction\n\
Exec={exec_path}\n\
Icon=hyprcorrect\n\
Terminal=false\n\
Categories=Utility;TextTools;\n\
StartupNotify=false\n\
X-GNOME-Autostart-enabled=true\n"
);
fs::write(path, contents)
}
pub fn ensure_user_icon() -> io::Result<Option<PathBuf>> {
let Some(base) = data_home() else {
return Ok(None);
};
let dir = base.join("icons/hicolor/scalable/apps");
if let Err(e) = fs::create_dir_all(&dir) {
eprintln!(
"hyprcorrect: could not create {} ({e}) — launcher will fall back to icon-theme",
dir.display()
);
return Ok(None);
}
let path = dir.join("hyprcorrect.svg");
if let Err(e) = fs::write(&path, crate::icon::app_icon_svg_bytes()) {
eprintln!(
"hyprcorrect: could not write {} ({e}) — launcher will fall back to icon-theme",
path.display()
);
return Ok(None);
}
Ok(Some(path))
}
pub fn ensure_apps_catalog_entry(exec_path: &str) -> io::Result<Option<PathBuf>> {
let Some(base) = data_home() else {
return Ok(None);
};
let dir = base.join("applications");
fs::create_dir_all(&dir)?;
let _ = ensure_user_icon()?;
let path = dir.join("hyprcorrect.desktop");
let contents = format!(
"[Desktop Entry]\n\
Type=Application\n\
Name=hyprcorrect\n\
GenericName=Spelling corrector\n\
Comment=Keyboard-driven desktop spelling and typo correction\n\
Exec={exec_path} prefs\n\
Icon=hyprcorrect\n\
Terminal=false\n\
Categories=Utility;TextTools;\n\
Keywords=spell;spellcheck;autocorrect;typo;correction;keyboard;\n\
StartupNotify=false\n"
);
fs::write(&path, contents)?;
Ok(Some(path))
}
fn data_home() -> Option<PathBuf> {
std::env::var_os("XDG_DATA_HOME")
.map(PathBuf::from)
.or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".local/share")))
}
fn user_apps_entry() -> Option<PathBuf> {
Some(
data_home()?
.join("applications")
.join("hyprcorrect.desktop"),
)
}
fn first_launch_marker() -> Option<PathBuf> {
let base = std::env::var_os("XDG_STATE_HOME")
.map(PathBuf::from)
.or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".local/state")))?;
Some(base.join("hyprcorrect").join("desktop-install-done"))
}
fn packaged_entry_exists() -> bool {
let dirs = std::env::var("XDG_DATA_DIRS")
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "/usr/local/share:/usr/share".to_string());
std::env::split_paths(&dirs).any(|d| d.join("applications/hyprcorrect.desktop").is_file())
}
pub fn ensure_first_launch(exec_path: &str) {
let _ = try_ensure_first_launch(exec_path);
}
fn try_ensure_first_launch(exec_path: &str) -> io::Result<()> {
if std::env::var_os("FLATPAK_ID").is_some() {
return Ok(());
}
let Some(marker) = first_launch_marker() else {
return Ok(());
};
if marker.exists() {
return Ok(());
}
let already = user_apps_entry().is_some_and(|p| p.exists()) || packaged_entry_exists();
if !already {
ensure_user_icon()?;
ensure_apps_catalog_entry(exec_path)?;
}
if let Some(parent) = marker.parent() {
fs::create_dir_all(parent)?;
}
fs::write(
&marker,
"hyprcorrect ran its one-time first-launch desktop integration.\n",
)
}
pub fn mark_install_done() {
let Some(marker) = first_launch_marker() else {
return;
};
if let Some(parent) = marker.parent() {
let _ = fs::create_dir_all(parent);
}
let _ = fs::write(
&marker,
"hyprcorrect desktop integration installed via install-desktop.\n",
);
}
pub fn disable() -> io::Result<()> {
let Some(path) = path() else {
return Ok(());
};
match fs::remove_file(path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(e),
}
}