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::{
|
2025-04-21 14:02:36 -07:00
|
|
|
cosmic_protocols::toplevel_management::v1::client::zcosmic_toplevel_manager_v1,
|
2025-01-31 14:17:56 -08:00
|
|
|
cosmic_protocols::workspace::v2::client::zcosmic_workspace_handle_v2,
|
2023-03-27 10:37:30 -07:00
|
|
|
sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer},
|
2025-09-10 14:18:51 +02:00
|
|
|
wayland_client::{Connection, Proxy, protocol::wl_output},
|
2025-02-20 14:28:56 -08:00
|
|
|
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::{
|
2025-03-11 15:29:30 -07:00
|
|
|
app::{Application, CosmicFlags},
|
|
|
|
|
cctk, dbus_activation,
|
2023-01-17 12:36:05 -08:00
|
|
|
iced::{
|
2025-09-10 14:18:51 +02:00
|
|
|
self, Size, Subscription, Task,
|
2025-01-31 14:17:56 -08:00
|
|
|
clipboard::dnd::{DndEvent, SourceEvent},
|
2025-01-16 14:50:45 -08:00
|
|
|
event::wayland::{Event as WaylandEvent, LayerEvent, OutputEvent},
|
2024-02-06 13:32:29 -08:00
|
|
|
keyboard::key::{Key, Named},
|
2025-01-24 15:35:45 -08:00
|
|
|
mouse::ScrollDelta,
|
2024-10-18 13:13:53 -07:00
|
|
|
},
|
|
|
|
|
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
|
|
|
};
|
2024-02-07 19:52:33 -08:00
|
|
|
use cosmic_comp_config::CosmicCompConfig;
|
2025-09-10 14:18:51 +02:00
|
|
|
use cosmic_config::{CosmicConfigEntry, cosmic_config_derive::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,
|
2025-02-06 13:30:34 -08:00
|
|
|
time::{Duration, Instant},
|
2023-11-03 13:06:43 -07:00
|
|
|
};
|
2022-12-30 14:07:39 -08:00
|
|
|
|
2025-07-21 14:06:47 -07:00
|
|
|
mod dbus;
|
2023-12-18 19:41:30 -08:00
|
|
|
mod desktop_info;
|
2023-12-26 13:54:29 -08:00
|
|
|
#[macro_use]
|
|
|
|
|
mod localize;
|
2024-04-23 13:44:59 -07:00
|
|
|
mod backend;
|
2023-11-16 19:25:28 -08:00
|
|
|
mod view;
|
2025-02-20 14:28:56 -08:00
|
|
|
use backend::{ExtForeignToplevelHandleV1, ExtWorkspaceHandleV1, ToplevelInfo};
|
2025-01-24 14:28:42 -08:00
|
|
|
mod dnd;
|
2024-04-23 13:44:59 -07:00
|
|
|
mod utils;
|
2023-12-13 15:54:33 -08:00
|
|
|
mod widgets;
|
2025-02-10 12:02:36 -08:00
|
|
|
use dnd::{DragSurface, DragToplevel, DragWorkspace, DropTarget};
|
2022-12-30 14:07:39 -08:00
|
|
|
|
2023-12-29 13:44:19 -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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-06 13:30:34 -08:00
|
|
|
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),
|
2024-04-23 13:44:59 -07:00
|
|
|
Wayland(backend::Event),
|
2022-12-30 14:07:39 -08:00
|
|
|
Close,
|
2025-02-20 14:28:56 -08:00
|
|
|
ActivateWorkspace(ExtWorkspaceHandleV1),
|
2024-03-05 12:25:20 -08:00
|
|
|
#[allow(dead_code)]
|
2025-02-20 14:28:56 -08:00
|
|
|
CloseWorkspace(ExtWorkspaceHandleV1),
|
2025-02-10 14:33:51 -08:00
|
|
|
ActivateToplevel(ExtForeignToplevelHandleV1),
|
|
|
|
|
CloseToplevel(ExtForeignToplevelHandleV1),
|
2024-10-18 13:13:53 -07:00
|
|
|
StartDrag(DragSurface),
|
2025-01-24 14:09:17 -08:00
|
|
|
DndEnter(DropTarget, f64, f64, Vec<String>),
|
|
|
|
|
DndLeave(DropTarget),
|
2025-02-10 10:11:04 -08:00
|
|
|
DndToplevelDrop(DragToplevel),
|
2025-02-10 12:02:36 -08:00
|
|
|
#[allow(dead_code)]
|
|
|
|
|
DndWorkspaceDrag,
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
DndWorkspaceDrop(DragWorkspace),
|
2023-12-11 14:06:41 -08:00
|
|
|
SourceFinished,
|
2024-03-05 12:25:20 -08:00
|
|
|
#[allow(dead_code)]
|
2023-12-12 15:36:20 -08:00
|
|
|
NewWorkspace,
|
2024-04-24 13:51:20 -07:00
|
|
|
CompConfig(Box<CosmicCompConfig>),
|
2023-12-29 13:44:19 -08:00
|
|
|
Config(CosmicWorkspacesConfig),
|
2024-05-01 13:32:36 -07:00
|
|
|
BgConfig(cosmic_bg_config::state::State),
|
2025-01-21 09:24:19 -08:00
|
|
|
UpdateToplevelIcon(String, Option<PathBuf>),
|
2025-01-24 15:35:45 -08:00
|
|
|
OnScroll(wl_output::WlOutput, ScrollDelta),
|
2025-01-31 14:17:56 -08:00
|
|
|
TogglePinned(ExtWorkspaceHandleV1),
|
|
|
|
|
EnteredWorkspaceSidebarEntry(ExtWorkspaceHandleV1, bool),
|
2025-07-21 14:06:47 -07:00
|
|
|
DbusInterface(zbus::Result<dbus::Interface>),
|
2025-09-22 11:49:06 -07:00
|
|
|
DBus(dbus::Event),
|
2024-10-18 13:13:53 -07:00
|
|
|
Ignore,
|
2022-12-30 14:07:39 -08:00
|
|
|
}
|
|
|
|
|
|
2025-02-10 12:02:36 -08:00
|
|
|
#[derive(Clone, Debug)]
|
2022-12-30 14:07:39 -08:00
|
|
|
struct Workspace {
|
2025-04-21 13:49:49 -07:00
|
|
|
info: backend::Workspace,
|
2025-01-29 15:09:07 -08:00
|
|
|
// img_for_output: HashMap<wl_output::WlOutput, backend::CaptureImage>,
|
|
|
|
|
img: Option<backend::CaptureImage>,
|
2023-11-08 13:59:53 -08:00
|
|
|
outputs: HashSet<wl_output::WlOutput>,
|
2025-01-31 14:17:56 -08:00
|
|
|
has_cursor: bool,
|
|
|
|
|
dnd_source_id: iced::id::Id,
|
2025-04-21 10:36:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Workspace {
|
|
|
|
|
fn handle(&self) -> &ExtWorkspaceHandleV1 {
|
|
|
|
|
&self.info.handle
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_active(&self) -> bool {
|
|
|
|
|
self.info
|
|
|
|
|
.state
|
|
|
|
|
.contains(ext_workspace_handle_v1::State::Active)
|
|
|
|
|
}
|
2025-01-31 14:17:56 -08:00
|
|
|
|
|
|
|
|
fn is_pinned(&self) -> bool {
|
|
|
|
|
self.info
|
|
|
|
|
.cosmic_state
|
|
|
|
|
.contains(zcosmic_workspace_handle_v2::State::Pinned)
|
|
|
|
|
}
|
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,
|
2024-04-23 13:44:59 -07:00
|
|
|
img: Option<backend::CaptureImage>,
|
2023-12-18 19:41:30 -08:00
|
|
|
icon: Option<PathBuf>,
|
2022-12-30 15:21:05 -08:00
|
|
|
}
|
|
|
|
|
|
2023-01-19 16:29:20 -08:00
|
|
|
#[derive(Clone)]
|
|
|
|
|
struct Output {
|
|
|
|
|
handle: wl_output::WlOutput,
|
2023-02-10 10:12:26 -08:00
|
|
|
name: String,
|
2023-01-19 16:29:20 -08:00
|
|
|
width: i32,
|
|
|
|
|
height: i32,
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 14:50:45 -08:00
|
|
|
#[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
|
|
|
}
|
|
|
|
|
|
2024-05-01 13:32:36 -07:00
|
|
|
#[derive(Default)]
|
2023-11-17 14:56:37 -08:00
|
|
|
struct Conf {
|
|
|
|
|
workspace_config: cosmic_comp_config::workspace::WorkspaceConfig,
|
2023-12-29 13:44:19 -08:00
|
|
|
config: CosmicWorkspacesConfig,
|
2024-05-01 13:32:36 -07:00
|
|
|
bg: cosmic_bg_config::state::State,
|
2023-11-17 14:56:37 -08:00
|
|
|
}
|
|
|
|
|
|
2022-12-30 14:07:39 -08:00
|
|
|
#[derive(Default)]
|
|
|
|
|
struct App {
|
2025-06-04 09:43:34 -07:00
|
|
|
capture_filter: backend::CaptureFilter,
|
2022-12-30 14:07:39 -08:00
|
|
|
layer_surfaces: HashMap<SurfaceId, LayerSurface>,
|
2023-01-19 16:29:20 -08:00
|
|
|
outputs: Vec<Output>,
|
2025-06-04 10:00:40 -07:00
|
|
|
workspaces: Workspaces,
|
|
|
|
|
toplevels: Toplevels,
|
2025-04-21 14:02:36 -07:00
|
|
|
toplevel_capabilities:
|
|
|
|
|
Vec<zcosmic_toplevel_manager_v1::ZcosmicToplelevelManagementCapabilitiesV1>,
|
2023-01-04 15:17:57 -08:00
|
|
|
conn: Option<Connection>,
|
2023-01-19 16:29:20 -08:00
|
|
|
visible: bool,
|
2024-04-23 13:44:59 -07:00
|
|
|
wayland_cmd_sender: Option<calloop::channel::Sender<backend::Cmd>>,
|
2024-10-18 13:13:53 -07:00
|
|
|
drag_surface: Option<(DragSurface, Size)>,
|
2023-11-17 14:56:37 -08:00
|
|
|
conf: Conf,
|
2023-11-21 16:15:02 -05:00
|
|
|
core: cosmic::app::Core,
|
2025-01-24 14:09:17 -08:00
|
|
|
drop_target: Option<DropTarget>,
|
2025-02-06 13:30:34 -08:00
|
|
|
scroll: Option<(f32, Instant)>,
|
2025-07-21 14:06:47 -07:00
|
|
|
dbus_interface: Option<dbus::Interface>,
|
2022-12-30 14:07:39 -08:00
|
|
|
}
|
|
|
|
|
|
2025-06-04 10:00:40 -07:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
|
struct Workspaces(Vec<Workspace>);
|
|
|
|
|
|
|
|
|
|
impl Workspaces {
|
|
|
|
|
fn for_handle(&self, handle: &ExtWorkspaceHandleV1) -> Option<&Workspace> {
|
|
|
|
|
self.0.iter().find(|i| i.handle() == handle)
|
2023-01-05 18:30:50 -08:00
|
|
|
}
|
|
|
|
|
|
2025-06-04 10:00:40 -07:00
|
|
|
fn for_handle_mut(&mut self, handle: &ExtWorkspaceHandleV1) -> Option<&mut Workspace> {
|
|
|
|
|
self.0.iter_mut().find(|i| i.handle() == handle)
|
2023-01-03 12:30:04 -08:00
|
|
|
}
|
|
|
|
|
|
2025-06-04 10:00:40 -07:00
|
|
|
fn for_output<'a>(
|
2025-01-24 15:30:15 -08:00
|
|
|
&'a self,
|
|
|
|
|
output: &'a wl_output::WlOutput,
|
2025-02-10 12:04:20 -08:00
|
|
|
) -> impl Iterator<Item = &'a Workspace> + 'a {
|
2025-06-04 10:00:40 -07:00
|
|
|
self.0.iter().filter(|w| w.outputs.contains(output))
|
2025-01-24 15:30:15 -08:00
|
|
|
}
|
2025-06-04 10:00:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
|
struct Toplevels(Vec<Toplevel>);
|
2025-01-24 15:30:15 -08:00
|
|
|
|
2025-06-04 10:00:40 -07:00
|
|
|
impl Toplevels {
|
|
|
|
|
fn for_handle_mut(&mut self, handle: &ExtForeignToplevelHandleV1) -> Option<&mut Toplevel> {
|
|
|
|
|
self.0.iter_mut().find(|i| &i.handle == handle)
|
2023-01-03 12:30:04 -08:00
|
|
|
}
|
2025-06-04 10:00:40 -07:00
|
|
|
}
|
2023-01-19 16:29:20 -08:00
|
|
|
|
2025-06-04 10:00:40 -07:00
|
|
|
impl App {
|
2025-03-11 15:29:30 -07:00
|
|
|
fn create_surface(&mut self, output: wl_output::WlOutput) -> Task<cosmic::Action<Msg>> {
|
2023-12-08 20:14:31 -08:00
|
|
|
let id = SurfaceId::unique();
|
2023-01-19 16:29:20 -08:00
|
|
|
self.layer_surfaces.insert(
|
2023-02-09 14:29:34 -08:00
|
|
|
id,
|
2023-01-19 16:29:20 -08:00
|
|
|
LayerSurface {
|
|
|
|
|
output: output.clone(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
get_layer_surface(SctkLayerSurfaceSettings {
|
|
|
|
|
id,
|
|
|
|
|
keyboard_interactivity: KeyboardInteractivity::Exclusive,
|
2023-02-21 15:20:43 -08:00
|
|
|
namespace: "cosmic-workspace-overview".into(),
|
2025-04-07 10:07:40 -07:00
|
|
|
layer: Layer::Top,
|
2023-03-27 10:37:30 -07:00
|
|
|
size: Some((None, None)),
|
2023-01-19 16:29:20 -08:00
|
|
|
output: IcedOutput::Output(output),
|
2023-03-27 10:37:30 -07:00
|
|
|
anchor: Anchor::all(),
|
2023-01-19 16:29:20 -08:00
|
|
|
..Default::default()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-11 15:29:30 -07:00
|
|
|
fn destroy_surface(&mut self, output: &wl_output::WlOutput) -> Task<cosmic::Action<Msg>> {
|
2023-01-19 16:29:20 -08:00
|
|
|
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()
|
2023-01-19 16:29:20 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-11 15:29:30 -07:00
|
|
|
fn toggle(&mut self) -> Task<cosmic::Action<Msg>> {
|
2023-01-19 16:29:20 -08:00
|
|
|
if self.visible {
|
|
|
|
|
self.hide()
|
|
|
|
|
} else {
|
|
|
|
|
self.show()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-11 15:29:30 -07:00
|
|
|
fn show(&mut self) -> Task<cosmic::Action<Msg>> {
|
2023-01-19 16:29:20 -08:00
|
|
|
if !self.visible {
|
|
|
|
|
self.visible = true;
|
|
|
|
|
let outputs = self.outputs.clone();
|
2024-10-18 13:13:53 -07:00
|
|
|
let cmd = Task::batch(
|
2023-01-19 16:29:20 -08:00
|
|
|
outputs
|
|
|
|
|
.into_iter()
|
2023-11-16 13:42:19 -08:00
|
|
|
.map(|output| self.create_surface(output.handle))
|
2023-01-19 16:29:20 -08:00
|
|
|
.collect::<Vec<_>>(),
|
2023-02-10 10:12:26 -08:00
|
|
|
);
|
|
|
|
|
self.update_capture_filter();
|
2024-04-30 12:49:08 -07:00
|
|
|
|
2025-07-21 14:06:47 -07:00
|
|
|
if let Some(interface) = self.dbus_interface.clone() {
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
let _ = interface.shown().await;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 10:12:26 -08:00
|
|
|
cmd
|
2023-01-19 16:29:20 -08:00
|
|
|
} else {
|
2024-10-18 13:13:53 -07:00
|
|
|
Task::none()
|
2023-01-19 16:29:20 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close all shell surfaces
|
2025-03-11 15:29:30 -07:00
|
|
|
fn hide(&mut self) -> Task<cosmic::Action<Msg>> {
|
2025-07-21 14:06:47 -07:00
|
|
|
if let Some(interface) = self.dbus_interface.clone() {
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
let _ = interface.hidden().await;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-19 16:29:20 -08:00
|
|
|
self.visible = false;
|
2023-02-10 10:12:26 -08:00
|
|
|
self.update_capture_filter();
|
2024-05-08 13:47:42 -07:00
|
|
|
self.drag_surface = None;
|
2024-10-18 13:13:53 -07:00
|
|
|
Task::batch(
|
2025-01-16 14:50:45 -08:00
|
|
|
self.layer_surfaces
|
|
|
|
|
.keys()
|
|
|
|
|
.copied()
|
|
|
|
|
.map(destroy_layer_surface),
|
2023-01-19 16:29:20 -08:00
|
|
|
)
|
|
|
|
|
}
|
2023-02-10 10:12:26 -08:00
|
|
|
|
2024-04-23 13:44:59 -07:00
|
|
|
fn send_wayland_cmd(&self, cmd: backend::Cmd) {
|
2023-02-10 10:12:26 -08:00
|
|
|
if let Some(sender) = self.wayland_cmd_sender.as_ref() {
|
2024-04-23 11:54:05 -07:00
|
|
|
sender.send(cmd).unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-03 18:44:27 -07:00
|
|
|
fn update_capture_filter(&mut self) {
|
2024-04-23 13:44:59 -07:00
|
|
|
let mut capture_filter = backend::CaptureFilter::default();
|
2024-04-23 11:54:05 -07:00
|
|
|
if self.visible {
|
|
|
|
|
capture_filter.workspaces_on_outputs =
|
|
|
|
|
self.outputs.iter().map(|x| x.handle.clone()).collect();
|
|
|
|
|
capture_filter.toplevels_on_workspaces = self
|
|
|
|
|
.workspaces
|
2025-06-04 10:00:40 -07:00
|
|
|
.0
|
2024-04-23 11:54:05 -07:00
|
|
|
.iter()
|
2025-04-21 10:36:44 -07:00
|
|
|
.filter(|x| x.is_active())
|
|
|
|
|
.map(|x| x.handle().clone())
|
2024-04-23 11:54:05 -07:00
|
|
|
.collect();
|
2023-02-10 10:12:26 -08:00
|
|
|
}
|
2025-06-03 18:44:27 -07:00
|
|
|
|
|
|
|
|
// Drop `CaptureImage` for workspaces and toplevels not matching new
|
|
|
|
|
// filter.
|
2025-06-04 10:00:40 -07:00
|
|
|
for workspace in &mut self.workspaces.0 {
|
2025-06-03 18:44:27 -07:00
|
|
|
if !capture_filter.workspace_outputs_matches(&workspace.outputs) {
|
|
|
|
|
workspace.img = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-04 10:00:40 -07:00
|
|
|
for toplevel in &mut self.toplevels.0 {
|
2025-06-03 18:44:27 -07:00
|
|
|
if !capture_filter.toplevel_matches(&toplevel.info) {
|
|
|
|
|
toplevel.img = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-04 09:43:34 -07:00
|
|
|
self.capture_filter = capture_filter.clone();
|
2024-04-23 13:44:59 -07:00
|
|
|
self.send_wayland_cmd(backend::Cmd::CaptureFilter(capture_filter));
|
2023-02-10 10:12:26 -08:00
|
|
|
}
|
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";
|
|
|
|
|
|
2025-03-11 15:29:30 -07:00
|
|
|
fn init(core: cosmic::app::Core, _flags: Self::Flags) -> (Self, Task<cosmic::Action<Msg>>) {
|
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
|
|
|
}
|
2023-01-03 12:30:04 -08:00
|
|
|
// TODO: show panel and dock? Drag?
|
|
|
|
|
|
2025-03-11 15:29:30 -07:00
|
|
|
fn update(&mut self, message: Msg) -> Task<cosmic::Action<Msg>> {
|
2022-12-30 14:07:39 -08:00
|
|
|
match message {
|
2024-04-19 12:05:35 -07:00
|
|
|
Msg::SourceFinished => {
|
|
|
|
|
self.drag_surface = None;
|
|
|
|
|
}
|
2022-12-30 14:07:39 -08:00
|
|
|
Msg::WaylandEvent(evt) => match evt {
|
2023-11-08 13:31:13 -08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
2023-11-08 13:31:13 -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-11-08 13:31:13 -08:00
|
|
|
}
|
2023-02-10 10:12:26 -08:00
|
|
|
}
|
2023-01-20 14:02:52 -08:00
|
|
|
}
|
2023-11-08 13:31:13 -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?
|
|
|
|
|
}
|
2023-01-19 16:29:20 -08:00
|
|
|
}
|
2023-11-08 13:31:13 -08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
2023-11-08 13:31:13 -08:00
|
|
|
}
|
2025-01-16 14:50:45 -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 {
|
2024-04-23 13:44:59 -07:00
|
|
|
backend::Event::CmdSender(sender) => {
|
2023-02-09 14:00:22 -08:00
|
|
|
self.wayland_cmd_sender = Some(sender);
|
|
|
|
|
}
|
2025-02-20 14:28:56 -08:00
|
|
|
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);
|
2023-11-08 13:59:53 -08:00
|
|
|
for (outputs, workspace) in workspaces {
|
2023-01-04 14:41:44 -08:00
|
|
|
// XXX efficiency
|
2025-06-04 10:00:40 -07:00
|
|
|
let old_workspace = old_workspaces.for_handle(&workspace.handle);
|
2025-01-31 14:17:56 -08:00
|
|
|
let img = old_workspace.map(|i| i.img.clone()).unwrap_or_default();
|
|
|
|
|
let has_cursor = old_workspace.is_some_and(|w| w.has_cursor);
|
|
|
|
|
let dnd_source_id = old_workspace
|
|
|
|
|
.map_or_else(iced::id::Id::unique, |w| w.dnd_source_id.clone());
|
2023-01-04 14:41:44 -08:00
|
|
|
|
2025-06-04 10:00:40 -07:00
|
|
|
self.workspaces.0.push(Workspace {
|
2025-04-21 10:36:44 -07:00
|
|
|
info: workspace,
|
2023-11-08 13:59:53 -08:00
|
|
|
outputs,
|
2025-01-29 15:09:07 -08:00
|
|
|
img,
|
2025-01-31 14:17:56 -08:00
|
|
|
has_cursor,
|
|
|
|
|
dnd_source_id,
|
2022-12-30 14:07:39 -08:00
|
|
|
});
|
|
|
|
|
}
|
2023-02-10 10:12:26 -08:00
|
|
|
self.update_capture_filter();
|
2022-12-30 14:07:39 -08:00
|
|
|
}
|
2024-04-23 13:44:59 -07:00
|
|
|
backend::Event::NewToplevel(handle, info) => {
|
2024-03-06 14:05:32 -08:00
|
|
|
log::debug!("New toplevel: {info:?}");
|
2025-01-21 09:24:19 -08:00
|
|
|
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),
|
|
|
|
|
)
|
2025-03-11 15:29:30 -07:00
|
|
|
.map(cosmic::Action::App);
|
2025-06-04 10:00:40 -07:00
|
|
|
self.toplevels.0.push(Toplevel {
|
2025-01-21 09:24:19 -08:00
|
|
|
icon: None,
|
2022-12-30 15:21:05 -08:00
|
|
|
handle,
|
|
|
|
|
info,
|
|
|
|
|
img: None,
|
|
|
|
|
});
|
2024-07-29 22:14:47 -07:00
|
|
|
// Close workspaces view if a window spawns while open
|
2024-12-16 17:55:14 -08:00
|
|
|
#[cfg(not(feature = "mock-backend"))]
|
2024-07-29 22:14:47 -07:00
|
|
|
if self.visible {
|
2025-01-21 09:24:19 -08:00
|
|
|
return Task::batch([icon_task, self.hide()]);
|
2024-07-29 22:14:47 -07:00
|
|
|
}
|
2025-01-21 09:24:19 -08:00
|
|
|
return icon_task;
|
2022-12-30 15:21:05 -08:00
|
|
|
}
|
2024-04-23 13:44:59 -07:00
|
|
|
backend::Event::UpdateToplevel(handle, info) => {
|
2025-06-04 10:00:40 -07:00
|
|
|
if let Some(toplevel) = self.toplevels.for_handle_mut(&handle) {
|
2025-01-21 09:24:19 -08:00
|
|
|
let mut task = Task::none();
|
2025-01-21 15:27:45 +01:00
|
|
|
if toplevel.info.app_id != info.app_id {
|
2025-01-21 09:24:19 -08:00
|
|
|
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),
|
|
|
|
|
)
|
2025-03-11 15:29:30 -07:00
|
|
|
.map(cosmic::Action::App);
|
2025-01-21 15:27:45 +01:00
|
|
|
}
|
2023-02-10 13:41:08 -08:00
|
|
|
toplevel.info = info;
|
2025-01-21 09:24:19 -08:00
|
|
|
return task;
|
2023-02-10 13:41:08 -08:00
|
|
|
}
|
|
|
|
|
}
|
2024-04-23 13:44:59 -07:00
|
|
|
backend::Event::CloseToplevel(handle) => {
|
2025-06-04 10:00:40 -07:00
|
|
|
if let Some(idx) = self.toplevels.0.iter().position(|x| x.handle == handle)
|
|
|
|
|
{
|
|
|
|
|
self.toplevels.0.remove(idx);
|
2023-01-18 11:01:47 -08:00
|
|
|
}
|
|
|
|
|
}
|
2025-01-29 15:09:07 -08:00
|
|
|
backend::Event::WorkspaceCapture(handle, image) => {
|
2024-10-18 13:13:53 -07:00
|
|
|
//println!("Workspace capture");
|
2025-06-04 10:00:40 -07:00
|
|
|
if let Some(workspace) = self.workspaces.for_handle_mut(&handle) {
|
|
|
|
|
if self
|
|
|
|
|
.capture_filter
|
|
|
|
|
.workspace_outputs_matches(&workspace.outputs)
|
|
|
|
|
{
|
2025-06-04 09:43:34 -07:00
|
|
|
workspace.img = Some(image);
|
|
|
|
|
}
|
2022-12-30 14:07:39 -08:00
|
|
|
}
|
|
|
|
|
}
|
2024-04-23 13:44:59 -07:00
|
|
|
backend::Event::ToplevelCapture(handle, image) => {
|
2025-06-04 10:00:40 -07:00
|
|
|
if let Some(toplevel) = self.toplevels.for_handle_mut(&handle) {
|
2024-10-18 13:13:53 -07:00
|
|
|
// println!("Got toplevel image!");
|
2025-06-04 10:00:40 -07:00
|
|
|
if self.capture_filter.toplevel_matches(&toplevel.info) {
|
2025-06-04 09:43:34 -07:00
|
|
|
toplevel.img = Some(image);
|
|
|
|
|
}
|
2022-12-30 15:21:05 -08:00
|
|
|
}
|
|
|
|
|
}
|
2025-04-21 14:02:36 -07:00
|
|
|
backend::Event::ToplevelCapabilities(capabilities) => {
|
|
|
|
|
self.toplevel_capabilities = capabilities;
|
|
|
|
|
}
|
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) => {
|
2025-06-04 10:00:40 -07:00
|
|
|
if let Some(workspace) = self.workspaces.for_handle(&workspace_handle) {
|
2025-04-21 10:36:44 -07:00
|
|
|
if workspace.is_active() {
|
2025-01-23 20:02:16 -08:00
|
|
|
return self.hide();
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-23 13:44:59 -07:00
|
|
|
self.send_wayland_cmd(backend::Cmd::ActivateWorkspace(workspace_handle));
|
2023-01-04 15:17:57 -08:00
|
|
|
}
|
|
|
|
|
Msg::ActivateToplevel(toplevel_handle) => {
|
2024-04-23 13:44:59 -07:00
|
|
|
self.send_wayland_cmd(backend::Cmd::ActivateToplevel(toplevel_handle));
|
2024-04-23 11:54:05 -07:00
|
|
|
return self.hide();
|
2022-12-30 15:21:05 -08:00
|
|
|
}
|
2023-12-13 15:54:33 -08:00
|
|
|
Msg::CloseWorkspace(_workspace_handle) => {
|
|
|
|
|
// XXX close specific workspace
|
2024-02-07 19:52:33 -08:00
|
|
|
/*
|
2023-12-13 15:54:33 -08:00
|
|
|
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-12-13 15:54:33 -08:00
|
|
|
}
|
2024-02-07 19:52:33 -08:00
|
|
|
*/
|
2023-12-13 15:54:33 -08:00
|
|
|
}
|
2023-01-18 10:58:22 -08:00
|
|
|
Msg::CloseToplevel(toplevel_handle) => {
|
|
|
|
|
// TODO confirmation?
|
2024-04-23 13:44:59 -07:00
|
|
|
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
|
|
|
}
|
2025-01-24 14:09:17 -08:00
|
|
|
Msg::DndEnter(drop_target, _x, _y, _mimes) => {
|
|
|
|
|
self.drop_target = Some(drop_target);
|
2023-07-20 15:48:19 -07:00
|
|
|
}
|
2025-01-24 14:09:17 -08:00
|
|
|
Msg::DndLeave(drop_target) => {
|
2024-05-02 12:53:09 -07:00
|
|
|
// 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.
|
2025-01-24 14:09:17 -08:00
|
|
|
if self.drop_target == Some(drop_target) {
|
2024-05-02 12:53:09 -07:00
|
|
|
self.drop_target = None;
|
|
|
|
|
}
|
2023-12-05 17:44:05 -08:00
|
|
|
}
|
2025-02-10 10:11:04 -08:00
|
|
|
Msg::DndToplevelDrop(_toplevel) => {
|
2025-01-24 14:41:36 -08:00
|
|
|
if let Some((DragSurface::Toplevel(handle), _)) = &self.drag_surface {
|
2025-01-24 14:54:29 -08:00
|
|
|
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,
|
|
|
|
|
));
|
|
|
|
|
}
|
2025-01-31 14:17:56 -08:00
|
|
|
Some(
|
|
|
|
|
DropTarget::WorkspacesBar(_)
|
|
|
|
|
| DropTarget::WorkspaceSidebarDragPlaceholder(_, _),
|
|
|
|
|
)
|
|
|
|
|
| None => {}
|
2023-12-06 10:51:55 -08:00
|
|
|
}
|
|
|
|
|
}
|
2023-12-05 17:44:05 -08:00
|
|
|
}
|
2023-12-12 15:36:20 -08:00
|
|
|
Msg::NewWorkspace => {
|
2024-02-07 19:52:33 -08:00
|
|
|
/*
|
2023-12-12 15:36:20 -08:00
|
|
|
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);
|
|
|
|
|
}
|
2024-02-07 19:52:33 -08:00
|
|
|
*/
|
2023-12-12 15:36:20 -08:00
|
|
|
}
|
2023-12-29 13:44:19 -08:00
|
|
|
Msg::Config(c) => {
|
|
|
|
|
self.conf.config = c;
|
|
|
|
|
}
|
2023-12-21 10:41:46 -08:00
|
|
|
Msg::CompConfig(c) => {
|
|
|
|
|
self.conf.workspace_config = c.workspaces;
|
|
|
|
|
}
|
2024-05-01 13:32:36 -07:00
|
|
|
Msg::BgConfig(c) => {
|
|
|
|
|
self.conf.bg = c;
|
|
|
|
|
}
|
2025-01-21 09:24:19 -08:00
|
|
|
Msg::UpdateToplevelIcon(app_id, path) => {
|
2025-06-04 10:00:40 -07:00
|
|
|
for toplevel in self.toplevels.0.iter_mut() {
|
2025-01-21 09:24:19 -08:00
|
|
|
if toplevel.info.app_id == app_id {
|
|
|
|
|
toplevel.icon = path.clone();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-24 15:35:45 -08:00
|
|
|
Msg::OnScroll(output, delta) => {
|
2025-02-06 13:30:34 -08:00
|
|
|
// 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;
|
2025-02-06 13:30:34 -08:00
|
|
|
let previous_scroll = if let Some((scroll, last_scroll_time)) = self.scroll
|
|
|
|
|
{
|
|
|
|
|
if last_scroll_time.elapsed() > Duration::from_millis(100) {
|
|
|
|
|
0.
|
|
|
|
|
} else {
|
|
|
|
|
scroll
|
|
|
|
|
}
|
2025-01-24 15:35:45 -08:00
|
|
|
} else {
|
2025-02-06 13:30:34 -08:00
|
|
|
0.
|
2025-01-24 15:35:45 -08:00
|
|
|
};
|
2025-02-06 13:30:34 -08:00
|
|
|
|
2025-02-06 13:37:53 -08:00
|
|
|
let scroll = previous_scroll + y;
|
2025-02-06 13:30:34 -08:00
|
|
|
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;
|
2025-02-06 13:30:34 -08:00
|
|
|
self.scroll = None;
|
|
|
|
|
if y < 0. {
|
|
|
|
|
ScrollDirection::Prev
|
|
|
|
|
} else if y > 0. {
|
|
|
|
|
ScrollDirection::Next
|
|
|
|
|
} else {
|
|
|
|
|
return Task::none();
|
2025-01-24 15:35:45 -08:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-06 13:30:34 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// TODO assumes only one active workspace per output
|
2025-06-04 10:00:40 -07:00
|
|
|
let workspaces = self.workspaces.for_output(&output).collect::<Vec<_>>();
|
2025-04-21 10:36:44 -07:00
|
|
|
if let Some(workspace_idx) = workspaces.iter().position(|i| i.is_active()) {
|
2025-02-06 13:30:34 -08:00
|
|
|
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(
|
2025-04-21 10:36:44 -07:00
|
|
|
workspace.handle().clone(),
|
2025-02-06 13:30:34 -08:00
|
|
|
));
|
|
|
|
|
}
|
2025-01-24 15:35:45 -08:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-10 12:02:36 -08:00
|
|
|
Msg::DndWorkspaceDrag => {}
|
2025-01-31 14:17:56 -08:00
|
|
|
Msg::DndWorkspaceDrop(_workspace) => {
|
|
|
|
|
if let Some((DragSurface::Workspace(handle), _)) = &self.drag_surface {
|
|
|
|
|
match self.drop_target.take() {
|
|
|
|
|
Some(
|
|
|
|
|
DropTarget::WorkspaceSidebarEntry(other_handle, _output)
|
|
|
|
|
| DropTarget::WorkspaceSidebarDragPlaceholder(other_handle, _output),
|
|
|
|
|
) => {
|
2025-06-04 10:00:40 -07:00
|
|
|
let workspace = self.workspaces.for_handle(handle);
|
|
|
|
|
let other_workspace = self.workspaces.for_handle(&other_handle);
|
2025-01-31 14:17:56 -08:00
|
|
|
if let (Some(workspace), Some(other_workspace)) =
|
|
|
|
|
(workspace, other_workspace)
|
|
|
|
|
{
|
|
|
|
|
if workspace.outputs == other_workspace.outputs
|
|
|
|
|
&& workspace.info.coordinates[0] + 1
|
|
|
|
|
== other_workspace.info.coordinates[0]
|
|
|
|
|
{
|
|
|
|
|
// Workspace is already in requested position
|
|
|
|
|
} else {
|
|
|
|
|
self.send_wayland_cmd(backend::Cmd::MoveWorkspaceBefore(
|
|
|
|
|
handle.clone(),
|
|
|
|
|
other_handle,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(DropTarget::OutputToplevels(_, _) | DropTarget::WorkspacesBar(_))
|
|
|
|
|
| None => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Msg::TogglePinned(workspace_handle) => {
|
2025-06-04 10:00:40 -07:00
|
|
|
if let Some(workspace) = self.workspaces.for_handle(&workspace_handle) {
|
2025-01-31 14:17:56 -08:00
|
|
|
self.send_wayland_cmd(backend::Cmd::SetWorkspacePinned(
|
|
|
|
|
workspace_handle,
|
|
|
|
|
!workspace.is_pinned(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Msg::EnteredWorkspaceSidebarEntry(workspace_handle, entered) => {
|
2025-06-04 10:00:40 -07:00
|
|
|
if let Some(workspace) = self.workspaces.for_handle_mut(&workspace_handle) {
|
2025-01-31 14:17:56 -08:00
|
|
|
workspace.has_cursor = entered;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-21 14:06:47 -07:00
|
|
|
Msg::DbusInterface(interface) => {
|
|
|
|
|
if let Ok(interface) = interface {
|
|
|
|
|
self.dbus_interface = Some(interface);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-22 11:49:06 -07:00
|
|
|
Msg::DBus(evt) => {
|
|
|
|
|
return match evt {
|
|
|
|
|
dbus::Event::Show => self.show(),
|
|
|
|
|
dbus::Event::Hide => self.hide(),
|
|
|
|
|
};
|
|
|
|
|
}
|
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
|
|
|
}
|
2025-03-11 15:29:30 -07:00
|
|
|
fn dbus_activation(&mut self, msg: dbus_activation::Message) -> Task<cosmic::Action<Msg>> {
|
|
|
|
|
if let dbus_activation::Details::Activate = msg.msg {
|
2023-11-21 16:15:02 -05:00
|
|
|
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
|
|
|
|
2025-07-21 14:06:47 -07:00
|
|
|
fn dbus_connection(&mut self, conn: zbus::Connection) -> Task<cosmic::Action<Msg>> {
|
|
|
|
|
Task::perform(dbus::Interface::new(conn), Msg::DbusInterface).map(cosmic::Action::App)
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-30 14:07:39 -08:00
|
|
|
fn subscription(&self) -> Subscription<Msg> {
|
2025-04-07 08:20:51 -07:00
|
|
|
let events = iced::event::listen_with(|evt, _, _| match evt {
|
|
|
|
|
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(evt)) => {
|
2025-03-21 12:57:28 -07:00
|
|
|
if !matches!(evt, WaylandEvent::RequestResize) {
|
|
|
|
|
Some(Msg::WaylandEvent(evt))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
2025-04-07 08:20:51 -07:00
|
|
|
}
|
|
|
|
|
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: _,
|
2025-04-07 08:20:51 -07:00
|
|
|
}) => Some(Msg::Close),
|
2025-01-31 14:17:56 -08:00
|
|
|
// XXX Workaround for `on_finish`/`on_cancel` not being called, seemingly
|
|
|
|
|
// due to state diffing behavior.
|
|
|
|
|
iced::Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
|
|
|
|
|
Some(Msg::SourceFinished)
|
|
|
|
|
}
|
2025-04-07 08:20:51 -07:00
|
|
|
_ => None,
|
2022-12-30 14:07:39 -08:00
|
|
|
});
|
2023-12-29 13:44:19 -08:00
|
|
|
let config_subscription = cosmic_config::config_subscription::<_, CosmicWorkspacesConfig>(
|
2023-12-21 10:41:46 -08:00
|
|
|
"config-sub",
|
2024-01-03 10:57:47 -08:00
|
|
|
"com.system76.CosmicWorkspaces".into(),
|
2023-12-21 10:41:46 -08:00
|
|
|
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);
|
2023-12-29 13:44:19 -08:00
|
|
|
}
|
2024-01-19 03:35:33 -08:00
|
|
|
Msg::Config(update.config)
|
2023-12-29 13:44:19 -08:00
|
|
|
});
|
|
|
|
|
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);
|
2023-12-21 10:41:46 -08:00
|
|
|
}
|
2024-04-24 13:51:20 -07:00
|
|
|
Msg::CompConfig(Box::new(update.config))
|
2023-12-21 10:41:46 -08:00
|
|
|
});
|
2024-05-01 13:32:36 -07:00
|
|
|
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,
|
|
|
|
|
];
|
2023-11-08 13:31:13 -08:00
|
|
|
if let Some(conn) = self.conn.clone() {
|
2024-04-23 13:44:59 -07:00
|
|
|
subscriptions.push(backend::subscription(conn).map(Msg::Wayland));
|
2023-11-08 13:31:13 -08:00
|
|
|
}
|
2025-09-22 11:49:06 -07:00
|
|
|
if let Some(interface) = &self.dbus_interface {
|
|
|
|
|
subscriptions.push(interface.subscription().map(Msg::DBus));
|
|
|
|
|
}
|
2023-11-08 13:31:13 -08:00
|
|
|
iced::Subscription::batch(subscriptions)
|
2022-12-30 14:07:39 -08:00
|
|
|
}
|
|
|
|
|
|
2025-08-22 13:11:32 -07:00
|
|
|
fn view(&self) -> cosmic::Element<'_, Self::Message> {
|
2023-11-21 16:15:02 -05:00
|
|
|
unreachable!()
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 13:11:32 -07:00
|
|
|
fn view_window(&self, id: iced::window::Id) -> cosmic::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
|
|
|
}
|
2025-01-16 14:50:45 -08:00
|
|
|
log::error!("non-existant layer shell id {}?", id);
|
2024-12-23 01:54:51 +01:00
|
|
|
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
|
|
|
}
|