Added a submenu to list system-declared sessions (#1).

This commit is contained in:
Antoine POPINEAU 2020-07-03 11:45:50 +02:00
parent f2ec800eed
commit 8322386f50
No known key found for this signature in database
GPG Key ID: A78AC64694F84063
9 changed files with 263 additions and 19 deletions

110
Cargo.lock generated
View File

@ -1,5 +1,11 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ahash"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
[[package]]
name = "autocfg"
version = "1.0.0"
@ -41,6 +47,15 @@ dependencies = [
"time",
]
[[package]]
name = "dlv-list"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b391911b9a786312a10cb9d2b3d0735adfd5a8113eb3648de26a75e91b0826c"
dependencies = [
"rand",
]
[[package]]
name = "either"
version = "1.5.3"
@ -56,6 +71,17 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "greetd_ipc"
version = "0.6.0"
@ -67,6 +93,16 @@ dependencies = [
"thiserror",
]
[[package]]
name = "hashbrown"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96282e96bfcd3da0d3aa9938bedf1e50df3269b6db08b4876d2da0bb1a0841cf"
dependencies = [
"ahash",
"autocfg",
]
[[package]]
name = "itertools"
version = "0.9.0"
@ -126,6 +162,22 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "ordered-multimap"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88f947c6799d5eff50e6cf8a2365c17ac4aa8f8f43aceeedc29b616d872a358"
dependencies = [
"dlv-list",
"hashbrown",
]
[[package]]
name = "ppv-lite86"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
[[package]]
name = "proc-macro2"
version = "1.0.18"
@ -144,6 +196,47 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
@ -159,6 +252,16 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "rust-ini"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a3679dd538c876a7b606f3bb951c8a20fc281a0ff7795f59f7cb490e3f979e1"
dependencies = [
"cfg-if",
"ordered-multimap",
]
[[package]]
name = "ryu"
version = "1.0.5"
@ -281,6 +384,7 @@ dependencies = [
"getopts",
"greetd_ipc",
"nix",
"rust-ini",
"termion",
"textwrap",
"tui",
@ -311,6 +415,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "winapi"
version = "0.3.9"

View File

@ -14,6 +14,7 @@ nix = "0.17.0"
textwrap = "0.12.0"
chrono = "0.4.11"
zeroize = "1.1.0"
rust-ini = "0.15.3"
[profile.release]
lto = true

View File

@ -28,7 +28,7 @@ The default configuration tends to be as minimal as possible, visually speaking,
The initial prompt container will be 80 column wide. You may change this with `--width` in case you need more space (for example, to account for large PAM challenge messages). Please refer to usage information (`--help`) for more customizaton options.
You may change the command that will be executed after opening a session by huttint `F2` and amending the command.
You may change the command that will be executed after opening a session by hittint `F2` and amending the command. Alternatively, you can list the system-declared sessions by hitting `F3`.
## Install

View File

@ -32,6 +32,7 @@ pub enum Mode {
Username,
Password,
Command,
Sessions,
}
impl Default for Mode {
@ -44,18 +45,25 @@ impl Default for Mode {
pub struct Greeter {
pub config: Option<Matches>,
pub stream: Option<UnixStream>,
pub command: Option<String>,
pub previous_mode: Mode,
pub mode: Mode,
pub request: Option<Request>,
pub mode: Mode,
pub previous_mode: Mode,
pub cursor_offset: i16,
pub username: String,
pub answer: String,
pub command: Option<String>,
pub new_command: String,
pub secret: bool,
pub sessions: Vec<(String, String)>,
pub selected_session: usize,
pub username: String,
pub prompt: String,
pub answer: String,
pub secret: bool,
pub greeting: Option<String>,
pub message: Option<String>,
pub working: bool,
pub done: bool,
}
@ -72,9 +80,13 @@ impl Drop for Greeter {
impl Greeter {
pub fn new() -> Self {
let mut greeter = Self::default();
greeter.parse_options();
greeter.sessions = crate::info::get_sessions().unwrap_or_default();
greeter.selected_session = greeter.sessions.iter().position(|(_, command)| Some(command) == greeter.command.as_ref()).unwrap_or(0);
greeter
}
pub fn config(&self) -> &Matches {
self.config.as_ref().unwrap()
}

View File

@ -1,7 +1,11 @@
use std::{env, fs};
use std::{env, error::Error, fs, path::Path};
use ini::Ini;
use nix::sys::utsname;
const X_SESSIONS: &str = "/usr/share/xsessions";
const WAYLAND_SESSIONS: &str = "/usr/share/wayland-sessions";
pub fn get_hostname() -> String {
utsname::uname().nodename().to_string()
}
@ -26,3 +30,28 @@ pub fn get_issue() -> Option<String> {
None
}
pub fn get_sessions() -> Result<Vec<(String, String)>, Box<dyn Error>> {
let directories = vec![X_SESSIONS, WAYLAND_SESSIONS];
let files = directories
.iter()
.flat_map(|directory| fs::read_dir(&directory))
.flat_map(|directory| directory.flat_map(|entry| entry.map(|entry| load_desktop_file(entry.path()))).flatten())
.collect::<Vec<_>>();
Ok(files)
}
fn load_desktop_file<P>(path: P) -> Result<(String, String), Box<dyn Error>>
where
P: AsRef<Path>,
{
let desktop = Ini::load_from_file(path)?;
let section = desktop.section(Some("Desktop Entry")).ok_or("no Desktop Entry section in desktop file")?;
let name = section.get("Name").ok_or("no Name property in desktop file")?;
let exec = section.get("Exec").ok_or("no Exec property in desktop file")?;
Ok((name.to_string(), exec.to_string()))
}

View File

@ -11,13 +11,10 @@ use crate::{
pub fn handle(greeter: &mut Greeter, events: &Events) -> Result<(), Box<dyn Error>> {
if let Event::Input(input) = events.next()? {
match input {
Key::Esc => {
if let Mode::Command = greeter.mode {
greeter.mode = greeter.previous_mode;
} else {
crate::exit(greeter, AuthStatus::Cancel)?;
}
}
Key::Esc => match greeter.mode {
Mode::Command | Mode::Sessions => greeter.mode = greeter.previous_mode,
_ => crate::exit(greeter, AuthStatus::Cancel)?,
},
Key::Left => greeter.cursor_offset -= 1,
Key::Right => greeter.cursor_offset += 1,
@ -28,6 +25,27 @@ pub fn handle(greeter: &mut Greeter, events: &Events) -> Result<(), Box<dyn Erro
greeter.mode = Mode::Command;
}
Key::F(3) => {
greeter.previous_mode = greeter.mode;
greeter.mode = Mode::Sessions;
}
Key::Up => {
if let Mode::Sessions = greeter.mode {
if greeter.selected_session > 0 {
greeter.selected_session -= 1;
}
}
}
Key::Down => {
if let Mode::Sessions = greeter.mode {
if greeter.selected_session < greeter.sessions.len() - 1 {
greeter.selected_session += 1;
}
}
}
Key::Ctrl('a') => greeter.cursor_offset = -(greeter.username.len() as i16),
Key::Ctrl('e') => greeter.cursor_offset = 0,
@ -52,6 +70,15 @@ pub fn handle(greeter: &mut Greeter, events: &Events) -> Result<(), Box<dyn Erro
Mode::Command => {
greeter.command = Some(greeter.new_command.clone());
greeter.selected_session = greeter.sessions.iter().position(|(_, command)| Some(command) == greeter.command.as_ref()).unwrap_or(0);
greeter.mode = greeter.previous_mode;
}
Mode::Sessions => {
if let Some((_, command)) = greeter.sessions.get(greeter.selected_session) {
greeter.command = Some(command.clone());
}
greeter.mode = greeter.previous_mode;
}
},
@ -72,6 +99,7 @@ fn insert_key(greeter: &mut Greeter, c: char) {
Mode::Username => &mut greeter.username,
Mode::Password => &mut greeter.answer,
Mode::Command => &mut greeter.new_command,
Mode::Sessions => return,
};
let index = value.len() as i16 + greeter.cursor_offset;
@ -84,6 +112,7 @@ fn delete_key(greeter: &mut Greeter, key: Key) {
Mode::Username => &mut greeter.username,
Mode::Password => &mut greeter.answer,
Mode::Command => &mut greeter.new_command,
Mode::Sessions => return,
};
let index = match key {

View File

@ -1,4 +1,5 @@
mod prompt;
mod sessions;
use std::{
error::Error,
@ -15,9 +16,10 @@ use tui::{
Terminal,
};
use crate::Greeter;
use crate::{Greeter, Mode};
const EXIT: &str = "Exit";
const SESSIONS: &str = "Choose session";
const CHANGE_COMMAND: &str = "Change command";
const COMMAND: &str = "COMMAND";
@ -56,6 +58,8 @@ pub fn draw(terminal: &mut Terminal<TermionBackend<RawTerminal<io::Stdout>>>, gr
status_value(EXIT),
status_label("F2"),
status_value(CHANGE_COMMAND),
status_label("F3"),
status_value(SESSIONS),
status_label(COMMAND),
status_value(command),
];
@ -63,7 +67,10 @@ pub fn draw(terminal: &mut Terminal<TermionBackend<RawTerminal<io::Stdout>>>, gr
f.render_widget(status, chunks[2]);
cursor = self::prompt::draw(greeter, &mut f).ok();
cursor = match greeter.mode {
Mode::Sessions => self::sessions::draw(greeter, &mut f).ok(),
_ => self::prompt::draw(greeter, &mut f).ok(),
}
})?;
if let Some(cursor) = cursor {

View File

@ -115,10 +115,12 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame<TermionBackend<RawTerminal<io::
match greeter.mode {
Mode::Username => f.render_widget(message, chunks[ANSWER_INDEX]),
Mode::Password => f.render_widget(message, chunks[MESSAGE_INDEX]),
Mode::Command => {}
Mode::Command | Mode::Sessions => {}
}
}
}
Mode::Sessions => {}
}
match greeter.mode {
@ -143,6 +145,8 @@ pub fn draw(greeter: &mut Greeter, f: &mut Frame<TermionBackend<RawTerminal<io::
Ok((2 + cursor.x + COMMAND.len() as u16 + offset as u16, USERNAME_INDEX as u16 + cursor.y))
}
Mode::Sessions => Ok((1, 1)),
}
}
@ -154,7 +158,8 @@ fn get_height(greeter: &Greeter) -> u16 {
let initial = match greeter.mode {
Mode::Username | Mode::Command => (2 * container_padding) + 1,
Mode::Password => (2 * container_padding) + 2 + (2 * prompt_padding),
Mode::Password => (2 * container_padding) + prompt_padding + 2,
Mode::Sessions => 0,
};
match greeter.mode {

51
src/ui/sessions.rs Normal file
View File

@ -0,0 +1,51 @@
use std::{error::Error, io};
use termion::raw::RawTerminal;
use tui::{
backend::TermionBackend,
layout::Rect,
style::{Modifier, Style},
widgets::{Block, BorderType, Borders, Paragraph, Text},
Frame,
};
use crate::Greeter;
const CHANGE_SESSION: &str = "Change session";
pub fn draw(greeter: &mut Greeter, f: &mut Frame<TermionBackend<RawTerminal<io::Stdout>>>) -> Result<(u16, u16), Box<dyn Error>> {
let size = f.size();
let width = greeter.width();
let height: u16 = greeter.sessions.len() as u16 + 4;
let x = (size.width - width) / 2;
let y = (size.height - height) / 2;
let container = Rect::new(x, y, width, height);
let title = format!(" {} ", CHANGE_SESSION);
let block = Block::default().title(&title).borders(Borders::ALL).border_type(BorderType::Plain);
for (index, (name, _)) in greeter.sessions.iter().enumerate() {
let frame = Rect::new(x + 2, y + 2 + index as u16, width, 1);
let option_text = [get_option(&greeter, name, index)];
let option = Paragraph::new(option_text.iter());
f.render_widget(option, frame);
}
f.render_widget(block, container);
Ok((1, 1))
}
fn get_option<'g, S>(greeter: &Greeter, name: S, index: usize) -> Text<'g>
where
S: Into<String>,
{
if greeter.selected_session == index {
Text::styled(name.into(), Style::default().modifier(Modifier::REVERSED))
} else {
Text::raw(name.into())
}
}