update libcosmic

this uses a subsurface for the greeter menu and a text input that manages its own input string state
This commit is contained in:
Ashley Wulber 2025-02-21 17:05:50 -05:00 committed by Jeremy Soller
parent 5527c9b866
commit 7918485651
9 changed files with 2440 additions and 1464 deletions

2685
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,15 @@ cosmic-greeter-daemon = { path = "daemon" }
dirs = "5"
env_logger.workspace = true
freedesktop_entry_parser = "1.3.0"
libcosmic = { workspace = true, features = ["tokio", "wayland"] }
libcosmic = { workspace = true, features = [
"autosize",
"winit",
"multi-window",
"desktop",
"wayland",
"tokio",
"dbus-config",
] }
log.workspace = true
pam-client = "0.5.0"
pwd.workspace = true
@ -81,6 +89,7 @@ serde = "1"
tokio = "1.39.1"
zbus = "4"
[workspace.dependencies.cosmic-bg-config]
git = "https://github.com/pop-os/cosmic-bg"
default-features = false
@ -103,3 +112,11 @@ default-features = false
[workspace.dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic"
default-features = false
[patch.'https://github.com/pop-os/libcosmic']
libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "drop-menu-tree-changes" }
cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "drop-menu-tree-changes" }
cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "drop-menu-tree-changes" }
# libcosmic = { path = "../libcosmic" }
# cosmic-config = { path = "../libcosmic/cosmic-config" }
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }

File diff suppressed because it is too large Load diff

View file

@ -11,10 +11,9 @@ use tokio::sync::mpsc;
pub fn subscription() -> Subscription<Message> {
struct GreetdSubscription;
cosmic::iced::subscription::channel(
Subscription::run_with_id(
std::any::TypeId::of::<GreetdSubscription>(),
1,
|mut sender| async move {
cosmic::iced_futures::stream::channel(1, |mut sender| async move {
let (tx, mut rx) = mpsc::channel::<greetd_ipc::Request>(1);
_ = sender.send(Message::GreetdChannel(tx)).await;
@ -123,6 +122,6 @@ pub fn subscription() -> Subscription<Message> {
}
futures_util::future::pending().await
},
}),
)
}

View file

@ -1,8 +1,11 @@
use cosmic::iced::widget::{
use cosmic::iced::ContentFit;
use cosmic::iced::{
widget::{
image::{draw, FilterMethod, Handle},
Container,
},
Rotation,
};
use cosmic::iced::ContentFit;
use cosmic::iced_core::event::{self, Event};
use cosmic::iced_core::layout;
use cosmic::iced_core::mouse;
@ -10,26 +13,16 @@ use cosmic::iced_core::overlay;
use cosmic::iced_core::renderer;
use cosmic::iced_core::widget::{Operation, Tree};
use cosmic::iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Widget};
use cosmic::iced_renderer::core::widget::OperationOutputWrapper;
use cosmic::{Renderer, Theme};
pub use cosmic::iced_style::container::StyleSheet;
pub struct ImageContainer<'a, Message, Theme, Renderer>
where
Renderer: cosmic::iced_core::Renderer + cosmic::iced_core::image::Renderer<Handle = Handle>,
Theme: StyleSheet,
{
pub struct ImageContainer<'a, Message> {
container: Container<'a, Message, Theme, Renderer>,
image_opt: Option<Handle>,
content_fit: ContentFit,
}
impl<'a, Message, Renderer> ImageContainer<'a, Message, cosmic::Theme, Renderer>
where
Renderer: cosmic::iced_core::Renderer + cosmic::iced_core::image::Renderer<Handle = Handle>,
cosmic::Theme: StyleSheet,
{
pub fn new(container: Container<'a, Message, cosmic::Theme, Renderer>) -> Self {
impl<'a, Message> ImageContainer<'a, Message> {
pub fn new(container: Container<'a, Message, Theme, Renderer>) -> Self {
Self {
container,
image_opt: None,
@ -48,11 +41,7 @@ where
}
}
impl<'a, Message, Renderer> Widget<Message, cosmic::Theme, Renderer>
for ImageContainer<'a, Message, cosmic::Theme, Renderer>
where
Renderer: cosmic::iced_core::Renderer + cosmic::iced_core::image::Renderer<Handle = Handle>,
{
impl<'a, Message> Widget<Message, Theme, Renderer> for ImageContainer<'a, Message> {
fn children(&self) -> Vec<Tree> {
self.container.children()
}
@ -79,7 +68,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation<()>,
) {
self.container.operate(tree, layout, renderer, operation)
}
@ -116,7 +105,7 @@ where
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &cosmic::Theme,
theme: &Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
@ -129,11 +118,15 @@ where
image,
self.content_fit,
FilterMethod::Linear,
Rotation::default(),
1.,
[0.0, 0.0, 0.0, 0.0],
),
None => {}
}
use cosmic::iced_renderer::core::Renderer as IcedRenderer;
renderer.with_layer(layout.bounds(), |renderer| {
self.container.draw(
tree,
renderer,
@ -143,28 +136,25 @@ where
cursor,
viewport,
)
});
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, cosmic::Theme, Renderer>> {
self.container.overlay(tree, layout, renderer)
translation: cosmic::iced::Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.container.overlay(state, layout, renderer, translation)
}
}
impl<'a, Message, Renderer> From<ImageContainer<'a, Message, cosmic::Theme, Renderer>>
for Element<'a, Message, cosmic::Theme, Renderer>
impl<'a, Message> From<ImageContainer<'a, Message>> for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Renderer:
'a + cosmic::iced_core::Renderer + cosmic::iced_core::image::Renderer<Handle = Handle>,
{
fn from(
container: ImageContainer<'a, Message, cosmic::Theme, Renderer>,
) -> Element<'a, Message, cosmic::Theme, Renderer> {
fn from(container: ImageContainer<'a, Message>) -> Element<'a, Message, Theme, Renderer> {
Element::new(container)
}
}

View file

@ -1,7 +1,8 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::app::{message, Command, Core, Settings};
use cosmic::app::{message, Core, Settings, Task};
use cosmic::surface_message::{MessageWrapper, SurfaceMessage, SurfaceMessageHandler};
use cosmic::{
executor,
iced::{
@ -11,8 +12,9 @@ use cosmic::{
wayland::{Event as WaylandEvent, OutputEvent, SessionLockEvent},
},
futures::{self, SinkExt},
subscription,
wayland::session_lock::{destroy_lock_surface, get_lock_surface, lock, unlock},
platform_specific::shell::wayland::commands::session_lock::{
destroy_lock_surface, get_lock_surface, lock, unlock,
},
Length, Subscription,
},
iced_runtime::core::window::Id as SurfaceId,
@ -48,7 +50,7 @@ pub fn main(current_user: pwd::Passwd) -> Result<(), Box<dyn std::error::Error>>
let icon_path = Path::new("/var/lib/AccountsService/icons").join(&current_user.name);
let icon_opt = if icon_path.is_file() {
match fs::read(&icon_path) {
Ok(icon_data) => Some(widget::image::Handle::from_memory(icon_data)),
Ok(icon_data) => Some(widget::image::Handle::from_bytes(icon_data)),
Err(err) => {
log::error!("failed to read {:?}: {:?}", icon_path, err);
None
@ -119,8 +121,12 @@ impl Conversation {
log::error!("failed to convert prompt to UTF-8: {:?}", err);
pam_client::ErrorCode::CONV_ERR
})?;
futures::executor::block_on(async {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
runtime
.block_on(async {
self.msg_tx
.send(Message::Prompt(
prompt.to_string(),
@ -201,6 +207,21 @@ pub struct Flags {
wallpapers: Vec<(String, cosmic_bg_config::Source)>,
}
impl SurfaceMessageHandler for Message {
fn to_surface_message(self) -> MessageWrapper<Self> {
match self {
Message::Surface(msg) => MessageWrapper::Surface(msg),
msg => MessageWrapper::Message(msg),
}
}
}
impl From<SurfaceMessage> for Message {
fn from(value: SurfaceMessage) -> Self {
Message::Surface(value)
}
}
/// Messages that are used specifically by our [`App`].
#[derive(Clone, Debug)]
pub enum Message {
@ -214,6 +235,7 @@ pub enum Message {
PowerInfo(Option<(String, f64)>),
Prompt(String, bool, Option<String>),
Submit,
Surface(SurfaceMessage),
Suspend,
Error(String),
Lock,
@ -267,7 +289,7 @@ impl App {
cosmic_bg_config::Source::Path(path) => {
match fs::read(path) {
Ok(bytes) => {
let image = widget::image::Handle::from_memory(bytes);
let image = widget::image::Handle::from_bytes(bytes);
self.surface_images.insert(*surface_id, image);
//TODO: what to do about duplicates?
break;
@ -316,7 +338,7 @@ impl cosmic::Application for App {
}
/// Creates the application, and optionally emits command on initialize.
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
fn init(mut core: Core, flags: Self::Flags) -> (Self, Task<Self::Message>) {
core.window.show_window_menu = false;
core.window.show_headerbar = false;
core.window.sharp_corners = true;
@ -354,7 +376,7 @@ impl cosmic::Application for App {
lock()
} else {
// When logind feature is used, wait for lock signal
Command::none()
Task::none()
}
} else {
// When logind feature not used, lock immediately
@ -367,7 +389,7 @@ impl cosmic::Application for App {
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message {
Message::None => {}
Message::OutputEvent(output_event, output) => {
@ -376,6 +398,7 @@ impl cosmic::Application for App {
log::info!("output {}: created", output.id());
let surface_id = SurfaceId::unique();
match self.surface_ids.insert(output.clone(), surface_id) {
Some(old_surface_id) => {
//TODO: remove old surface?
@ -384,6 +407,7 @@ impl cosmic::Application for App {
output.id(),
old_surface_id
);
return Task::none();
}
None => {}
}
@ -409,7 +433,7 @@ impl cosmic::Application for App {
.insert(surface_id, text_input_id.clone());
if matches!(self.state, State::Locked) {
return Command::batch([
return Task::batch([
get_lock_surface(surface_id, output),
widget::text_input::focus(text_input_id),
]);
@ -454,7 +478,7 @@ impl cosmic::Application for App {
for (output, surface_id) in self.surface_ids.iter() {
commands.push(get_lock_surface(*surface_id, output.clone()));
}
return Command::batch(commands);
return Task::batch(commands);
}
SessionLockEvent::Unlocked => {
log::info!("session unlocked");
@ -504,7 +528,7 @@ impl cosmic::Application for App {
Some(value_tx) => {
// Clear errors
self.error_opt = None;
return cosmic::command::future(async move {
return cosmic::task::future(async move {
value_tx.send(value).await.unwrap();
Message::Channel(value_tx)
});
@ -517,7 +541,7 @@ impl cosmic::Application for App {
},
Message::Suspend => {
#[cfg(feature = "logind")]
return cosmic::command::future(async move {
return cosmic::task::future(async move {
match crate::logind::suspend().await {
Ok(()) => message::none(),
Err(err) => {
@ -577,7 +601,7 @@ impl cosmic::Application for App {
// Tell compositor to unlock
commands.push(unlock());
// Wait to exit until `Unlocked` event, when server has processed unlock
return Command::batch(commands);
return Task::batch(commands);
}
State::Locking => {
log::info!("session still locking");
@ -587,8 +611,9 @@ impl cosmic::Application for App {
}
}
}
Message::Surface(_) => {}
}
Command::none()
Task::none()
}
// Not used for layer surface window
@ -607,13 +632,13 @@ impl cosmic::Application for App {
let date = dt.format_localized("%A, %B %-d", locale);
column = column
.push(widget::text::title2(format!("{}", date)).style(style::Text::Accent));
.push(widget::text::title2(format!("{}", date)).class(style::Text::Accent));
let time = dt.format_localized("%R", locale);
column = column.push(
widget::text(format!("{}", time))
.size(112.0)
.style(style::Text::Accent),
.class(style::Text::Accent),
);
column
@ -634,18 +659,18 @@ impl cosmic::Application for App {
//TODO: implement these buttons
let button_row = iced::widget::row![
widget::button(widget::icon::from_name(
widget::button::custom(widget::icon::from_name(
"applications-accessibility-symbolic"
))
.padding(12.0)
.on_press(Message::None),
widget::button(widget::icon::from_name("input-keyboard-symbolic"))
widget::button::custom(widget::icon::from_name("input-keyboard-symbolic"))
.padding(12.0)
.on_press(Message::None),
widget::button(widget::icon::from_name("system-users-symbolic"))
widget::button::custom(widget::icon::from_name("system-users-symbolic"))
.padding(12.0)
.on_press(Message::None),
widget::button(widget::icon::from_name("system-suspend-symbolic"))
widget::button::custom(widget::icon::from_name("system-suspend-symbolic"))
.padding(12.0)
.on_press(Message::Suspend),
]
@ -714,7 +739,7 @@ impl cosmic::Application for App {
*secret,
)
.on_input(|value| Message::Prompt(prompt.clone(), *secret, Some(value)))
.on_submit(Message::Submit);
.on_submit(|_| Message::Submit);
if let Some(text_input_id) = self.text_input_ids.get(&surface_id) {
text_input = text_input.id(text_input_id.clone());
@ -746,18 +771,18 @@ impl cosmic::Application for App {
widget::container(
widget::layer_container(
iced::widget::row![left_element, right_element]
.align_items(alignment::Alignment::Center),
.align_y(alignment::Alignment::Center),
)
.layer(cosmic::cosmic_theme::Layer::Background)
.padding(16)
.style(cosmic::theme::Container::Custom(Box::new(
.class(cosmic::theme::Container::Custom(Box::new(
|theme: &cosmic::Theme| {
// Use background appearance as the base
let mut appearance = widget::container::StyleSheet::appearance(
let mut appearance = widget::container::Catalog::style(
theme,
&cosmic::theme::Container::Background,
);
appearance.border = iced::Border::with_radius(16.0);
appearance.border = iced::Border::default().rounded(16.0);
appearance
},
)))
@ -768,12 +793,12 @@ impl cosmic::Application for App {
.height(Length::Fill)
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Top)
.style(cosmic::theme::Container::Transparent),
.class(cosmic::theme::Container::Transparent),
)
.image(match self.surface_images.get(&surface_id) {
Some(some) => some.clone(),
//TODO: default image
None => widget::image::Handle::from_pixels(1, 1, vec![0x00, 0x00, 0x00, 0xFF]),
None => widget::image::Handle::from_rgba(1, 1, vec![0x00, 0x00, 0x00, 0xFF]),
})
.content_fit(iced::ContentFit::Cover)
.into()
@ -782,7 +807,7 @@ impl cosmic::Application for App {
fn subscription(&self) -> Subscription<Self::Message> {
let mut subscriptions = Vec::with_capacity(7);
subscriptions.push(event::listen_with(|event, _| match event {
subscriptions.push(event::listen_with(|event, _, _| match event {
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(
wayland_event,
)) => match wayland_event {
@ -812,26 +837,24 @@ impl cosmic::Application for App {
if matches!(self.state, State::Locked) {
struct HeartbeatSubscription;
subscriptions.push(subscription::channel(
subscriptions.push(Subscription::run_with_id(
TypeId::of::<HeartbeatSubscription>(),
16,
|mut msg_tx| async move {
cosmic::iced_futures::stream::channel(16, |mut msg_tx| async move {
loop {
// Send heartbeat once a second to update time
//TODO: only send this when needed
msg_tx.send(Message::None).await.unwrap();
time::sleep(time::Duration::new(1, 0)).await;
}
},
}),
));
struct PamSubscription;
//TODO: how to avoid cloning this on every time subscription is called?
let username = self.flags.current_user.name.clone();
subscriptions.push(subscription::channel(
subscriptions.push(Subscription::run_with_id(
TypeId::of::<PamSubscription>(),
16,
|mut msg_tx| async move {
cosmic::iced_futures::stream::channel(16, |mut msg_tx| async move {
loop {
let (value_tx, value_rx) = mpsc::channel(16);
msg_tx.send(Message::Channel(value_tx)).await.unwrap();
@ -862,7 +885,7 @@ impl cosmic::Application for App {
loop {
time::sleep(time::Duration::new(60, 0)).await;
}
},
}),
));
}

View file

@ -1,6 +1,6 @@
use cosmic::iced::{
futures::{channel::mpsc, SinkExt, StreamExt},
subscription, Subscription,
Subscription,
};
use logind_zbus::{
manager::{InhibitType, ManagerProxy},
@ -46,10 +46,9 @@ async fn inhibit(manager: &ManagerProxy<'_>) -> zbus::Result<OwnedFd> {
pub fn subscription() -> Subscription<Message> {
struct LogindSubscription;
subscription::channel(
Subscription::run_with_id(
TypeId::of::<LogindSubscription>(),
16,
|mut msg_tx| async move {
cosmic::iced_futures::stream::channel(16, |mut msg_tx| async move {
match handler(&mut msg_tx).await {
Ok(()) => {}
Err(err) => {
@ -59,7 +58,7 @@ pub fn subscription() -> Subscription<Message> {
}
std::process::exit(1);
},
}),
)
}

View file

@ -1,6 +1,6 @@
use cosmic::iced::{
futures::{channel::mpsc, SinkExt, StreamExt},
subscription, Subscription,
Subscription,
};
use cosmic_dbus_networkmanager::{device::SpecificDevice, nm::NetworkManager};
use std::{any::TypeId, cmp};
@ -37,10 +37,9 @@ impl NetworkIcon {
pub fn subscription() -> Subscription<Option<&'static str>> {
struct NetworkSubscription;
subscription::channel(
Subscription::run_with_id(
TypeId::of::<NetworkSubscription>(),
16,
|mut msg_tx| async move {
cosmic::iced_futures::stream::channel(16, |mut msg_tx| async move {
match handler(&mut msg_tx).await {
Ok(()) => {}
Err(err) => {
@ -56,7 +55,7 @@ pub fn subscription() -> Subscription<Option<&'static str>> {
loop {
time::sleep(time::Duration::new(60, 0)).await;
}
},
}),
)
}

View file

@ -1,6 +1,6 @@
use cosmic::iced::{
futures::{channel::mpsc, SinkExt, StreamExt},
subscription, Subscription,
Subscription,
};
use std::any::TypeId;
use tokio::time;
@ -10,10 +10,9 @@ use zbus::{Connection, Result};
pub fn subscription() -> Subscription<Option<(String, f64)>> {
struct PowerSubscription;
subscription::channel(
Subscription::run_with_id(
TypeId::of::<PowerSubscription>(),
16,
|mut msg_tx| async move {
cosmic::iced_futures::stream::channel(16, |mut msg_tx| async move {
match handler(&mut msg_tx).await {
Ok(()) => {}
Err(err) => {
@ -29,7 +28,7 @@ pub fn subscription() -> Subscription<Option<(String, f64)>> {
loop {
time::sleep(time::Duration::new(60, 0)).await;
}
},
}),
)
}