free_launch/launch_entries/
launch_entry.rs1use chrono::Local;
2use niri_ipc::{Timestamp, Window as NiriWindow};
3use std::fmt;
4use std::fs::create_dir_all;
5use std::path::Path;
6use tracing::{error, info, warn};
7
8use crate::free_launch::free_launch::PROJECT_DIRS;
9use crate::launch_entries::action_name::ActionName;
10use crate::launch_entries::action_result::ActionResult;
11use crate::launch_entries::launch_entry_trait::Launchable;
12use crate::model::invocation::Invocation;
13use crate::model::window::Window;
14
15pub struct LaunchEntry {
17 pub id: String,
18 pub name: String,
19 pub selected: bool,
20 pub(crate) items: Vec<Box<dyn Launchable>>,
21 pub invocations: Vec<Invocation>,
23 pub activation_order: Option<Timestamp>,
26}
27
28impl LaunchEntry {
29 pub fn new(id: String, name: String) -> Self {
31 Self {
32 id,
33 name,
34 selected: false,
35 items: Vec::new(),
36 invocations: Vec::new(),
37 activation_order: None,
38 }
39 }
40
41 pub fn name(&self) -> &str {
42 &self.name
43 }
44
45 pub(crate) fn add_item(&mut self, item: Box<dyn Launchable>) {
47 self.items.push(item);
48 }
49
50 pub(crate) fn add_invocation(&mut self, invocation: Invocation) {
52 self.invocations.push(invocation);
53 }
54
55 pub fn is_valid(&self) -> bool {
57 !self.id.is_empty() && !self.items.is_empty()
58 }
59
60 pub fn sort_key(&self) -> u64 {
65 let now = Local::now().timestamp() as u64;
66 self.invocations
67 .iter()
68 .fold(0, |acc, i| acc + (i.timestamp / (now - i.timestamp).max(1)))
69 }
70
71 pub(crate) fn toggle_selected(&mut self) {
72 self.selected = !self.selected
73 }
74
75 pub(crate) fn preferred_item(&self) -> Option<&Box<dyn Launchable>> {
77 self.items.first()
78 }
79
80 pub(crate) fn id(&self) -> Option<String> {
81 self.preferred_item().map(|i| i.id())
82 }
83
84 pub(crate) fn file_path(&self) -> Option<&Path> {
85 self.preferred_item().map(|i| i.file_path())
86 }
87
88 pub(crate) fn comment(&self) -> Option<&str> {
89 self.preferred_item().and_then(|i| i.comment())
90 }
91
92 pub(crate) fn icon_name(&self) -> Option<&str> {
93 self.preferred_item().and_then(|i| i.icon_name())
94 }
95
96 pub(crate) fn invoke(&self, action: &ActionName, search_query: &str) -> ActionResult {
97 info!("Launching entry: {}", self.name());
98
99 match self.preferred_item().map(|i| i.invoke(action)) {
100 Some(result) => {
101 self.log_invocation(action, search_query, &result);
103 result
104 }
105 None => ActionResult::Error("could not find a preferred item".to_owned()),
106 }
107 }
108
109 pub(crate) fn invoke_default(&self, search_query: &str) -> ActionResult {
110 self.invoke(&ActionName::Default, search_query)
111 }
112
113 pub(crate) fn invoke_secondary(&self, search_query: &str) -> ActionResult {
114 match self.preferred_item() {
115 Some(item) => {
116 let secondary_action_name = item.secondary_action();
117 self.invoke(
118 &ActionName::Action(secondary_action_name.to_owned()),
119 search_query,
120 )
121 }
122 None => ActionResult::Error("could not find a preferred item".to_owned()),
123 }
124 }
125
126 fn log_invocation(&self, action: &ActionName, search_query: &str, result: &ActionResult) {
127 let cache_dir = PROJECT_DIRS.cache_dir();
129
130 if let Err(e) = create_dir_all(cache_dir) {
132 error!(
133 "Failed to create cache directory {}: {}",
134 cache_dir.display(),
135 e
136 );
137 return;
138 }
139
140 if let Err(e) = Invocation::log_entry(search_query, action.to_string(), self) {
144 warn!("Could not log invocation:{}", e)
145 }
146 }
147}
148
149impl From<Window> for LaunchEntry {
150 fn from(window: Window) -> Self {
151 let id = window.id();
152 let name = window.name().to_owned();
153 let activation_order = window.focus_timestamp();
154
155 let mut launch_entry = LaunchEntry::new(id, name.unwrap_or("UNNAMED".to_owned()));
157 launch_entry.activation_order = activation_order;
158 launch_entry.add_item(Box::new(window));
159 launch_entry
160 }
161}
162
163impl From<NiriWindow> for LaunchEntry {
164 fn from(niri_window: NiriWindow) -> Self {
165 let window: Window = niri_window.into();
166 window.into()
167 }
168}
169
170impl fmt::Display for LaunchEntry {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 write!(f, "{}", self.name)
173 }
174}
175
176