Skip to main content

slowshell_core/
message.rs

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}