cosmic-workspaces/src/main.rs

746 lines
26 KiB
Rust
Raw Normal View History

2023-12-26 13:54:29 -08:00
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
2023-02-09 14:29:34 -08:00
#![allow(clippy::single_match)]
2022-12-30 14:07:39 -08:00
use cctk::{
2023-03-27 10:37:30 -07:00
sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer},
wayland_client::{protocol::wl_output, Connection, Proxy},
wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1,
2022-12-30 14:07:39 -08:00
};
2023-11-21 16:15:02 -05:00
use clap::Parser;
2023-01-17 12:36:05 -08:00
use cosmic::{
2023-11-21 16:15:02 -05:00
app::{Application, CosmicFlags, DbusActivationDetails, Message},
cctk,
2023-01-17 12:36:05 -08:00
iced::{
self,
event::wayland::{Event as WaylandEvent, LayerEvent, OutputEvent},
2024-02-06 13:32:29 -08:00
keyboard::key::{Key, Named},
mouse::ScrollDelta,
2024-10-18 13:13:53 -07:00
Size, Subscription, Task,
},
iced_core::window::Id as SurfaceId,
iced_runtime::platform_specific::wayland::layer_surface::{
IcedOutput, SctkLayerSurfaceSettings,
2023-01-17 12:36:05 -08:00
},
2024-10-18 13:13:53 -07:00
iced_winit::platform_specific::wayland::commands::layer_surface::{
destroy_layer_surface, get_layer_surface,
2023-01-17 12:36:05 -08:00
},
2022-12-30 14:07:39 -08:00
};
use cosmic_comp_config::CosmicCompConfig;
2024-01-19 03:35:33 -08:00
use cosmic_config::{cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry};
2023-12-26 13:54:29 -08:00
use i18n_embed::DesktopLanguageRequester;
2023-11-03 13:06:43 -07:00
use std::{
collections::{HashMap, HashSet},
mem,
2023-12-18 19:41:30 -08:00
path::PathBuf,
2024-10-18 13:13:53 -07:00
str,
time::{Duration, Instant},
2023-11-03 13:06:43 -07:00
};
2022-12-30 14:07:39 -08:00
2023-12-18 19:41:30 -08:00
mod desktop_info;
2023-12-26 13:54:29 -08:00
#[macro_use]
mod localize;
mod backend;
2023-11-16 19:25:28 -08:00
mod view;
use backend::{ExtForeignToplevelHandleV1, ExtWorkspaceHandleV1, ToplevelInfo};
mod dnd;
mod utils;
mod widgets;
use dnd::{DragSurface, DragToplevel, DragWorkspace, DropTarget};
2022-12-30 14:07:39 -08:00
#[derive(Clone, Debug, Default, PartialEq, CosmicConfigEntry)]
struct CosmicWorkspacesConfig {
show_workspace_number: bool,
show_workspace_name: bool,
}
2023-12-26 13:54:29 -08:00
2023-11-21 16:15:02 -05:00
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
pub struct Args {}
#[derive(Default, Debug, Clone)]
pub struct WorkspaceCommands;
2025-01-15 11:56:23 -08:00
#[allow(clippy::to_string_trait_impl)]
2023-11-21 16:15:02 -05:00
impl ToString for WorkspaceCommands {
fn to_string(&self) -> String {
String::new()
}
}
impl CosmicFlags for Args {
type SubCommand = WorkspaceCommands;
type Args = Vec<String>;
fn action(&self) -> Option<&WorkspaceCommands> {
None
}
}
enum ScrollDirection {
Next,
Prev,
}
2022-12-30 15:21:05 -08:00
#[derive(Clone, Debug)]
2022-12-30 14:07:39 -08:00
enum Msg {
WaylandEvent(WaylandEvent),
Wayland(backend::Event),
2022-12-30 14:07:39 -08:00
Close,
ActivateWorkspace(ExtWorkspaceHandleV1),
2024-03-05 12:25:20 -08:00
#[allow(dead_code)]
CloseWorkspace(ExtWorkspaceHandleV1),
2025-02-10 14:33:51 -08:00
ActivateToplevel(ExtForeignToplevelHandleV1),
CloseToplevel(ExtForeignToplevelHandleV1),
2024-10-18 13:13:53 -07:00
StartDrag(DragSurface),
DndEnter(DropTarget, f64, f64, Vec<String>),
DndLeave(DropTarget),
DndToplevelDrop(DragToplevel),
#[allow(dead_code)]
DndWorkspaceDrag,
#[allow(dead_code)]
DndWorkspaceDrop(DragWorkspace),
SourceFinished,
2024-03-05 12:25:20 -08:00
#[allow(dead_code)]
NewWorkspace,
2024-04-24 13:51:20 -07:00
CompConfig(Box<CosmicCompConfig>),
Config(CosmicWorkspacesConfig),
BgConfig(cosmic_bg_config::state::State),
UpdateToplevelIcon(String, Option<PathBuf>),
OnScroll(wl_output::WlOutput, ScrollDelta),
2024-10-18 13:13:53 -07:00
Ignore,
2022-12-30 14:07:39 -08:00
}
#[derive(Clone, Debug)]
2022-12-30 14:07:39 -08:00
struct Workspace {
name: String,
// img_for_output: HashMap<wl_output::WlOutput, backend::CaptureImage>,
img: Option<backend::CaptureImage>,
handle: ExtWorkspaceHandleV1,
outputs: HashSet<wl_output::WlOutput>,
2023-01-05 18:30:50 -08:00
is_active: bool,
2022-12-30 14:07:39 -08:00
}
2024-10-18 13:13:53 -07:00
#[derive(Clone, Debug)]
2022-12-30 15:21:05 -08:00
struct Toplevel {
2025-02-10 14:33:51 -08:00
handle: ExtForeignToplevelHandleV1,
2022-12-30 15:21:05 -08:00
info: ToplevelInfo,
img: Option<backend::CaptureImage>,
2023-12-18 19:41:30 -08:00
icon: Option<PathBuf>,
2022-12-30 15:21:05 -08:00
}
#[derive(Clone)]
struct Output {
handle: wl_output::WlOutput,
name: String,
width: i32,
height: i32,
}
#[derive(Debug)]
2022-12-30 14:07:39 -08:00
struct LayerSurface {
output: wl_output::WlOutput,
2022-12-30 15:35:36 -08:00
// for transitions, would need windows in more than one workspace? But don't capture all of
// them all the time every frame.
2022-12-30 14:07:39 -08:00
}
#[derive(Default)]
struct Conf {
workspace_config: cosmic_comp_config::workspace::WorkspaceConfig,
config: CosmicWorkspacesConfig,
bg: cosmic_bg_config::state::State,
}
2022-12-30 14:07:39 -08:00
#[derive(Default)]
struct App {
layer_surfaces: HashMap<SurfaceId, LayerSurface>,
outputs: Vec<Output>,
2022-12-30 14:07:39 -08:00
workspaces: Vec<Workspace>,
2022-12-30 15:21:05 -08:00
toplevels: Vec<Toplevel>,
2023-01-04 15:17:57 -08:00
conn: Option<Connection>,
visible: bool,
wayland_cmd_sender: Option<calloop::channel::Sender<backend::Cmd>>,
2024-10-18 13:13:53 -07:00
drag_surface: Option<(DragSurface, Size)>,
conf: Conf,
2023-11-21 16:15:02 -05:00
core: cosmic::app::Core,
drop_target: Option<DropTarget>,
scroll: Option<(f32, Instant)>,
2022-12-30 14:07:39 -08:00
}
impl App {
fn workspace_for_handle(&self, handle: &ExtWorkspaceHandleV1) -> Option<&Workspace> {
2023-01-05 18:30:50 -08:00
self.workspaces.iter().find(|i| &i.handle == handle)
}
fn workspace_for_handle_mut(
&mut self,
handle: &ExtWorkspaceHandleV1,
) -> Option<&mut Workspace> {
self.workspaces.iter_mut().find(|i| &i.handle == handle)
}
// TODO iterate in order based on `coordinates`
fn workspaces_for_output<'a>(
&'a self,
output: &'a wl_output::WlOutput,
2025-02-10 12:04:20 -08:00
) -> impl Iterator<Item = &'a Workspace> + 'a {
self.workspaces
.iter()
.filter(|w| w.outputs.contains(output))
}
fn toplevel_for_handle_mut(
&mut self,
2025-02-10 14:33:51 -08:00
handle: &ExtForeignToplevelHandleV1,
) -> Option<&mut Toplevel> {
self.toplevels.iter_mut().find(|i| &i.handle == handle)
}
2024-10-18 13:13:53 -07:00
fn create_surface(&mut self, output: wl_output::WlOutput) -> Task<cosmic::app::Message<Msg>> {
2023-12-08 20:14:31 -08:00
let id = SurfaceId::unique();
self.layer_surfaces.insert(
2023-02-09 14:29:34 -08:00
id,
LayerSurface {
output: output.clone(),
},
);
get_layer_surface(SctkLayerSurfaceSettings {
id,
keyboard_interactivity: KeyboardInteractivity::Exclusive,
namespace: "cosmic-workspace-overview".into(),
layer: Layer::Overlay,
2023-03-27 10:37:30 -07:00
size: Some((None, None)),
output: IcedOutput::Output(output),
2023-03-27 10:37:30 -07:00
anchor: Anchor::all(),
..Default::default()
})
}
2024-10-18 13:13:53 -07:00
fn destroy_surface(&mut self, output: &wl_output::WlOutput) -> Task<cosmic::app::Message<Msg>> {
if let Some((id, _)) = self
.layer_surfaces
.iter()
.find(|(_id, surface)| &surface.output == output)
{
let id = *id;
destroy_layer_surface(id)
} else {
2024-10-18 13:13:53 -07:00
Task::none()
}
}
2024-10-18 13:13:53 -07:00
fn toggle(&mut self) -> Task<cosmic::app::Message<Msg>> {
if self.visible {
self.hide()
} else {
self.show()
}
}
2024-10-18 13:13:53 -07:00
fn show(&mut self) -> Task<cosmic::app::Message<Msg>> {
if !self.visible {
self.visible = true;
let outputs = self.outputs.clone();
2024-10-18 13:13:53 -07:00
let cmd = Task::batch(
outputs
.into_iter()
2023-11-16 13:42:19 -08:00
.map(|output| self.create_surface(output.handle))
.collect::<Vec<_>>(),
);
self.update_capture_filter();
cmd
} else {
2024-10-18 13:13:53 -07:00
Task::none()
}
}
// Close all shell surfaces
2024-10-18 13:13:53 -07:00
fn hide(&mut self) -> Task<cosmic::app::Message<Msg>> {
self.visible = false;
self.update_capture_filter();
self.drag_surface = None;
2024-10-18 13:13:53 -07:00
Task::batch(
self.layer_surfaces
.keys()
.copied()
.map(destroy_layer_surface),
)
}
fn send_wayland_cmd(&self, cmd: backend::Cmd) {
if let Some(sender) = self.wayland_cmd_sender.as_ref() {
sender.send(cmd).unwrap();
}
}
fn update_capture_filter(&self) {
let mut capture_filter = backend::CaptureFilter::default();
if self.visible {
capture_filter.workspaces_on_outputs =
self.outputs.iter().map(|x| x.handle.clone()).collect();
capture_filter.toplevels_on_workspaces = self
.workspaces
.iter()
.filter(|x| x.is_active)
.map(|x| x.handle.clone())
.collect();
}
self.send_wayland_cmd(backend::Cmd::CaptureFilter(capture_filter));
}
2022-12-30 14:07:39 -08:00
}
impl Application for App {
type Message = Msg;
2024-04-19 10:53:54 -07:00
type Executor = cosmic::SingleThreadExecutor;
2023-11-21 16:15:02 -05:00
type Flags = Args;
const APP_ID: &'static str = "com.system76.CosmicWorkspaces";
2024-10-18 13:13:53 -07:00
fn init(core: cosmic::app::Core, _flags: Self::Flags) -> (Self, Task<Message<Self::Message>>) {
2023-11-21 16:15:02 -05:00
(
Self {
core,
..Default::default()
},
2024-10-18 13:13:53 -07:00
Task::none(),
2023-11-21 16:15:02 -05:00
)
2022-12-30 14:07:39 -08:00
}
// TODO: show panel and dock? Drag?
2024-10-18 13:13:53 -07:00
fn update(&mut self, message: Msg) -> Task<cosmic::app::Message<Msg>> {
2022-12-30 14:07:39 -08:00
match message {
Msg::SourceFinished => {
self.drag_surface = None;
}
2022-12-30 14:07:39 -08:00
Msg::WaylandEvent(evt) => match evt {
WaylandEvent::Output(evt, output) => {
// TODO: Less hacky way to get connection from iced-sctk
if self.conn.is_none() {
if let Some(backend) = output.backend().upgrade() {
self.conn = Some(Connection::from_backend(backend));
2022-12-30 14:07:39 -08:00
}
}
match evt {
OutputEvent::Created(Some(info)) => {
if let (Some((width, height)), Some(name)) =
(info.logical_size, info.name)
{
self.outputs.push(Output {
handle: output.clone(),
name: name.clone(),
width,
height,
});
if self.visible {
2023-11-16 13:42:19 -08:00
return self.create_surface(output.clone());
}
}
2023-01-20 14:02:52 -08:00
}
OutputEvent::Created(None) => {} // XXX?
OutputEvent::InfoUpdate(info) => {
if let Some(output) =
self.outputs.iter_mut().find(|x| x.handle == output)
{
if let Some((width, height)) = info.logical_size {
output.width = width;
output.height = height;
}
if let Some(name) = info.name {
output.name = name;
}
// XXX re-create surface?
}
}
OutputEvent::Removed => {
if let Some(idx) = self.outputs.iter().position(|x| x.handle == output)
{
self.outputs.remove(idx);
}
if self.visible {
return self.destroy_surface(&output);
}
2022-12-30 14:07:39 -08:00
}
}
}
WaylandEvent::Layer(LayerEvent::Done, _surface, id) => {
if self.layer_surfaces.remove(&id).is_none() {
log::error!("removing non-existant layer shell id {}?", id);
}
}
2022-12-30 14:07:39 -08:00
_ => {}
},
Msg::Wayland(evt) => {
match evt {
backend::Event::CmdSender(sender) => {
self.wayland_cmd_sender = Some(sender);
}
backend::Event::Workspaces(mut workspaces) => {
workspaces.sort_by(|(_, w1), (_, w2)| w1.coordinates.cmp(&w2.coordinates));
2023-01-04 14:41:44 -08:00
let old_workspaces = mem::take(&mut self.workspaces);
2022-12-30 14:07:39 -08:00
self.workspaces = Vec::new();
for (outputs, workspace) in workspaces {
let is_active = workspace
.state
.contains(ext_workspace_handle_v1::State::Active);
2023-01-04 14:41:44 -08:00
// XXX efficiency
2025-01-15 11:56:23 -08:00
#[allow(clippy::mutable_key_type)]
let img = old_workspaces
2023-01-04 14:41:44 -08:00
.iter()
2023-02-09 14:29:34 -08:00
.find(|i| i.handle == workspace.handle)
.map(|i| i.img.clone())
2023-03-22 10:03:18 -07:00
.unwrap_or_default();
2023-01-04 14:41:44 -08:00
2022-12-30 14:07:39 -08:00
self.workspaces.push(Workspace {
name: workspace.name,
handle: workspace.handle,
outputs,
img,
2023-01-05 18:30:50 -08:00
is_active,
2022-12-30 14:07:39 -08:00
});
}
self.update_capture_filter();
2022-12-30 14:07:39 -08:00
}
backend::Event::NewToplevel(handle, info) => {
2024-03-06 14:05:32 -08:00
log::debug!("New toplevel: {info:?}");
let app_id = info.app_id.clone();
let icon_task = iced::Task::perform(
desktop_info::icon_for_app_id(app_id.clone()),
move |path| Msg::UpdateToplevelIcon(app_id.clone(), path),
)
.map(cosmic::app::Message::App);
2022-12-30 15:21:05 -08:00
self.toplevels.push(Toplevel {
icon: None,
2022-12-30 15:21:05 -08:00
handle,
info,
img: None,
});
// Close workspaces view if a window spawns while open
#[cfg(not(feature = "mock-backend"))]
if self.visible {
return Task::batch([icon_task, self.hide()]);
}
return icon_task;
2022-12-30 15:21:05 -08:00
}
backend::Event::UpdateToplevel(handle, info) => {
2023-02-10 13:41:08 -08:00
if let Some(toplevel) =
self.toplevels.iter_mut().find(|x| x.handle == handle)
{
let mut task = Task::none();
if toplevel.info.app_id != info.app_id {
let app_id = info.app_id.clone();
task = iced::Task::perform(
desktop_info::icon_for_app_id(app_id.clone()),
move |path| Msg::UpdateToplevelIcon(app_id.clone(), path),
)
.map(cosmic::app::Message::App);
}
2023-02-10 13:41:08 -08:00
toplevel.info = info;
return task;
2023-02-10 13:41:08 -08:00
}
}
backend::Event::CloseToplevel(handle) => {
2023-01-18 11:01:47 -08:00
if let Some(idx) = self.toplevels.iter().position(|x| x.handle == handle) {
self.toplevels.remove(idx);
}
}
backend::Event::WorkspaceCapture(handle, image) => {
2024-10-18 13:13:53 -07:00
//println!("Workspace capture");
if let Some(workspace) = self.workspace_for_handle_mut(&handle) {
workspace.img = Some(image);
2022-12-30 14:07:39 -08:00
}
}
backend::Event::ToplevelCapture(handle, image) => {
if let Some(toplevel) = self.toplevel_for_handle_mut(&handle) {
2024-10-18 13:13:53 -07:00
// println!("Got toplevel image!");
2023-02-09 14:29:34 -08:00
toplevel.img = Some(image);
2022-12-30 15:21:05 -08:00
}
}
2022-12-30 14:07:39 -08:00
}
}
Msg::Close => {
2023-01-26 16:01:43 -08:00
return self.hide();
2022-12-30 14:07:39 -08:00
}
2022-12-30 15:35:36 -08:00
Msg::ActivateWorkspace(workspace_handle) => {
if let Some(workspace) = self.workspace_for_handle(&workspace_handle) {
if workspace.is_active {
return self.hide();
}
}
self.send_wayland_cmd(backend::Cmd::ActivateWorkspace(workspace_handle));
2023-01-04 15:17:57 -08:00
}
Msg::ActivateToplevel(toplevel_handle) => {
self.send_wayland_cmd(backend::Cmd::ActivateToplevel(toplevel_handle));
return self.hide();
2022-12-30 15:21:05 -08:00
}
Msg::CloseWorkspace(_workspace_handle) => {
// XXX close specific workspace
/*
if let WorkspaceAmount::Static(n) = &mut self.conf.workspace_config.workspace_amount
{
2023-12-14 16:19:53 -08:00
if *n != 1 {
*n -= 1;
self.conf
.cosmic_comp_config
.set("workspaces", &self.conf.workspace_config);
}
}
*/
}
2023-01-18 10:58:22 -08:00
Msg::CloseToplevel(toplevel_handle) => {
// TODO confirmation?
self.send_wayland_cmd(backend::Cmd::CloseToplevel(toplevel_handle));
2023-01-18 10:58:22 -08:00
}
2024-10-18 13:13:53 -07:00
Msg::StartDrag(drag_surface) => {
self.drag_surface = Some((drag_surface, Default::default()));
2023-07-10 13:55:32 -07:00
}
Msg::DndEnter(drop_target, _x, _y, _mimes) => {
self.drop_target = Some(drop_target);
2023-07-20 15:48:19 -07:00
}
Msg::DndLeave(drop_target) => {
// Currently in iced-sctk, a `DndOfferEvent::Motion` may cause a leave event after
// an enter event, based on which widget handles it first. So we need a test here.
if self.drop_target == Some(drop_target) {
self.drop_target = None;
}
}
Msg::DndToplevelDrop(_toplevel) => {
2025-01-24 14:41:36 -08:00
if let Some((DragSurface::Toplevel(handle), _)) = &self.drag_surface {
match self.drop_target.take() {
Some(
DropTarget::WorkspaceSidebarEntry(workspace, output)
| DropTarget::OutputToplevels(workspace, output),
) => {
self.send_wayland_cmd(backend::Cmd::MoveToplevelToWorkspace(
handle.clone(),
workspace,
output,
));
}
Some(DropTarget::WorkspacesBar(_)) | None => {}
}
}
}
Msg::NewWorkspace => {
/*
if let WorkspaceAmount::Static(n) = &mut self.conf.workspace_config.workspace_amount
{
*n += 1;
self.conf
.cosmic_comp_config
.set("workspaces", &self.conf.workspace_config);
}
*/
}
Msg::Config(c) => {
self.conf.config = c;
}
Msg::CompConfig(c) => {
self.conf.workspace_config = c.workspaces;
}
Msg::BgConfig(c) => {
self.conf.bg = c;
}
Msg::UpdateToplevelIcon(app_id, path) => {
for toplevel in self.toplevels.iter_mut() {
if toplevel.info.app_id == app_id {
toplevel.icon = path.clone();
}
}
}
Msg::OnScroll(output, delta) => {
// Accumulate delta with a timer
// TODO: Should x scroll be handled too?
// Best time/pixel count?
let direction = match delta {
2025-02-20 18:51:26 -05:00
ScrollDelta::Pixels { x: _, mut y } => {
y = -y;
let previous_scroll = if let Some((scroll, last_scroll_time)) = self.scroll
{
if last_scroll_time.elapsed() > Duration::from_millis(100) {
0.
} else {
scroll
}
} else {
0.
};
2025-02-06 13:37:53 -08:00
let scroll = previous_scroll + y;
if scroll <= -4. {
self.scroll = None;
ScrollDirection::Prev
} else if scroll >= 4. {
self.scroll = None;
ScrollDirection::Next
} else {
// If scroll has y element, accumulate scroll
self.scroll = if y != 0. {
Some((scroll, Instant::now()))
} else {
None
};
return Task::none();
}
}
2025-02-20 18:51:26 -05:00
ScrollDelta::Lines { x: _, mut y } => {
y = -y;
self.scroll = None;
if y < 0. {
ScrollDirection::Prev
} else if y > 0. {
ScrollDirection::Next
} else {
return Task::none();
}
}
};
// TODO assumes only one active workspace per output
let workspaces = self.workspaces_for_output(&output).collect::<Vec<_>>();
if let Some(workspace_idx) = workspaces.iter().position(|i| i.is_active) {
let workspace = match direction {
// Next workspace on output
ScrollDirection::Next => workspaces[workspace_idx + 1..].iter().next(),
// Previous workspace on output
ScrollDirection::Prev => workspaces[..workspace_idx].iter().last(),
};
if let Some(workspace) = workspace {
self.send_wayland_cmd(backend::Cmd::ActivateWorkspace(
workspace.handle.clone(),
));
}
}
}
Msg::DndWorkspaceDrag => {}
Msg::DndWorkspaceDrop(_workspace) => {}
2024-10-18 13:13:53 -07:00
Msg::Ignore => {}
2022-12-30 14:07:39 -08:00
}
2024-10-18 13:13:53 -07:00
Task::none()
2022-12-30 14:07:39 -08:00
}
2023-11-21 16:15:02 -05:00
fn dbus_activation(
&mut self,
msg: cosmic::app::DbusActivationMessage,
2024-10-18 13:13:53 -07:00
) -> Task<cosmic::app::Message<Self::Message>> {
2023-11-21 16:15:02 -05:00
if let DbusActivationDetails::Activate = msg.msg {
self.toggle()
} else {
2024-10-18 13:13:53 -07:00
Task::none()
2023-11-21 16:15:02 -05:00
}
}
2022-12-30 14:07:39 -08:00
fn subscription(&self) -> Subscription<Msg> {
2024-10-18 13:13:53 -07:00
let events = iced::event::listen_with(|evt, _, _| {
2022-12-30 14:07:39 -08:00
if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(evt)) = evt
{
Some(Msg::WaylandEvent(evt))
} else if let iced::Event::Keyboard(iced::keyboard::Event::KeyReleased {
2024-02-06 13:32:29 -08:00
key: Key::Named(Named::Escape),
2022-12-30 14:07:39 -08:00
modifiers: _,
2024-02-06 13:32:29 -08:00
location: _,
2024-10-18 13:13:53 -07:00
modified_key: _,
physical_key: _,
2022-12-30 14:07:39 -08:00
}) = evt
{
Some(Msg::Close)
} else {
None
}
});
let config_subscription = cosmic_config::config_subscription::<_, CosmicWorkspacesConfig>(
"config-sub",
2024-01-03 10:57:47 -08:00
"com.system76.CosmicWorkspaces".into(),
1,
)
2024-01-19 03:35:33 -08:00
.map(|update| {
if !update.errors.is_empty() {
log::error!("Failed to load workspaces config: {:?}", update.errors);
}
2024-01-19 03:35:33 -08:00
Msg::Config(update.config)
});
let comp_config_subscription = cosmic_config::config_subscription::<_, CosmicCompConfig>(
"comp-config-sub",
"com.system76.CosmicComp".into(),
1,
)
2024-01-19 03:35:33 -08:00
.map(|update| {
if !update.errors.is_empty() {
log::error!("Failed to load compositor config: {:?}", update.errors);
}
2024-04-24 13:51:20 -07:00
Msg::CompConfig(Box::new(update.config))
});
let bg_subscription =
cosmic_config::config_state_subscription::<_, cosmic_bg_config::state::State>(
"bg-sub",
cosmic_bg_config::NAME.into(),
cosmic_bg_config::state::State::version(),
)
.map(|update| {
if !update.errors.is_empty() {
log::error!("Failed to load bg config: {:?}", update.errors);
}
Msg::BgConfig(update.config)
});
let mut subscriptions = vec![
events,
config_subscription,
comp_config_subscription,
bg_subscription,
];
if let Some(conn) = self.conn.clone() {
subscriptions.push(backend::subscription(conn).map(Msg::Wayland));
}
iced::Subscription::batch(subscriptions)
2022-12-30 14:07:39 -08:00
}
2023-11-21 16:15:02 -05:00
fn view(&self) -> cosmic::prelude::Element<Self::Message> {
unreachable!()
}
fn view_window(&self, id: iced::window::Id) -> cosmic::prelude::Element<Self::Message> {
2023-04-04 16:20:40 -07:00
if let Some(surface) = self.layer_surfaces.get(&id) {
2023-11-16 19:25:28 -08:00
return view::layer_surface(self, surface);
2023-04-04 16:20:40 -07:00
}
log::error!("non-existant layer shell id {}?", id);
cosmic::widget::text("workspaces").into()
2022-12-30 14:07:39 -08:00
}
2024-04-19 15:44:55 -07:00
fn on_close_requested(&self, _id: SurfaceId) -> Option<Msg> {
None
2023-11-21 16:15:02 -05:00
}
fn core(&self) -> &cosmic::app::Core {
&self.core
}
fn core_mut(&mut self) -> &mut cosmic::app::Core {
&mut self.core
2022-12-30 14:07:39 -08:00
}
}
2023-12-26 13:54:29 -08:00
fn init_localizer() {
let localizer = crate::localize::localizer();
let requested_languages = DesktopLanguageRequester::requested_languages();
if let Err(why) = localizer.select(&requested_languages) {
log::error!("error while loading fluent localizations: {}", why);
}
}
2022-12-30 14:07:39 -08:00
pub fn main() -> iced::Result {
2023-04-13 14:30:47 -07:00
env_logger::init();
2023-12-26 13:54:29 -08:00
init_localizer();
2023-04-13 14:30:47 -07:00
2023-11-21 16:15:02 -05:00
cosmic::app::run_single_instance::<App>(
cosmic::app::Settings::default()
.no_main_window(true)
.exit_on_close(false),
Args::parse(),
)
2022-12-30 14:07:39 -08:00
}