refactor: lock surface subsurface menu
This commit is contained in:
parent
b84ee1827c
commit
d6b94406e6
5 changed files with 573 additions and 616 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
|
@ -1046,7 +1046,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"atomicwrites",
|
||||
"calloop 0.14.2",
|
||||
|
|
@ -1069,7 +1069,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
|
|
@ -1212,7 +1212,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-theme"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"almost",
|
||||
"cosmic-config",
|
||||
|
|
@ -2487,7 +2487,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"dnd",
|
||||
"iced_accessibility",
|
||||
|
|
@ -2505,7 +2505,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_accessibility"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_winit",
|
||||
|
|
@ -2514,7 +2514,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_core"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"bytes",
|
||||
|
|
@ -2538,7 +2538,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_futures"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"iced_core",
|
||||
|
|
@ -2564,7 +2564,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_graphics"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"bytemuck",
|
||||
|
|
@ -2586,7 +2586,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_renderer"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"iced_graphics",
|
||||
"iced_tiny_skia",
|
||||
|
|
@ -2598,7 +2598,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_runtime"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cosmic-client-toolkit",
|
||||
|
|
@ -2613,7 +2613,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_tiny_skia"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cosmic-text",
|
||||
|
|
@ -2629,7 +2629,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_wgpu"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"as-raw-xcb-connection",
|
||||
"bitflags 2.8.0",
|
||||
|
|
@ -2660,7 +2660,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_widget"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"cosmic-client-toolkit",
|
||||
"dnd",
|
||||
|
|
@ -2678,7 +2678,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_winit"
|
||||
version = "0.14.0-dev"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"cosmic-client-toolkit",
|
||||
"dnd",
|
||||
|
|
@ -3144,7 +3144,7 @@ checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
|||
[[package]]
|
||||
name = "libcosmic"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#4c78a5620218a67f6653b171e9136f4bd9e89bf1"
|
||||
source = "git+https://github.com/pop-os/libcosmic//?branch=drop-menu-tree-changes#5976b48f677649067d1240f6386d153261204278"
|
||||
dependencies = [
|
||||
"apply",
|
||||
"ashpd 0.9.2",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ mod ipc;
|
|||
|
||||
use cosmic::app::{Core, Settings, Task};
|
||||
use cosmic::cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity;
|
||||
use cosmic::iced::{Point, Rectangle, Size};
|
||||
use cosmic::iced::{Point, Size};
|
||||
use cosmic::iced_core::{image, window};
|
||||
use cosmic::iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings;
|
||||
use cosmic::surface;
|
||||
|
|
@ -400,11 +400,11 @@ pub enum Message {
|
|||
DropdownToggle(Dropdown),
|
||||
Error(String),
|
||||
Exit,
|
||||
Focus(SurfaceId),
|
||||
// Sets channel used to communicate with the greetd IPC subscription.
|
||||
GreetdChannel(tokio::sync::mpsc::Sender<Request>),
|
||||
Heartbeat,
|
||||
KeyboardLayout(usize),
|
||||
LayerEvent(LayerEvent, SurfaceId),
|
||||
Login,
|
||||
NetworkIcon(Option<&'static str>),
|
||||
None,
|
||||
|
|
@ -419,7 +419,6 @@ pub enum Message {
|
|||
Surface(surface::Action),
|
||||
Suspend,
|
||||
Username(String),
|
||||
WindowOpen(SurfaceId),
|
||||
}
|
||||
|
||||
/// The [`App`] stores application-specific state.
|
||||
|
|
@ -718,9 +717,6 @@ impl App {
|
|||
)
|
||||
.id(text_input_id)
|
||||
.manage_value(true)
|
||||
// .on_input(|value| {
|
||||
// Message::Prompt(prompt.clone(), *secret, Some(value))
|
||||
// })
|
||||
.on_submit(|v| Message::Auth(Some(v)));
|
||||
|
||||
if let Some(text_input_id) = self
|
||||
|
|
@ -1141,13 +1137,16 @@ impl cosmic::Application for App {
|
|||
Size::new(800., unwrapped_size.1 as f32 - 32.),
|
||||
)
|
||||
} else {
|
||||
(Point::ORIGIN, Size::new(1920., 1080.))
|
||||
(
|
||||
Point::new(0., 32.),
|
||||
Size::new(unwrapped_size.0 as f32, unwrapped_size.1 as f32 - 32.),
|
||||
)
|
||||
};
|
||||
self.window_size.insert(
|
||||
surface_id,
|
||||
Size::new(unwrapped_size.0 as f32, unwrapped_size.1 as f32),
|
||||
);
|
||||
dbg!(loc, size);
|
||||
|
||||
let msg = cosmic::surface::action::subsurface(
|
||||
move |_: &mut App| SctkSubsurfaceSettings {
|
||||
parent: surface_id,
|
||||
|
|
@ -1209,20 +1208,6 @@ impl cosmic::Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
Message::LayerEvent(layer_event, surface_id) => match layer_event {
|
||||
LayerEvent::Focused => {
|
||||
log::info!("focus surface {:?}", surface_id);
|
||||
self.active_surface_id_opt = Some(surface_id);
|
||||
if let Some(text_input_id) = self
|
||||
.surface_names
|
||||
.get(&surface_id)
|
||||
.and_then(|id| self.text_input_ids.get(id))
|
||||
{
|
||||
return widget::text_input::focus(text_input_id.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Message::Socket(socket_state) => {
|
||||
self.socket_state = socket_state;
|
||||
match &self.socket_state {
|
||||
|
|
@ -1481,22 +1466,17 @@ impl cosmic::Application for App {
|
|||
cosmic::app::Action::Surface(a),
|
||||
));
|
||||
}
|
||||
Message::WindowOpen(id) if self.surface_ids.values().any(|i| *i == id) => {
|
||||
if let Some(text_input_id) = self.surface_names.get(&id).and_then(|id| {
|
||||
if self
|
||||
.active_surface_id_opt
|
||||
.and_then(|active_id| self.surface_names.get(&active_id))
|
||||
== Some(id)
|
||||
|
||||
Message::Focus(surface_id) => {
|
||||
self.active_surface_id_opt = Some(surface_id);
|
||||
if let Some(text_input_id) = self
|
||||
.surface_names
|
||||
.get(&surface_id)
|
||||
.and_then(|id| self.text_input_ids.get(id))
|
||||
{
|
||||
self.text_input_ids.get(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
return widget::text_input::focus(text_input_id.clone());
|
||||
}
|
||||
}
|
||||
Message::WindowOpen(_) => {}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -1530,12 +1510,10 @@ impl cosmic::Application for App {
|
|||
WaylandEvent::Output(output_event, output) => {
|
||||
Some(Message::OutputEvent(output_event, output))
|
||||
}
|
||||
WaylandEvent::Layer(layer_event, _surface, surface_id) => {
|
||||
Some(Message::LayerEvent(layer_event, surface_id))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
},
|
||||
iced::Event::Window(window::Event::Opened { .. }) => Some(Message::WindowOpen(id)),
|
||||
iced::Event::Window(iced::window::Event::Focused) => Some(Message::Focus(id)),
|
||||
_ => None,
|
||||
}),
|
||||
Subscription::run_with_id(
|
||||
|
|
|
|||
|
|
@ -1,160 +0,0 @@
|
|||
use cosmic::iced::ContentFit;
|
||||
use cosmic::iced::{
|
||||
widget::{
|
||||
image::{draw, FilterMethod, Handle},
|
||||
Container,
|
||||
},
|
||||
Rotation,
|
||||
};
|
||||
use cosmic::iced_core::event::{self, Event};
|
||||
use cosmic::iced_core::layout;
|
||||
use cosmic::iced_core::mouse;
|
||||
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::{Renderer, Theme};
|
||||
|
||||
pub struct ImageContainer<'a, Message> {
|
||||
container: Container<'a, Message, Theme, Renderer>,
|
||||
image_opt: Option<Handle>,
|
||||
content_fit: ContentFit,
|
||||
}
|
||||
|
||||
impl<'a, Message> ImageContainer<'a, Message> {
|
||||
pub fn new(container: Container<'a, Message, Theme, Renderer>) -> Self {
|
||||
Self {
|
||||
container,
|
||||
image_opt: None,
|
||||
content_fit: ContentFit::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image(mut self, image: Handle) -> Self {
|
||||
self.image_opt = Some(image);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn content_fit(mut self, content_fit: ContentFit) -> Self {
|
||||
self.content_fit = content_fit;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> Widget<Message, Theme, Renderer> for ImageContainer<'a, Message> {
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.container.children()
|
||||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
self.container.diff(tree)
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<Length> {
|
||||
self.container.size()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.container.layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<()>,
|
||||
) {
|
||||
self.container.operate(tree, layout, renderer, operation)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.container.on_event(
|
||||
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.container
|
||||
.mouse_interaction(tree, layout, cursor, viewport, renderer)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Theme,
|
||||
renderer_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
match &self.image_opt {
|
||||
Some(image) => draw(
|
||||
renderer,
|
||||
layout,
|
||||
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,
|
||||
theme,
|
||||
renderer_style,
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
translation: cosmic::iced::Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.container.overlay(state, layout, renderer, translation)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> From<ImageContainer<'a, Message>> for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(container: ImageContainer<'a, Message>) -> Element<'a, Message, Theme, Renderer> {
|
||||
Element::new(container)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
pub mod greeter;
|
||||
pub mod locker;
|
||||
|
||||
mod image_container;
|
||||
mod localize;
|
||||
|
||||
#[cfg(feature = "logind")]
|
||||
|
|
|
|||
894
src/locker.rs
894
src/locker.rs
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::app::{Action, Core, Settings, Task};
|
||||
use cosmic::app::{Core, Settings, Task};
|
||||
use cosmic::cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity;
|
||||
use cosmic::iced::{Point, Rectangle, Size};
|
||||
use cosmic::iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings;
|
||||
use cosmic::surface;
|
||||
use cosmic::{
|
||||
executor,
|
||||
|
|
@ -74,12 +77,15 @@ pub fn main(current_user: pwd::Passwd) -> Result<(), Box<dyn std::error::Error>>
|
|||
log::error!("failed to create cosmic-bg state helper: {:?}", err);
|
||||
}
|
||||
}
|
||||
let fallback_background =
|
||||
widget::image::Handle::from_bytes(include_bytes!("../res/background.jpg").as_slice());
|
||||
|
||||
let flags = Flags {
|
||||
current_user,
|
||||
icon_opt,
|
||||
lockfile_opt: lockfile_opt(),
|
||||
wallpapers,
|
||||
fallback_background,
|
||||
};
|
||||
|
||||
let settings = Settings::default().no_main_window(true);
|
||||
|
|
@ -205,6 +211,7 @@ pub struct Flags {
|
|||
icon_opt: Option<widget::image::Handle>,
|
||||
lockfile_opt: Option<PathBuf>,
|
||||
wallpapers: Vec<(String, cosmic_bg_config::Source)>,
|
||||
fallback_background: widget::image::Handle,
|
||||
}
|
||||
|
||||
/// Messages that are used specifically by our [`App`].
|
||||
|
|
@ -215,11 +222,12 @@ pub enum Message {
|
|||
SessionLockEvent(SessionLockEvent),
|
||||
Channel(mpsc::Sender<String>),
|
||||
BackgroundState(cosmic_bg_config::state::State),
|
||||
Focus(SurfaceId),
|
||||
Inhibit(Arc<OwnedFd>),
|
||||
NetworkIcon(Option<&'static str>),
|
||||
PowerInfo(Option<(String, f64)>),
|
||||
Prompt(String, bool, Option<String>),
|
||||
Submit,
|
||||
Submit(String),
|
||||
Surface(surface::Action),
|
||||
Suspend,
|
||||
Error(String),
|
||||
|
|
@ -240,11 +248,13 @@ pub struct App {
|
|||
core: Core,
|
||||
flags: Flags,
|
||||
state: State,
|
||||
output_names: HashMap<WlOutput, String>,
|
||||
surface_ids: HashMap<WlOutput, SurfaceId>,
|
||||
subsurface_rects: HashMap<WlOutput, Rectangle>,
|
||||
active_surface_id_opt: Option<SurfaceId>,
|
||||
surface_images: HashMap<SurfaceId, widget::image::Handle>,
|
||||
surface_names: HashMap<SurfaceId, String>,
|
||||
text_input_ids: HashMap<SurfaceId, widget::Id>,
|
||||
text_input_ids: HashMap<String, widget::Id>,
|
||||
inhibit_opt: Option<Arc<OwnedFd>>,
|
||||
network_icon_opt: Option<&'static str>,
|
||||
power_info_opt: Option<(String, f64)>,
|
||||
|
|
@ -254,364 +264,7 @@ pub struct App {
|
|||
}
|
||||
|
||||
impl App {
|
||||
//TODO: cache wallpapers by source?
|
||||
fn update_wallpapers(&mut self) {
|
||||
for (output, surface_id) in self.surface_ids.iter() {
|
||||
if self.surface_images.contains_key(surface_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let output_name = match self.surface_names.get(surface_id) {
|
||||
Some(some) => some,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
log::info!("updating wallpaper for {:?}", output_name);
|
||||
|
||||
for (wallpaper_output_name, wallpaper_source) in self.flags.wallpapers.iter() {
|
||||
if wallpaper_output_name == output_name {
|
||||
match wallpaper_source {
|
||||
cosmic_bg_config::Source::Path(path) => {
|
||||
match fs::read(path) {
|
||||
Ok(bytes) => {
|
||||
let image = widget::image::Handle::from_bytes(bytes);
|
||||
self.surface_images.insert(*surface_id, image);
|
||||
//TODO: what to do about duplicates?
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"output {}: failed to load wallpaper {:?}: {:?}",
|
||||
output.id(),
|
||||
path,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
cosmic_bg_config::Source::Color(color) => {
|
||||
//TODO: support color sources
|
||||
log::warn!("output {}: unsupported source {:?}", output.id(), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||
impl cosmic::Application for App {
|
||||
/// Default async executor to use with the app.
|
||||
type Executor = executor::Default;
|
||||
|
||||
/// Argument received [`cosmic::Application::new`].
|
||||
type Flags = Flags;
|
||||
|
||||
/// Message type specific to our [`App`].
|
||||
type Message = Message;
|
||||
|
||||
/// The unique application ID to supply to the window manager.
|
||||
const APP_ID: &'static str = "com.system76.CosmicGreeter";
|
||||
|
||||
fn core(&self) -> &Core {
|
||||
&self.core
|
||||
}
|
||||
|
||||
fn core_mut(&mut self) -> &mut Core {
|
||||
&mut self.core
|
||||
}
|
||||
|
||||
/// Creates the application, and optionally emits command on initialize.
|
||||
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;
|
||||
core.window.show_maximize = false;
|
||||
core.window.show_minimize = false;
|
||||
core.window.use_template = false;
|
||||
|
||||
let already_locked = match flags.lockfile_opt {
|
||||
Some(ref lockfile) => lockfile.exists(),
|
||||
None => false,
|
||||
};
|
||||
|
||||
let mut app = App {
|
||||
core,
|
||||
flags,
|
||||
state: State::Unlocked,
|
||||
surface_ids: HashMap::new(),
|
||||
active_surface_id_opt: None,
|
||||
surface_images: HashMap::new(),
|
||||
surface_names: HashMap::new(),
|
||||
text_input_ids: HashMap::new(),
|
||||
inhibit_opt: None,
|
||||
network_icon_opt: None,
|
||||
power_info_opt: None,
|
||||
value_tx_opt: None,
|
||||
prompt_opt: None,
|
||||
error_opt: None,
|
||||
};
|
||||
|
||||
let command = if cfg!(feature = "logind") {
|
||||
if already_locked {
|
||||
// Recover previously locked state
|
||||
log::info!("recovering previous locked state");
|
||||
app.state = State::Locking;
|
||||
lock()
|
||||
} else {
|
||||
// When logind feature is used, wait for lock signal
|
||||
Task::none()
|
||||
}
|
||||
} else {
|
||||
// When logind feature not used, lock immediately
|
||||
log::info!("locking immediately");
|
||||
app.state = State::Locking;
|
||||
lock()
|
||||
};
|
||||
|
||||
(app, command)
|
||||
}
|
||||
|
||||
/// Handle application events here.
|
||||
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
|
||||
match message {
|
||||
Message::None => {}
|
||||
Message::OutputEvent(output_event, output) => {
|
||||
match output_event {
|
||||
OutputEvent::Created(output_info_opt) => {
|
||||
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?
|
||||
log::warn!(
|
||||
"output {}: already had surface ID {:?}",
|
||||
output.id(),
|
||||
old_surface_id
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
match output_info_opt {
|
||||
Some(output_info) => match output_info.name {
|
||||
Some(output_name) => {
|
||||
self.surface_names.insert(surface_id, output_name.clone());
|
||||
self.surface_images.remove(&surface_id);
|
||||
self.update_wallpapers();
|
||||
}
|
||||
None => {
|
||||
log::warn!("output {}: no output name", output.id());
|
||||
}
|
||||
},
|
||||
None => {
|
||||
log::warn!("output {}: no output info", output.id());
|
||||
}
|
||||
}
|
||||
|
||||
let text_input_id = widget::Id::unique();
|
||||
self.text_input_ids
|
||||
.insert(surface_id, text_input_id.clone());
|
||||
|
||||
if matches!(self.state, State::Locked) {
|
||||
return Task::batch([
|
||||
get_lock_surface(surface_id, output),
|
||||
widget::text_input::focus(text_input_id),
|
||||
]);
|
||||
}
|
||||
}
|
||||
OutputEvent::Removed => {
|
||||
log::info!("output {}: removed", output.id());
|
||||
match self.surface_ids.remove(&output) {
|
||||
Some(surface_id) => {
|
||||
self.surface_images.remove(&surface_id);
|
||||
self.surface_names.remove(&surface_id);
|
||||
self.text_input_ids.remove(&surface_id);
|
||||
if matches!(self.state, State::Locked) {
|
||||
return destroy_lock_surface(surface_id);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("output {}: no surface found", output.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
OutputEvent::InfoUpdate(_output_info) => {
|
||||
log::info!("output {}: info update", output.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::SessionLockEvent(session_lock_event) => match session_lock_event {
|
||||
SessionLockEvent::Focused(_, surface_id) => {
|
||||
log::info!("focus surface {:?}", surface_id);
|
||||
self.active_surface_id_opt = Some(surface_id);
|
||||
if let Some(text_input_id) = self.text_input_ids.get(&surface_id) {
|
||||
return widget::text_input::focus(text_input_id.clone());
|
||||
}
|
||||
}
|
||||
SessionLockEvent::Locked => {
|
||||
log::info!("session locked");
|
||||
self.state = State::Locked;
|
||||
// Allow suspend
|
||||
self.inhibit_opt = None;
|
||||
// Create lock surfaces
|
||||
let mut commands = Vec::with_capacity(self.surface_ids.len());
|
||||
for (output, surface_id) in self.surface_ids.iter() {
|
||||
commands.push(get_lock_surface(*surface_id, output.clone()));
|
||||
}
|
||||
return Task::batch(commands);
|
||||
}
|
||||
SessionLockEvent::Unlocked => {
|
||||
log::info!("session unlocked");
|
||||
self.state = State::Unlocked;
|
||||
if cfg!(feature = "logind") {
|
||||
// When using logind feature, stick around for more lock signals
|
||||
} else {
|
||||
// When not using logind feature, exit immediately after unlocking
|
||||
//TODO: cleaner method to exit?
|
||||
process::exit(0);
|
||||
}
|
||||
}
|
||||
//TODO: handle finished signal
|
||||
_ => {}
|
||||
},
|
||||
Message::Channel(value_tx) => {
|
||||
self.value_tx_opt = Some(value_tx);
|
||||
}
|
||||
Message::BackgroundState(background_state) => {
|
||||
self.flags.wallpapers = background_state.wallpapers;
|
||||
self.surface_images.clear();
|
||||
self.update_wallpapers();
|
||||
}
|
||||
Message::Inhibit(inhibit) => {
|
||||
self.inhibit_opt = Some(inhibit);
|
||||
}
|
||||
Message::NetworkIcon(network_icon_opt) => {
|
||||
self.network_icon_opt = network_icon_opt;
|
||||
}
|
||||
Message::PowerInfo(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.text_input_ids.get(&surface_id) {
|
||||
return widget::text_input::focus(text_input_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Submit => match self.prompt_opt.take() {
|
||||
Some((_prompt, _secret, value_opt)) => match value_opt {
|
||||
Some(value) => match self.value_tx_opt.take() {
|
||||
Some(value_tx) => {
|
||||
// Clear errors
|
||||
self.error_opt = None;
|
||||
return cosmic::task::future(async move {
|
||||
value_tx.send(value).await.unwrap();
|
||||
Message::Channel(value_tx)
|
||||
});
|
||||
}
|
||||
None => log::warn!("tried to submit when value_tx_opt not set"),
|
||||
},
|
||||
None => log::warn!("tried to submit without value"),
|
||||
},
|
||||
None => log::warn!("tried to submit without prompt"),
|
||||
},
|
||||
Message::Suspend => {
|
||||
#[cfg(feature = "logind")]
|
||||
return cosmic::task::future(async move {
|
||||
match crate::logind::suspend().await {
|
||||
Ok(()) => cosmic::action::none(),
|
||||
Err(err) => {
|
||||
log::error!("failed to suspend: {:?}", err);
|
||||
cosmic::Action::App(Message::Error(err.to_string()))
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Message::Error(error) => {
|
||||
self.error_opt = Some(error);
|
||||
}
|
||||
Message::Lock => match self.state {
|
||||
State::Unlocked => {
|
||||
log::info!("session locking");
|
||||
self.state = State::Locking;
|
||||
// Clear errors
|
||||
self.error_opt = None;
|
||||
// Clear value_tx
|
||||
self.value_tx_opt = None;
|
||||
// Try to create lockfile when locking
|
||||
if let Some(ref lockfile) = self.flags.lockfile_opt {
|
||||
if let Err(err) = fs::File::create(lockfile) {
|
||||
log::warn!("failed to create lockfile {:?}: {}", lockfile, err);
|
||||
}
|
||||
}
|
||||
// Tell compositor to lock
|
||||
return lock();
|
||||
}
|
||||
State::Unlocking => {
|
||||
log::info!("session still unlocking");
|
||||
}
|
||||
State::Locking | State::Locked => {
|
||||
log::info!("session already locking or locked");
|
||||
}
|
||||
},
|
||||
Message::Unlock => {
|
||||
match self.state {
|
||||
State::Locked => {
|
||||
log::info!("sessing unlocking");
|
||||
self.state = State::Unlocking;
|
||||
// Clear errors
|
||||
self.error_opt = None;
|
||||
// Clear value_tx
|
||||
self.value_tx_opt = None;
|
||||
// Try to delete lockfile when unlocking
|
||||
if let Some(ref lockfile) = self.flags.lockfile_opt {
|
||||
if let Err(err) = fs::remove_file(lockfile) {
|
||||
log::warn!("failed to remove lockfile {:?}: {}", lockfile, err);
|
||||
}
|
||||
}
|
||||
// Destroy lock surfaces
|
||||
let mut commands = Vec::with_capacity(self.surface_ids.len() + 1);
|
||||
for (_output, surface_id) in self.surface_ids.iter() {
|
||||
commands.push(destroy_lock_surface(*surface_id));
|
||||
}
|
||||
// Tell compositor to unlock
|
||||
commands.push(unlock());
|
||||
// Wait to exit until `Unlocked` event, when server has processed unlock
|
||||
return Task::batch(commands);
|
||||
}
|
||||
State::Locking => {
|
||||
log::info!("session still locking");
|
||||
}
|
||||
State::Unlocking | State::Unlocked => {
|
||||
log::info!("session already unlocking or unlocked");
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Surface(a) => {
|
||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(a),
|
||||
));
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// Not used for layer surface window
|
||||
fn view(&self) -> Element<Self::Message> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
fn view_window(&self, surface_id: SurfaceId) -> Element<Self::Message> {
|
||||
fn menu<'a>(&'a self, surface_id: SurfaceId) -> Element<'a, Message> {
|
||||
let left_element = {
|
||||
let date_time_column = {
|
||||
let mut column = widget::column::with_capacity(2).padding(16.0);
|
||||
|
|
@ -717,9 +370,16 @@ impl cosmic::Application for App {
|
|||
match &self.prompt_opt {
|
||||
Some((prompt, secret, value_opt)) => match value_opt {
|
||||
Some(value) => {
|
||||
let text_input_id = self
|
||||
.surface_names
|
||||
.get(&surface_id)
|
||||
.and_then(|id| self.text_input_ids.get(id))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| cosmic::widget::Id::new("text_input"));
|
||||
|
||||
let mut text_input = widget::secure_input(
|
||||
prompt.clone(),
|
||||
value.clone(),
|
||||
"",
|
||||
Some(Message::Prompt(
|
||||
prompt.clone(),
|
||||
!*secret,
|
||||
|
|
@ -727,12 +387,9 @@ impl cosmic::Application for App {
|
|||
)),
|
||||
*secret,
|
||||
)
|
||||
.on_input(|value| Message::Prompt(prompt.clone(), *secret, Some(value)))
|
||||
.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());
|
||||
}
|
||||
.id(text_input_id)
|
||||
.manage_value(true)
|
||||
.on_submit(|v| Message::Submit(v));
|
||||
|
||||
if *secret {
|
||||
text_input = text_input.password()
|
||||
|
|
@ -756,7 +413,6 @@ impl cosmic::Application for App {
|
|||
.width(Length::Fill)
|
||||
};
|
||||
|
||||
crate::image_container::ImageContainer::new(
|
||||
widget::container(
|
||||
widget::layer_container(
|
||||
iced::widget::row![left_element, right_element]
|
||||
|
|
@ -775,28 +431,511 @@ impl cosmic::Application for App {
|
|||
appearance
|
||||
},
|
||||
)))
|
||||
.width(Length::Fixed(800.0)),
|
||||
.width(Length::Fill)
|
||||
.height(Length::Shrink),
|
||||
)
|
||||
.padding([32.0, 0.0, 0.0, 0.0])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.align_x(alignment::Horizontal::Center)
|
||||
.align_y(alignment::Vertical::Top)
|
||||
.class(cosmic::theme::Container::Transparent),
|
||||
.class(cosmic::theme::Container::Transparent)
|
||||
.into()
|
||||
}
|
||||
|
||||
//TODO: cache wallpapers by source?
|
||||
fn update_wallpapers(&mut self) {
|
||||
for (output, surface_id) in self.surface_ids.iter() {
|
||||
if self.surface_images.contains_key(surface_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let output_name = match self.surface_names.get(surface_id) {
|
||||
Some(some) => some,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
log::info!("updating wallpaper for {:?}", output_name);
|
||||
|
||||
for (wallpaper_output_name, wallpaper_source) in self.flags.wallpapers.iter() {
|
||||
if wallpaper_output_name == output_name {
|
||||
match wallpaper_source {
|
||||
cosmic_bg_config::Source::Path(path) => {
|
||||
match fs::read(path) {
|
||||
Ok(bytes) => {
|
||||
let image = widget::image::Handle::from_bytes(bytes);
|
||||
self.surface_images.insert(*surface_id, image);
|
||||
//TODO: what to do about duplicates?
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"output {}: failed to load wallpaper {:?}: {:?}",
|
||||
output.id(),
|
||||
path,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
cosmic_bg_config::Source::Color(color) => {
|
||||
//TODO: support color sources
|
||||
log::warn!("output {}: unsupported source {:?}", output.id(), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||
impl cosmic::Application for App {
|
||||
/// Default async executor to use with the app.
|
||||
type Executor = executor::Default;
|
||||
|
||||
/// Argument received [`cosmic::Application::new`].
|
||||
type Flags = Flags;
|
||||
|
||||
/// Message type specific to our [`App`].
|
||||
type Message = Message;
|
||||
|
||||
/// The unique application ID to supply to the window manager.
|
||||
const APP_ID: &'static str = "com.system76.CosmicGreeter";
|
||||
|
||||
fn core(&self) -> &Core {
|
||||
&self.core
|
||||
}
|
||||
|
||||
fn core_mut(&mut self) -> &mut Core {
|
||||
&mut self.core
|
||||
}
|
||||
|
||||
/// Creates the application, and optionally emits command on initialize.
|
||||
fn init(mut core: Core, flags: Self::Flags) -> (Self, Task<Self::Message>) {
|
||||
core.window.show_window_menu = false;
|
||||
core.window.show_headerbar = false;
|
||||
// XXX must be false or define custom style to have transparent bg
|
||||
core.window.sharp_corners = false;
|
||||
core.window.show_maximize = false;
|
||||
core.window.show_minimize = false;
|
||||
core.window.use_template = false;
|
||||
|
||||
let already_locked = match flags.lockfile_opt {
|
||||
Some(ref lockfile) => lockfile.exists(),
|
||||
None => false,
|
||||
};
|
||||
|
||||
let mut app = App {
|
||||
core,
|
||||
flags,
|
||||
state: State::Unlocked,
|
||||
surface_ids: HashMap::new(),
|
||||
active_surface_id_opt: None,
|
||||
output_names: HashMap::new(),
|
||||
surface_images: HashMap::new(),
|
||||
surface_names: HashMap::new(),
|
||||
text_input_ids: HashMap::new(),
|
||||
subsurface_rects: HashMap::new(),
|
||||
inhibit_opt: None,
|
||||
network_icon_opt: None,
|
||||
power_info_opt: None,
|
||||
value_tx_opt: None,
|
||||
prompt_opt: None,
|
||||
error_opt: None,
|
||||
};
|
||||
|
||||
let command = if cfg!(feature = "logind") {
|
||||
if already_locked {
|
||||
// Recover previously locked state
|
||||
log::info!("recovering previous locked state");
|
||||
app.state = State::Locking;
|
||||
lock()
|
||||
} else {
|
||||
// When logind feature is used, wait for lock signal
|
||||
Task::none()
|
||||
}
|
||||
} else {
|
||||
// When logind feature not used, lock immediately
|
||||
log::info!("locking immediately");
|
||||
app.state = State::Locking;
|
||||
lock()
|
||||
};
|
||||
|
||||
(app, command)
|
||||
}
|
||||
|
||||
/// Handle application events here.
|
||||
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
|
||||
match message {
|
||||
Message::None => {}
|
||||
Message::OutputEvent(output_event, output) => {
|
||||
match output_event {
|
||||
OutputEvent::Created(output_info_opt) => {
|
||||
log::info!("output {}: created", output.id());
|
||||
|
||||
let surface_id = SurfaceId::unique();
|
||||
let subsurface_id = SurfaceId::unique();
|
||||
|
||||
match self.surface_ids.insert(output.clone(), surface_id) {
|
||||
Some(old_surface_id) => {
|
||||
//TODO: remove old surface?
|
||||
log::warn!(
|
||||
"output {}: already had surface ID {:?}",
|
||||
output.id(),
|
||||
old_surface_id
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
let size = if let Some((w, h)) =
|
||||
output_info_opt.as_ref().and_then(|info| info.logical_size)
|
||||
{
|
||||
Some((Some(w as u32), Some(h as u32)))
|
||||
} else {
|
||||
Some((None, None))
|
||||
};
|
||||
match output_info_opt {
|
||||
Some(output_info) => match output_info.name {
|
||||
Some(output_name) => {
|
||||
self.output_names
|
||||
.insert(output.clone(), output_name.clone());
|
||||
self.surface_names.insert(surface_id, output_name.clone());
|
||||
self.surface_names
|
||||
.insert(subsurface_id, output_name.clone());
|
||||
self.surface_images.remove(&surface_id);
|
||||
self.update_wallpapers();
|
||||
let text_input_id =
|
||||
widget::Id::new(format!("input-{output_name}",));
|
||||
self.text_input_ids
|
||||
.insert(output_name.clone(), text_input_id.clone());
|
||||
}
|
||||
None => {
|
||||
log::warn!("output {}: no output name", output.id());
|
||||
}
|
||||
},
|
||||
None => {
|
||||
log::warn!("output {}: no output info", output.id());
|
||||
}
|
||||
}
|
||||
|
||||
let unwrapped_size = size
|
||||
.map(|s| (s.0.unwrap_or(1920), s.1.unwrap_or(1080)))
|
||||
.unwrap_or((1920, 1080));
|
||||
let (loc, sub_size) = if unwrapped_size.0 > 800 {
|
||||
(
|
||||
Point::new(unwrapped_size.0 as f32 / 2. - 400., 32.),
|
||||
Size::new(800., unwrapped_size.1 as f32 - 32.),
|
||||
)
|
||||
.image(match self.surface_images.get(&surface_id) {
|
||||
Some(some) => some.clone(),
|
||||
//TODO: default image
|
||||
None => widget::image::Handle::from_rgba(1, 1, vec![0x00, 0x00, 0x00, 0xFF]),
|
||||
})
|
||||
} else {
|
||||
(
|
||||
Point::new(0., 32.),
|
||||
Size::new(unwrapped_size.0 as f32, unwrapped_size.1 as f32 - 32.),
|
||||
)
|
||||
};
|
||||
self.subsurface_rects
|
||||
.insert(output.clone(), Rectangle::new(loc, sub_size));
|
||||
|
||||
let msg = cosmic::surface::action::subsurface(
|
||||
move |_: &mut App| SctkSubsurfaceSettings {
|
||||
parent: surface_id,
|
||||
id: subsurface_id,
|
||||
loc,
|
||||
size: Some(sub_size),
|
||||
z: 10,
|
||||
steal_keyboard_focus: true,
|
||||
gravity: Gravity::BottomRight,
|
||||
offset: (0, 0),
|
||||
input_zone: None,
|
||||
},
|
||||
Some(Box::new(move |app: &App| {
|
||||
app.menu(subsurface_id).map(cosmic::Action::App)
|
||||
})),
|
||||
);
|
||||
|
||||
if matches!(self.state, State::Locked) {
|
||||
return Task::batch([
|
||||
get_lock_surface(surface_id, output),
|
||||
cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(msg),
|
||||
)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
OutputEvent::Removed => {
|
||||
log::info!("output {}: removed", output.id());
|
||||
match self.surface_ids.remove(&output) {
|
||||
Some(surface_id) => {
|
||||
self.surface_images.remove(&surface_id);
|
||||
self.surface_names.remove(&surface_id);
|
||||
if let Some(n) = self.surface_names.remove(&surface_id) {
|
||||
self.text_input_ids.remove(&n);
|
||||
}
|
||||
if matches!(self.state, State::Locked) {
|
||||
return destroy_lock_surface(surface_id);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("output {}: no surface found", output.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
OutputEvent::InfoUpdate(info) => {
|
||||
let size = if let Some((w, h)) = info.logical_size {
|
||||
Some((Some(w as u32), Some(h as u32)))
|
||||
} else {
|
||||
Some((None, None))
|
||||
};
|
||||
let unwrapped_size = size
|
||||
.map(|s| (s.0.unwrap_or(1920), s.1.unwrap_or(1080)))
|
||||
.unwrap_or((1920, 1080));
|
||||
let (loc, sub_size) = if unwrapped_size.0 > 800 {
|
||||
(
|
||||
Point::new(unwrapped_size.0 as f32 / 2. - 400., 32.),
|
||||
Size::new(800., unwrapped_size.1 as f32 - 32.),
|
||||
)
|
||||
} else {
|
||||
(Point::ORIGIN, Size::new(1920., 1080.))
|
||||
};
|
||||
self.subsurface_rects
|
||||
.insert(output.clone(), Rectangle::new(loc, sub_size));
|
||||
|
||||
log::info!("output {}: info update", output.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::SessionLockEvent(session_lock_event) => match session_lock_event {
|
||||
SessionLockEvent::Focused(..) => {}
|
||||
SessionLockEvent::Locked => {
|
||||
log::info!("session locked");
|
||||
if matches!(self.state, State::Locked) {
|
||||
return Task::none();
|
||||
}
|
||||
self.state = State::Locked;
|
||||
// Allow suspend
|
||||
self.inhibit_opt = None;
|
||||
// Create lock surfaces
|
||||
|
||||
let mut commands = Vec::with_capacity(self.surface_ids.len());
|
||||
for (output, surface_id) in self.surface_ids.iter() {
|
||||
commands.push(get_lock_surface(*surface_id, output.clone()));
|
||||
|
||||
if let Some((rect, name)) = self
|
||||
.subsurface_rects
|
||||
.get(output)
|
||||
.copied()
|
||||
.zip(self.output_names.get(output))
|
||||
{
|
||||
let subsurface_id = SurfaceId::unique();
|
||||
let surface_id = *surface_id;
|
||||
self.surface_names.insert(surface_id, name.clone());
|
||||
self.surface_names.insert(subsurface_id, name.clone());
|
||||
let msg = cosmic::surface::action::subsurface(
|
||||
move |_: &mut App| SctkSubsurfaceSettings {
|
||||
parent: surface_id,
|
||||
id: subsurface_id,
|
||||
loc: Point::new(rect.x, rect.y),
|
||||
size: Some(Size::new(rect.width, rect.height)),
|
||||
z: 10,
|
||||
steal_keyboard_focus: true,
|
||||
gravity: Gravity::BottomRight,
|
||||
offset: (0, 0),
|
||||
input_zone: None,
|
||||
},
|
||||
Some(Box::new(move |app: &App| {
|
||||
app.menu(subsurface_id).map(cosmic::Action::App)
|
||||
})),
|
||||
);
|
||||
commands.push(cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(msg),
|
||||
)));
|
||||
} else {
|
||||
log::error!("no rectangle for subsurface...");
|
||||
}
|
||||
}
|
||||
return Task::batch(commands);
|
||||
}
|
||||
SessionLockEvent::Unlocked => {
|
||||
log::info!("session unlocked");
|
||||
self.state = State::Unlocked;
|
||||
|
||||
let mut commands = Vec::new();
|
||||
for (_output, surface_id) in self.surface_ids.iter() {
|
||||
self.surface_names.remove(surface_id);
|
||||
|
||||
commands.push(destroy_lock_surface(*surface_id));
|
||||
}
|
||||
if cfg!(feature = "logind") {
|
||||
return Task::batch(commands);
|
||||
// When using logind feature, stick around for more lock signals
|
||||
} else {
|
||||
// When not using logind feature, exit immediately after unlocking
|
||||
//TODO: cleaner method to exit?
|
||||
process::exit(0);
|
||||
}
|
||||
}
|
||||
//TODO: handle finished signal
|
||||
_ => {}
|
||||
},
|
||||
Message::Channel(value_tx) => {
|
||||
self.value_tx_opt = Some(value_tx);
|
||||
}
|
||||
Message::BackgroundState(background_state) => {
|
||||
self.flags.wallpapers = background_state.wallpapers;
|
||||
self.surface_images.clear();
|
||||
self.update_wallpapers();
|
||||
}
|
||||
Message::Inhibit(inhibit) => {
|
||||
self.inhibit_opt = Some(inhibit);
|
||||
}
|
||||
Message::NetworkIcon(network_icon_opt) => {
|
||||
self.network_icon_opt = network_icon_opt;
|
||||
}
|
||||
Message::PowerInfo(power_info_opt) => {
|
||||
self.power_info_opt = power_info_opt;
|
||||
}
|
||||
Message::Focus(surface_id) => {
|
||||
self.active_surface_id_opt = Some(surface_id);
|
||||
self.active_surface_id_opt = Some(surface_id);
|
||||
if let Some(text_input_id) = self
|
||||
.surface_names
|
||||
.get(&surface_id)
|
||||
.and_then(|id| self.text_input_ids.get(id))
|
||||
{
|
||||
return widget::text_input::focus(text_input_id.clone());
|
||||
}
|
||||
}
|
||||
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::error!("focus surface found id {:?}", text_input_id);
|
||||
|
||||
return widget::text_input::focus(text_input_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Submit(value) => match self.value_tx_opt.take() {
|
||||
Some(value_tx) => {
|
||||
// Clear errors
|
||||
self.error_opt = None;
|
||||
return cosmic::task::future(async move {
|
||||
value_tx.send(value).await.unwrap();
|
||||
Message::Channel(value_tx)
|
||||
});
|
||||
}
|
||||
None => log::warn!("tried to submit when value_tx_opt not set"),
|
||||
},
|
||||
Message::Suspend => {
|
||||
#[cfg(feature = "logind")]
|
||||
return cosmic::task::future(async move {
|
||||
match crate::logind::suspend().await {
|
||||
Ok(()) => cosmic::action::none(),
|
||||
Err(err) => {
|
||||
log::error!("failed to suspend: {:?}", err);
|
||||
cosmic::Action::App(Message::Error(err.to_string()))
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Message::Error(error) => {
|
||||
self.error_opt = Some(error);
|
||||
}
|
||||
Message::Lock => match self.state {
|
||||
State::Unlocked => {
|
||||
log::info!("session locking");
|
||||
self.state = State::Locking;
|
||||
// Clear errors
|
||||
self.error_opt = None;
|
||||
// Clear value_tx
|
||||
self.value_tx_opt = None;
|
||||
// Try to create lockfile when locking
|
||||
if let Some(ref lockfile) = self.flags.lockfile_opt {
|
||||
if let Err(err) = fs::File::create(lockfile) {
|
||||
log::warn!("failed to create lockfile {:?}: {}", lockfile, err);
|
||||
}
|
||||
}
|
||||
// Tell compositor to lock
|
||||
return lock();
|
||||
}
|
||||
State::Unlocking => {
|
||||
log::info!("session still unlocking");
|
||||
}
|
||||
State::Locking | State::Locked => {
|
||||
log::info!("session already locking or locked");
|
||||
}
|
||||
},
|
||||
Message::Unlock => {
|
||||
match self.state {
|
||||
State::Locked => {
|
||||
log::info!("sessing unlocking");
|
||||
self.state = State::Unlocking;
|
||||
// Clear errors
|
||||
self.error_opt = None;
|
||||
// Clear value_tx
|
||||
self.value_tx_opt = None;
|
||||
// Try to delete lockfile when unlocking
|
||||
if let Some(ref lockfile) = self.flags.lockfile_opt {
|
||||
if let Err(err) = fs::remove_file(lockfile) {
|
||||
log::warn!("failed to remove lockfile {:?}: {}", lockfile, err);
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy lock surfaces
|
||||
let mut commands = Vec::with_capacity(self.surface_ids.len() + 1);
|
||||
// Tell compositor to unlock
|
||||
commands.push(unlock());
|
||||
|
||||
// Wait to exit until `Unlocked` event, when server has processed unlock
|
||||
return Task::batch(commands);
|
||||
}
|
||||
State::Locking => {
|
||||
log::info!("session still locking");
|
||||
}
|
||||
State::Unlocking | State::Unlocked => {
|
||||
log::info!("session already unlocking or unlocked");
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Surface(a) => {
|
||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(a),
|
||||
));
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// Not used for layer surface window
|
||||
fn view(&self) -> Element<Self::Message> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
fn view_window(&self, surface_id: SurfaceId) -> Element<Self::Message> {
|
||||
let img = self
|
||||
.surface_images
|
||||
.get(&surface_id)
|
||||
.unwrap_or(&self.flags.fallback_background);
|
||||
widget::image(img)
|
||||
.content_fit(iced::ContentFit::Cover)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
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, _, id| match event {
|
||||
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(
|
||||
wayland_event,
|
||||
)) => match wayland_event {
|
||||
|
|
@ -806,6 +945,7 @@ impl cosmic::Application for App {
|
|||
WaylandEvent::SessionLock(evt) => Some(Message::SessionLockEvent(evt)),
|
||||
_ => None,
|
||||
},
|
||||
iced::Event::Window(iced::window::Event::Focused) => Some(Message::Focus(id)),
|
||||
_ => None,
|
||||
}));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue