feat/fix: track output state in app-list
This commit is contained in:
parent
3c74e433dd
commit
27324f34e2
3 changed files with 136 additions and 78 deletions
|
|
@ -3,15 +3,20 @@ use crate::config::AppListConfig;
|
|||
use crate::config::APP_ID;
|
||||
use crate::fl;
|
||||
use crate::wayland_subscription::wayland_subscription;
|
||||
use crate::wayland_subscription::OutputUpdate;
|
||||
use crate::wayland_subscription::ToplevelRequest;
|
||||
use crate::wayland_subscription::ToplevelUpdate;
|
||||
use crate::wayland_subscription::WaylandImage;
|
||||
use crate::wayland_subscription::WaylandRequest;
|
||||
use crate::wayland_subscription::WaylandUpdate;
|
||||
use cctk::sctk::output::OutputInfo;
|
||||
use cctk::sctk::output::OutputState;
|
||||
use cctk::sctk::reexports::calloop::channel::Sender;
|
||||
use cctk::toplevel_info::ToplevelInfo;
|
||||
use cctk::wayland_client::protocol::wl_data_device_manager::DndAction;
|
||||
use cctk::wayland_client::protocol::wl_output::WlOutput;
|
||||
use cctk::wayland_client::protocol::wl_seat::WlSeat;
|
||||
use cctk::wayland_client::Proxy;
|
||||
use cosmic::applet::cosmic_panel_config::PanelSize;
|
||||
use cosmic::applet::Size;
|
||||
use cosmic::cosmic_config::{Config, CosmicConfigEntry};
|
||||
|
|
@ -105,6 +110,40 @@ pub fn load_applications_for_app_ids_sorted<'a, 'b>(
|
|||
ret
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct AppletIconData {
|
||||
icon_size: u16,
|
||||
icon_spacing: f32,
|
||||
dot_size: f32,
|
||||
dot_spacing: f32,
|
||||
padding: [u16; 2],
|
||||
}
|
||||
|
||||
impl AppletIconData {
|
||||
fn new(applet: &Context) -> Self {
|
||||
let suggested_size = applet.suggested_size().0;
|
||||
let (icon_size, icon_spacing, dot_size, dot_spacing, p_padding) = match applet.size {
|
||||
Size::PanelSize(PanelSize::XL) => (10 + suggested_size, 0.0, 2.0, 4.0, 5),
|
||||
Size::PanelSize(PanelSize::L) => (10 + suggested_size, 0.0, 2.0, 4.0, 5),
|
||||
Size::PanelSize(PanelSize::M) => (10 + suggested_size, 0.0, 2.0, 4.0, 5),
|
||||
Size::PanelSize(PanelSize::S) => (16 + suggested_size, 0.0, 1.0, 2.0, 3),
|
||||
Size::PanelSize(PanelSize::XS) => (8 + suggested_size, 0.0, 1.0, 2.0, 3),
|
||||
Size::Hardcoded(_) => (10 + suggested_size, 4.0, 0.0, 4.0, 5),
|
||||
};
|
||||
let padding = match applet.anchor {
|
||||
PanelAnchor::Bottom | PanelAnchor::Top => [0, p_padding],
|
||||
PanelAnchor::Left | PanelAnchor::Right => [p_padding, 0],
|
||||
};
|
||||
AppletIconData {
|
||||
icon_size,
|
||||
icon_spacing,
|
||||
dot_size,
|
||||
dot_spacing,
|
||||
padding,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct DockItem {
|
||||
id: u32,
|
||||
|
|
@ -156,23 +195,14 @@ impl DockItem {
|
|||
..
|
||||
} = self;
|
||||
|
||||
let (app_icon_size_modifier, dot_radius, p_padding) = match applet.size {
|
||||
Size::PanelSize(PanelSize::XL) => (10, 2.0, 5),
|
||||
Size::PanelSize(PanelSize::L) => (10, 2.0, 5),
|
||||
Size::PanelSize(PanelSize::M) => (10, 2.0, 5),
|
||||
Size::PanelSize(PanelSize::S) => (16, 1.0, 3),
|
||||
Size::PanelSize(PanelSize::XS) => (8, 1.0, 3),
|
||||
Size::Hardcoded(_) => (10, 2.0, 5),
|
||||
};
|
||||
let app_icon = AppletIconData::new(applet);
|
||||
|
||||
let app_icon_size = applet.suggested_size().0 + app_icon_size_modifier;
|
||||
|
||||
let cosmic_icon = desktop_info.icon.as_cosmic_icon().size(app_icon_size);
|
||||
let cosmic_icon = desktop_info.icon.as_cosmic_icon().size(app_icon.icon_size);
|
||||
|
||||
let dot_spacer = (0..1)
|
||||
.map(|_| {
|
||||
container(vertical_space(Length::Fixed(0.0)))
|
||||
.padding(dot_radius)
|
||||
.padding(app_icon.dot_size)
|
||||
.into()
|
||||
})
|
||||
.collect_vec();
|
||||
|
|
@ -181,7 +211,7 @@ impl DockItem {
|
|||
(0..1)
|
||||
.map(|_| {
|
||||
container(vertical_space(Length::Fixed(0.0)))
|
||||
.padding(dot_radius)
|
||||
.padding(app_icon.dot_size)
|
||||
.into()
|
||||
})
|
||||
.collect_vec()
|
||||
|
|
@ -189,7 +219,7 @@ impl DockItem {
|
|||
(0..min(toplevels.len(), 3))
|
||||
.map(|_| {
|
||||
container(vertical_space(Length::Fixed(0.0)))
|
||||
.padding(dot_radius)
|
||||
.padding(app_icon.dot_size)
|
||||
.style(<Theme as container::StyleSheet>::Style::Custom(Box::new(
|
||||
|theme| container::Appearance {
|
||||
text_color: Some(Color::TRANSPARENT),
|
||||
|
|
@ -212,55 +242,43 @@ impl DockItem {
|
|||
|
||||
let icon_wrapper: Element<_> = match applet.anchor {
|
||||
PanelAnchor::Left => row(vec![
|
||||
column(dots).spacing(4).into(),
|
||||
column(dots).spacing(app_icon.dot_spacing).into(),
|
||||
cosmic_icon.into(),
|
||||
column(dot_spacer).spacing(4).into(),
|
||||
column(dot_spacer).spacing(app_icon.dot_spacing).into(),
|
||||
])
|
||||
.align_items(iced::Alignment::Center)
|
||||
.spacing(1)
|
||||
.into(),
|
||||
PanelAnchor::Right => row(vec![
|
||||
column(dot_spacer).spacing(4).into(),
|
||||
column(dot_spacer).spacing(app_icon.dot_spacing).into(),
|
||||
cosmic_icon.into(),
|
||||
column(dots).spacing(4).into(),
|
||||
column(dots).spacing(app_icon.dot_spacing).into(),
|
||||
])
|
||||
.align_items(iced::Alignment::Center)
|
||||
.spacing(1)
|
||||
.into(),
|
||||
PanelAnchor::Top => column(vec![
|
||||
row(dots).spacing(4).into(),
|
||||
row(dots).spacing(app_icon.dot_spacing).into(),
|
||||
cosmic_icon.into(),
|
||||
row(dot_spacer).spacing(4).into(),
|
||||
row(dot_spacer).spacing(app_icon.dot_spacing).into(),
|
||||
])
|
||||
.align_items(iced::Alignment::Center)
|
||||
.spacing(1)
|
||||
.into(),
|
||||
PanelAnchor::Bottom => column(vec![
|
||||
row(dot_spacer).spacing(4).into(),
|
||||
row(dot_spacer).spacing(app_icon.dot_spacing).into(),
|
||||
cosmic_icon.into(),
|
||||
row(dots).spacing(4).into(),
|
||||
row(dots).spacing(app_icon.dot_spacing).into(),
|
||||
])
|
||||
.align_items(iced::Alignment::Center)
|
||||
.spacing(1)
|
||||
.into(),
|
||||
};
|
||||
|
||||
let icon_button = match applet.anchor {
|
||||
PanelAnchor::Left => cosmic::widget::button(icon_wrapper)
|
||||
.style(Button::Text)
|
||||
.padding([p_padding, 0]),
|
||||
PanelAnchor::Right => cosmic::widget::button(icon_wrapper)
|
||||
.style(Button::Text)
|
||||
.padding([p_padding, 0]),
|
||||
PanelAnchor::Top => cosmic::widget::button(icon_wrapper)
|
||||
.style(Button::Text)
|
||||
.padding([0, p_padding]),
|
||||
PanelAnchor::Bottom => cosmic::widget::button(icon_wrapper)
|
||||
.style(Button::Text)
|
||||
.padding([0, p_padding]),
|
||||
}
|
||||
.selected(is_focused)
|
||||
.style(app_list_icon_style(is_focused));
|
||||
let icon_button = cosmic::widget::button(icon_wrapper)
|
||||
.padding(app_icon.padding)
|
||||
.selected(is_focused)
|
||||
.style(app_list_icon_style(is_focused));
|
||||
|
||||
let icon_button = if interaction_enabled {
|
||||
dnd_source(
|
||||
|
|
@ -320,7 +338,8 @@ struct CosmicAppList {
|
|||
dnd_offer: Option<DndOffer>,
|
||||
is_listening_for_dnd: bool,
|
||||
gpus: Option<Vec<Gpu>>,
|
||||
active_workspace: Option<ZcosmicWorkspaceHandleV1>,
|
||||
active_workspaces: Vec<ZcosmicWorkspaceHandleV1>,
|
||||
output_list: HashMap<WlOutput, OutputInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
|
|
@ -1093,9 +1112,18 @@ impl cosmic::Application for CosmicAppList {
|
|||
}
|
||||
}
|
||||
},
|
||||
WaylandUpdate::Workspace(handle) => {
|
||||
self.active_workspace = Some(handle);
|
||||
}
|
||||
WaylandUpdate::Workspace(workspaces) => self.active_workspaces = workspaces,
|
||||
WaylandUpdate::Output(event) => match event {
|
||||
OutputUpdate::Add(output, info) => {
|
||||
self.output_list.insert(output, info);
|
||||
}
|
||||
OutputUpdate::Update(output, info) => {
|
||||
self.output_list.insert(output, info);
|
||||
}
|
||||
OutputUpdate::Remove(output) => {
|
||||
self.output_list.remove(&output);
|
||||
}
|
||||
},
|
||||
WaylandUpdate::ActivationToken {
|
||||
token,
|
||||
exec,
|
||||
|
|
@ -1205,6 +1233,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let focused_item = self.currently_active_toplevel();
|
||||
let app_icon = AppletIconData::new(&self.core.applet);
|
||||
let is_horizontal = match self.core.applet.anchor {
|
||||
PanelAnchor::Top | PanelAnchor::Bottom => true,
|
||||
PanelAnchor::Left | PanelAnchor::Right => false,
|
||||
|
|
@ -1274,20 +1303,18 @@ impl cosmic::Application for CosmicAppList {
|
|||
(
|
||||
Length::Shrink,
|
||||
Length::Shrink,
|
||||
dnd_listener(row(favorites)),
|
||||
row(active).into(),
|
||||
container(vertical_rule(1))
|
||||
.height(self.core.applet.suggested_size().1)
|
||||
.into(),
|
||||
dnd_listener(row(favorites).spacing(app_icon.icon_spacing)),
|
||||
row(active).spacing(app_icon.icon_spacing).into(),
|
||||
container(vertical_rule(1)).height(Length::Fill).into(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Length::Shrink,
|
||||
Length::Shrink,
|
||||
dnd_listener(column(favorites)),
|
||||
column(active).into(),
|
||||
dnd_listener(column(favorites).spacing(app_icon.icon_spacing)),
|
||||
column(active).spacing(app_icon.icon_spacing).into(),
|
||||
container(divider::horizontal::default())
|
||||
.width(self.core.applet.suggested_size().1)
|
||||
.width(Length::Fill)
|
||||
.into(),
|
||||
)
|
||||
};
|
||||
|
|
@ -1342,14 +1369,14 @@ impl cosmic::Application for CosmicAppList {
|
|||
let mut content = match &self.core.applet.anchor {
|
||||
PanelAnchor::Left | PanelAnchor::Right => container(
|
||||
Column::with_children(content_list)
|
||||
.spacing(4)
|
||||
.spacing(4.0)
|
||||
.align_items(Alignment::Center)
|
||||
.height(h)
|
||||
.width(w),
|
||||
),
|
||||
PanelAnchor::Top | PanelAnchor::Bottom => container(
|
||||
Row::with_children(content_list)
|
||||
.spacing(4)
|
||||
.spacing(4.0)
|
||||
.align_items(Alignment::Center)
|
||||
.height(h)
|
||||
.width(w),
|
||||
|
|
@ -1592,16 +1619,23 @@ impl cosmic::Application for CosmicAppList {
|
|||
|
||||
impl CosmicAppList {
|
||||
fn currently_active_toplevel(&self) -> Vec<ZcosmicToplevelHandleV1> {
|
||||
if self.active_workspace.is_none() {
|
||||
println!("No active workspace?");
|
||||
if self.active_workspaces.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let current_output = self.core.applet.output_name.clone();
|
||||
let mut focused_toplevels: Vec<ZcosmicToplevelHandleV1> = Vec::new();
|
||||
let active_workspace = self.active_workspace.as_ref().unwrap().clone();
|
||||
let active_workspaces = self.active_workspaces.clone();
|
||||
for toplevel_list in self.active_list.iter().chain(self.favorite_list.iter()) {
|
||||
for (t_handle, t_info, _) in &toplevel_list.toplevels {
|
||||
if t_info.workspace.contains(&active_workspace)
|
||||
&& t_info.state.contains(&State::Activated)
|
||||
if t_info.state.contains(&State::Activated)
|
||||
&& active_workspaces
|
||||
.iter()
|
||||
.any(|workspace| t_info.workspace.contains(workspace))
|
||||
&& t_info.output.iter().any(|x| {
|
||||
self.output_list.get(x).is_some_and(|val| {
|
||||
val.name.as_ref().is_some_and(|n| *n == current_output)
|
||||
})
|
||||
})
|
||||
{
|
||||
focused_toplevels.push(t_handle.clone());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::wayland_subscription::{
|
||||
ToplevelRequest, ToplevelUpdate, WaylandImage, WaylandRequest, WaylandUpdate,
|
||||
OutputUpdate, ToplevelRequest, ToplevelUpdate, WaylandImage, WaylandRequest, WaylandUpdate,
|
||||
};
|
||||
use std::{
|
||||
os::{
|
||||
|
|
@ -58,6 +58,7 @@ struct AppData {
|
|||
tx: UnboundedSender<WaylandUpdate>,
|
||||
conn: Connection,
|
||||
queue_handle: QueueHandle<Self>,
|
||||
output_state: OutputState,
|
||||
workspace_state: WorkspaceState,
|
||||
toplevel_info_state: ToplevelInfoState,
|
||||
toplevel_manager_state: ToplevelManagerState,
|
||||
|
|
@ -66,7 +67,6 @@ struct AppData {
|
|||
seat_state: SeatState,
|
||||
shm_state: Shm,
|
||||
activation_state: Option<ActivationState>,
|
||||
output_state: OutputState,
|
||||
}
|
||||
|
||||
// Workspace and toplevel handling
|
||||
|
|
@ -83,14 +83,30 @@ impl OutputHandler for AppData {
|
|||
_qh: &QueueHandle<Self>,
|
||||
output: wl_output::WlOutput,
|
||||
) {
|
||||
if let Some(info) = self.output_state.info(&output) {
|
||||
let _ = self
|
||||
.tx
|
||||
.unbounded_send(WaylandUpdate::Output(OutputUpdate::Add(
|
||||
output.clone(),
|
||||
info.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
fn update_output(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_output: wl_output::WlOutput,
|
||||
output: wl_output::WlOutput,
|
||||
) {
|
||||
if let Some(info) = self.output_state.info(&output) {
|
||||
let _ = self
|
||||
.tx
|
||||
.unbounded_send(WaylandUpdate::Output(OutputUpdate::Update(
|
||||
output.clone(),
|
||||
info.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
fn output_destroyed(
|
||||
|
|
@ -99,6 +115,9 @@ impl OutputHandler for AppData {
|
|||
_qh: &QueueHandle<Self>,
|
||||
output: wl_output::WlOutput,
|
||||
) {
|
||||
let _ = self
|
||||
.tx
|
||||
.unbounded_send(WaylandUpdate::Output(OutputUpdate::Remove(output.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,19 +127,23 @@ impl WorkspaceHandler for AppData {
|
|||
}
|
||||
|
||||
fn done(&mut self) {
|
||||
'workspaces_loop: for group in self.workspace_state.workspace_groups() {
|
||||
for workspace in &group.workspaces {
|
||||
if workspace
|
||||
.state
|
||||
.contains(&WEnum::Value(WorkspaceUpdateState::Active))
|
||||
{
|
||||
let _ = self
|
||||
.tx
|
||||
.unbounded_send(WaylandUpdate::Workspace(workspace.handle.clone()));
|
||||
break 'workspaces_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
let active_workspaces = self
|
||||
.workspace_state
|
||||
.workspace_groups()
|
||||
.iter()
|
||||
.filter_map(|x| {
|
||||
x.workspaces.iter().find(|w| {
|
||||
w.state
|
||||
.contains(&WEnum::Value(WorkspaceUpdateState::Active))
|
||||
})
|
||||
})
|
||||
.map(|workspace| workspace.handle.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let _ = self
|
||||
.tx
|
||||
.unbounded_send(WaylandUpdate::Workspace(
|
||||
active_workspaces.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -667,14 +690,14 @@ pub(crate) fn wayland_handler(
|
|||
return;
|
||||
}
|
||||
let registry_state = RegistryState::new(&globals);
|
||||
let workspace_state = WorkspaceState::new(®istry_state, &qh); // Create before toplevel info state
|
||||
|
||||
let mut app_data = AppData {
|
||||
exit: false,
|
||||
tx,
|
||||
conn,
|
||||
queue_handle: qh.clone(),
|
||||
workspace_state,
|
||||
output_state: OutputState::new(&globals, &qh),
|
||||
workspace_state: WorkspaceState::new(®istry_state, &qh),
|
||||
toplevel_info_state: ToplevelInfoState::new(®istry_state, &qh),
|
||||
toplevel_manager_state: ToplevelManagerState::new(®istry_state, &qh),
|
||||
screencopy_state: ScreencopyState::new(&globals, &qh),
|
||||
|
|
@ -682,7 +705,6 @@ pub(crate) fn wayland_handler(
|
|||
seat_state: SeatState::new(&globals, &qh),
|
||||
shm_state: Shm::bind(&globals, &qh).unwrap(),
|
||||
activation_state: ActivationState::bind::<AppData>(&globals, &qh).ok(),
|
||||
output_state: OutputState::new(&globals, &qh),
|
||||
};
|
||||
|
||||
loop {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
//!
|
||||
//! This code was generated by `zbus-xmlgen` `2.0.1` from DBus introspection data.
|
||||
//! Source: `Interface '/org/freedesktop/UPower/KbdBacklight' from service 'org.freedesktop.UPower' on system bus`.
|
||||
use cctk::sctk::reexports::calloop;
|
||||
use cctk::sctk::{output::OutputInfo, reexports::calloop};
|
||||
use cctk::toplevel_info::ToplevelInfo;
|
||||
use cctk::wayland_client::protocol::wl_output::WlOutput;
|
||||
use cosmic::iced;
|
||||
use cosmic::iced::subscription;
|
||||
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
|
||||
|
|
@ -99,7 +100,8 @@ pub enum WaylandUpdate {
|
|||
Init(calloop::channel::Sender<WaylandRequest>),
|
||||
Finished,
|
||||
Toplevel(ToplevelUpdate),
|
||||
Workspace(ZcosmicWorkspaceHandleV1),
|
||||
Workspace(Vec<ZcosmicWorkspaceHandleV1>),
|
||||
Output(OutputUpdate),
|
||||
ActivationToken {
|
||||
token: Option<String>,
|
||||
exec: String,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue