feat(app-list): implement workspace and configured-output filtering
Implement optional filtering of apps by active workspace or configured output. The filter_top_levels config option accepts None (no filtering), ActiveWorkspace (workspace-only), or ConfiguredOutput (monitor and workspace filtering). Signed-off-by: Tobias Schaffner <tobiasschaffner87@outlook.com>
This commit is contained in:
parent
2852f3cc16
commit
8ea267abfe
1 changed files with 123 additions and 40 deletions
|
|
@ -182,6 +182,7 @@ impl DockItem {
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
dot_border_radius: [f32; 4],
|
dot_border_radius: [f32; 4],
|
||||||
window_id: window::Id,
|
window_id: window::Id,
|
||||||
|
filter: Option<&dyn Fn(&ToplevelInfo) -> bool>,
|
||||||
) -> Element<'_, Message> {
|
) -> Element<'_, Message> {
|
||||||
let Self {
|
let Self {
|
||||||
toplevels,
|
toplevels,
|
||||||
|
|
@ -190,6 +191,16 @@ impl DockItem {
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
let filtered_toplevels: Vec<_> = if let Some(filter_fn) = filter {
|
||||||
|
toplevels
|
||||||
|
.iter()
|
||||||
|
.filter(|(info, _)| filter_fn(info))
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
toplevels.iter().collect()
|
||||||
|
};
|
||||||
|
let toplevel_count = filtered_toplevels.len();
|
||||||
|
|
||||||
let app_icon = AppletIconData::new(applet);
|
let app_icon = AppletIconData::new(applet);
|
||||||
|
|
||||||
let cosmic_icon = fde::IconSource::from_unknown(desktop_info.icon().unwrap_or_default())
|
let cosmic_icon = fde::IconSource::from_unknown(desktop_info.icon().unwrap_or_default())
|
||||||
|
|
@ -200,7 +211,7 @@ impl DockItem {
|
||||||
.height(app_icon.icon_size.into());
|
.height(app_icon.icon_size.into());
|
||||||
|
|
||||||
let indicator = {
|
let indicator = {
|
||||||
let container = if toplevels.len() <= 1 {
|
let container = if toplevel_count <= 1 {
|
||||||
vertical_space().height(Length::Fixed(0.0))
|
vertical_space().height(Length::Fixed(0.0))
|
||||||
} else {
|
} else {
|
||||||
match applet.anchor {
|
match applet.anchor {
|
||||||
|
|
@ -215,7 +226,7 @@ impl DockItem {
|
||||||
.apply(container)
|
.apply(container)
|
||||||
.padding(app_icon.dot_radius);
|
.padding(app_icon.dot_radius);
|
||||||
|
|
||||||
if toplevels.is_empty() {
|
if toplevel_count == 0 {
|
||||||
container
|
container
|
||||||
} else {
|
} else {
|
||||||
container.class(theme::Container::custom(move |theme| container::Style {
|
container.class(theme::Container::custom(move |theme| container::Style {
|
||||||
|
|
@ -272,10 +283,10 @@ impl DockItem {
|
||||||
let icon_button: Element<_> = if interaction_enabled {
|
let icon_button: Element<_> = if interaction_enabled {
|
||||||
mouse_area(
|
mouse_area(
|
||||||
icon_button
|
icon_button
|
||||||
.on_press_maybe(if toplevels.is_empty() {
|
.on_press_maybe(if toplevel_count == 0 {
|
||||||
launch_on_preferred_gpu(desktop_info, gpus)
|
launch_on_preferred_gpu(desktop_info, gpus)
|
||||||
} else if toplevels.len() == 1 {
|
} else if toplevel_count == 1 {
|
||||||
toplevels
|
filtered_toplevels
|
||||||
.first()
|
.first()
|
||||||
.map(|t| Message::Toggle(t.0.foreign_toplevel.clone()))
|
.map(|t| Message::Toggle(t.0.foreign_toplevel.clone()))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -635,6 +646,32 @@ impl CosmicAppList {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_on_current_monitor_and_workspace(&self, toplevel_info: &ToplevelInfo) -> bool {
|
||||||
|
use cosmic_app_list_config::ToplevelFilter;
|
||||||
|
|
||||||
|
let on_active_workspace = self.active_workspaces.is_empty()
|
||||||
|
|| toplevel_info.workspace.is_empty()
|
||||||
|
|| self.active_workspaces
|
||||||
|
.iter()
|
||||||
|
.any(|workspace| toplevel_info.workspace.contains(workspace));
|
||||||
|
|
||||||
|
match &self.config.filter_top_levels {
|
||||||
|
None => true,
|
||||||
|
Some(ToplevelFilter::ActiveWorkspace) => on_active_workspace,
|
||||||
|
Some(ToplevelFilter::ConfiguredOutput) => {
|
||||||
|
let on_active_output = self
|
||||||
|
.output_list
|
||||||
|
.iter()
|
||||||
|
.find(|(_, info)| info.name.as_ref() == Some(&self.core.applet.output_name))
|
||||||
|
.map_or(true, |(active_output, _)| {
|
||||||
|
toplevel_info.output.iter().any(|output| output == active_output)
|
||||||
|
});
|
||||||
|
|
||||||
|
on_active_output && on_active_workspace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update pinned items using the cached desktop entries as a source.
|
// Update pinned items using the cached desktop entries as a source.
|
||||||
fn update_pinned_list(&mut self) {
|
fn update_pinned_list(&mut self) {
|
||||||
self.pinned_list = find_desktop_entries(&self.desktop_entries, &self.config.favorites)
|
self.pinned_list = find_desktop_entries(&self.desktop_entries, &self.config.favorites)
|
||||||
|
|
@ -1715,6 +1752,12 @@ impl cosmic::Application for CosmicAppList {
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.map(|dock_item| {
|
.map(|dock_item| {
|
||||||
|
let filtered_is_focused = dock_item
|
||||||
|
.toplevels
|
||||||
|
.iter()
|
||||||
|
.filter(|(info, _)| self.is_on_current_monitor_and_workspace(info))
|
||||||
|
.any(|y| focused_item.contains(&y.0.foreign_toplevel));
|
||||||
|
|
||||||
self.core
|
self.core
|
||||||
.applet
|
.applet
|
||||||
.applet_tooltip::<Message>(
|
.applet_tooltip::<Message>(
|
||||||
|
|
@ -1724,12 +1767,10 @@ impl cosmic::Application for CosmicAppList {
|
||||||
self.popup.is_none(),
|
self.popup.is_none(),
|
||||||
self.config.enable_drag_source,
|
self.config.enable_drag_source,
|
||||||
self.gpus.as_deref(),
|
self.gpus.as_deref(),
|
||||||
dock_item
|
filtered_is_focused,
|
||||||
.toplevels
|
|
||||||
.iter()
|
|
||||||
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
|
|
||||||
dot_radius,
|
dot_radius,
|
||||||
self.core.main_window_id().unwrap(),
|
self.core.main_window_id().unwrap(),
|
||||||
|
Some(&|info| self.is_on_current_monitor_and_workspace(info)),
|
||||||
),
|
),
|
||||||
dock_item
|
dock_item
|
||||||
.desktop_info
|
.desktop_info
|
||||||
|
|
@ -1772,6 +1813,12 @@ impl cosmic::Application for CosmicAppList {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|o| o.dock_item.as_ref().map(|item| (item, o.preview_index)))
|
.and_then(|o| o.dock_item.as_ref().map(|item| (item, o.preview_index)))
|
||||||
{
|
{
|
||||||
|
let filtered_is_focused = item
|
||||||
|
.toplevels
|
||||||
|
.iter()
|
||||||
|
.filter(|(info, _)| self.is_on_current_monitor_and_workspace(info))
|
||||||
|
.any(|y| focused_item.contains(&y.0.foreign_toplevel));
|
||||||
|
|
||||||
favorites.insert(
|
favorites.insert(
|
||||||
index.min(favorites.len()),
|
index.min(favorites.len()),
|
||||||
item.as_icon(
|
item.as_icon(
|
||||||
|
|
@ -1780,11 +1827,10 @@ impl cosmic::Application for CosmicAppList {
|
||||||
false,
|
false,
|
||||||
self.config.enable_drag_source,
|
self.config.enable_drag_source,
|
||||||
self.gpus.as_deref(),
|
self.gpus.as_deref(),
|
||||||
item.toplevels
|
filtered_is_focused,
|
||||||
.iter()
|
|
||||||
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
|
|
||||||
dot_radius,
|
dot_radius,
|
||||||
self.core.main_window_id().unwrap(),
|
self.core.main_window_id().unwrap(),
|
||||||
|
Some(&|info| self.is_on_current_monitor_and_workspace(info)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if self.is_listening_for_dnd && self.pinned_list.is_empty() {
|
} else if self.is_listening_for_dnd && self.pinned_list.is_empty() {
|
||||||
|
|
@ -1799,9 +1845,19 @@ impl cosmic::Application for CosmicAppList {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filtered_active_list: Vec<_> = self
|
||||||
|
.active_list
|
||||||
|
.iter()
|
||||||
|
.filter(|dock_item| {
|
||||||
|
dock_item.toplevels.iter().any(|(toplevel_info, _)| {
|
||||||
|
self.is_on_current_monitor_and_workspace(toplevel_info)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut active: Vec<_> =
|
let mut active: Vec<_> =
|
||||||
self.active_list[..active_popup_cutoff.map_or(self.active_list.len(), |n| {
|
filtered_active_list[..active_popup_cutoff.map_or(filtered_active_list.len(), |n| {
|
||||||
if n < self.active_list.len() {
|
if n < filtered_active_list.len() {
|
||||||
n.saturating_sub(1)
|
n.saturating_sub(1)
|
||||||
} else {
|
} else {
|
||||||
n
|
n
|
||||||
|
|
@ -1809,6 +1865,12 @@ impl cosmic::Application for CosmicAppList {
|
||||||
})]
|
})]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|dock_item| {
|
.map(|dock_item| {
|
||||||
|
let filtered_is_focused = dock_item
|
||||||
|
.toplevels
|
||||||
|
.iter()
|
||||||
|
.filter(|(info, _)| self.is_on_current_monitor_and_workspace(info))
|
||||||
|
.any(|y| focused_item.contains(&y.0.foreign_toplevel));
|
||||||
|
|
||||||
self.core
|
self.core
|
||||||
.applet
|
.applet
|
||||||
.applet_tooltip(
|
.applet_tooltip(
|
||||||
|
|
@ -1818,12 +1880,10 @@ impl cosmic::Application for CosmicAppList {
|
||||||
self.popup.is_none(),
|
self.popup.is_none(),
|
||||||
self.config.enable_drag_source,
|
self.config.enable_drag_source,
|
||||||
self.gpus.as_deref(),
|
self.gpus.as_deref(),
|
||||||
dock_item
|
filtered_is_focused,
|
||||||
.toplevels
|
|
||||||
.iter()
|
|
||||||
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
|
|
||||||
dot_radius,
|
dot_radius,
|
||||||
self.core.main_window_id().unwrap(),
|
self.core.main_window_id().unwrap(),
|
||||||
|
Some(&|info| self.is_on_current_monitor_and_workspace(info)),
|
||||||
),
|
),
|
||||||
dock_item
|
dock_item
|
||||||
.desktop_info
|
.desktop_info
|
||||||
|
|
@ -1838,7 +1898,7 @@ impl cosmic::Application for CosmicAppList {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if active_popup_cutoff.is_some_and(|n| n < self.active_list.len()) {
|
if active_popup_cutoff.is_some_and(|n| n < filtered_active_list.len()) {
|
||||||
// button to show more active
|
// button to show more active
|
||||||
let icon = match self.core.applet.anchor {
|
let icon = match self.core.applet.anchor {
|
||||||
PanelAnchor::Bottom => "go-up-symbolic",
|
PanelAnchor::Bottom => "go-up-symbolic",
|
||||||
|
|
@ -1987,14 +2047,7 @@ impl cosmic::Application for CosmicAppList {
|
||||||
..
|
..
|
||||||
}) = self.popup.as_ref().filter(|p| id == p.id)
|
}) = self.popup.as_ref().filter(|p| id == p.id)
|
||||||
{
|
{
|
||||||
let (
|
let (dock_item, is_pinned) = match self.pinned_list.iter().find(|i| i.id == *id) {
|
||||||
DockItem {
|
|
||||||
toplevels,
|
|
||||||
desktop_info,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
is_pinned,
|
|
||||||
) = match self.pinned_list.iter().find(|i| i.id == *id) {
|
|
||||||
Some(e) => (e, true),
|
Some(e) => (e, true),
|
||||||
None => match self.active_list.iter().find(|i| i.id == *id) {
|
None => match self.active_list.iter().find(|i| i.id == *id) {
|
||||||
Some(e) => (e, false),
|
Some(e) => (e, false),
|
||||||
|
|
@ -2002,6 +2055,18 @@ impl cosmic::Application for CosmicAppList {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Filter toplevels to only show windows on current monitor and workspace
|
||||||
|
let filtered_toplevels: Vec<_> = dock_item
|
||||||
|
.toplevels
|
||||||
|
.iter()
|
||||||
|
.filter(|(toplevel_info, _)| {
|
||||||
|
self.is_on_current_monitor_and_workspace(toplevel_info)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let toplevels = &filtered_toplevels;
|
||||||
|
let desktop_info = &dock_item.desktop_info;
|
||||||
|
|
||||||
match popup_type {
|
match popup_type {
|
||||||
PopupType::RightClickMenu => {
|
PopupType::RightClickMenu => {
|
||||||
fn menu_button<'a, Message: Clone + 'a>(
|
fn menu_button<'a, Message: Clone + 'a>(
|
||||||
|
|
@ -2203,19 +2268,34 @@ impl cosmic::Application for CosmicAppList {
|
||||||
|
|
||||||
let focused_item = self.currently_active_toplevel();
|
let focused_item = self.currently_active_toplevel();
|
||||||
let dot_radius = theme.cosmic().radius_xs();
|
let dot_radius = theme.cosmic().radius_xs();
|
||||||
// show the overflow popup for active list
|
|
||||||
let active: Vec<_> = self
|
let filtered_active_list: Vec<_> = self
|
||||||
.active_list
|
.active_list
|
||||||
|
.iter()
|
||||||
|
.filter(|dock_item| {
|
||||||
|
dock_item.toplevels.iter().any(|(toplevel_info, _)| {
|
||||||
|
self.is_on_current_monitor_and_workspace(toplevel_info)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let active: Vec<_> = filtered_active_list
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.take(active_popup_cutoff.map_or(self.active_list.len(), |n| {
|
.take(active_popup_cutoff.map_or(filtered_active_list.len(), |n| {
|
||||||
if n < self.active_list.len() {
|
if n < filtered_active_list.len() {
|
||||||
self.active_list.len() - n + 1
|
filtered_active_list.len() - n + 1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.map(|dock_item| {
|
.map(|dock_item| {
|
||||||
|
let filtered_is_focused = dock_item
|
||||||
|
.toplevels
|
||||||
|
.iter()
|
||||||
|
.filter(|(info, _)| self.is_on_current_monitor_and_workspace(info))
|
||||||
|
.any(|y| focused_item.contains(&y.0.foreign_toplevel));
|
||||||
|
|
||||||
self.core
|
self.core
|
||||||
.applet
|
.applet
|
||||||
.applet_tooltip(
|
.applet_tooltip(
|
||||||
|
|
@ -2225,12 +2305,10 @@ impl cosmic::Application for CosmicAppList {
|
||||||
self.popup.is_none(),
|
self.popup.is_none(),
|
||||||
self.config.enable_drag_source,
|
self.config.enable_drag_source,
|
||||||
self.gpus.as_deref(),
|
self.gpus.as_deref(),
|
||||||
dock_item
|
filtered_is_focused,
|
||||||
.toplevels
|
|
||||||
.iter()
|
|
||||||
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
|
|
||||||
dot_radius,
|
dot_radius,
|
||||||
id,
|
id,
|
||||||
|
Some(&|info| self.is_on_current_monitor_and_workspace(info)),
|
||||||
),
|
),
|
||||||
dock_item
|
dock_item
|
||||||
.desktop_info
|
.desktop_info
|
||||||
|
|
@ -2290,6 +2368,7 @@ impl cosmic::Application for CosmicAppList {
|
||||||
let focused_item = self.currently_active_toplevel();
|
let focused_item = self.currently_active_toplevel();
|
||||||
let dot_radius = theme.cosmic().radius_xs();
|
let dot_radius = theme.cosmic().radius_xs();
|
||||||
// show the overflow popup for favorites list
|
// show the overflow popup for favorites list
|
||||||
|
|
||||||
let mut favorite_to_remove = if let Some(cutoff) = favorite_popup_cutoff {
|
let mut favorite_to_remove = if let Some(cutoff) = favorite_popup_cutoff {
|
||||||
if cutoff < self.pinned_list.len() {
|
if cutoff < self.pinned_list.len() {
|
||||||
self.pinned_list.len() - cutoff + 1
|
self.pinned_list.len() - cutoff + 1
|
||||||
|
|
@ -2319,6 +2398,12 @@ impl cosmic::Application for CosmicAppList {
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.map(|dock_item| {
|
.map(|dock_item| {
|
||||||
|
let filtered_is_focused = dock_item
|
||||||
|
.toplevels
|
||||||
|
.iter()
|
||||||
|
.filter(|(info, _)| self.is_on_current_monitor_and_workspace(info))
|
||||||
|
.any(|y| focused_item.contains(&y.0.foreign_toplevel));
|
||||||
|
|
||||||
self.core
|
self.core
|
||||||
.applet
|
.applet
|
||||||
.applet_tooltip(
|
.applet_tooltip(
|
||||||
|
|
@ -2328,12 +2413,10 @@ impl cosmic::Application for CosmicAppList {
|
||||||
self.popup.is_none(),
|
self.popup.is_none(),
|
||||||
self.config.enable_drag_source,
|
self.config.enable_drag_source,
|
||||||
self.gpus.as_deref(),
|
self.gpus.as_deref(),
|
||||||
dock_item
|
filtered_is_focused,
|
||||||
.toplevels
|
|
||||||
.iter()
|
|
||||||
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
|
|
||||||
dot_radius,
|
dot_radius,
|
||||||
id,
|
id,
|
||||||
|
Some(&|info| self.is_on_current_monitor_and_workspace(info)),
|
||||||
),
|
),
|
||||||
dock_item
|
dock_item
|
||||||
.desktop_info
|
.desktop_info
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue