Skip to content

Commit c02bdf8

Browse files
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>
1 parent 2852f3c commit c02bdf8

File tree

1 file changed

+109
-32
lines changed

1 file changed

+109
-32
lines changed

cosmic-app-list/src/app.rs

Lines changed: 109 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ impl DockItem {
182182
is_focused: bool,
183183
dot_border_radius: [f32; 4],
184184
window_id: window::Id,
185+
filter: Option<&dyn Fn(&ToplevelInfo) -> bool>,
185186
) -> Element<'_, Message> {
186187
let Self {
187188
toplevels,
@@ -190,6 +191,16 @@ impl DockItem {
190191
..
191192
} = self;
192193

194+
let filtered_toplevels: Vec<_> = if let Some(filter_fn) = filter {
195+
toplevels
196+
.iter()
197+
.filter(|(info, _)| filter_fn(info))
198+
.collect()
199+
} else {
200+
toplevels.iter().collect()
201+
};
202+
let toplevel_count = filtered_toplevels.len();
203+
193204
let app_icon = AppletIconData::new(applet);
194205

195206
let cosmic_icon = fde::IconSource::from_unknown(desktop_info.icon().unwrap_or_default())
@@ -200,7 +211,7 @@ impl DockItem {
200211
.height(app_icon.icon_size.into());
201212

202213
let indicator = {
203-
let container = if toplevels.len() <= 1 {
214+
let container = if toplevel_count <= 1 {
204215
vertical_space().height(Length::Fixed(0.0))
205216
} else {
206217
match applet.anchor {
@@ -215,7 +226,7 @@ impl DockItem {
215226
.apply(container)
216227
.padding(app_icon.dot_radius);
217228

218-
if toplevels.is_empty() {
229+
if toplevel_count == 0 {
219230
container
220231
} else {
221232
container.class(theme::Container::custom(move |theme| container::Style {
@@ -272,10 +283,10 @@ impl DockItem {
272283
let icon_button: Element<_> = if interaction_enabled {
273284
mouse_area(
274285
icon_button
275-
.on_press_maybe(if toplevels.is_empty() {
286+
.on_press_maybe(if toplevel_count == 0 {
276287
launch_on_preferred_gpu(desktop_info, gpus)
277-
} else if toplevels.len() == 1 {
278-
toplevels
288+
} else if toplevel_count == 1 {
289+
filtered_toplevels
279290
.first()
280291
.map(|t| Message::Toggle(t.0.foreign_toplevel.clone()))
281292
} else {
@@ -635,6 +646,32 @@ impl CosmicAppList {
635646
.collect::<Vec<_>>();
636647
}
637648

649+
fn is_on_current_monitor_and_workspace(&self, toplevel_info: &ToplevelInfo) -> bool {
650+
use cosmic_app_list_config::TopLevelFilter;
651+
652+
let on_active_workspace = self.active_workspaces.is_empty()
653+
|| toplevel_info.workspace.is_empty()
654+
|| self.active_workspaces
655+
.iter()
656+
.any(|workspace| toplevel_info.workspace.contains(workspace));
657+
658+
match &self.config.filter_top_levels {
659+
None => true,
660+
Some(TopLevelFilter::ActiveWorkspace) => on_active_workspace,
661+
Some(TopLevelFilter::ConfiguredOutput) => {
662+
let on_active_output = self
663+
.output_list
664+
.iter()
665+
.find(|(_, info)| info.name.as_ref() == Some(&self.core.applet.output_name))
666+
.map_or(true, |(active_output, _)| {
667+
toplevel_info.output.iter().any(|output| output == active_output)
668+
});
669+
670+
on_active_output && on_active_workspace
671+
}
672+
}
673+
}
674+
638675
// Update pinned items using the cached desktop entries as a source.
639676
fn update_pinned_list(&mut self) {
640677
self.pinned_list = find_desktop_entries(&self.desktop_entries, &self.config.favorites)
@@ -1715,6 +1752,12 @@ impl cosmic::Application for CosmicAppList {
17151752
.iter()
17161753
.rev()
17171754
.map(|dock_item| {
1755+
let filtered_is_focused = dock_item
1756+
.toplevels
1757+
.iter()
1758+
.filter(|(info, _)| self.is_on_current_monitor_and_workspace(info))
1759+
.any(|y| focused_item.contains(&y.0.foreign_toplevel));
1760+
17181761
self.core
17191762
.applet
17201763
.applet_tooltip::<Message>(
@@ -1724,12 +1767,10 @@ impl cosmic::Application for CosmicAppList {
17241767
self.popup.is_none(),
17251768
self.config.enable_drag_source,
17261769
self.gpus.as_deref(),
1727-
dock_item
1728-
.toplevels
1729-
.iter()
1730-
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
1770+
filtered_is_focused,
17311771
dot_radius,
17321772
self.core.main_window_id().unwrap(),
1773+
Some(&|info| self.is_on_current_monitor_and_workspace(info)),
17331774
),
17341775
dock_item
17351776
.desktop_info
@@ -1772,6 +1813,12 @@ impl cosmic::Application for CosmicAppList {
17721813
.as_ref()
17731814
.and_then(|o| o.dock_item.as_ref().map(|item| (item, o.preview_index)))
17741815
{
1816+
let filtered_is_focused = item
1817+
.toplevels
1818+
.iter()
1819+
.filter(|(info, _)| self.is_on_current_monitor_and_workspace(info))
1820+
.any(|y| focused_item.contains(&y.0.foreign_toplevel));
1821+
17751822
favorites.insert(
17761823
index.min(favorites.len()),
17771824
item.as_icon(
@@ -1780,11 +1827,10 @@ impl cosmic::Application for CosmicAppList {
17801827
false,
17811828
self.config.enable_drag_source,
17821829
self.gpus.as_deref(),
1783-
item.toplevels
1784-
.iter()
1785-
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
1830+
filtered_is_focused,
17861831
dot_radius,
17871832
self.core.main_window_id().unwrap(),
1833+
Some(&|info| self.is_on_current_monitor_and_workspace(info)),
17881834
),
17891835
);
17901836
} else if self.is_listening_for_dnd && self.pinned_list.is_empty() {
@@ -1799,9 +1845,19 @@ impl cosmic::Application for CosmicAppList {
17991845
);
18001846
}
18011847

1848+
let filtered_active_list: Vec<_> = self
1849+
.active_list
1850+
.iter()
1851+
.filter(|dock_item| {
1852+
dock_item.toplevels.iter().any(|(toplevel_info, _)| {
1853+
self.is_on_current_monitor_and_workspace(toplevel_info)
1854+
})
1855+
})
1856+
.collect();
1857+
18021858
let mut active: Vec<_> =
1803-
self.active_list[..active_popup_cutoff.map_or(self.active_list.len(), |n| {
1804-
if n < self.active_list.len() {
1859+
filtered_active_list[..active_popup_cutoff.map_or(filtered_active_list.len(), |n| {
1860+
if n < filtered_active_list.len() {
18051861
n.saturating_sub(1)
18061862
} else {
18071863
n
@@ -1824,6 +1880,7 @@ impl cosmic::Application for CosmicAppList {
18241880
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
18251881
dot_radius,
18261882
self.core.main_window_id().unwrap(),
1883+
None,
18271884
),
18281885
dock_item
18291886
.desktop_info
@@ -1838,7 +1895,7 @@ impl cosmic::Application for CosmicAppList {
18381895
})
18391896
.collect();
18401897

1841-
if active_popup_cutoff.is_some_and(|n| n < self.active_list.len()) {
1898+
if active_popup_cutoff.is_some_and(|n| n < filtered_active_list.len()) {
18421899
// button to show more active
18431900
let icon = match self.core.applet.anchor {
18441901
PanelAnchor::Bottom => "go-up-symbolic",
@@ -1987,21 +2044,26 @@ impl cosmic::Application for CosmicAppList {
19872044
..
19882045
}) = self.popup.as_ref().filter(|p| id == p.id)
19892046
{
1990-
let (
1991-
DockItem {
1992-
toplevels,
1993-
desktop_info,
1994-
..
1995-
},
1996-
is_pinned,
1997-
) = match self.pinned_list.iter().find(|i| i.id == *id) {
2047+
let (dock_item, is_pinned) = match self.pinned_list.iter().find(|i| i.id == *id) {
19982048
Some(e) => (e, true),
19992049
None => match self.active_list.iter().find(|i| i.id == *id) {
20002050
Some(e) => (e, false),
20012051
None => return text::body("").into(),
20022052
},
20032053
};
20042054

2055+
// Filter toplevels to only show windows on current monitor and workspace
2056+
let filtered_toplevels: Vec<_> = dock_item
2057+
.toplevels
2058+
.iter()
2059+
.filter(|(toplevel_info, _)| {
2060+
self.is_on_current_monitor_and_workspace(toplevel_info)
2061+
})
2062+
.collect();
2063+
2064+
let toplevels = &filtered_toplevels;
2065+
let desktop_info = &dock_item.desktop_info;
2066+
20052067
match popup_type {
20062068
PopupType::RightClickMenu => {
20072069
fn menu_button<'a, Message: Clone + 'a>(
@@ -2203,14 +2265,23 @@ impl cosmic::Application for CosmicAppList {
22032265

22042266
let focused_item = self.currently_active_toplevel();
22052267
let dot_radius = theme.cosmic().radius_xs();
2206-
// show the overflow popup for active list
2207-
let active: Vec<_> = self
2268+
2269+
let filtered_active_list: Vec<_> = self
22082270
.active_list
2271+
.iter()
2272+
.filter(|dock_item| {
2273+
dock_item.toplevels.iter().any(|(toplevel_info, _)| {
2274+
self.is_on_current_monitor_and_workspace(toplevel_info)
2275+
})
2276+
})
2277+
.collect();
2278+
2279+
let active: Vec<_> = filtered_active_list
22092280
.iter()
22102281
.rev()
2211-
.take(active_popup_cutoff.map_or(self.active_list.len(), |n| {
2212-
if n < self.active_list.len() {
2213-
self.active_list.len() - n + 1
2282+
.take(active_popup_cutoff.map_or(filtered_active_list.len(), |n| {
2283+
if n < filtered_active_list.len() {
2284+
filtered_active_list.len() - n + 1
22142285
} else {
22152286
0
22162287
}
@@ -2231,6 +2302,7 @@ impl cosmic::Application for CosmicAppList {
22312302
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
22322303
dot_radius,
22332304
id,
2305+
None,
22342306
),
22352307
dock_item
22362308
.desktop_info
@@ -2290,6 +2362,7 @@ impl cosmic::Application for CosmicAppList {
22902362
let focused_item = self.currently_active_toplevel();
22912363
let dot_radius = theme.cosmic().radius_xs();
22922364
// show the overflow popup for favorites list
2365+
22932366
let mut favorite_to_remove = if let Some(cutoff) = favorite_popup_cutoff {
22942367
if cutoff < self.pinned_list.len() {
22952368
self.pinned_list.len() - cutoff + 1
@@ -2319,6 +2392,12 @@ impl cosmic::Application for CosmicAppList {
23192392
.iter()
23202393
.rev()
23212394
.map(|dock_item| {
2395+
let filtered_is_focused = dock_item
2396+
.toplevels
2397+
.iter()
2398+
.filter(|(info, _)| self.is_on_current_monitor_and_workspace(info))
2399+
.any(|y| focused_item.contains(&y.0.foreign_toplevel));
2400+
23222401
self.core
23232402
.applet
23242403
.applet_tooltip(
@@ -2328,12 +2407,10 @@ impl cosmic::Application for CosmicAppList {
23282407
self.popup.is_none(),
23292408
self.config.enable_drag_source,
23302409
self.gpus.as_deref(),
2331-
dock_item
2332-
.toplevels
2333-
.iter()
2334-
.any(|y| focused_item.contains(&y.0.foreign_toplevel)),
2410+
filtered_is_focused,
23352411
dot_radius,
23362412
id,
2413+
Some(&|info| self.is_on_current_monitor_and_workspace(info)),
23372414
),
23382415
dock_item
23392416
.desktop_info

0 commit comments

Comments
 (0)