1use std::{collections::HashMap, hash::Hash};
2
3use crate::{listeners::IpcCommand, types::Ustr};
4
5use super::listeners::{CompositorEvent, ListenerAction};
6use iced_exwlshell::{
7 reexport::{Anchor, IcedId, LayerSize, NewLayerShellSettings, Task},
8 to_layer_message,
9};
10
11#[derive(Debug, Clone, Eq)]
12pub enum EventFilter {
13 UpdateCompositor,
14 Compositor(CompositorEvent),
15 Named(Ustr),
16 Payload(Ustr),
17 Ipc(IpcCommand),
18 Tick,
19 Frame,
20 All,
21 StartUp,
22}
23
24impl PartialEq for EventFilter {
25 fn eq(&self, other: &Self) -> bool {
26 match (self, other) {
27 (Self::UpdateCompositor, Self::UpdateCompositor) => true,
28 (Self::Compositor(a), Self::Compositor(b)) => a == b,
29 (Self::Named(a), Self::Named(b)) => a == b,
30 (Self::Payload(a), Self::Payload(b)) => a == b,
31 (Self::Tick, Self::Tick) => true,
32 (Self::Frame, Self::Frame) => true,
33 (Self::Ipc(cmd1), Self::Ipc(cmd2)) => cmd1.name == cmd2.name,
34 (Self::All, Self::All) => true,
35 (Self::StartUp, Self::StartUp) => true,
36 _ => false,
37 }
38 }
39}
40
41impl std::hash::Hash for EventFilter {
42 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
43 use std::mem::discriminant;
44
45 discriminant(self).hash(state);
46
47 match self {
48 EventFilter::UpdateCompositor => {}
49 EventFilter::Compositor(events) => {
50 events.hash(state);
51 }
52 EventFilter::Named(name) => {
53 name.hash(state);
54 }
55 EventFilter::Payload(name) => {
56 name.hash(state);
57 }
58 EventFilter::Ipc(cmd) => {
59 cmd.name.hash(state);
60 }
61 EventFilter::Tick => {}
62 EventFilter::Frame => {}
63 EventFilter::All => {}
64 EventFilter::StartUp => {}
65 }
66 }
67}
68
69impl EventFilter {
70 pub fn matches(&self, action: &ListenerAction) -> bool {
71 match self {
72 EventFilter::UpdateCompositor => matches!(action, ListenerAction::UpdateCompositor(_)),
73 EventFilter::Compositor(events) => {
74 matches!(action, ListenerAction::UpdateCompositor(changed) if changed.intersects(*events))
75 }
76 EventFilter::Named(name) => {
77 matches!(
78 action,
79 ListenerAction::Named(n)
80 | ListenerAction::Timer { name: n, .. }
81 | ListenerAction::Signal { name: n, .. }
82 if n == name
83 ) || matches!(
84 action,
85 ListenerAction::Payload { name: n, payload: None } if n == name
86 )
87 }
88 EventFilter::Payload(name) => {
89 matches!(action, ListenerAction::Payload { name: n, ..} if n == name)
90 }
91 EventFilter::Ipc(cmd1) => {
92 matches!(action, ListenerAction::Ipc(cmd2) if cmd2.name == cmd1.name)
93 }
94 EventFilter::Tick => false,
95 EventFilter::Frame => matches!(action, ListenerAction::Frame),
96 EventFilter::All => true,
97 EventFilter::StartUp => matches!(action, ListenerAction::StartUp),
98 }
99 }
100}
101
102impl From<EventFilter> for ListenerAction {
103 fn from(val: EventFilter) -> Self {
104 match val {
105 EventFilter::All => ListenerAction::UpdateCompositor(CompositorEvent::ALL),
106 EventFilter::UpdateCompositor => ListenerAction::UpdateCompositor(CompositorEvent::ALL),
107 EventFilter::Compositor(events) => ListenerAction::UpdateCompositor(events),
108 EventFilter::Tick => ListenerAction::None,
109 EventFilter::Frame => ListenerAction::Frame,
110 EventFilter::Named(n) => ListenerAction::Named(n),
111 EventFilter::Payload(name) => ListenerAction::Payload {
112 name,
113 payload: None,
114 },
115 EventFilter::Ipc(_) => ListenerAction::None,
116 EventFilter::StartUp => ListenerAction::None,
117 }
118 }
119}
120
121impl Into<EventFilter> for &ListenerAction {
122 fn into(self) -> EventFilter {
123 match self {
124 ListenerAction::Named(n) => EventFilter::Named(n.clone()),
125 ListenerAction::Timer { name, .. } => EventFilter::Named(name.clone()),
126 ListenerAction::Signal { name, .. } => EventFilter::Named(name.clone()),
127 ListenerAction::UpdateCompositor(_) => EventFilter::UpdateCompositor,
128 ListenerAction::Frame => EventFilter::Frame,
129 ListenerAction::None => EventFilter::Tick,
130 ListenerAction::StartUp => EventFilter::StartUp,
131 ListenerAction::FocusWorkspace(_) => EventFilter::Tick,
132 ListenerAction::FocusWindow(_) => EventFilter::Tick,
133 ListenerAction::CloseWindow(_) => EventFilter::Tick,
134 ListenerAction::KillWindow(_) => EventFilter::Tick,
135 ListenerAction::FocusMonitor(_) => EventFilter::Tick,
136 ListenerAction::Ipc(cmd) => EventFilter::Ipc(cmd.clone()),
137 ListenerAction::Payload { name, .. } => EventFilter::Payload(name.clone()),
138 }
139 }
140}
141
142#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
143pub struct WindowSettings {
144 pub margin: Option<(i32, i32, i32, i32)>,
145 pub size: Option<(u32, u32)>,
146 pub exclusive_zone: Option<i32>,
147 pub anchor: Option<Anchor>,
148}
149
150pub fn layer_size(width: u32, height: u32) -> LayerSize {
151 match (width, height) {
152 (0, 0) => LayerSize::FILL,
153 (0, height) => LayerSize::fill_width(height),
154 (width, 0) => LayerSize::fill_height(width),
155 (width, height) => LayerSize::px(width, height),
156 }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Hash)]
160pub enum ItemEffect {
161 None,
162 Redraw,
163 Show,
164 ShowAndSubscribe(Vec<EventFilter>),
165 Hide,
166 ReallyHide,
167 Destroy,
168 ReallyDestroy,
169 Subscribe(Vec<EventFilter>),
170 UpdateWindow(WindowSettings),
171 Custom([usize; 4]),
172}
173
174#[derive(Clone)]
175pub enum ItemMessage {
176 Effect(IcedId, ItemEffect),
177 Action(ListenerAction),
178 EffectAction(IcedId, ItemEffect, ListenerAction),
179 Noop,
180 Task(std::sync::Arc<dyn Fn() -> Task<Message> + Send + Sync>),
181 Payload(IcedId, HashMap<Ustr, Ustr>),
182}
183
184impl std::fmt::Debug for ItemMessage {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 match self {
187 Self::Effect(id, eff) => f.debug_tuple("Effect").field(id).field(eff).finish(),
188 Self::Action(act) => f.debug_tuple("Action").field(act).finish(),
189 Self::EffectAction(id, eff, act) => f
190 .debug_tuple("EffectAction")
191 .field(id)
192 .field(eff)
193 .field(act)
194 .finish(),
195 Self::Noop => write!(f, "Noop"),
196 Self::Task(_) => write!(f, "Task(Arc<dyn Fn() -> Task<Message>>)"),
197 Self::Payload(id, map) => f.debug_tuple("Payload").field(id).field(map).finish(),
198 }
199 }
200}
201
202#[to_layer_message(multi)]
203#[derive(Debug, Clone)]
204pub enum Message {
205 Tick,
206 FdUpdate(ListenerAction),
207 FdBatch(Vec<ListenerAction>),
208 LinkAction(u32, ListenerAction),
209 LinkedAction(u32, ListenerAction),
210 Item(ItemMessage),
211 UpdateMonitors,
212 Noop,
213}
214
215impl Message {
216 pub fn open_layer(settings: NewLayerShellSettings) -> (IcedId, Task<Message>) {
217 Self::layershell_open(settings)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::listeners::{CompositorEvent, ListenerAction};
225
226 #[test]
227 fn compositor_event_masks_compose() {
228 let mut mask = CompositorEvent::NONE;
229 assert!(mask.is_empty());
230
231 mask.insert(CompositorEvent::WINDOWS);
232 mask |= CompositorEvent::FOCUS;
233 assert!(mask.contains(CompositorEvent::WINDOWS));
234 assert!(mask.contains(CompositorEvent::WINDOWS | CompositorEvent::FOCUS));
235 assert!(!mask.contains(CompositorEvent::WORKSPACES));
236 assert!(mask.intersects(CompositorEvent::FOCUS | CompositorEvent::MONITORS));
237 assert!(!mask.intersects(CompositorEvent::MONITORS));
238 assert!(CompositorEvent::ALL.contains(mask));
239 }
240
241 #[test]
242 fn compositor_filters_match_changed_areas() {
243 let any = EventFilter::UpdateCompositor;
244 let workspaces = EventFilter::Compositor(CompositorEvent::WORKSPACES);
245
246 assert!(any.matches(&ListenerAction::UpdateCompositor(CompositorEvent::FOCUS)));
247 assert!(workspaces.matches(&ListenerAction::UpdateCompositor(
248 CompositorEvent::WORKSPACES
249 )));
250 assert!(workspaces.matches(&ListenerAction::UpdateCompositor(
251 CompositorEvent::WORKSPACES | CompositorEvent::MONITORS
252 )));
253 assert!(!workspaces.matches(&ListenerAction::UpdateCompositor(CompositorEvent::MONITORS)));
254 assert!(!workspaces.matches(&ListenerAction::Named("other".into())));
255 }
256
257 #[test]
258 fn named_filters_match_payload_actions_without_a_payload() {
259 let bare = ListenerAction::Payload {
260 name: "fixture.toggle".into(),
261 payload: None,
262 };
263 let with_payload = ListenerAction::Payload {
264 name: "fixture.toggle".into(),
265 payload: Some(crate::types::PayloadBox::new("value")),
266 };
267
268 assert!(EventFilter::Named("fixture.toggle".into()).matches(&bare));
269 assert!(!EventFilter::Named("fixture.toggle".into()).matches(&with_payload));
270 assert!(!EventFilter::Named("other".into()).matches(&bare));
271 }
272
273 #[test]
274 fn startup_filters_compare_equal() {
275 assert_eq!(EventFilter::StartUp, EventFilter::StartUp);
276 assert!(EventFilter::StartUp.matches(&ListenerAction::StartUp));
277 }
278
279 #[test]
280 fn compositor_actions_map_back_to_the_catch_all_filter() {
281 let action = ListenerAction::UpdateCompositor(CompositorEvent::ALL);
282 let filter: EventFilter = (&action).into();
283 assert_eq!(filter, EventFilter::UpdateCompositor);
284 }
285}