Move layout and prompt handling to common code

This commit is contained in:
Jeremy Soller 2025-05-09 18:47:52 -06:00
parent 8812240f50
commit 2fff220392
4 changed files with 308 additions and 215 deletions

View file

@ -12,15 +12,26 @@ use cosmic::{
iced_runtime::core::window::Id as SurfaceId, iced_runtime::core::window::Id as SurfaceId,
widget, widget,
}; };
use cosmic_greeter_daemon::{BgSource, UserData}; use cosmic_config::{ConfigSet, CosmicConfigEntry};
use std::collections::HashMap; use cosmic_greeter_daemon::{BgSource, CosmicCompConfig, UserData};
use std::{collections::HashMap, sync::Arc};
use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_output::WlOutput;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ActiveLayout {
pub layout: String,
pub description: String,
pub variant: String,
}
pub struct Common<M> { pub struct Common<M> {
pub active_layouts: Vec<ActiveLayout>,
pub active_surface_id_opt: Option<SurfaceId>, pub active_surface_id_opt: Option<SurfaceId>,
pub comp_config_handler: Option<cosmic_config::Config>,
pub core: Core, pub core: Core,
pub error_opt: Option<String>, pub error_opt: Option<String>,
pub input: String, pub fallback_background: widget::image::Handle,
pub layouts_opt: Option<Arc<xkb_data::KeyboardLayouts>>,
pub network_icon_opt: Option<&'static str>, pub network_icon_opt: Option<&'static str>,
pub on_output_event: Option<Box<dyn Fn(OutputEvent, WlOutput) -> M>>, pub on_output_event: Option<Box<dyn Fn(OutputEvent, WlOutput) -> M>>,
pub on_session_lock_event: Option<Box<dyn Fn(SessionLockEvent) -> M>>, pub on_session_lock_event: Option<Box<dyn Fn(SessionLockEvent) -> M>>,
@ -39,11 +50,11 @@ pub struct Common<M> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
Focus(SurfaceId), Focus(SurfaceId),
Input(String),
Key(Modifiers, Key, Option<SmolStr>), Key(Modifiers, Key, Option<SmolStr>),
NetworkIcon(Option<&'static str>), NetworkIcon(Option<&'static str>),
OutputEvent(OutputEvent, WlOutput), OutputEvent(OutputEvent, WlOutput),
PowerInfo(Option<(String, f64)>), PowerInfo(Option<(String, f64)>),
Prompt(String, bool, Option<String>),
SessionLockEvent(SessionLockEvent), SessionLockEvent(SessionLockEvent),
Tick, Tick,
Tz(chrono_tz::Tz), Tz(chrono_tz::Tz),
@ -59,11 +70,35 @@ impl<M: From<Message> + Send + 'static> Common<M> {
core.window.show_minimize = false; core.window.show_minimize = false;
core.window.use_template = false; core.window.use_template = false;
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 layouts_opt = match xkb_data::all_keyboard_layouts() {
Ok(ok) => Some(Arc::new(ok)),
Err(err) => {
log::warn!("failed to load keyboard layouts: {}", err);
None
}
};
let app = Self { let app = Self {
active_layouts: Vec::new(),
active_surface_id_opt: None, active_surface_id_opt: None,
comp_config_handler,
core, core,
error_opt: None, error_opt: None,
input: String::new(), fallback_background: widget::image::Handle::from_bytes(
include_bytes!("../res/background.jpg").as_slice(),
),
layouts_opt,
network_icon_opt: None, network_icon_opt: None,
on_output_event: None, on_output_event: None,
on_session_lock_event: None, on_session_lock_event: None,
@ -87,6 +122,27 @@ impl<M: From<Message> + Send + 'static> Common<M> {
) )
} }
pub fn set_xkb_config(&self, user_data: &UserData) {
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.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),
}
}
}
}
pub fn update_wallpapers(&mut self, user_data: &UserData) { pub fn update_wallpapers(&mut self, user_data: &UserData) {
for (_output, surface_id) in self.surface_ids.iter() { for (_output, surface_id) in self.surface_ids.iter() {
if self.surface_images.contains_key(surface_id) { if self.surface_images.contains_key(surface_id) {
@ -129,6 +185,55 @@ impl<M: From<Message> + Send + 'static> Common<M> {
} }
} }
pub fn update_user_data(&mut self, user_data: &UserData) {
self.update_wallpapers(user_data);
// From cosmic-applet-input-sources
if let Some(keyboard_layouts) = &self.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(""));
'outer: 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 'outer;
}
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);
continue 'outer;
}
}
}
log::info!("{:?}", self.active_layouts);
}
}
}
pub fn update(&mut self, message: Message) -> Task<M> { pub fn update(&mut self, message: Message) -> Task<M> {
match message { match message {
Message::Focus(surface_id) => { Message::Focus(surface_id) => {
@ -141,9 +246,6 @@ impl<M: From<Message> + Send + 'static> Common<M> {
return widget::text_input::focus(text_input_id.clone()); return widget::text_input::focus(text_input_id.clone());
} }
} }
Message::Input(input) => {
self.input = input;
}
Message::Key(modifiers, key, text) => { Message::Key(modifiers, key, text) => {
// Uncaptured keys with only shift modifiers go to the password box // Uncaptured keys with only shift modifiers go to the password box
if !modifiers.logo() if !modifiers.logo()
@ -152,7 +254,9 @@ impl<M: From<Message> + Send + 'static> Common<M> {
&& matches!(key, Key::Character(_)) && matches!(key, Key::Character(_))
{ {
if let Some(text) = text { if let Some(text) = text {
self.input.push_str(&text); if let Some((_, _, Some(value))) = &mut self.prompt_opt {
value.push_str(&text);
}
} }
if let Some(surface_id) = self.active_surface_id_opt { if let Some(surface_id) = self.active_surface_id_opt {
@ -177,6 +281,22 @@ impl<M: From<Message> + Send + 'static> Common<M> {
Message::PowerInfo(power_info_opt) => { Message::PowerInfo(power_info_opt) => {
self.power_info_opt = power_info_opt; self.power_info_opt = power_info_opt;
} }
Message::Prompt(prompt, secret, value_opt) => {
let prompt_was_none = self.prompt_opt.is_none();
self.prompt_opt = Some((prompt, secret, value_opt));
if prompt_was_none {
if let Some(surface_id) = self.active_surface_id_opt {
if let Some(text_input_id) = self
.surface_names
.get(&surface_id)
.and_then(|id| self.text_input_ids.get(id))
{
log::info!("focus surface found id {:?}", text_input_id);
return widget::text_input::focus(text_input_id.clone());
}
}
}
}
Message::SessionLockEvent(lock_event) => { Message::SessionLockEvent(lock_event) => {
if let Some(on_session_lock_event) = &self.on_session_lock_event { if let Some(on_session_lock_event) = &self.on_session_lock_event {
return Task::done(cosmic::Action::App(on_session_lock_event(lock_event))); return Task::done(cosmic::Action::App(on_session_lock_event(lock_event)));

View file

@ -11,7 +11,7 @@ use cosmic::surface;
use cosmic::widget::text; use cosmic::widget::text;
use cosmic::{ use cosmic::{
Element, Element,
cosmic_config::{self, ConfigSet, CosmicConfigEntry}, cosmic_config::{self, ConfigSet},
executor, executor,
iced::{ iced::{
self, Background, Border, Length, Subscription, alignment, self, Background, Border, Length, Subscription, alignment,
@ -27,7 +27,6 @@ use cosmic::{
iced_runtime::core::window::Id as SurfaceId, iced_runtime::core::window::Id as SurfaceId,
theme, widget, theme, widget,
}; };
use cosmic_comp_config::CosmicCompConfig;
use cosmic_greeter_config::Config as CosmicGreeterConfig; use cosmic_greeter_config::Config as CosmicGreeterConfig;
use cosmic_greeter_daemon::UserData; use cosmic_greeter_daemon::UserData;
use greetd_ipc::Request; use greetd_ipc::Request;
@ -260,34 +259,11 @@ pub fn main() -> Result<(), Box<dyn Error>> {
sessions sessions
}; };
let layouts_opt = match xkb_data::all_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_bytes(include_bytes!("../res/background.jpg").as_slice());
let flags = Flags { let flags = Flags {
user_datas, user_datas,
sessions, sessions,
layouts_opt,
comp_config_handler,
greeter_config, greeter_config,
greeter_config_handler, greeter_config_handler,
fallback_background,
}; };
let settings = Settings::default().no_main_window(true); let settings = Settings::default().no_main_window(true);
@ -301,11 +277,8 @@ pub fn main() -> Result<(), Box<dyn Error>> {
pub struct Flags { pub struct Flags {
user_datas: Vec<UserData>, user_datas: Vec<UserData>,
sessions: HashMap<String, (Vec<String>, Vec<String>)>, sessions: HashMap<String, (Vec<String>, Vec<String>)>,
layouts_opt: Option<Arc<xkb_data::KeyboardLayouts>>,
comp_config_handler: Option<cosmic_config::Config>,
greeter_config: CosmicGreeterConfig, greeter_config: CosmicGreeterConfig,
greeter_config_handler: Option<cosmic_config::Config>, greeter_config_handler: Option<cosmic_config::Config>,
fallback_background: widget::image::Handle,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -320,13 +293,6 @@ pub enum SocketState {
Error(Arc<io::Error>), Error(Arc<io::Error>),
} }
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ActiveLayout {
layout: String,
description: String,
variant: String,
}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum DialogPage { pub enum DialogPage {
Restart(Instant), Restart(Instant),
@ -377,7 +343,6 @@ pub enum Message {
Heartbeat, Heartbeat,
KeyboardLayout(usize), KeyboardLayout(usize),
Login, Login,
Prompt(String, bool, Option<String>),
Reconnect, Reconnect,
Restart, Restart,
Session(String), Session(String),
@ -404,7 +369,6 @@ pub struct App {
selected_username: NameIndexPair, selected_username: NameIndexPair,
session_names: Vec<String>, session_names: Vec<String>,
selected_session: String, selected_session: String,
active_layouts: Vec<ActiveLayout>,
dialog_page_opt: Option<DialogPage>, dialog_page_opt: Option<DialogPage>,
dropdown_opt: Option<Dropdown>, dropdown_opt: Option<Dropdown>,
heartbeat_handle: Option<cosmic::iced::task::Handle>, heartbeat_handle: Option<cosmic::iced::task::Handle>,
@ -495,8 +459,8 @@ impl App {
) )
.position(widget::popover::Position::Bottom); .position(widget::popover::Position::Bottom);
if matches!(self.dropdown_opt, Some(Dropdown::Keyboard)) { if matches!(self.dropdown_opt, Some(Dropdown::Keyboard)) {
let mut items = Vec::with_capacity(self.active_layouts.len()); let mut items = Vec::with_capacity(self.common.active_layouts.len());
for (i, layout) in self.active_layouts.iter().enumerate() { for (i, layout) in self.common.active_layouts.iter().enumerate() {
items.push(menu_checklist( items.push(menu_checklist(
&layout.description, &layout.description,
i == 0, i == 0,
@ -648,16 +612,22 @@ impl App {
.unwrap_or_else(|| cosmic::widget::Id::new("text_input")); .unwrap_or_else(|| cosmic::widget::Id::new("text_input"));
let mut text_input = widget::secure_input( let mut text_input = widget::secure_input(
prompt.clone(), prompt.clone(),
&self.common.input, value.as_str(),
Some(Message::Prompt( Some(
prompt.clone(), common::Message::Prompt(
!*secret, prompt.clone(),
Some(value.clone()), !*secret,
)), Some(value.clone()),
)
.into(),
),
*secret, *secret,
) )
.id(text_input_id) .id(text_input_id)
.on_input(|input| common::Message::Input(input).into()) .on_input(|input| {
common::Message::Prompt(prompt.clone(), *secret, Some(input))
.into()
})
.on_submit(|v| Message::Auth(Some(v))); .on_submit(|v| Message::Auth(Some(v)));
if let Some(text_input_id) = self if let Some(text_input_id) = self
@ -773,6 +743,7 @@ impl App {
None => popover.into(), None => popover.into(),
} }
} }
/// Send a [`Request`] to the greetd IPC subscription. /// Send a [`Request`] to the greetd IPC subscription.
fn send_request(&self, request: Request) { fn send_request(&self, request: Request) {
if let Some(ref sender) = self.greetd_sender { if let Some(ref sender) = self.greetd_sender {
@ -793,27 +764,10 @@ impl App {
None => return, None => return,
}; };
if let Some(mut xkb_config) = user_data.xkb_config_opt.clone() { self.common.set_xkb_config(&user_data);
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) -> Task<Message> { fn update_user_data(&mut self) -> Task<Message> {
let user_data = match self let user_data = match self
.selected_username .selected_username
.data_idx .data_idx
@ -825,54 +779,10 @@ impl App {
} }
}; };
self.common.update_wallpapers(&user_data); self.common.update_user_data(&user_data);
// From cosmic-applet-input-sources // Ensure that user's xkb config is used
if let Some(keyboard_layouts) = &self.flags.layouts_opt { self.common.set_xkb_config(&user_data);
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 { match &user_data.theme_opt {
Some(theme) => { Some(theme) => {
@ -958,7 +868,6 @@ impl cosmic::Application for App {
selected_username, selected_username,
session_names, session_names,
selected_session, selected_session,
active_layouts: Vec::new(),
dialog_page_opt: None, dialog_page_opt: None,
dropdown_opt: None, dropdown_opt: None,
heartbeat_handle: None, heartbeat_handle: None,
@ -1059,7 +968,7 @@ impl cosmic::Application for App {
})), })),
); );
return Task::batch([ return Task::batch([
self.update_user_config(), self.update_user_data(),
get_layer_surface(SctkLayerSurfaceSettings { get_layer_surface(SctkLayerSurfaceSettings {
id: surface_id, id: surface_id,
layer: Layer::Overlay, layer: Layer::Overlay,
@ -1116,27 +1025,6 @@ impl cosmic::Application for App {
_ => {} _ => {}
} }
} }
Message::Prompt(prompt, secret, value_opt) => {
let value_was_some = self
.common
.prompt_opt
.as_ref()
.map_or(false, |(_, _, x)| x.is_some());
let value_is_some = value_opt.is_some();
self.common.prompt_opt = Some((prompt, secret, value_opt));
if value_is_some && !value_was_some {
if let Some(surface_id) = self.common.active_surface_id_opt {
if let Some(text_input_id) = self
.common
.surface_names
.get(&surface_id)
.and_then(|id| self.common.text_input_ids.get(id))
{
return widget::text_input::focus(text_input_id.clone());
}
}
}
}
Message::Session(selected_session) => { Message::Session(selected_session) => {
self.selected_session = selected_session; self.selected_session = selected_session;
if self.dropdown_opt == Some(Dropdown::Session) { if self.dropdown_opt == Some(Dropdown::Session) {
@ -1241,7 +1129,6 @@ impl cosmic::Application for App {
} }
} }
Message::Auth(response) => { Message::Auth(response) => {
self.common.input.clear();
self.common.prompt_opt = None; self.common.prompt_opt = None;
self.common.error_opt = None; self.common.error_opt = None;
self.send_request(Request::PostAuthMessageResponse { response }); self.send_request(Request::PostAuthMessageResponse { response });
@ -1262,7 +1149,7 @@ impl cosmic::Application for App {
self.send_request(Request::CancelSession); self.send_request(Request::CancelSession);
} }
Message::Reconnect => { Message::Reconnect => {
return self.update_user_config(); return self.update_user_data();
} }
Message::DialogCancel => { Message::DialogCancel => {
self.dialog_page_opt = None; self.dialog_page_opt = None;
@ -1305,8 +1192,8 @@ impl cosmic::Application for App {
} }
} }
Message::KeyboardLayout(layout_i) => { Message::KeyboardLayout(layout_i) => {
if layout_i < self.active_layouts.len() { if layout_i < self.common.active_layouts.len() {
self.active_layouts.swap(0, layout_i); self.common.active_layouts.swap(0, layout_i);
self.set_xkb_config(); self.set_xkb_config();
} }
if self.dropdown_opt == Some(Dropdown::Keyboard) { if self.dropdown_opt == Some(Dropdown::Keyboard) {
@ -1400,7 +1287,7 @@ impl cosmic::Application for App {
.common .common
.surface_images .surface_images
.get(&surface_id) .get(&surface_id)
.unwrap_or(&self.flags.fallback_background); .unwrap_or(&self.common.fallback_background);
widget::image(img) widget::image(img)
.content_fit(iced::ContentFit::Cover) .content_fit(iced::ContentFit::Cover)
.width(Length::Fill) .width(Length::Fill)

View file

@ -10,6 +10,8 @@ use std::time::Duration;
use tokio::net::UnixStream; use tokio::net::UnixStream;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::common;
pub fn subscription() -> Subscription<Message> { pub fn subscription() -> Subscription<Message> {
struct GreetdSubscription; struct GreetdSubscription;
Subscription::run_with_id( Subscription::run_with_id(
@ -53,27 +55,36 @@ pub fn subscription() -> Subscription<Message> {
} => match auth_message_type { } => match auth_message_type {
greetd_ipc::AuthMessageType::Secret => { greetd_ipc::AuthMessageType::Secret => {
_ = sender _ = sender
.send(Message::Prompt( .send(
auth_message, common::Message::Prompt(
true, auth_message,
Some(String::new()), true,
)) Some(String::new()),
)
.into(),
)
.await; .await;
} }
greetd_ipc::AuthMessageType::Visible => { greetd_ipc::AuthMessageType::Visible => {
_ = sender _ = sender
.send(Message::Prompt( .send(
auth_message, common::Message::Prompt(
false, auth_message,
Some(String::new()), false,
)) Some(String::new()),
)
.into(),
)
.await; .await;
} }
//TODO: treat error type differently? //TODO: treat error type differently?
greetd_ipc::AuthMessageType::Info greetd_ipc::AuthMessageType::Info
| greetd_ipc::AuthMessageType::Error => { | greetd_ipc::AuthMessageType::Error => {
_ = sender _ = sender
.send(Message::Prompt(auth_message, false, None)) .send(
common::Message::Prompt(auth_message, false, None)
.into(),
)
.await; .await;
} }
}, },

View file

@ -9,7 +9,7 @@ use cosmic::surface;
use cosmic::{ use cosmic::{
Element, executor, Element, executor,
iced::{ iced::{
self, Length, Subscription, alignment, self, Background, Border, Length, Subscription, alignment,
event::wayland::{OutputEvent, SessionLockEvent}, event::wayland::{OutputEvent, SessionLockEvent},
futures::{self, SinkExt}, futures::{self, SinkExt},
platform_specific::shell::wayland::commands::session_lock::{ platform_specific::shell::wayland::commands::session_lock::{
@ -17,7 +17,7 @@ use cosmic::{
}, },
}, },
iced_runtime::core::window::Id as SurfaceId, iced_runtime::core::window::Id as SurfaceId,
widget, theme, widget,
}; };
use cosmic_config::CosmicConfigEntry; use cosmic_config::CosmicConfigEntry;
use cosmic_greeter_daemon::{TimeAppletConfig, UserData}; use cosmic_greeter_daemon::{TimeAppletConfig, UserData};
@ -35,7 +35,10 @@ use std::{
use tokio::{sync::mpsc, task}; use tokio::{sync::mpsc, task};
use wayland_client::{Proxy, protocol::wl_output::WlOutput}; use wayland_client::{Proxy, protocol::wl_output::WlOutput};
use crate::common::{self, Common}; use crate::{
common::{self, Common},
fl,
};
fn lockfile_opt() -> Option<PathBuf> { fn lockfile_opt() -> Option<PathBuf> {
let runtime_dir = dirs::runtime_dir()?; let runtime_dir = dirs::runtime_dir()?;
@ -52,13 +55,9 @@ pub fn main(user: pwd::Passwd) -> Result<(), Box<dyn std::error::Error>> {
// We are already the user at this point // We are already the user at this point
user_data.load_config_as_user(); user_data.load_config_as_user();
let fallback_background =
widget::image::Handle::from_bytes(include_bytes!("../res/background.jpg").as_slice());
let flags = Flags { let flags = Flags {
user_data, user_data,
lockfile_opt: lockfile_opt(), lockfile_opt: lockfile_opt(),
fallback_background,
}; };
let settings = Settings::default().no_main_window(true); let settings = Settings::default().no_main_window(true);
@ -103,11 +102,9 @@ impl Conversation {
futures::executor::block_on(async { futures::executor::block_on(async {
self.msg_tx self.msg_tx
.send(cosmic::Action::App(Message::Prompt( .send(cosmic::Action::App(
prompt.to_string(), common::Message::Prompt(prompt.to_string(), secret, Some(String::new())).into(),
secret, ))
Some(String::new()),
)))
.await .await
}) })
.map_err(|err| { .map_err(|err| {
@ -134,11 +131,9 @@ impl Conversation {
futures::executor::block_on(async { futures::executor::block_on(async {
self.msg_tx self.msg_tx
.send(cosmic::Action::App(Message::Prompt( .send(cosmic::Action::App(
prompt.to_string(), common::Message::Prompt(prompt.to_string(), false, None).into(),
false, ))
None,
)))
.await .await
}) })
.map_err(|err| { .map_err(|err| {
@ -182,7 +177,12 @@ impl pam_client::ConversationHandler for Conversation {
pub struct Flags { pub struct Flags {
user_data: UserData, user_data: UserData,
lockfile_opt: Option<PathBuf>, lockfile_opt: Option<PathBuf>,
fallback_background: widget::image::Handle, }
///TODO: this is custom code that should be better handled by libcosmic
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Dropdown {
Keyboard,
} }
/// Messages that are used specifically by our [`App`]. /// Messages that are used specifically by our [`App`].
@ -194,12 +194,13 @@ pub enum Message {
SessionLockEvent(SessionLockEvent), SessionLockEvent(SessionLockEvent),
Channel(mpsc::Sender<String>), Channel(mpsc::Sender<String>),
BackgroundState(cosmic_bg_config::state::State), BackgroundState(cosmic_bg_config::state::State),
TimeAppletConfig(TimeAppletConfig), DropdownToggle(Dropdown),
KeyboardLayout(usize),
Inhibit(Arc<OwnedFd>), Inhibit(Arc<OwnedFd>),
Prompt(String, bool, Option<String>),
Submit(String), Submit(String),
Surface(surface::Action), Surface(surface::Action),
Suspend, Suspend,
TimeAppletConfig(TimeAppletConfig),
Error(String), Error(String),
Lock, Lock,
Unlock, Unlock,
@ -236,6 +237,7 @@ pub struct App {
common: Common<Message>, common: Common<Message>,
flags: Flags, flags: Flags,
state: State, state: State,
dropdown_opt: Option<Dropdown>,
inhibit_opt: Option<Arc<OwnedFd>>, inhibit_opt: Option<Arc<OwnedFd>>,
value_tx_opt: Option<mpsc::Sender<String>>, value_tx_opt: Option<mpsc::Sender<String>>,
} }
@ -270,22 +272,88 @@ impl App {
]); ]);
} }
//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)
.align_x(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
.class(theme::Container::custom(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
widget::container::Style {
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::custom(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.common.active_layouts.len());
for (i, layout) in self.common.active_layouts.iter().enumerate() {
items.push(menu_checklist(
&layout.description,
i == 0,
Message::KeyboardLayout(i),
));
}
input_button = input_button.popup(dropdown_menu(items));
}
//TODO: implement these buttons //TODO: implement these buttons
let button_row = iced::widget::row![ let button_row = iced::widget::row![
/*TODO: greeter accessibility options
widget::button::custom(widget::icon::from_name( widget::button::custom(widget::icon::from_name(
"applications-accessibility-symbolic" "applications-accessibility-symbolic"
)) ))
.padding(12.0) .padding(12.0)
.on_press(Message::None), .on_press(Message::None),
widget::button::custom(widget::icon::from_name("input-keyboard-symbolic")) */
.padding(12.0) widget::tooltip(
.on_press(Message::None), input_button,
widget::button::custom(widget::icon::from_name("system-users-symbolic")) widget::text(fl!("keyboard-layout")),
.padding(12.0) widget::tooltip::Position::Top
.on_press(Message::None), ),
widget::button::custom(widget::icon::from_name("system-suspend-symbolic")) widget::tooltip(
.padding(12.0) widget::button::custom(widget::icon::from_name("system-suspend-symbolic"))
.on_press(Message::Suspend), .padding(12.0)
.on_press(Message::Suspend),
widget::text(fl!("suspend")),
widget::tooltip::Position::Top
),
] ]
.padding([16.0, 0.0, 0.0, 0.0]) .padding([16.0, 0.0, 0.0, 0.0])
.spacing(8.0); .spacing(8.0);
@ -340,16 +408,21 @@ impl App {
let mut text_input = widget::secure_input( let mut text_input = widget::secure_input(
prompt.clone(), prompt.clone(),
&self.common.input, value.as_str(),
Some(Message::Prompt( Some(
prompt.clone(), common::Message::Prompt(
!*secret, prompt.clone(),
Some(value.clone()), !*secret,
)), Some(value.clone()),
)
.into(),
),
*secret, *secret,
) )
.id(text_input_id) .id(text_input_id)
.on_input(|input| common::Message::Input(input).into()) .on_input(|input| {
common::Message::Prompt(prompt.clone(), *secret, Some(input)).into()
})
.on_submit(Message::Submit); .on_submit(Message::Submit);
if *secret { if *secret {
@ -434,6 +507,7 @@ impl cosmic::Application for App {
Message::OutputEvent(output_event, output) Message::OutputEvent(output_event, output)
})); }));
common.on_session_lock_event = Some(Box::new(|evt| Message::SessionLockEvent(evt))); common.on_session_lock_event = Some(Box::new(|evt| Message::SessionLockEvent(evt)));
common.update_user_data(&flags.user_data);
let already_locked = match flags.lockfile_opt { let already_locked = match flags.lockfile_opt {
Some(ref lockfile) => lockfile.exists(), Some(ref lockfile) => lockfile.exists(),
@ -444,6 +518,7 @@ impl cosmic::Application for App {
common, common,
flags, flags,
state: State::Unlocked, state: State::Unlocked,
dropdown_opt: None,
inhibit_opt: None, inhibit_opt: None,
value_tx_opt: None, value_tx_opt: None,
}; };
@ -783,31 +858,28 @@ impl cosmic::Application for App {
self.common.surface_images.clear(); self.common.surface_images.clear();
self.common.update_wallpapers(&self.flags.user_data); self.common.update_wallpapers(&self.flags.user_data);
} }
Message::TimeAppletConfig(config) => { Message::DropdownToggle(dropdown) => {
self.flags.user_data.time_applet_config = config; if self.dropdown_opt == Some(dropdown) {
self.dropdown_opt = None;
} else {
self.dropdown_opt = Some(dropdown);
}
} }
Message::Inhibit(inhibit) => { Message::Inhibit(inhibit) => {
self.inhibit_opt = Some(inhibit); self.inhibit_opt = Some(inhibit);
} }
Message::Prompt(prompt, secret, value_opt) => { Message::KeyboardLayout(layout_i) => {
let prompt_was_none = self.common.prompt_opt.is_none(); if layout_i < self.common.active_layouts.len() {
self.common.prompt_opt = Some((prompt, secret, value_opt)); self.common.active_layouts.swap(0, layout_i);
if prompt_was_none { self.common.set_xkb_config(&self.flags.user_data);
if let Some(surface_id) = self.common.active_surface_id_opt { }
if let Some(text_input_id) = self if self.dropdown_opt == Some(Dropdown::Keyboard) {
.common self.dropdown_opt = None
.surface_names
.get(&surface_id)
.and_then(|id| self.common.text_input_ids.get(id))
{
log::info!("focus surface found id {:?}", text_input_id);
return widget::text_input::focus(text_input_id.clone());
}
}
} }
} }
Message::Submit(value) => { Message::Submit(value) => {
self.common.input.clear(); self.common.prompt_opt = None;
self.common.error_opt = None;
match self.value_tx_opt.take() { match self.value_tx_opt.take() {
Some(value_tx) => { Some(value_tx) => {
// Clear errors // Clear errors
@ -828,6 +900,9 @@ impl cosmic::Application for App {
cosmic::task::message(cosmic::Action::App(Message::Error(err.to_string()))) cosmic::task::message(cosmic::Action::App(Message::Error(err.to_string())))
}); });
} }
Message::TimeAppletConfig(config) => {
self.flags.user_data.time_applet_config = config;
}
Message::Error(error) => { Message::Error(error) => {
self.common.error_opt = Some(error); self.common.error_opt = Some(error);
} }
@ -914,7 +989,7 @@ impl cosmic::Application for App {
.common .common
.surface_images .surface_images
.get(&surface_id) .get(&surface_id)
.unwrap_or(&self.flags.fallback_background); .unwrap_or(&self.common.fallback_background);
widget::image(img) widget::image(img)
.content_fit(iced::ContentFit::Cover) .content_fit(iced::ContentFit::Cover)
.width(Length::Fill) .width(Length::Fill)