Add greeter keyboard and user selection (#55)
* Add greeter keyboard and user selection Fixes #37 Fixes #38 * Support switching users and add tooltips to icon buttons * Implement switching users * Implement keyboard layout switching * Ensure that user's xkb_config is used
This commit is contained in:
parent
4653bb1de9
commit
f6ccf0146e
10 changed files with 481 additions and 148 deletions
77
Cargo.lock
generated
77
Cargo.lock
generated
|
|
@ -915,6 +915,16 @@ dependencies = [
|
|||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-comp-config"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/cosmic-comp#cbca13803cb499786b080ea9e4ee74cd4f704556"
|
||||
dependencies = [
|
||||
"cosmic-config",
|
||||
"input",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-config"
|
||||
version = "0.1.0"
|
||||
|
|
@ -964,6 +974,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"chrono",
|
||||
"cosmic-bg-config",
|
||||
"cosmic-comp-config",
|
||||
"cosmic-config",
|
||||
"cosmic-dbus-networkmanager",
|
||||
"cosmic-greeter-daemon",
|
||||
|
|
@ -985,6 +996,7 @@ dependencies = [
|
|||
"upower_dbus",
|
||||
"wayland-client",
|
||||
"xdg",
|
||||
"xkb-data",
|
||||
"zbus 4.2.2",
|
||||
]
|
||||
|
||||
|
|
@ -993,6 +1005,7 @@ name = "cosmic-greeter-daemon"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmic-bg-config",
|
||||
"cosmic-comp-config",
|
||||
"cosmic-config",
|
||||
"cosmic-theme",
|
||||
"env_logger",
|
||||
|
|
@ -2583,6 +2596,26 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "input"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7911ce3db9c10c5ab4a35c49af778a5f9a827bd0f7371d9be56175d8dd2740d0"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"input-sys",
|
||||
"io-lifetimes 1.0.11",
|
||||
"libc",
|
||||
"log",
|
||||
"udev",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "input-sys"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd4f5b4d1c00331c5245163aacfe5f20be75b564c7112d45893d4ae038119eb0"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
|
|
@ -2834,6 +2867,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
|
|
@ -4100,6 +4143,18 @@ dependencies = [
|
|||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-xml-rs"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.203"
|
||||
|
|
@ -4738,6 +4793,18 @@ version = "1.17.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "udev"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50051c6e22be28ee6f217d50014f3bc29e81c20dc66ff7ca0d5c5226e1dcc5a1"
|
||||
dependencies = [
|
||||
"io-lifetimes 1.0.11",
|
||||
"libc",
|
||||
"libudev-sys",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.1.0"
|
||||
|
|
@ -5551,6 +5618,16 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkb-data"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "294a599fc9e6a43c9f44f5d6c560b89fd751be413717442b31c17fa367d3c764"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde-xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon"
|
||||
version = "0.7.0"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
chrono = "0.4.31"
|
||||
cosmic-bg-config.workspace = true
|
||||
cosmic-comp-config.workspace = true
|
||||
cosmic-config = { workspace = true, features = ["calloop", "macro"] }
|
||||
cosmic-greeter-daemon = { path = "daemon" }
|
||||
env_logger.workspace = true
|
||||
|
|
@ -16,6 +17,7 @@ pam-client = "0.5.0"
|
|||
pwd.workspace = true
|
||||
ron.workspace = true
|
||||
shlex = "1.2.0"
|
||||
xkb-data = "0.1"
|
||||
xdg = "2.5.2"
|
||||
#TODO: reduce features
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
|
@ -64,6 +66,10 @@ zbus = "4"
|
|||
git = "https://github.com/pop-os/cosmic-bg"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.cosmic-comp-config]
|
||||
git = "https://github.com/pop-os/cosmic-comp"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.cosmic-config]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
default-features = false
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
cosmic-bg-config.workspace = true
|
||||
cosmic-comp-config.workspace = true
|
||||
cosmic-config.workspace = true
|
||||
cosmic-theme.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
pub use cosmic_bg_config::Color;
|
||||
pub use cosmic_comp_config::XkbConfig;
|
||||
pub use cosmic_theme::Theme;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
|
|
@ -9,6 +10,7 @@ pub struct UserData {
|
|||
pub icon_opt: Option<Vec<u8>>,
|
||||
pub theme_opt: Option<Theme>,
|
||||
pub wallpapers_opt: Option<Vec<(String, WallpaperData)>>,
|
||||
pub xkb_config_opt: Option<XkbConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use cosmic_bg_config::Source;
|
||||
use cosmic_comp_config::CosmicCompConfig;
|
||||
use cosmic_config::CosmicConfigEntry;
|
||||
use cosmic_greeter_daemon::{UserData, WallpaperData};
|
||||
use std::{env, error::Error, fs, future::pending, io, path::Path};
|
||||
|
|
@ -113,6 +114,7 @@ impl GreeterProxy {
|
|||
theme_opt: None,
|
||||
//TODO: should wallpapers come from a per-user call?
|
||||
wallpapers_opt: None,
|
||||
xkb_config_opt: None,
|
||||
};
|
||||
|
||||
//IMPORTANT: Assume the identity of the user to ensure we don't read wallpaper file data as root
|
||||
|
|
@ -188,6 +190,24 @@ impl GreeterProxy {
|
|||
}
|
||||
user_data.wallpapers_opt = Some(wallpaper_datas);
|
||||
}
|
||||
|
||||
match cosmic_config::Config::new(
|
||||
"com.system76.CosmicComp",
|
||||
CosmicCompConfig::VERSION,
|
||||
) {
|
||||
Ok(config_handler) => match CosmicCompConfig::get_entry(&config_handler) {
|
||||
Ok(config) => {
|
||||
user_data.xkb_config_opt = Some(config.xkb_config);
|
||||
}
|
||||
Err((errs, config)) => {
|
||||
log::error!("errors loading cosmic-comp config: {:?}", errs);
|
||||
user_data.xkb_config_opt = Some(config.xkb_config);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("failed to create cosmic-comp config handler: {}", err);
|
||||
}
|
||||
};
|
||||
})
|
||||
.map_err(|err| GreeterError::RunAsUser(err.to_string()))?;
|
||||
|
||||
|
|
|
|||
1
debian/control
vendored
1
debian/control
vendored
|
|
@ -7,6 +7,7 @@ Build-Depends:
|
|||
git,
|
||||
just (>= 1.13.0),
|
||||
libclang-dev,
|
||||
libinput-dev,
|
||||
libpam-dev,
|
||||
libwayland-dev,
|
||||
libxkbcommon-dev,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
cancel = Cancel
|
||||
keyboard-layout = Keyboard layout
|
||||
restart = Restart
|
||||
restart-now = Restart now?
|
||||
restart-timeout = The system will restart automatically in {$seconds} seconds.
|
||||
session = Session
|
||||
shutdown = Shut down
|
||||
shutdown-now = Shut down now?
|
||||
shutdown-timeout = The system will shut down automatically in {$seconds} seconds.
|
||||
suspend = Suspend
|
||||
user = User
|
||||
|
|
|
|||
514
src/greeter.rs
514
src/greeter.rs
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
use cosmic::app::{message, Command, Core, Settings};
|
||||
use cosmic::{
|
||||
cosmic_config::{self, ConfigSet, CosmicConfigEntry},
|
||||
executor,
|
||||
iced::{
|
||||
self, alignment,
|
||||
|
|
@ -18,11 +19,12 @@ use cosmic::{
|
|||
destroy_layer_surface, get_layer_surface, Anchor, KeyboardInteractivity, Layer,
|
||||
},
|
||||
},
|
||||
Length, Subscription,
|
||||
Background, Border, Length, Subscription,
|
||||
},
|
||||
iced_runtime::core::window::Id as SurfaceId,
|
||||
style, widget, Element,
|
||||
style, theme, widget, Element,
|
||||
};
|
||||
use cosmic_comp_config::CosmicCompConfig;
|
||||
use cosmic_greeter_daemon::{UserData, WallpaperData};
|
||||
use greetd_ipc::{codec::TokioCodec, AuthMessageType, Request, Response};
|
||||
use std::{
|
||||
|
|
@ -105,6 +107,7 @@ fn user_data_fallback() -> Vec<UserData> {
|
|||
icon_opt,
|
||||
theme_opt: None,
|
||||
wallpapers_opt: None,
|
||||
xkb_config_opt: None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -112,6 +115,8 @@ fn user_data_fallback() -> Vec<UserData> {
|
|||
}
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn Error>> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
crate::localize::localize();
|
||||
|
||||
let mut user_datas = match futures::executor::block_on(async { user_data_dbus().await }) {
|
||||
|
|
@ -257,12 +262,31 @@ pub fn main() -> Result<(), Box<dyn Error>> {
|
|||
sessions
|
||||
};
|
||||
|
||||
let layouts_opt = match xkb_data::keyboard_layouts() {
|
||||
Ok(ok) => Some(Arc::new(ok)),
|
||||
Err(err) => {
|
||||
log::warn!("failed to load keyboard layouts: {}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let comp_config_handler =
|
||||
match cosmic_config::Config::new("com.system76.CosmicComp", CosmicCompConfig::VERSION) {
|
||||
Ok(config_handler) => Some(config_handler),
|
||||
Err(err) => {
|
||||
log::error!("failed to create cosmic-comp config handler: {}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let fallback_background =
|
||||
widget::image::Handle::from_memory(include_bytes!("../res/background.png"));
|
||||
|
||||
let flags = Flags {
|
||||
user_datas,
|
||||
sessions,
|
||||
layouts_opt,
|
||||
comp_config_handler,
|
||||
fallback_background,
|
||||
};
|
||||
|
||||
|
|
@ -345,6 +369,8 @@ fn request_command(socket: Arc<Mutex<UnixStream>>, request: Request) -> Command<
|
|||
pub struct Flags {
|
||||
user_datas: Vec<UserData>,
|
||||
sessions: HashMap<String, Vec<String>>,
|
||||
layouts_opt: Option<Arc<xkb_data::KeyboardLayouts>>,
|
||||
comp_config_handler: Option<cosmic_config::Config>,
|
||||
fallback_background: widget::image::Handle,
|
||||
}
|
||||
|
||||
|
|
@ -360,6 +386,13 @@ pub enum SocketState {
|
|||
Error(Arc<io::Error>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ActiveLayout {
|
||||
layout: String,
|
||||
description: String,
|
||||
variant: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum DialogPage {
|
||||
Restart(Instant),
|
||||
|
|
@ -378,6 +411,14 @@ impl DialogPage {
|
|||
}
|
||||
}
|
||||
|
||||
///TODO: this is custom code that should be better handled by libcosmic
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Dropdown {
|
||||
Keyboard,
|
||||
User,
|
||||
Session,
|
||||
}
|
||||
|
||||
/// Messages that are used specifically by our [`App`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
|
|
@ -389,12 +430,14 @@ pub enum Message {
|
|||
PowerInfo(Option<(String, f64)>),
|
||||
Prompt(String, bool, Option<String>),
|
||||
Session(String),
|
||||
Username(Arc<Mutex<UnixStream>>, String),
|
||||
Username(String),
|
||||
Auth(Arc<Mutex<UnixStream>>, Option<String>),
|
||||
Login(Arc<Mutex<UnixStream>>),
|
||||
Error(Arc<Mutex<UnixStream>>, String),
|
||||
DialogCancel,
|
||||
DialogConfirm,
|
||||
DropdownToggle(Dropdown),
|
||||
KeyboardLayout(usize),
|
||||
Reconnect,
|
||||
Suspend,
|
||||
Restart,
|
||||
|
|
@ -415,22 +458,56 @@ pub struct App {
|
|||
network_icon_opt: Option<&'static str>,
|
||||
power_info_opt: Option<(String, f64)>,
|
||||
socket_state: SocketState,
|
||||
username_opt: Option<String>,
|
||||
usernames: Vec<(String, String)>,
|
||||
selected_username: String,
|
||||
prompt_opt: Option<(String, bool, Option<String>)>,
|
||||
session_names: Vec<String>,
|
||||
selected_session: String,
|
||||
active_layouts: Vec<ActiveLayout>,
|
||||
error_opt: Option<String>,
|
||||
dialog_page_opt: Option<DialogPage>,
|
||||
dropdown_opt: Option<Dropdown>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn update_user_config(&mut self) -> Command<Message> {
|
||||
let username = match &self.username_opt {
|
||||
fn set_xkb_config(&self) {
|
||||
let user_data = match self
|
||||
.flags
|
||||
.user_datas
|
||||
.iter()
|
||||
.find(|x| &x.name == &self.selected_username)
|
||||
{
|
||||
Some(some) => some,
|
||||
None => return Command::none(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let user_data = match self.flags.user_datas.iter().find(|x| &x.name == username) {
|
||||
if let Some(mut xkb_config) = user_data.xkb_config_opt.clone() {
|
||||
xkb_config.layout = String::new();
|
||||
xkb_config.variant = String::new();
|
||||
for (i, layout) in self.active_layouts.iter().enumerate() {
|
||||
if i > 0 {
|
||||
xkb_config.layout.push(',');
|
||||
xkb_config.variant.push(',');
|
||||
}
|
||||
xkb_config.layout.push_str(&layout.layout);
|
||||
xkb_config.variant.push_str(&layout.variant);
|
||||
}
|
||||
if let Some(comp_config_handler) = &self.flags.comp_config_handler {
|
||||
match comp_config_handler.set("xkb_config", xkb_config) {
|
||||
Ok(()) => log::info!("updated cosmic-comp xkb_config"),
|
||||
Err(err) => log::error!("failed to update cosmic-comp xkb_config: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_user_config(&mut self) -> Command<Message> {
|
||||
let user_data = match self
|
||||
.flags
|
||||
.user_datas
|
||||
.iter()
|
||||
.find(|x| &x.name == &self.selected_username)
|
||||
{
|
||||
Some(some) => some,
|
||||
None => return Command::none(),
|
||||
};
|
||||
|
|
@ -471,6 +548,53 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
// From cosmic-applet-input-sources
|
||||
if let Some(keyboard_layouts) = &self.flags.layouts_opt {
|
||||
if let Some(xkb_config) = &user_data.xkb_config_opt {
|
||||
self.active_layouts.clear();
|
||||
let config_layouts = xkb_config.layout.split_terminator(',');
|
||||
let config_variants = xkb_config
|
||||
.variant
|
||||
.split_terminator(',')
|
||||
.chain(std::iter::repeat(""));
|
||||
for (config_layout, config_variant) in config_layouts.zip(config_variants) {
|
||||
for xkb_layout in keyboard_layouts.layouts() {
|
||||
if config_layout != xkb_layout.name() {
|
||||
continue;
|
||||
}
|
||||
if config_variant.is_empty() {
|
||||
let active_layout = ActiveLayout {
|
||||
description: xkb_layout.description().to_owned(),
|
||||
layout: config_layout.to_owned(),
|
||||
variant: config_variant.to_owned(),
|
||||
};
|
||||
self.active_layouts.push(active_layout);
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(xkb_variants) = xkb_layout.variants() else {
|
||||
continue;
|
||||
};
|
||||
for xkb_variant in xkb_variants {
|
||||
if config_variant != xkb_variant.name() {
|
||||
continue;
|
||||
}
|
||||
let active_layout = ActiveLayout {
|
||||
description: xkb_variant.description().to_owned(),
|
||||
layout: config_layout.to_owned(),
|
||||
variant: config_variant.to_owned(),
|
||||
};
|
||||
self.active_layouts.push(active_layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("{:?}", self.active_layouts);
|
||||
|
||||
// Ensure that user's xkb config is used
|
||||
self.set_xkb_config();
|
||||
}
|
||||
}
|
||||
|
||||
match &user_data.theme_opt {
|
||||
Some(theme) => {
|
||||
cosmic::app::command::set_theme(cosmic::Theme::custom(Arc::new(theme.clone())))
|
||||
|
|
@ -514,9 +638,28 @@ impl cosmic::Application for App {
|
|||
let mut session_names: Vec<_> = flags.sessions.keys().map(|x| x.to_string()).collect();
|
||||
session_names.sort();
|
||||
|
||||
//TODO: determine default session?
|
||||
//TODO: use last selected session
|
||||
let selected_session = session_names.first().cloned().unwrap_or(String::new());
|
||||
|
||||
//TODO: use full_name_opt
|
||||
let mut usernames: Vec<_> = flags
|
||||
.user_datas
|
||||
.iter()
|
||||
.map(|x| {
|
||||
let name = x.name.clone();
|
||||
let full_name = x.full_name_opt.clone().unwrap_or_else(|| name.clone());
|
||||
(name, full_name)
|
||||
})
|
||||
.collect();
|
||||
usernames.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
|
||||
//TODO: use last selected user
|
||||
let selected_username = flags
|
||||
.user_datas
|
||||
.first()
|
||||
.map(|x| x.name.clone())
|
||||
.unwrap_or(String::new());
|
||||
|
||||
let mut app = App {
|
||||
core,
|
||||
flags,
|
||||
|
|
@ -528,12 +671,15 @@ impl cosmic::Application for App {
|
|||
network_icon_opt: None,
|
||||
power_info_opt: None,
|
||||
socket_state: SocketState::Pending,
|
||||
username_opt: None,
|
||||
usernames,
|
||||
selected_username,
|
||||
prompt_opt: None,
|
||||
session_names,
|
||||
selected_session,
|
||||
active_layouts: Vec::new(),
|
||||
error_opt: None,
|
||||
dialog_page_opt: None,
|
||||
dropdown_opt: None,
|
||||
};
|
||||
let command = app.update(Message::Reconnect);
|
||||
(app, command)
|
||||
|
|
@ -636,15 +782,13 @@ impl cosmic::Application for App {
|
|||
self.socket_state = socket_state;
|
||||
match &self.socket_state {
|
||||
SocketState::Open(socket) => {
|
||||
// When socket is opened, request default user
|
||||
//TODO: choose last used user
|
||||
match self.flags.user_datas.first().map(|x| x.name.clone()) {
|
||||
Some(username) => {
|
||||
let socket = socket.clone();
|
||||
return self.update(Message::Username(socket, username));
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
// When socket is opened, send create session
|
||||
return Command::batch([request_command(
|
||||
socket.clone(),
|
||||
Request::CreateSession {
|
||||
username: self.selected_username.clone(),
|
||||
},
|
||||
)]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -672,15 +816,25 @@ impl cosmic::Application for App {
|
|||
}
|
||||
Message::Session(selected_session) => {
|
||||
self.selected_session = selected_session;
|
||||
if self.dropdown_opt == Some(Dropdown::Session) {
|
||||
self.dropdown_opt = None;
|
||||
}
|
||||
}
|
||||
Message::Username(socket, username) => {
|
||||
self.username_opt = Some(username.clone());
|
||||
self.prompt_opt = None;
|
||||
self.surface_images.clear();
|
||||
return Command::batch([
|
||||
self.update_user_config(),
|
||||
request_command(socket, Request::CreateSession { username }),
|
||||
]);
|
||||
Message::Username(username) => {
|
||||
if self.dropdown_opt == Some(Dropdown::User) {
|
||||
self.dropdown_opt = None;
|
||||
}
|
||||
if username != self.selected_username {
|
||||
self.selected_username = username.clone();
|
||||
self.surface_images.clear();
|
||||
match &self.socket_state {
|
||||
SocketState::Open(socket) => {
|
||||
self.prompt_opt = None;
|
||||
return request_command(socket.clone(), Request::CancelSession);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Auth(socket, response) => {
|
||||
self.prompt_opt = None;
|
||||
|
|
@ -708,18 +862,23 @@ impl cosmic::Application for App {
|
|||
return request_command(socket, Request::CancelSession);
|
||||
}
|
||||
Message::Reconnect => {
|
||||
return Command::perform(
|
||||
async {
|
||||
message::app(Message::Socket(match env::var_os("GREETD_SOCK") {
|
||||
Some(socket_path) => match UnixStream::connect(&socket_path).await {
|
||||
Ok(socket) => SocketState::Open(Arc::new(socket.into())),
|
||||
Err(err) => SocketState::Error(Arc::new(err)),
|
||||
},
|
||||
None => SocketState::NotSet,
|
||||
}))
|
||||
},
|
||||
|x| x,
|
||||
);
|
||||
return Command::batch([
|
||||
self.update_user_config(),
|
||||
Command::perform(
|
||||
async {
|
||||
message::app(Message::Socket(match env::var_os("GREETD_SOCK") {
|
||||
Some(socket_path) => {
|
||||
match UnixStream::connect(&socket_path).await {
|
||||
Ok(socket) => SocketState::Open(Arc::new(socket.into())),
|
||||
Err(err) => SocketState::Error(Arc::new(err)),
|
||||
}
|
||||
}
|
||||
None => SocketState::NotSet,
|
||||
}))
|
||||
},
|
||||
|x| x,
|
||||
),
|
||||
]);
|
||||
}
|
||||
Message::DialogCancel => {
|
||||
self.dialog_page_opt = None;
|
||||
|
|
@ -757,6 +916,22 @@ impl cosmic::Application for App {
|
|||
}
|
||||
None => {}
|
||||
},
|
||||
Message::DropdownToggle(dropdown) => {
|
||||
if self.dropdown_opt == Some(dropdown) {
|
||||
self.dropdown_opt = None;
|
||||
} else {
|
||||
self.dropdown_opt = Some(dropdown);
|
||||
}
|
||||
}
|
||||
Message::KeyboardLayout(layout_i) => {
|
||||
if layout_i < self.active_layouts.len() {
|
||||
self.active_layouts.swap(0, layout_i);
|
||||
self.set_xkb_config();
|
||||
}
|
||||
if self.dropdown_opt == Some(Dropdown::Keyboard) {
|
||||
self.dropdown_opt = None
|
||||
}
|
||||
}
|
||||
Message::Suspend => {
|
||||
#[cfg(feature = "logind")]
|
||||
return Command::perform(
|
||||
|
|
@ -843,31 +1018,143 @@ impl cosmic::Application for App {
|
|||
]);
|
||||
}
|
||||
|
||||
//TODO: implement these buttons
|
||||
//TODO: move code for custom dropdowns to libcosmic
|
||||
let menu_checklist = |label, value, message| {
|
||||
Element::from(
|
||||
widget::menu::menu_button(vec![
|
||||
if value {
|
||||
widget::icon::from_name("object-select-symbolic")
|
||||
.size(16)
|
||||
.icon()
|
||||
.width(Length::Fixed(16.0))
|
||||
.into()
|
||||
} else {
|
||||
widget::Space::with_width(Length::Fixed(17.0)).into()
|
||||
},
|
||||
widget::Space::with_width(Length::Fixed(8.0)).into(),
|
||||
widget::text(label)
|
||||
.horizontal_alignment(iced::alignment::Horizontal::Left)
|
||||
.into(),
|
||||
])
|
||||
.on_press(message),
|
||||
)
|
||||
};
|
||||
let dropdown_menu = |items| {
|
||||
widget::container(widget::column::with_children(items))
|
||||
.padding(1)
|
||||
//TODO: move style to libcosmic
|
||||
.style(theme::Container::custom(|theme| {
|
||||
let cosmic = theme.cosmic();
|
||||
let component = &cosmic.background.component;
|
||||
widget::container::Appearance {
|
||||
icon_color: Some(component.on.into()),
|
||||
text_color: Some(component.on.into()),
|
||||
background: Some(Background::Color(component.base.into())),
|
||||
border: Border {
|
||||
radius: 8.0.into(),
|
||||
width: 1.0,
|
||||
color: component.divider.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}))
|
||||
.width(Length::Fixed(240.0))
|
||||
};
|
||||
|
||||
let mut input_button = widget::popover(
|
||||
widget::button(widget::icon::from_name("input-keyboard-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::DropdownToggle(Dropdown::Keyboard)),
|
||||
)
|
||||
.position(widget::popover::Position::Bottom);
|
||||
if matches!(self.dropdown_opt, Some(Dropdown::Keyboard)) {
|
||||
let mut items = Vec::with_capacity(self.active_layouts.len());
|
||||
for (i, layout) in self.active_layouts.iter().enumerate() {
|
||||
items.push(menu_checklist(
|
||||
&layout.description,
|
||||
i == 0,
|
||||
Message::KeyboardLayout(i),
|
||||
));
|
||||
}
|
||||
input_button = input_button.popup(dropdown_menu(items));
|
||||
}
|
||||
|
||||
let mut user_button = widget::popover(
|
||||
widget::button(widget::icon::from_name("system-users-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::DropdownToggle(Dropdown::User)),
|
||||
)
|
||||
.position(widget::popover::Position::Bottom);
|
||||
if matches!(self.dropdown_opt, Some(Dropdown::User)) {
|
||||
let mut items = Vec::with_capacity(self.usernames.len());
|
||||
for (name, full_name) in self.usernames.iter() {
|
||||
items.push(menu_checklist(
|
||||
full_name,
|
||||
name == &self.selected_username,
|
||||
Message::Username(name.clone()),
|
||||
));
|
||||
}
|
||||
user_button = user_button.popup(dropdown_menu(items));
|
||||
}
|
||||
|
||||
let mut session_button = widget::popover(
|
||||
widget::button(widget::icon::from_name("application-menu-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::DropdownToggle(Dropdown::Session)),
|
||||
)
|
||||
.position(widget::popover::Position::Bottom);
|
||||
if matches!(self.dropdown_opt, Some(Dropdown::Session)) {
|
||||
let mut items = Vec::with_capacity(self.session_names.len());
|
||||
for session_name in self.session_names.iter() {
|
||||
items.push(menu_checklist(
|
||||
session_name,
|
||||
session_name == &self.selected_session,
|
||||
Message::Session(session_name.clone()),
|
||||
));
|
||||
}
|
||||
session_button = session_button.popup(dropdown_menu(items));
|
||||
}
|
||||
|
||||
let button_row = iced::widget::row![
|
||||
/*TODO: greeter accessibility options
|
||||
widget::button(widget::icon::from_name(
|
||||
"applications-accessibility-symbolic"
|
||||
))
|
||||
.padding(12.0)
|
||||
.on_press(Message::None),
|
||||
widget::button(widget::icon::from_name("input-keyboard-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::None),
|
||||
widget::button(widget::icon::from_name("system-users-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::None),
|
||||
widget::button(widget::icon::from_name("application-menu-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::None),
|
||||
widget::button(widget::icon::from_name("system-suspend-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::Suspend),
|
||||
widget::button(widget::icon::from_name("system-reboot-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::Restart),
|
||||
widget::button(widget::icon::from_name("system-shutdown-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::Shutdown),
|
||||
*/
|
||||
widget::tooltip(
|
||||
input_button,
|
||||
fl!("keyboard-layout"),
|
||||
widget::tooltip::Position::Top
|
||||
),
|
||||
widget::tooltip(user_button, fl!("user"), widget::tooltip::Position::Top),
|
||||
widget::tooltip(
|
||||
session_button,
|
||||
fl!("session"),
|
||||
widget::tooltip::Position::Top
|
||||
),
|
||||
widget::tooltip(
|
||||
widget::button(widget::icon::from_name("system-suspend-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::Suspend),
|
||||
fl!("suspend"),
|
||||
widget::tooltip::Position::Top
|
||||
),
|
||||
widget::tooltip(
|
||||
widget::button(widget::icon::from_name("system-reboot-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::Restart),
|
||||
fl!("restart"),
|
||||
widget::tooltip::Position::Top
|
||||
),
|
||||
widget::tooltip(
|
||||
widget::button(widget::icon::from_name("system-shutdown-symbolic"))
|
||||
.padding(12.0)
|
||||
.on_press(Message::Shutdown),
|
||||
fl!("shutdown"),
|
||||
widget::tooltip::Position::Top
|
||||
)
|
||||
]
|
||||
.padding([16.0, 0.0, 0.0, 0.0])
|
||||
.spacing(8.0);
|
||||
|
|
@ -893,91 +1180,35 @@ impl cosmic::Application for App {
|
|||
column = column.push(widget::text("Opening GREETD_SOCK"));
|
||||
}
|
||||
SocketState::Open(socket) => {
|
||||
match &self.username_opt {
|
||||
Some(username) => {
|
||||
for user_data in &self.flags.user_datas {
|
||||
if &user_data.name == username {
|
||||
match &user_data.icon_opt {
|
||||
Some(icon) => {
|
||||
column = column.push(
|
||||
widget::container(
|
||||
widget::Image::new(
|
||||
//TODO: cache handle
|
||||
widget::image::Handle::from_memory(
|
||||
icon.clone(),
|
||||
),
|
||||
)
|
||||
.width(Length::Fixed(78.0))
|
||||
.height(Length::Fixed(78.0)),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.align_x(alignment::Horizontal::Center),
|
||||
for user_data in &self.flags.user_datas {
|
||||
if &user_data.name == &self.selected_username {
|
||||
match &user_data.icon_opt {
|
||||
Some(icon) => {
|
||||
column = column.push(
|
||||
widget::container(
|
||||
widget::Image::new(
|
||||
//TODO: cache handle
|
||||
widget::image::Handle::from_memory(icon.clone()),
|
||||
)
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
match &user_data.full_name_opt {
|
||||
Some(full_name) => {
|
||||
column = column.push(
|
||||
widget::container(widget::text::title4(full_name))
|
||||
.width(Length::Fill)
|
||||
.align_x(alignment::Horizontal::Center),
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
.width(Length::Fixed(78.0))
|
||||
.height(Length::Fixed(78.0)),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.align_x(alignment::Horizontal::Center),
|
||||
)
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let mut row = widget::row::with_capacity(self.flags.user_datas.len())
|
||||
.spacing(12.0);
|
||||
for user_data in &self.flags.user_datas {
|
||||
let mut column = widget::column::with_capacity(2).spacing(12.0);
|
||||
|
||||
match &user_data.icon_opt {
|
||||
Some(icon) => {
|
||||
column = column.push(
|
||||
widget::container(
|
||||
widget::Image::new(
|
||||
//TODO: cache handle
|
||||
widget::image::Handle::from_memory(
|
||||
icon.clone(),
|
||||
),
|
||||
)
|
||||
.width(Length::Fixed(78.0))
|
||||
.height(Length::Fixed(78.0)),
|
||||
)
|
||||
match &user_data.full_name_opt {
|
||||
Some(full_name) => {
|
||||
column = column.push(
|
||||
widget::container(widget::text::title4(full_name))
|
||||
.width(Length::Fill)
|
||||
.align_x(alignment::Horizontal::Center),
|
||||
)
|
||||
}
|
||||
None => {}
|
||||
);
|
||||
}
|
||||
match &user_data.full_name_opt {
|
||||
Some(full_name) => {
|
||||
column = column.push(
|
||||
widget::container(widget::text::title4(full_name))
|
||||
.width(Length::Fill)
|
||||
.align_x(alignment::Horizontal::Center),
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
row = row.push(
|
||||
widget::MouseArea::new(
|
||||
widget::layer_container(column)
|
||||
.layer(cosmic::cosmic_theme::Layer::Primary)
|
||||
.padding(16)
|
||||
.style(cosmic::theme::Container::Card),
|
||||
)
|
||||
.on_press(
|
||||
Message::Username(socket.clone(), user_data.name.clone()),
|
||||
),
|
||||
);
|
||||
None => {}
|
||||
}
|
||||
column = column.push(row);
|
||||
}
|
||||
}
|
||||
match &self.prompt_opt {
|
||||
|
|
@ -1036,15 +1267,6 @@ impl cosmic::Application for App {
|
|||
column = column.push(widget::text(error));
|
||||
}
|
||||
|
||||
column = column.push(
|
||||
//TODO: use button
|
||||
iced::widget::pick_list(
|
||||
&self.session_names,
|
||||
Some(self.selected_session.clone()),
|
||||
Message::Session,
|
||||
),
|
||||
);
|
||||
|
||||
widget::container(column)
|
||||
.align_x(alignment::Horizontal::Center)
|
||||
.width(Length::Fill)
|
||||
|
|
@ -1091,7 +1313,7 @@ impl cosmic::Application for App {
|
|||
popover
|
||||
.popup(
|
||||
widget::dialog(fl!("restart-now"))
|
||||
.icon(widget::icon::from_name("system-restart-symbolic").size(64))
|
||||
.icon(widget::icon::from_name("system-reboot-symbolic").size(64))
|
||||
.body(fl!("restart-timeout", seconds = remaining.as_secs()))
|
||||
.primary_action(
|
||||
widget::button::suggested(fl!("restart"))
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ use tokio::{sync::mpsc, task, time};
|
|||
use wayland_client::{protocol::wl_output::WlOutput, Proxy};
|
||||
|
||||
pub fn main(current_user: pwd::Passwd) -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
crate::localize::localize();
|
||||
|
||||
//TODO: use accountsservice
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
use cosmic_greeter::{greeter, locker};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
match pwd::Passwd::current_user() {
|
||||
Some(current_user) => match current_user.name.as_str() {
|
||||
"cosmic-greeter" => greeter::main(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue