Skip to main content

rmux_core/
lifecycle.rs

1use rmux_proto::{HookName, PaneTarget, ScopeSelector, SessionName, Target, WindowTarget};
2
3/// A typed server lifecycle event that may dispatch a registered hook.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum LifecycleEvent {
6    /// The client became the active client for its attached session/window.
7    ClientActive {
8        /// The session associated with the event.
9        session_name: SessionName,
10        /// The best available rmux client identifier.
11        client_name: Option<String>,
12    },
13    /// A client attached to a session.
14    ClientAttached {
15        /// The session associated with the event.
16        session_name: SessionName,
17        /// The best available rmux client identifier.
18        client_name: Option<String>,
19    },
20    /// A client detached from a session.
21    ClientDetached {
22        /// The session associated with the event.
23        session_name: SessionName,
24        /// The best available rmux client identifier.
25        client_name: Option<String>,
26    },
27    /// The outer terminal reported focus-in for a client.
28    ClientFocusIn {
29        /// The session associated with the event.
30        session_name: SessionName,
31        /// The best available rmux client identifier.
32        client_name: Option<String>,
33    },
34    /// The outer terminal reported focus-out for a client.
35    ClientFocusOut {
36        /// The session associated with the event.
37        session_name: SessionName,
38        /// The best available rmux client identifier.
39        client_name: Option<String>,
40    },
41    /// A client switched to another session.
42    ClientSessionChanged {
43        /// The session associated with the event.
44        session_name: SessionName,
45        /// The best available rmux client identifier.
46        client_name: Option<String>,
47    },
48    /// A client changed its terminal size.
49    ClientResized {
50        /// The session associated with the event.
51        session_name: SessionName,
52        /// The best available rmux client identifier.
53        client_name: Option<String>,
54    },
55    /// The client reported a light terminal theme.
56    ClientLightTheme {
57        /// The session associated with the event.
58        session_name: SessionName,
59        /// The best available rmux client identifier.
60        client_name: Option<String>,
61    },
62    /// The client reported a dark terminal theme.
63    ClientDarkTheme {
64        /// The session associated with the event.
65        session_name: SessionName,
66        /// The best available rmux client identifier.
67        client_name: Option<String>,
68    },
69    /// A session was created.
70    SessionCreated {
71        /// The session associated with the event.
72        session_name: SessionName,
73    },
74    /// A session was closed.
75    SessionClosed {
76        /// The session associated with the event.
77        session_name: SessionName,
78        /// The removed session ID captured at notification time, when known.
79        session_id: Option<u32>,
80    },
81    /// A session was renamed.
82    SessionRenamed {
83        /// The session associated with the event.
84        session_name: SessionName,
85    },
86    /// A session's active window changed.
87    SessionWindowChanged {
88        /// The session associated with the event.
89        session_name: SessionName,
90    },
91    /// A window was linked into a session.
92    WindowLinked {
93        /// The session associated with the event.
94        session_name: SessionName,
95        /// The linked window, when it is known at the call site.
96        target: Option<WindowTarget>,
97    },
98    /// A window was unlinked from a session.
99    WindowUnlinked {
100        /// The session associated with the event.
101        session_name: SessionName,
102        /// The unlinked window, when it is known at the call site.
103        target: Option<WindowTarget>,
104        /// The removed window ID captured at notification time, when known.
105        window_id: Option<u32>,
106        /// The removed window name captured at notification time, when known.
107        window_name: Option<String>,
108    },
109    /// A window was renamed.
110    WindowRenamed {
111        /// The targeted window.
112        target: WindowTarget,
113    },
114    /// A window layout changed.
115    WindowLayoutChanged {
116        /// The targeted window.
117        target: WindowTarget,
118    },
119    /// A window size changed.
120    WindowResized {
121        /// The targeted window.
122        target: WindowTarget,
123    },
124    /// A window's active pane changed.
125    WindowPaneChanged {
126        /// The targeted window.
127        target: WindowTarget,
128    },
129    /// A bell alert fired for a window.
130    AlertBell {
131        /// The targeted window.
132        target: WindowTarget,
133    },
134    /// An activity alert fired for a window.
135    AlertActivity {
136        /// The targeted window.
137        target: WindowTarget,
138    },
139    /// A silence alert fired for a window.
140    AlertSilence {
141        /// The targeted window.
142        target: WindowTarget,
143    },
144    /// A pane exited or was removed from service.
145    PaneExited {
146        /// The targeted pane.
147        target: PaneTarget,
148        /// The removed pane ID captured at notification time, when known.
149        pane_id: Option<u32>,
150        /// The removed window ID captured at notification time, when known.
151        window_id: Option<u32>,
152        /// The removed window name captured at notification time, when known.
153        window_name: Option<String>,
154    },
155    /// A pane died and was kept by `remain-on-exit`.
156    PaneDied {
157        /// The targeted pane.
158        target: PaneTarget,
159        /// The dead pane ID captured at notification time, when known.
160        pane_id: Option<u32>,
161        /// The dead pane's window ID captured at notification time, when known.
162        window_id: Option<u32>,
163        /// The dead pane's window name captured at notification time, when known.
164        window_name: Option<String>,
165    },
166    /// A pane mode or display overlay state changed.
167    PaneModeChanged {
168        /// The targeted pane.
169        target: PaneTarget,
170    },
171    /// A pane gained focus.
172    PaneFocusIn {
173        /// The targeted pane.
174        target: PaneTarget,
175    },
176    /// A pane lost focus.
177    PaneFocusOut {
178        /// The targeted pane.
179        target: PaneTarget,
180    },
181    /// A pane program emitted an OSC 52 clipboard sequence.
182    PaneSetClipboard {
183        /// The targeted pane.
184        target: PaneTarget,
185    },
186    /// A pane title changed.
187    PaneTitleChanged {
188        /// The targeted pane.
189        target: PaneTarget,
190    },
191    /// A paste buffer was created or replaced.
192    PasteBufferChanged {
193        /// The affected paste buffer name.
194        buffer_name: String,
195    },
196    /// A paste buffer was deleted or evicted.
197    PasteBufferDeleted {
198        /// The affected paste buffer name.
199        buffer_name: String,
200    },
201    /// A `select-window`-family command completed successfully.
202    AfterSelectWindow {
203        /// The selected window.
204        target: WindowTarget,
205    },
206    /// A `select-pane`-family command completed successfully.
207    AfterSelectPane {
208        /// The selected pane.
209        target: PaneTarget,
210    },
211    /// A `send-keys` command completed successfully.
212    AfterSendKeys {
213        /// The targeted pane.
214        target: PaneTarget,
215    },
216    /// A `set-option` command completed successfully.
217    AfterSetOption {
218        /// The session associated with the option scope, when one exists.
219        session_name: Option<SessionName>,
220    },
221}
222
223impl LifecycleEvent {
224    /// Returns the hook name corresponding to this lifecycle event.
225    #[must_use]
226    pub const fn hook_name(&self) -> HookName {
227        match self {
228            Self::ClientActive { .. } => HookName::ClientActive,
229            Self::ClientAttached { .. } => HookName::ClientAttached,
230            Self::ClientDetached { .. } => HookName::ClientDetached,
231            Self::ClientFocusIn { .. } => HookName::ClientFocusIn,
232            Self::ClientFocusOut { .. } => HookName::ClientFocusOut,
233            Self::ClientSessionChanged { .. } => HookName::ClientSessionChanged,
234            Self::ClientResized { .. } => HookName::ClientResized,
235            Self::ClientLightTheme { .. } => HookName::ClientLightTheme,
236            Self::ClientDarkTheme { .. } => HookName::ClientDarkTheme,
237            Self::SessionCreated { .. } => HookName::SessionCreated,
238            Self::SessionClosed { .. } => HookName::SessionClosed,
239            Self::SessionRenamed { .. } => HookName::SessionRenamed,
240            Self::SessionWindowChanged { .. } => HookName::SessionWindowChanged,
241            Self::WindowLinked { .. } => HookName::WindowLinked,
242            Self::WindowUnlinked { .. } => HookName::WindowUnlinked,
243            Self::WindowRenamed { .. } => HookName::WindowRenamed,
244            Self::WindowLayoutChanged { .. } => HookName::WindowLayoutChanged,
245            Self::WindowResized { .. } => HookName::WindowResized,
246            Self::WindowPaneChanged { .. } => HookName::WindowPaneChanged,
247            Self::AlertBell { .. } => HookName::AlertBell,
248            Self::AlertActivity { .. } => HookName::AlertActivity,
249            Self::AlertSilence { .. } => HookName::AlertSilence,
250            Self::PaneExited { .. } => HookName::PaneExited,
251            Self::PaneDied { .. } => HookName::PaneDied,
252            Self::PaneModeChanged { .. } => HookName::PaneModeChanged,
253            Self::PaneFocusIn { .. } => HookName::PaneFocusIn,
254            Self::PaneFocusOut { .. } => HookName::PaneFocusOut,
255            Self::PaneSetClipboard { .. } => HookName::PaneSetClipboard,
256            Self::PaneTitleChanged { .. } => HookName::PaneTitleChanged,
257            Self::PasteBufferChanged { .. } => HookName::PasteBufferChanged,
258            Self::PasteBufferDeleted { .. } => HookName::PasteBufferDeleted,
259            Self::AfterSelectWindow { .. } => HookName::AfterSelectWindow,
260            Self::AfterSelectPane { .. } => HookName::AfterSelectPane,
261            Self::AfterSendKeys { .. } => HookName::AfterSendKeys,
262            Self::AfterSetOption { .. } => HookName::AfterSetOption,
263        }
264    }
265
266    /// Returns the event scope used for hook resolution.
267    #[must_use]
268    pub fn scope(&self) -> ScopeSelector {
269        match self {
270            Self::ClientActive { session_name, .. }
271            | Self::ClientAttached { session_name, .. }
272            | Self::ClientDetached { session_name, .. }
273            | Self::ClientFocusIn { session_name, .. }
274            | Self::ClientFocusOut { session_name, .. }
275            | Self::ClientSessionChanged { session_name, .. }
276            | Self::ClientResized { session_name, .. }
277            | Self::ClientLightTheme { session_name, .. }
278            | Self::ClientDarkTheme { session_name, .. }
279            | Self::SessionCreated { session_name }
280            | Self::SessionClosed { session_name, .. }
281            | Self::SessionRenamed { session_name }
282            | Self::SessionWindowChanged { session_name }
283            | Self::WindowLinked { session_name, .. }
284            | Self::WindowUnlinked { session_name, .. } => {
285                ScopeSelector::Session(session_name.clone())
286            }
287            Self::WindowRenamed { target }
288            | Self::WindowLayoutChanged { target }
289            | Self::WindowResized { target }
290            | Self::WindowPaneChanged { target }
291            | Self::AlertBell { target }
292            | Self::AlertActivity { target }
293            | Self::AlertSilence { target }
294            | Self::AfterSelectWindow { target } => ScopeSelector::Window(target.clone()),
295            Self::PaneExited { target, .. }
296            | Self::PaneDied { target, .. }
297            | Self::PaneModeChanged { target }
298            | Self::PaneFocusIn { target }
299            | Self::PaneFocusOut { target }
300            | Self::PaneSetClipboard { target }
301            | Self::PaneTitleChanged { target }
302            | Self::AfterSelectPane { target }
303            | Self::AfterSendKeys { target } => ScopeSelector::Pane(target.clone()),
304            Self::PasteBufferChanged { .. } | Self::PasteBufferDeleted { .. } => {
305                ScopeSelector::Global
306            }
307            Self::AfterSetOption {
308                session_name: Some(session_name),
309            } => ScopeSelector::Session(session_name.clone()),
310            Self::AfterSetOption { session_name: None } => ScopeSelector::Global,
311        }
312    }
313
314    /// Returns the session associated with this lifecycle event, when one exists.
315    #[must_use]
316    pub fn session_name(&self) -> Option<&SessionName> {
317        match self {
318            Self::ClientActive { session_name, .. }
319            | Self::ClientAttached { session_name, .. }
320            | Self::ClientDetached { session_name, .. }
321            | Self::ClientFocusIn { session_name, .. }
322            | Self::ClientFocusOut { session_name, .. }
323            | Self::ClientSessionChanged { session_name, .. }
324            | Self::ClientResized { session_name, .. }
325            | Self::ClientLightTheme { session_name, .. }
326            | Self::ClientDarkTheme { session_name, .. }
327            | Self::SessionCreated { session_name }
328            | Self::SessionClosed { session_name, .. }
329            | Self::SessionRenamed { session_name }
330            | Self::SessionWindowChanged { session_name }
331            | Self::WindowLinked { session_name, .. }
332            | Self::WindowUnlinked { session_name, .. } => Some(session_name),
333            Self::WindowRenamed { target }
334            | Self::WindowLayoutChanged { target }
335            | Self::WindowResized { target }
336            | Self::WindowPaneChanged { target }
337            | Self::AlertBell { target }
338            | Self::AlertActivity { target }
339            | Self::AlertSilence { target }
340            | Self::AfterSelectWindow { target } => Some(target.session_name()),
341            Self::PaneExited { target, .. }
342            | Self::PaneDied { target, .. }
343            | Self::PaneModeChanged { target }
344            | Self::PaneFocusIn { target }
345            | Self::PaneFocusOut { target }
346            | Self::PaneSetClipboard { target }
347            | Self::PaneTitleChanged { target }
348            | Self::AfterSelectPane { target }
349            | Self::AfterSendKeys { target } => Some(target.session_name()),
350            Self::AfterSetOption { session_name } => session_name.as_ref(),
351            Self::PasteBufferChanged { .. } | Self::PasteBufferDeleted { .. } => None,
352        }
353    }
354
355    /// Returns the event client identifier when one is available.
356    #[must_use]
357    pub fn client_name(&self) -> Option<&str> {
358        match self {
359            Self::ClientAttached { client_name, .. }
360            | Self::ClientDetached { client_name, .. }
361            | Self::ClientActive { client_name, .. }
362            | Self::ClientFocusIn { client_name, .. }
363            | Self::ClientFocusOut { client_name, .. }
364            | Self::ClientSessionChanged { client_name, .. }
365            | Self::ClientResized { client_name, .. }
366            | Self::ClientLightTheme { client_name, .. }
367            | Self::ClientDarkTheme { client_name, .. } => client_name.as_deref(),
368            _ => None,
369        }
370    }
371
372    /// Returns the event window target when one is available.
373    #[must_use]
374    pub fn window_target(&self) -> Option<WindowTarget> {
375        match self {
376            Self::WindowLinked { target, .. } | Self::WindowUnlinked { target, .. } => {
377                target.clone()
378            }
379            Self::WindowRenamed { target }
380            | Self::WindowLayoutChanged { target }
381            | Self::WindowResized { target }
382            | Self::WindowPaneChanged { target }
383            | Self::AlertBell { target }
384            | Self::AlertActivity { target }
385            | Self::AlertSilence { target }
386            | Self::AfterSelectWindow { target } => Some(target.clone()),
387            Self::PaneExited { target, .. }
388            | Self::PaneDied { target, .. }
389            | Self::PaneModeChanged { target }
390            | Self::PaneFocusIn { target }
391            | Self::PaneFocusOut { target }
392            | Self::PaneSetClipboard { target }
393            | Self::PaneTitleChanged { target }
394            | Self::AfterSelectPane { target }
395            | Self::AfterSendKeys { target } => Some(WindowTarget::with_window(
396                target.session_name().clone(),
397                target.window_index(),
398            )),
399            _ => None,
400        }
401    }
402
403    /// Returns the event pane target when one is available.
404    #[must_use]
405    pub fn pane_target(&self) -> Option<&PaneTarget> {
406        match self {
407            Self::PaneExited { target, .. }
408            | Self::PaneDied { target, .. }
409            | Self::PaneModeChanged { target }
410            | Self::PaneFocusIn { target }
411            | Self::PaneFocusOut { target }
412            | Self::PaneSetClipboard { target }
413            | Self::PaneTitleChanged { target }
414            | Self::AfterSelectPane { target }
415            | Self::AfterSendKeys { target } => Some(target),
416            _ => None,
417        }
418    }
419
420    /// Returns the event session ID captured at notification time, when one exists.
421    #[must_use]
422    pub const fn session_id(&self) -> Option<u32> {
423        match self {
424            Self::SessionClosed { session_id, .. } => *session_id,
425            _ => None,
426        }
427    }
428
429    /// Returns the event window ID captured at notification time, when one exists.
430    #[must_use]
431    pub const fn window_id(&self) -> Option<u32> {
432        match self {
433            Self::WindowUnlinked { window_id, .. }
434            | Self::PaneExited { window_id, .. }
435            | Self::PaneDied { window_id, .. } => *window_id,
436            _ => None,
437        }
438    }
439
440    /// Returns the event window name captured at notification time, when one exists.
441    #[must_use]
442    pub fn window_name_snapshot(&self) -> Option<&str> {
443        match self {
444            Self::WindowUnlinked { window_name, .. }
445            | Self::PaneExited { window_name, .. }
446            | Self::PaneDied { window_name, .. } => window_name.as_deref(),
447            _ => None,
448        }
449    }
450
451    /// Returns the event pane ID captured at notification time, when one exists.
452    #[must_use]
453    pub const fn pane_id(&self) -> Option<u32> {
454        match self {
455            Self::PaneExited { pane_id, .. } | Self::PaneDied { pane_id, .. } => *pane_id,
456            _ => None,
457        }
458    }
459
460    /// Returns the paste buffer name when one is available.
461    #[must_use]
462    pub fn buffer_name(&self) -> Option<&str> {
463        match self {
464            Self::PasteBufferChanged { buffer_name } | Self::PasteBufferDeleted { buffer_name } => {
465                Some(buffer_name)
466            }
467            _ => None,
468        }
469    }
470
471    /// Returns the best available current target for hook execution.
472    #[must_use]
473    pub fn current_target(&self) -> Option<Target> {
474        match self {
475            Self::WindowLinked {
476                session_name,
477                target: Some(target),
478            }
479            | Self::WindowUnlinked {
480                session_name,
481                target: Some(target),
482                ..
483            } => Some(Target::Window(WindowTarget::with_window(
484                session_name.clone(),
485                target.window_index(),
486            ))),
487            Self::WindowLinked { session_name, .. }
488            | Self::WindowUnlinked { session_name, .. }
489            | Self::ClientActive { session_name, .. }
490            | Self::ClientAttached { session_name, .. }
491            | Self::ClientDetached { session_name, .. }
492            | Self::ClientFocusIn { session_name, .. }
493            | Self::ClientFocusOut { session_name, .. }
494            | Self::ClientSessionChanged { session_name, .. }
495            | Self::ClientResized { session_name, .. }
496            | Self::ClientLightTheme { session_name, .. }
497            | Self::ClientDarkTheme { session_name, .. }
498            | Self::SessionCreated { session_name }
499            | Self::SessionClosed { session_name, .. }
500            | Self::SessionRenamed { session_name }
501            | Self::SessionWindowChanged { session_name } => {
502                Some(Target::Session(session_name.clone()))
503            }
504            Self::WindowRenamed { target }
505            | Self::WindowLayoutChanged { target }
506            | Self::WindowResized { target }
507            | Self::WindowPaneChanged { target }
508            | Self::AlertBell { target }
509            | Self::AlertActivity { target }
510            | Self::AlertSilence { target }
511            | Self::AfterSelectWindow { target } => Some(Target::Window(target.clone())),
512            Self::PaneExited { target, .. }
513            | Self::PaneDied { target, .. }
514            | Self::PaneModeChanged { target }
515            | Self::PaneFocusIn { target }
516            | Self::PaneFocusOut { target }
517            | Self::PaneSetClipboard { target }
518            | Self::PaneTitleChanged { target }
519            | Self::AfterSelectPane { target }
520            | Self::AfterSendKeys { target } => Some(Target::Pane(target.clone())),
521            Self::AfterSetOption {
522                session_name: Some(session_name),
523            } => Some(Target::Session(session_name.clone())),
524            Self::AfterSetOption { session_name: None }
525            | Self::PasteBufferChanged { .. }
526            | Self::PasteBufferDeleted { .. } => None,
527        }
528    }
529}