use std::fs;
use std::process::Command;
use crate::config::CONFIG_DIR;
const GNOME_SCHEMA: &str = "org.gnome.desktop.peripherals.mouse";
pub const GNOME_BACKUP: &str = "/etc/wayland-mouse/gnome-accel.backup";
#[derive(Debug, Clone, PartialEq)]
pub enum Desktop {
Gnome,
Kde,
Sway,
Hyprland,
Other(String),
Unknown,
}
pub fn detect() -> Desktop {
let xdg = std::env::var("XDG_CURRENT_DESKTOP")
.unwrap_or_default()
.to_lowercase();
if xdg.contains("gnome") {
Desktop::Gnome
} else if xdg.contains("kde") || xdg.contains("plasma") {
Desktop::Kde
} else if xdg.contains("sway") {
Desktop::Sway
} else if xdg.contains("hyprland") {
Desktop::Hyprland
} else if xdg.is_empty() {
Desktop::Unknown
} else {
Desktop::Other(xdg)
}
}
fn sudo_user() -> Option<String> {
std::env::var("SUDO_USER").ok().filter(|s| !s.is_empty())
}
fn user_uid(user: &str) -> Option<String> {
let out = Command::new("id").arg("-u").arg(user).output().ok()?;
if out.status.success() {
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
} else {
None
}
}
fn run_user_gsettings(user: &str, args: &[&str]) -> Result<String, String> {
let uid = user_uid(user).ok_or_else(|| format!("could not resolve uid for {user}"))?;
let bus = format!("DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{uid}/bus");
let out = Command::new("sudo")
.arg("-u")
.arg(user)
.arg(bus)
.arg("gsettings")
.args(args)
.output()
.map_err(|e| format!("spawning gsettings: {e}"))?;
if out.status.success() {
Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
} else {
Err(String::from_utf8_lossy(&out.stderr).trim().to_string())
}
}
pub fn disable_native_accel() -> bool {
match detect() {
Desktop::Gnome => gnome_disable(),
other => {
print_manual(&other);
false
}
}
}
fn gnome_disable() -> bool {
let Some(user) = sudo_user() else {
eprintln!(" note: run as your user to disable GNOME accel:");
eprintln!(" gsettings set {GNOME_SCHEMA} accel-profile flat && gsettings set {GNOME_SCHEMA} speed 0.0");
return false;
};
if !std::path::Path::new(GNOME_BACKUP).exists() {
let profile =
run_user_gsettings(&user, &["get", GNOME_SCHEMA, "accel-profile"]).unwrap_or_default();
let speed = run_user_gsettings(&user, &["get", GNOME_SCHEMA, "speed"]).unwrap_or_default();
let _ = fs::create_dir_all(CONFIG_DIR);
let _ = fs::write(GNOME_BACKUP, format!("profile={profile}\nspeed={speed}\n"));
}
let ok = run_user_gsettings(&user, &["set", GNOME_SCHEMA, "accel-profile", "flat"]).is_ok()
&& run_user_gsettings(&user, &["set", GNOME_SCHEMA, "speed", "0.0"]).is_ok();
if ok {
eprintln!(" GNOME pointer accel set to flat (so only our curve applies)");
} else {
eprintln!(" note: couldn't set gsettings automatically. Run as your user:");
eprintln!(" gsettings set {GNOME_SCHEMA} accel-profile flat && gsettings set {GNOME_SCHEMA} speed 0.0");
}
ok
}
pub fn restore_native_accel() {
if detect() != Desktop::Gnome {
return;
}
let Some(user) = sudo_user() else { return };
let backup = GNOME_BACKUP;
if !std::path::Path::new(backup).exists() {
return;
}
let Ok(text) = fs::read_to_string(backup) else {
return;
};
let mut profile = String::from("'default'");
let mut speed = String::new();
for line in text.lines() {
if let Some(v) = line.strip_prefix("profile=") {
if !v.is_empty() {
profile = v.to_string();
}
} else if let Some(v) = line.strip_prefix("speed=") {
speed = v.to_string();
}
}
let _ = run_user_gsettings(&user, &["set", GNOME_SCHEMA, "accel-profile", &profile]);
if !speed.is_empty() {
let _ = run_user_gsettings(&user, &["set", GNOME_SCHEMA, "speed", &speed]);
}
let _ = fs::remove_file(backup);
eprintln!(" restored GNOME mouse acceleration to its previous values");
}
fn print_manual(de: &Desktop) {
eprintln!(" for best results, disable your compositor's own pointer accel:");
match de {
Desktop::Kde => {
eprintln!(" KDE: System Settings → Mouse → set Acceleration Profile to 'Flat'");
eprintln!(" (or per-device libinput settings in kcminputrc)");
}
Desktop::Sway => {
eprintln!(" sway: in your config — input \"type:pointer\" {{ accel_profile flat; pointer_accel 0 }}");
}
Desktop::Hyprland => {
eprintln!(
" Hyprland: in your config — input {{ accel_profile = flat; sensitivity = 0 }}"
);
}
_ => {
eprintln!(
" set your compositor's pointer acceleration profile to 'flat' / sensitivity 0."
);
eprintln!(" (not needed if you only use wheel accel: set pointer.enabled = false)");
}
}
}