Skip to main content

slowshell_core/
lib.rs

1// TODO: Add comments for api-ref
2
3use std::{
4  any::{Any, TypeId},
5  collections::HashMap,
6  ffi::c_void,
7};
8
9use crate::types::{PayloadBox, PayloadBuilderRegistry, Ustr};
10
11pub mod animations;
12pub mod commands;
13pub mod listeners;
14pub mod message;
15pub mod monitors;
16pub mod persistence;
17pub mod types;
18
19#[derive(Clone)]
20pub struct ActionDispatcher(pub futures_channel::mpsc::UnboundedSender<crate::message::Message>);
21
22impl ActionDispatcher {
23  pub fn dispatch(&self, command: &str) -> bool {
24    self.dispatch_payload(command, None)
25  }
26
27  pub fn dispatch_named(&self, name: &str) -> bool {
28    self.send(crate::listeners::ListenerAction::Named(Ustr::from(name)))
29  }
30
31  pub fn dispatch_payload(&self, name: &str, payload: Option<PayloadBox>) -> bool {
32    self.send(crate::listeners::ListenerAction::Payload {
33      name: Ustr::from(name),
34      payload,
35    })
36  }
37
38  pub fn dispatch_with_args(
39    &self,
40    name: &str,
41    args: &[Ustr],
42    builders: &PayloadBuilderRegistry,
43  ) -> bool {
44    match builders.build(name, args) {
45      Some(payload) => self.dispatch_payload(name, Some(payload)),
46      None => self.dispatch_named(name),
47    }
48  }
49
50  pub fn dispatch_action(&self, action: crate::listeners::ListenerAction) -> bool {
51    self.send(action)
52  }
53
54  fn send(&self, action: crate::listeners::ListenerAction) -> bool {
55    self
56      .0
57      .unbounded_send(crate::message::Message::FdUpdate(action))
58      .is_ok()
59  }
60}
61
62#[derive(Debug, PartialEq, Clone)]
63pub struct Workspace {
64  pub id: u64,
65  pub idx: u8,
66  pub name: Option<String>,
67  pub output: Option<String>,
68  pub is_urgent: bool,
69  pub is_active: bool,
70  pub is_focused: bool,
71}
72
73#[derive(Debug, PartialEq, Clone)]
74pub struct Monitor {
75  pub name: Ustr,
76  pub active_workspace: u32,
77  pub width: u32,
78  pub height: u32,
79  pub scale: f64,
80}
81
82impl Monitor {
83  pub fn named(name: impl Into<Ustr>) -> Self {
84    Self {
85      name: name.into(),
86      active_workspace: 0,
87      width: 0,
88      height: 0,
89      scale: 1.0,
90    }
91  }
92}
93
94#[derive(Debug, Clone)]
95pub struct Window {
96  pub id: u64,
97  pub title: String,
98  pub class: String,
99  pub is_active: bool,
100  // TODO: Make metadata into a proper map
101  pub metadata: HashMap<String, String>,
102}
103
104impl Window {
105  pub const META_PID: &'static str = "pid";
106  pub const META_WIDTH: &'static str = "width";
107  pub const META_HEIGHT: &'static str = "height";
108
109  pub fn pid(&self) -> Option<i32> {
110    self.metadata.get(Self::META_PID)?.parse().ok()
111  }
112
113  pub fn size(&self) -> Option<(u32, u32)> {
114    let width = self.metadata.get(Self::META_WIDTH)?.parse().ok()?;
115    let height = self.metadata.get(Self::META_HEIGHT)?.parse().ok()?;
116
117    Some((width, height))
118  }
119}
120
121#[doc(hidden)]
122trait HandleStateTuple: Sized {
123  fn run<R>(
124    state: &mut Store,
125    f: impl FnOnce(&mut Store, Self) -> miette::Result<R>,
126  ) -> miette::Result<R>;
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
130pub struct StoreKey {
131  pub type_name: &'static str,
132  pub size: usize,
133  pub align: usize,
134}
135
136impl StoreKey {
137  pub fn of<T: 'static>() -> Self {
138    Self {
139      type_name: std::any::type_name::<T>(),
140      size: core::mem::size_of::<T>(),
141      align: core::mem::align_of::<T>(),
142    }
143  }
144
145  pub fn matches<T: 'static>(&self) -> bool {
146    self == &Self::of::<T>()
147  }
148}
149
150pub struct ErasedEntry {
151  pub key: StoreKey,
152  pub ptr: *mut c_void,
153  pub drop_fn: extern "C" fn(*mut c_void),
154}
155
156impl ErasedEntry {
157  pub fn new<T: 'static>(value: T) -> Self {
158    extern "C" fn drop_it<T>(ptr: *mut c_void) {
159      unsafe { drop(Box::from_raw(ptr as *mut T)) };
160    }
161    Self {
162      key: StoreKey::of::<T>(),
163      ptr: Box::into_raw(Box::new(value)) as *mut c_void,
164      drop_fn: drop_it::<T>,
165    }
166  }
167
168  pub fn take<T: 'static>(mut self) -> Option<T> {
169    if !self.key.matches::<T>() {
170      return None;
171    }
172    let ptr = self.ptr;
173    self.ptr = core::ptr::null_mut();
174    Some(unsafe { *Box::from_raw(ptr as *mut T) })
175  }
176}
177
178impl Drop for ErasedEntry {
179  fn drop(&mut self) {
180    if !self.ptr.is_null() {
181      (self.drop_fn)(self.ptr);
182      self.ptr = core::ptr::null_mut();
183    }
184  }
185}
186
187#[derive(Clone, Copy, PartialEq, Eq, Hash)]
188enum StoreId {
189  TypeId(TypeId),
190  Erased(StoreKey),
191}
192
193enum Entry {
194  Any(Box<dyn Any>),
195  Erased(ErasedEntry),
196}
197
198impl Entry {
199  fn ptr_mut<T: 'static>(&mut self) -> Option<*mut T> {
200    match self {
201      Entry::Any(boxed) => boxed.downcast_mut::<T>().map(|value| value as *mut T),
202      Entry::Erased(entry) => {
203        if entry.key.matches::<T>() {
204          Some(entry.ptr as *mut T)
205        } else {
206          None
207        }
208      }
209    }
210  }
211}
212
213pub struct Store {
214  contents: HashMap<StoreId, Entry>,
215}
216
217impl Store {
218  pub fn new() -> Self {
219    Self {
220      contents: Default::default(),
221    }
222  }
223
224  pub fn insert_raw(&mut self, type_id: TypeId, val: Box<dyn Any>) -> bool {
225    let id = StoreId::TypeId(type_id);
226    if self.contents.contains_key(&id) {
227      return false;
228    }
229
230    self.contents.insert(id, Entry::Any(val));
231    true
232  }
233
234  pub fn insert<T: 'static>(&mut self, val: T) -> bool {
235    let id = StoreId::Erased(StoreKey::of::<T>());
236    if self.contents.contains_key(&id) {
237      return false;
238    }
239
240    self
241      .contents
242      .insert(id, Entry::Erased(ErasedEntry::new(val)));
243    true
244  }
245
246  fn lookup_ids<T: 'static>() -> [StoreId; 2] {
247    [
248      StoreId::Erased(StoreKey::of::<T>()),
249      StoreId::TypeId(TypeId::of::<T>()),
250    ]
251  }
252
253  pub fn borrow<T: 'static>(&self) -> Option<&T> {
254    for id in Self::lookup_ids::<T>() {
255      match self.contents.get(&id) {
256        Some(Entry::Any(boxed)) => {
257          if let Some(value) = boxed.downcast_ref::<T>() {
258            return Some(value);
259          }
260        }
261        Some(Entry::Erased(entry)) if entry.key.matches::<T>() => {
262          return Some(unsafe { &*(entry.ptr as *const T) });
263        }
264        _ => {}
265      }
266    }
267    None
268  }
269
270  pub fn borrow_mut<T: 'static>(&mut self) -> Option<&mut T> {
271    for id in Self::lookup_ids::<T>() {
272      let matches = match self.contents.get_mut(&id) {
273        Some(entry) => entry.ptr_mut::<T>(),
274        None => None,
275      };
276      if let Some(ptr) = matches {
277        return Some(unsafe { &mut *ptr });
278      }
279    }
280    None
281  }
282
283  pub fn remove<T: 'static>(&mut self) -> Option<T> {
284    for id in Self::lookup_ids::<T>() {
285      let matches = matches!(self.contents.get(&id), Some(Entry::Erased(entry)) if entry.key.matches::<T>())
286        || matches!(self.contents.get(&id), Some(Entry::Any(boxed)) if boxed.is::<T>());
287      if !matches {
288        continue;
289      }
290      return match self.contents.remove(&id) {
291        Some(Entry::Any(boxed)) => boxed.downcast::<T>().ok().map(|boxed| *boxed),
292        Some(Entry::Erased(entry)) => entry.take::<T>(),
293        None => None,
294      };
295    }
296    None
297  }
298
299  pub fn erased_insert(&mut self, entry: ErasedEntry) -> bool {
300    let id = StoreId::Erased(entry.key);
301    if self.contents.contains_key(&id) {
302      return false;
303    }
304    self.contents.insert(id, Entry::Erased(entry));
305    true
306  }
307
308  pub fn erased_contains(&self, key: StoreKey) -> bool {
309    matches!(
310      self.contents.get(&StoreId::Erased(key)),
311      Some(Entry::Erased(entry)) if entry.key == key
312    )
313  }
314
315  pub fn erased_get(&self, key: StoreKey) -> Option<*mut c_void> {
316    match self.contents.get(&StoreId::Erased(key)) {
317      Some(Entry::Erased(entry)) if entry.key == key => Some(entry.ptr),
318      _ => None,
319    }
320  }
321
322  pub fn erased_remove(&mut self, key: StoreKey) -> Option<ErasedEntry> {
323    let id = StoreId::Erased(key);
324    let matches = matches!(self.contents.get(&id), Some(Entry::Erased(entry)) if entry.key == key);
325    if !matches {
326      return None;
327    }
328    match self.contents.remove(&id) {
329      Some(Entry::Erased(entry)) => Some(entry),
330      _ => None,
331    }
332  }
333
334  #[allow(warnings)]
335  pub fn handle<T, R>(
336    &mut self,
337    f: impl FnOnce(&mut Store, T) -> miette::Result<R>,
338  ) -> miette::Result<R>
339  where
340    T: HandleStateTuple,
341  {
342    T::run(self, f)
343  }
344}
345
346macro_rules! impl_handle_tuple {
347  ($($T:ident),+) => {
348    impl<$($T: 'static),+> HandleStateTuple for ($(&mut $T,)+) {
349      fn run<R>(
350        store: &mut Store,
351        f: impl FnOnce(&mut Store, Self) -> miette::Result<R>,
352      ) -> miette::Result<R> {
353        {
354          let keys = [$(StoreKey::of::<$T>()),+];
355
356          for i in 0..keys.len() {
357            for j in (i + 1)..keys.len() {
358              if keys[i] == keys[j] {
359                miette::bail!("Duplicate type requested");
360              }
361            }
362          }
363        }
364
365        $(
366          #[allow(non_snake_case)]
367          let mut $T = {
368            let mut taken = None;
369            for id in Store::lookup_ids::<$T>() {
370              if let Some(entry) = store.contents.remove(&id) {
371                taken = Some((id, entry));
372                break;
373              }
374            }
375
376            taken.ok_or_else(|| miette::miette!("Missing instance"))?
377          };
378        )+
379
380        let result = {
381          let tuple = (
382            $(
383              {
384                let ptr = $T
385                  .1
386                  .ptr_mut::<$T>()
387                  .ok_or_else(|| miette::miette!("Type mismatch"))?;
388
389                unsafe { &mut *ptr }
390              },
391            )+
392          );
393
394          f(store, tuple)
395        };
396
397        $(
398          store.contents.insert($T.0, $T.1);
399        )+
400
401        result
402      }
403    }
404  };
405}
406
407impl_handle_tuple!(A, B);
408impl_handle_tuple!(A, B, C);
409impl_handle_tuple!(A, B, C, D);
410impl_handle_tuple!(A, B, C, D, E);
411impl_handle_tuple!(A, B, C, D, E, F);