Make the new login button stuff more automatic

By default it'll look at your environment to try to figure out which
display manager is used in order to start a new session.  We first try
the org.freedesktop.DisplayManager dbus interface, and if that fails,
inspect XDG_SESSION_DESKTOP to try to figure out which display manager
is running.

The user can also still specify the correct display manager, or a custom
command.
This commit is contained in:
2022-05-15 20:50:45 -07:00
parent 99ffa88657
commit dda1a53856
8 changed files with 287 additions and 36 deletions

View File

@ -12,6 +12,8 @@ env_logger = "0.9"
humantime = "2"
lazy_static = "1"
libc = "0.2"
shell-words = "1"
toml = "0.5"
xcb = { git = "https://github.com/rust-x-bindings/rust-xcb", rev = "d09b5f91bc07d56673f1bc0d6c7ecd72b5ff7b3e", features = ["randr", "screensaver", "xfixes"] }
xdg = "2"
zbus = "2"

171
util/src/desktop.rs Normal file
View File

@ -0,0 +1,171 @@
use std::{env, fmt, process::Command};
const GDM_CMD: &[&str] = &[ "gdmflexiserver", "-ls" ];
const KDM_CMD: &[&str] = &[ "kdmctl", "reserve" ];
const LIGHTDM_CMD: &[&str] = &[ "dm-tool", "switch-to-greeter" ];
const LXDM_CMD: &[&str] = &[ "lxdm", "-c", "USER_SWITCH" ];
#[derive(Debug, Clone, PartialEq)]
pub enum NewLoginCommand {
Auto,
DisplayManagerDBus,
Gdm,
Kdm,
LightDm,
Lxdm,
Custom(String),
Disabled,
}
impl NewLoginCommand {
pub fn run(&self) -> anyhow::Result<()> {
match self {
Self::Auto => auto_run(),
Self::DisplayManagerDBus => call_dbus(),
Self::Gdm => run(GDM_CMD),
Self::Kdm => run(KDM_CMD),
Self::LightDm => run(LIGHTDM_CMD),
Self::Lxdm => run(LXDM_CMD),
Self::Custom(cmd) => shell_words::split(cmd.as_str())
.map_err(|err| err.into())
.and_then(|argvec| {
let argv: Vec<&str> = argvec.iter().map(|s| s.as_str()).collect();
run(&argv).map_err(|err| err.into())
}),
Self::Disabled => Ok(()),
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Auto => "Auto",
Self::DisplayManagerDBus => "DBus (desktop-agnostic)",
Self::Gdm => "GDM (GNOME)",
Self::Kdm => "KDM (KDE)",
Self::LightDm => "LightDM",
Self::Lxdm => "LXDM (LXDE)",
Self::Custom(_) => "Custom",
Self::Disabled => "Disabled",
}
}
pub fn ord(&self) -> u32 {
match self {
Self::Auto => 0,
Self::DisplayManagerDBus => 1,
Self::Gdm => 2,
Self::Kdm => 3,
Self::LightDm => 4,
Self::Lxdm => 5,
Self::Custom(_) => 6,
Self::Disabled => 7,
}
}
}
impl fmt::Display for NewLoginCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl TryFrom<u32> for NewLoginCommand {
type Error = anyhow::Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Auto),
1 => Ok(Self::DisplayManagerDBus),
2 => Ok(Self::Gdm),
3 => Ok(Self::Kdm),
4 => Ok(Self::LightDm),
5 => Ok(Self::Lxdm),
6 => Ok(Self::Custom("".to_string())),
7 => Ok(Self::Disabled),
other => Err(anyhow::anyhow!("Unknown ordinal for NewLoginCommand: {}", other)),
}
}
}
impl TryFrom<&toml::Value> for NewLoginCommand {
type Error = anyhow::Error;
fn try_from(value: &toml::Value) -> Result<Self, Self::Error> {
if let Some(s) = value.as_str() {
return match s {
"auto" => Ok(Self::Auto),
"dbus" => Ok(Self::DisplayManagerDBus),
"gdm" => Ok(Self::Gdm),
"kdm" => Ok(Self::Kdm),
"lightdm" => Ok(Self::LightDm),
"lxdm" => Ok(Self::Lxdm),
"disabled" => Ok(Self::Disabled),
other => Err(anyhow::anyhow!("Unknown value '{}' for new login command", other)),
};
}
if let Some(arr) = value.as_array() {
let argv = arr.into_iter()
.map(|v| v.as_str().map(|s| s.to_string()).ok_or_else(|| anyhow::anyhow!("Custom command must be made of strings")))
.collect::<anyhow::Result<Vec<String>>>()?;
return Ok(Self::Custom(shell_words::join(argv)))
}
Err(anyhow::anyhow!("Unknown value for new login command"))
}
}
impl TryInto<toml::Value> for NewLoginCommand {
type Error = anyhow::Error;
fn try_into(self) -> Result<toml::Value, Self::Error> {
match self {
Self::Auto => Ok(toml::Value::String("auto".to_string())),
Self::DisplayManagerDBus => Ok(toml::Value::String("dbus".to_string())),
Self::Gdm => Ok(toml::Value::String("gdm".to_string())),
Self::Kdm => Ok(toml::Value::String("kdm".to_string())),
Self::LightDm => Ok(toml::Value::String("lightdm".to_string())),
Self::Lxdm => Ok(toml::Value::String("lxdm".to_string())),
Self::Custom(cmd) => shell_words::split(cmd.as_str())
.map_err(|err| err.into())
.map(|words| toml::Value::Array(words.into_iter().map(|s| toml::Value::String(s)).collect())),
Self::Disabled => Ok(toml::Value::String("disabled".to_string())),
}
}
}
fn auto_run() -> anyhow::Result<()> {
if let Ok(_) = call_dbus() {
return Ok(())
}
if let Some(session_desktop) = env::var("XDG_SESSION_DESKTOP").map(|s| s.to_lowercase()).ok() {
match session_desktop.as_str() {
"gnome" | "gdm" => run(GDM_CMD),
"kde" | "kdm" => run(KDM_CMD),
"lightdm-session" => run(LIGHTDM_CMD),
"lxde" | "lxdm" => run(LXDM_CMD),
other => Err(anyhow::anyhow!("Couldn't determine how to start a new login session for session type {}", other)),
}
} else {
Err(anyhow::anyhow!("Couldn't determine how to start a new login session"))
}
}
fn call_dbus() -> anyhow::Result<()> {
let seat_path = env::var("XDG_SEAT_PATH")
.unwrap_or_else(|_| "/org/freedesktop/DisplayManager/Seat0".to_string());
let bus = zbus::blocking::Connection::system()?;
let proxy = zbus::blocking::Proxy::new(
&bus,
"org.freedesktop.DisplayManager",
seat_path,
"org.freedesktop.DisplayManager.Seat"
)?;
proxy.call("SwitchToGreeter", &())?;
Ok(())
}
fn run(argv: &[&str]) -> anyhow::Result<()> {
Command::new(argv[0])
.args(if argv.len() > 1 { &argv[1..argv.len()-1] } else { &[] })
.spawn()?;
Ok(())
}

