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:
@ -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
171
util/src/desktop.rs
Normal 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(())
|
||||
}
|
@ -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";
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user