1use crate::{DocumentActivation, DocumentDescriptor};
8use incular_core::{Code, Modifiers};
9use std::{ffi::OsString, sync::Arc};
10use url::Url;
11
12#[derive(Clone, Debug, Default, PartialEq, Eq)]
19pub struct LaunchActivation {
20 arguments: Arc<[OsString]>,
21 documents: DocumentActivation,
22 urls: Arc<[Url]>,
23}
24
25impl LaunchActivation {
26 #[must_use]
27 pub fn new(arguments: impl IntoIterator<Item = OsString>) -> Self {
28 Self {
29 arguments: arguments.into_iter().collect::<Vec<_>>().into(),
30 ..Self::default()
31 }
32 }
33
34 #[must_use]
36 pub fn current_process() -> Self {
37 Self::new(std::env::args_os().skip(1))
38 }
39
40 #[must_use]
41 pub fn with_documents(mut self, documents: DocumentActivation) -> Self {
42 self.documents = documents;
43 self
44 }
45
46 #[must_use]
47 pub fn with_urls(mut self, urls: impl IntoIterator<Item = Url>) -> Self {
48 self.urls = urls.into_iter().collect::<Vec<_>>().into();
49 self
50 }
51
52 #[must_use]
53 pub fn arguments(&self) -> &[OsString] {
54 &self.arguments
55 }
56
57 #[must_use]
58 pub const fn documents(&self) -> &DocumentActivation {
59 &self.documents
60 }
61
62 #[must_use]
63 pub fn urls(&self) -> &[Url] {
64 &self.urls
65 }
66}
67
68#[derive(Clone, Debug, Default, PartialEq, Eq)]
70pub struct UrlActivation {
71 urls: Arc<[Url]>,
72}
73
74impl UrlActivation {
75 #[must_use]
76 pub fn new(urls: impl IntoIterator<Item = Url>) -> Self {
77 Self {
78 urls: urls.into_iter().collect::<Vec<_>>().into(),
79 }
80 }
81
82 #[must_use]
83 pub fn urls(&self) -> &[Url] {
84 &self.urls
85 }
86
87 #[must_use]
88 pub fn is_empty(&self) -> bool {
89 self.urls.is_empty()
90 }
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
95pub struct GlobalShortcutId(u64);
96
97impl GlobalShortcutId {
98 #[must_use]
99 pub const fn new(value: u64) -> Self {
100 Self(value)
101 }
102
103 #[must_use]
104 pub const fn get(self) -> u64 {
105 self.0
106 }
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
115pub struct GlobalShortcutChord {
116 key: Code,
117 modifiers: Modifiers,
118}
119
120impl GlobalShortcutChord {
121 pub fn new(key: Code, modifiers: Modifiers) -> Result<Self, GlobalShortcutChordError> {
122 let supported = Modifiers::ALT | Modifiers::CONTROL | Modifiers::META | Modifiers::SHIFT;
123 if modifiers.bits() & !supported.bits() != 0 {
124 return Err(GlobalShortcutChordError::UnsupportedModifiers(modifiers));
125 }
126 if key == Code::Unidentified || is_modifier_code(key) {
127 return Err(GlobalShortcutChordError::InvalidKey(key));
128 }
129 Ok(Self { key, modifiers })
130 }
131
132 #[must_use]
133 pub const fn key(self) -> Code {
134 self.key
135 }
136
137 #[must_use]
138 pub const fn modifiers(self) -> Modifiers {
139 self.modifiers
140 }
141}
142
143fn is_modifier_code(code: Code) -> bool {
144 matches!(
145 code,
146 Code::AltLeft
147 | Code::AltRight
148 | Code::ControlLeft
149 | Code::ControlRight
150 | Code::MetaLeft
151 | Code::MetaRight
152 | Code::ShiftLeft
153 | Code::ShiftRight
154 )
155}
156
157#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
158pub enum GlobalShortcutChordError {
159 #[error("{0} cannot be a global shortcut key")]
160 InvalidKey(Code),
161 #[error("global shortcut modifiers {0:?} contain unsupported modifier state")]
162 UnsupportedModifiers(Modifiers),
163}
164
165#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
167pub enum GlobalShortcutError {
168 #[error("global shortcuts are unsupported")]
169 Unsupported,
170 #[error("invalid global shortcut: {0}")]
171 InvalidChord(GlobalShortcutChordError),
172 #[error("global shortcut {} is already registered", .0.get())]
173 AlreadyRegistered(GlobalShortcutId),
174 #[error("global shortcut {} is not registered", .0.get())]
175 NotRegistered(GlobalShortcutId),
176 #[error(
177 "global shortcut {:?}+{} conflicts with another registration",
178 .0.modifiers(),
179 .0.key()
180 )]
181 Conflict(GlobalShortcutChord),
182 #[error("global shortcut application service stopped")]
183 ApplicationStopped,
184 #[error("global shortcut backend failed: {0}")]
185 Backend(String),
186}
187
188impl From<GlobalShortcutChordError> for GlobalShortcutError {
189 fn from(value: GlobalShortcutChordError) -> Self {
190 Self::InvalidChord(value)
191 }
192}
193
194#[derive(Clone, Debug, PartialEq, Eq)]
197pub enum ApplicationActivation {
198 Launch(LaunchActivation),
199 OpenFiles(DocumentActivation),
200 OpenUrls(UrlActivation),
201 Reopen,
202 GlobalShortcutInvoked(GlobalShortcutId),
203}
204
205impl ApplicationActivation {
206 #[must_use]
207 pub fn open_files(paths: impl IntoIterator<Item = impl Into<std::path::PathBuf>>) -> Self {
208 Self::OpenFiles(DocumentActivation::from_paths(paths))
209 }
210
211 #[must_use]
212 pub fn open_urls(urls: impl IntoIterator<Item = Url>) -> Self {
213 Self::OpenUrls(UrlActivation::new(urls))
214 }
215
216 #[must_use]
217 pub fn documents(&self) -> &[DocumentDescriptor] {
218 match self {
219 Self::Launch(launch) => launch.documents().documents(),
220 Self::OpenFiles(documents) => documents.documents(),
221 Self::OpenUrls(_) | Self::Reopen | Self::GlobalShortcutInvoked(_) => &[],
222 }
223 }
224
225 #[must_use]
226 pub fn urls(&self) -> &[Url] {
227 match self {
228 Self::Launch(launch) => launch.urls(),
229 Self::OpenUrls(urls) => urls.urls(),
230 Self::OpenFiles(_) | Self::Reopen | Self::GlobalShortcutInvoked(_) => &[],
231 }
232 }
233}
234
235#[derive(Clone, Debug, PartialEq, Eq, Hash)]
237pub struct SingleInstancePolicy {
238 application_id: Arc<str>,
239}
240
241impl SingleInstancePolicy {
242 pub fn new(application_id: impl Into<String>) -> Result<Self, SingleInstancePolicyError> {
243 let application_id = application_id.into();
244 let valid = !application_id.is_empty()
245 && application_id.len() <= 128
246 && application_id
247 .bytes()
248 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'));
249 if !valid {
250 return Err(SingleInstancePolicyError::InvalidApplicationId(
251 application_id,
252 ));
253 }
254 Ok(Self {
255 application_id: Arc::from(application_id),
256 })
257 }
258
259 #[must_use]
260 pub fn application_id(&self) -> &str {
261 &self.application_id
262 }
263}
264
265#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
266pub enum SingleInstancePolicyError {
267 #[error(
268 "single-instance application id {0:?} must be 1-128 ASCII letters, digits, '.', '_' or '-'"
269 )]
270 InvalidApplicationId(String),
271}