View File

@ -1,6 +1,7 @@
use std::{ffi::CStr, io};
use xcb::x;
pub mod desktop;
pub mod settings;
pub const BSCREENSAVER_WM_CLASS: &[u8] = b"bscreensaver\0Bscreensaver\0";

View File

@ -2,6 +2,8 @@ use anyhow::anyhow;
use std::{fmt, fs::{self, File}, io::{Read, Write}, path::PathBuf, time::Duration};
use toml::Value;
use crate::desktop::NewLoginCommand;
const CONFIG_FILE_RELATIVE_PATH: &str = "bscreensaver/bscreensaver.toml";
const LOCK_TIMEOUT: &str = "lock-timeout";
@ -51,7 +53,7 @@ pub struct Configuration {
pub lock_timeout: Duration,
pub blank_before_locking: Duration,
pub dialog_backend: DialogBackend,
pub new_login_command: Option<String>,
pub new_login_command: NewLoginCommand,
}
impl Configuration {
@ -80,7 +82,7 @@ impl Configuration {
};
config.new_login_command = match config_toml.get("new-login-command") {
None => config.new_login_command,
Some(val) => val.as_str().map(|s| s.to_string()),
Some(val) => val.try_into().unwrap_or(NewLoginCommand::Auto),
};
}
@ -99,9 +101,7 @@ impl Configuration {
config_map.insert(LOCK_TIMEOUT.to_string(), Value::String(format_duration(self.lock_timeout).to_string()));
config_map.insert(BLANK_BEFORE_LOCKING.to_string(), Value::String(format_duration(self.blank_before_locking).to_string()));
config_map.insert(DIALOG_BACKEND.to_string(), Value::String(self.dialog_backend.to_string()));
if let Some(new_login_command) = &self.new_login_command {
config_map.insert(NEW_LOGIN_COMMAND.to_string(), Value::String(new_login_command.clone()));
}
config_map.insert(NEW_LOGIN_COMMAND.to_string(), self.new_login_command.clone().try_into()?);
let config_path = xdg::BaseDirectories::new()?.place_config_file(CONFIG_FILE_RELATIVE_PATH)?;
let mut tmp_filename = config_path.file_name().unwrap().to_os_string();
@ -122,7 +122,7 @@ impl Default for Configuration {
lock_timeout: Duration::from_secs(60 * 10),
blank_before_locking: Duration::ZERO,
dialog_backend: DialogBackend::Gtk3,
new_login_command: None,
new_login_command: NewLoginCommand::Auto,
}
}
}