1use std::fmt;
11use std::io;
12use std::str::FromStr;
13use thiserror::Error as ThisError;
14
15use strum_macros::Display as StrumDisplay;
16
17pub type ReturnCode = u16;
19
20pub type MessageId = u32;
22
23pub type ClientId = u32;
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
28pub enum MessageScope {
29 Last,
31 All,
33 Message(MessageId),
35}
36
37impl fmt::Display for MessageScope {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 MessageScope::Last => write!(f, "self"),
41 MessageScope::All => write!(f, "all"),
42 MessageScope::Message(id) => write!(f, "{}", id),
43 }
44 }
45}
46
47#[derive(Debug, Clone, Hash, Eq, PartialEq)]
49pub enum ClientScope {
50 Current,
52 All,
54 Client(ClientId),
56}
57
58impl fmt::Display for ClientScope {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 ClientScope::Current => write!(f, "self"),
62 ClientScope::All => write!(f, "all"),
63 ClientScope::Client(id) => write!(f, "{}", id),
64 }
65 }
66}
67
68#[derive(StrumDisplay, Debug, Clone, PartialEq, Eq, Hash)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
72pub enum Priority {
73 #[strum(serialize = "progress")]
74 Progress,
75 #[strum(serialize = "notification")]
76 Notification,
77 #[strum(serialize = "message")]
78 Message,
79 #[strum(serialize = "text")]
80 Text,
81 #[strum(serialize = "important")]
82 Important,
83}
84
85#[derive(StrumDisplay, Debug, Clone, PartialEq, Eq, Hash)]
87#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
89pub enum PunctuationMode {
90 #[strum(serialize = "none")]
91 None,
92 #[strum(serialize = "some")]
93 Some,
94 #[strum(serialize = "most")]
95 Most,
96 #[strum(serialize = "all")]
97 All,
98}
99
100#[derive(StrumDisplay, Debug, Clone, Hash, Eq, PartialEq)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
104pub enum CapitalLettersRecognitionMode {
105 #[strum(serialize = "none")]
106 None,
107 #[strum(serialize = "spell")]
108 Spell,
109 #[strum(serialize = "icon")]
110 Icon,
111}
112
113#[derive(StrumDisplay, Debug, Clone, PartialEq, Eq, Hash)]
115#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
116#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
117pub enum KeyName {
118 #[strum(serialize = "space")]
119 Space,
120 #[strum(serialize = "underscore")]
121 Underscore,
122 #[strum(serialize = "double-quote")]
123 DoubleQuote,
124 #[strum(serialize = "alt")]
125 Alt,
126 #[strum(serialize = "control")]
127 Control,
128 #[strum(serialize = "hyper")]
129 Hyper,
130 #[strum(serialize = "meta")]
131 Meta,
132 #[strum(serialize = "shift")]
133 Shift,
134 #[strum(serialize = "super")]
135 Super,
136 #[strum(serialize = "backspace")]
137 Backspace,
138 #[strum(serialize = "break")]
139 Break,
140 #[strum(serialize = "delete")]
141 Delete,
142 #[strum(serialize = "down")]
143 Down,
144 #[strum(serialize = "end")]
145 End,
146 #[strum(serialize = "enter")]
147 Enter,
148 #[strum(serialize = "escape")]
149 Escape,
150 #[strum(serialize = "f1")]
151 F1,
152 #[strum(serialize = "f2")]
153 F2,
154 #[strum(serialize = "f3")]
155 F3,
156 #[strum(serialize = "f4")]
157 F4,
158 #[strum(serialize = "f5")]
159 F5,
160 #[strum(serialize = "f6")]
161 F6,
162 #[strum(serialize = "f7")]
163 F7,
164 #[strum(serialize = "f8")]
165 F8,
166 #[strum(serialize = "f9")]
167 F9,
168 #[strum(serialize = "f10")]
169 F10,
170 #[strum(serialize = "f11")]
171 F11,
172 #[strum(serialize = "f12")]
173 F12,
174 #[strum(serialize = "f13")]
175 F13,
176 #[strum(serialize = "f14")]
177 F14,
178 #[strum(serialize = "f15")]
179 F15,
180 #[strum(serialize = "f16")]
181 F16,
182 #[strum(serialize = "f17")]
183 F17,
184 #[strum(serialize = "f18")]
185 F18,
186 #[strum(serialize = "f19")]
187 F19,
188 #[strum(serialize = "f20")]
189 F20,
190 #[strum(serialize = "f21")]
191 F21,
192 #[strum(serialize = "f22")]
193 F22,
194 #[strum(serialize = "f23")]
195 F23,
196 #[strum(serialize = "f24")]
197 F24,
198 #[strum(serialize = "home")]
199 Home,
200 #[strum(serialize = "insert")]
201 Insert,
202 #[strum(serialize = "kp-*")]
203 KpMultiply,
204 #[strum(serialize = "kp-+")]
205 KpPlus,
206 #[strum(serialize = "kp--")]
207 KpMinus,
208 #[strum(serialize = "kp-.")]
209 KpDot,
210 #[strum(serialize = "kp-/")]
211 KpDivide,
212 #[strum(serialize = "kp-0")]
213 Kp0,
214 #[strum(serialize = "kp-1")]
215 Kp1,
216 #[strum(serialize = "kp-2")]
217 Kp2,
218 #[strum(serialize = "kp-3")]
219 Kp3,
220 #[strum(serialize = "kp-4")]
221 Kp4,
222 #[strum(serialize = "kp-5")]
223 Kp5,
224 #[strum(serialize = "kp-6")]
225 Kp6,
226 #[strum(serialize = "kp-7")]
227 Kp7,
228 #[strum(serialize = "kp-8")]
229 Kp8,
230 #[strum(serialize = "kp-9")]
231 Kp9,
232 #[strum(serialize = "kp-enter")]
233 KpEnter,
234 #[strum(serialize = "left")]
235 Left,
236 #[strum(serialize = "menu")]
237 Menu,
238 #[strum(serialize = "next")]
239 Next,
240 #[strum(serialize = "num-lock")]
241 NumLock,
242 #[strum(serialize = "pause")]
243 Pause,
244 #[strum(serialize = "print")]
245 Print,
246 #[strum(serialize = "prior")]
247 Prior,
248 #[strum(serialize = "return")]
249 Return,
250 #[strum(serialize = "right")]
251 Right,
252 #[strum(serialize = "scroll-lock")]
253 ScrollLock,
254 #[strum(serialize = "tab")]
255 Tab,
256 #[strum(serialize = "up")]
257 Up,
258 #[strum(serialize = "window")]
259 Window,
260}
261
262#[derive(StrumDisplay, Debug, Clone, Hash, Eq, PartialEq)]
264#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
265#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
266pub enum NotificationType {
267 #[strum(serialize = "begin")]
268 Begin,
269 #[strum(serialize = "end")]
270 End,
271 #[strum(serialize = "cancel")]
272 Cancel,
273 #[strum(serialize = "pause")]
274 Pause,
275 #[strum(serialize = "resume")]
276 Resume,
277 #[strum(serialize = "index_mark")]
278 IndexMark,
279 #[strum(serialize = "all")]
280 All,
281}
282
283#[derive(StrumDisplay, Debug, Clone)]
285pub enum EventType {
286 Begin,
287 End,
288 Cancel,
289 Pause,
290 Resume,
291 IndexMark(String),
292}
293
294#[derive(Debug, Clone, Hash, Eq, PartialEq)]
296#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
297#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
298pub struct EventId {
299 pub message: String,
301 pub client: String,
303}
304
305impl EventId {
306 pub fn new(message: &str, client: &str) -> Self {
308 Self {
309 message: message.to_string(),
310 client: client.to_string(),
311 }
312 }
313}
314
315#[derive(Debug, Clone)]
317pub struct Event {
318 pub ntype: EventType,
319 pub id: EventId,
320}
321
322impl Event {
323 pub fn new(ntype: EventType, message: &str, client: &str) -> Event {
324 Event {
325 ntype,
326 id: EventId::new(message, client),
327 }
328 }
329
330 pub fn begin(message: &str, client: &str) -> Event {
331 Event::new(EventType::Begin, message, client)
332 }
333
334 pub fn end(message: &str, client: &str) -> Event {
335 Event::new(EventType::End, message, client)
336 }
337
338 pub fn index_mark(mark: String, message: &str, client: &str) -> Event {
339 Event::new(EventType::IndexMark(mark), message, client)
340 }
341
342 pub fn cancel(message: &str, client: &str) -> Event {
343 Event::new(EventType::Cancel, message, client)
344 }
345
346 pub fn pause(message: &str, client: &str) -> Event {
347 Event::new(EventType::Pause, message, client)
348 }
349
350 pub fn resume(message: &str, client: &str) -> Event {
351 Event::new(EventType::Resume, message, client)
352 }
353}
354
355#[derive(Debug, PartialEq, Eq, Clone, Hash)]
357pub struct SynthesisVoice {
358 pub name: String,
359 pub language: Option<String>,
360 pub dialect: Option<String>,
361}
362
363impl SynthesisVoice {
364 pub fn new(name: &str, language: Option<&str>, dialect: Option<&str>) -> SynthesisVoice {
365 SynthesisVoice {
366 name: name.to_string(),
367 language: language.map(|s| s.to_string()),
368 dialect: dialect.map(|s| s.to_string()),
369 }
370 }
371 fn parse_none(token: Option<&str>) -> Option<String> {
373 match token {
374 Some(s) => match s {
375 "none" => None,
376 s => Some(s.to_string()),
377 },
378 None => None,
379 }
380 }
381}
382
383impl FromStr for SynthesisVoice {
384 type Err = ClientError;
385
386 fn from_str(s: &str) -> Result<Self, Self::Err> {
387 let mut iter = s.split('\t');
388 match iter.next() {
389 Some(name) => Ok(SynthesisVoice {
390 name: name.to_string(),
391 language: SynthesisVoice::parse_none(iter.next()),
392 dialect: SynthesisVoice::parse_none(iter.next()),
393 }),
394 None => Err(ClientError::unexpected_eof("missing synthesis voice name")),
395 }
396 }
397}
398
399#[derive(Debug, PartialEq, Eq)]
407#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
408#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
409pub struct StatusLine {
410 pub code: ReturnCode,
411 pub message: String,
412}
413
414impl fmt::Display for StatusLine {
415 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
416 write!(f, "{} {}", self.code, self.message)
417 }
418}
419#[derive(ThisError, Debug)]
421pub enum ClientError {
422 #[error("I/O: {0}")]
423 Io(io::Error),
424 #[error("Not ready")]
425 NotReady,
426 #[error("SSIP: {0}")]
427 Ssip(StatusLine),
428 #[error("Too few lines")]
429 TooFewLines,
430 #[error("Too many lines")]
431 TooManyLines,
432 #[error("Unexpected status: {0}")]
433 UnexpectedStatus(ReturnCode),
434}
435
436impl ClientError {
437 pub fn io_error(kind: io::ErrorKind, msg: &str) -> Self {
439 Self::Io(io::Error::new(kind, msg))
440 }
441
442 pub fn invalid_data(msg: &str) -> Self {
444 ClientError::io_error(io::ErrorKind::InvalidData, msg)
445 }
446
447 pub fn unexpected_eof(msg: &str) -> Self {
449 ClientError::io_error(io::ErrorKind::UnexpectedEof, msg)
450 }
451}
452
453impl From<io::Error> for ClientError {
454 fn from(err: io::Error) -> Self {
455 if err.kind() == io::ErrorKind::WouldBlock {
456 ClientError::NotReady
457 } else {
458 ClientError::Io(err)
459 }
460 }
461}
462
463pub type ClientResult<T> = Result<T, ClientError>;
465
466pub type ClientStatus = ClientResult<StatusLine>;
468
469#[derive(Debug, Clone, PartialEq, Eq, Hash)]
471#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
472#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
473pub struct ClientName {
474 pub user: String,
475 pub application: String,
476 pub component: String,
477}
478
479impl ClientName {
480 pub fn new(user: &str, application: &str) -> Self {
481 ClientName::with_component(user, application, "main")
482 }
483
484 pub fn with_component(user: &str, application: &str, component: &str) -> Self {
485 ClientName {
486 user: user.to_string(),
487 application: application.to_string(),
488 component: component.to_string(),
489 }
490 }
491}
492
493#[derive(StrumDisplay, Debug, Clone, PartialEq, Eq, Hash)]
495#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
496#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
497pub enum CursorDirection {
498 #[strum(serialize = "backward")]
499 Backward,
500 #[strum(serialize = "forward")]
501 Forward,
502}
503
504#[derive(StrumDisplay, Debug, Clone, Eq, PartialEq, Hash)]
506#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
507#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
508pub enum SortDirection {
509 #[strum(serialize = "asc")]
510 Ascending,
511 #[strum(serialize = "desc")]
512 Descending,
513}
514
515#[derive(StrumDisplay, Debug, Clone, PartialEq, Eq, Hash)]
517#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
518#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
519pub enum SortKey {
520 #[strum(serialize = "client_name")]
521 ClientName,
522 #[strum(serialize = "priority")]
523 Priority,
524 #[strum(serialize = "message_type")]
525 MessageType,
526 #[strum(serialize = "time")]
527 Time,
528 #[strum(serialize = "user")]
529 User,
530}
531
532#[derive(StrumDisplay, Debug, Clone, PartialEq, Eq, Hash)]
534#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
535#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
536pub enum Ordering {
537 #[strum(serialize = "text")]
538 Text,
539 #[strum(serialize = "sound_icon")]
540 SoundIcon,
541 #[strum(serialize = "char")]
542 Char,
543 #[strum(serialize = "key")]
544 Key,
545}
546
547#[derive(Debug, Clone, Eq, PartialEq, Hash)]
549pub enum HistoryPosition {
550 First,
551 Last,
552 Pos(u16),
553}
554
555impl fmt::Display for HistoryPosition {
556 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557 match self {
558 HistoryPosition::First => write!(f, "first"),
559 HistoryPosition::Last => write!(f, "last"),
560 HistoryPosition::Pos(n) => write!(f, "pos {}", n),
561 }
562 }
563}
564
565#[derive(Debug, PartialEq, Eq, Clone, Hash)]
567#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
568#[cfg_attr(feature = "dbus", derive(zvariant::Type))]
569pub struct HistoryClientStatus {
570 pub id: ClientId,
571 pub name: String,
572 pub connected: bool,
573}
574
575impl HistoryClientStatus {
576 pub fn new(id: ClientId, name: &str, connected: bool) -> Self {
577 Self {
578 id,
579 name: name.to_string(),
580 connected,
581 }
582 }
583}
584
585impl FromStr for HistoryClientStatus {
586 type Err = ClientError;
587
588 fn from_str(s: &str) -> Result<Self, Self::Err> {
589 let mut iter = s.splitn(3, ' ');
590 match iter.next() {
591 Some("") => Err(ClientError::unexpected_eof("expecting client id")),
592 Some(client_id) => match client_id.parse::<u32>() {
593 Ok(id) => match iter.next() {
594 Some(name) => match iter.next() {
595 Some("0") => Ok(HistoryClientStatus::new(id, name, false)),
596 Some("1") => Ok(HistoryClientStatus::new(id, name, true)),
597 Some(_) => Err(ClientError::invalid_data("invalid client status")),
598 None => Err(ClientError::unexpected_eof("expecting client status")),
599 },
600 None => Err(ClientError::unexpected_eof("expecting client name")),
601 },
602 Err(_) => Err(ClientError::invalid_data("invalid client id")),
603 },
604 None => Err(ClientError::unexpected_eof("expecting client id")),
605 }
606 }
607}
608
609#[derive(Debug, Clone, Hash, PartialEq, Eq)]
610pub enum Request {
612 SetName(ClientName),
613 Speak,
615 SendLine(String),
616 SendLines(Vec<String>),
617 SpeakChar(char),
618 SpeakKey(KeyName),
619 Stop(MessageScope),
621 Cancel(MessageScope),
622 Pause(MessageScope),
623 Resume(MessageScope),
624 SetPriority(Priority),
626 SetDebug(bool),
627 SetOutputModule(ClientScope, String),
628 GetOutputModule,
629 ListOutputModules,
630 SetLanguage(ClientScope, String),
631 GetLanguage,
632 SetSsmlMode(bool),
633 SetPunctuationMode(ClientScope, PunctuationMode),
634 SetSpelling(ClientScope, bool),
635 SetCapitalLettersRecognitionMode(ClientScope, CapitalLettersRecognitionMode),
636 SetVoiceType(ClientScope, String),
637 GetVoiceType,
638 ListVoiceTypes,
639 SetSynthesisVoice(ClientScope, String),
640 ListSynthesisVoices,
641 SetRate(ClientScope, i8),
642 GetRate,
643 SetPitch(ClientScope, i8),
644 GetPitch,
645 SetVolume(ClientScope, i8),
646 GetVolume,
647 SetPauseContext(ClientScope, u32),
648 SetNotification(NotificationType, bool),
649 Begin,
651 End,
652 SetHistory(ClientScope, bool),
654 HistoryGetClients,
655 HistoryGetClientId,
656 HistoryGetClientMsgs(ClientScope, u32, u32),
657 HistoryGetLastMsgId,
658 HistoryGetMsg(MessageId),
659 HistoryCursorGet,
660 HistoryCursorSet(ClientScope, HistoryPosition),
661 HistoryCursorMove(CursorDirection),
662 HistorySpeak(MessageId),
663 HistorySort(SortDirection, SortKey),
664 HistorySetShortMsgLength(u32),
665 HistorySetMsgTypeOrdering(Vec<Ordering>),
666 HistorySearch(ClientScope, String),
667 Quit,
669}
670
671#[derive(Debug, Clone, Hash, PartialEq, Eq)]
672pub enum Response {
674 LanguageSet, PrioritySet, RateSet, PitchSet, PunctuationSet, CapLetRecognSet, SpellingSet, ClientNameSet, VoiceSet, Stopped, Paused, Resumed, Canceled, TableSet, OutputModuleSet, PauseContextSet, VolumeSet, SsmlModeSet, NotificationSet, PitchRangeSet, DebugSet, HistoryCurSetFirst, HistoryCurSetLast, HistoryCurSetPos, HistoryCurMoveFor, HistoryCurMoveBack, MessageQueued, SoundIconQueued, MessageCanceled, ReceivingData, Bye, HistoryClientListSent(Vec<HistoryClientStatus>), HistoryMsgsListSent(Vec<String>), HistoryLastMsg(String), HistoryCurPosRet(String), TableListSent(Vec<String>), HistoryClientIdSent(ClientId), MessageTextSent, HelpSent(Vec<String>), VoicesListSent(Vec<SynthesisVoice>), OutputModulesListSent(Vec<String>), Get(String), InsideBlock, OutsideBlock, NotImplemented, EventIndexMark(EventId, String), EventBegin(EventId), EventEnd(EventId), EventCanceled(EventId), EventPaused(EventId), EventResumed(EventId), }
726
727#[cfg(test)]
728mod tests {
729
730 use std::io;
731 use std::str::FromStr;
732
733 use super::{ClientError, HistoryClientStatus, HistoryPosition, MessageScope, SynthesisVoice};
734
735 #[test]
736 fn parse_synthesis_voice() {
737 let v1 =
739 SynthesisVoice::from_str("Portuguese (Portugal)+Kaukovalta\tpt\tKaukovalta").unwrap();
740 assert_eq!("Portuguese (Portugal)+Kaukovalta", v1.name);
741 assert_eq!("pt", v1.language.unwrap());
742 assert_eq!("Kaukovalta", v1.dialect.unwrap());
743
744 let v2 = SynthesisVoice::from_str("Esperanto\teo\tnone").unwrap();
746 assert_eq!("Esperanto", v2.name);
747 assert_eq!("eo", v2.language.unwrap());
748 assert!(v2.dialect.is_none());
749 }
750
751 #[test]
752 fn format_message_scope() {
753 assert_eq!("self", format!("{}", MessageScope::Last).as_str());
754 assert_eq!("all", format!("{}", MessageScope::All).as_str());
755 assert_eq!("123", format!("{}", MessageScope::Message(123)).as_str());
756 }
757
758 #[test]
759 fn format_history_position() {
760 assert_eq!("first", format!("{}", HistoryPosition::First).as_str());
761 assert_eq!("last", format!("{}", HistoryPosition::Last).as_str());
762 assert_eq!("pos 15", format!("{}", HistoryPosition::Pos(15)).as_str());
763 }
764
765 #[test]
766 fn parse_history_client_status() {
767 assert_eq!(
768 HistoryClientStatus::new(10, "joe:speechd_client:main", false),
769 HistoryClientStatus::from_str("10 joe:speechd_client:main 0").unwrap()
770 );
771 assert_eq!(
772 HistoryClientStatus::new(11, "joe:speechd_client:main", true),
773 HistoryClientStatus::from_str("11 joe:speechd_client:main 1").unwrap()
774 );
775 for line in &[
776 "9 joe:speechd_client:main xxx",
777 "xxx joe:speechd_client:main 1",
778 ] {
779 match HistoryClientStatus::from_str(line) {
780 Ok(_) => panic!("parsing should have failed"),
781 Err(ClientError::Io(err)) if err.kind() == io::ErrorKind::InvalidData => (),
782 Err(_) => panic!("expecting error 'invalid data' parsing \"{}\"", line),
783 }
784 }
785 for line in &["8 joe:speechd_client:main", "8", ""] {
786 match HistoryClientStatus::from_str(line) {
787 Ok(_) => panic!("parsing should have failed"),
788 Err(ClientError::Io(err)) if err.kind() == io::ErrorKind::UnexpectedEof => (),
789 Err(_) => panic!("expecting error 'unexpected EOF' parsing \"{}\"", line),
790 }
791 }
792 }
793}