From de426553896801c394ea4e45e480d0b2d206a35a Mon Sep 17 00:00:00 2001 From: dmackdev Date: Mon, 4 Nov 2024 21:02:56 +0000 Subject: [PATCH 01/23] add speaker notes mode cli option add speaker note CommentCommand push text when processing speaker note comment --- examples/code.md | 26 +++++++++++++++----------- src/custom.rs | 3 ++- src/lib.rs | 2 +- src/main.rs | 11 ++++++++--- src/presenter.rs | 23 +++++++++++++++++++++-- src/processing/builder.rs | 39 +++++++++++++++++++++++++++++---------- 6 files changed, 76 insertions(+), 28 deletions(-) diff --git a/examples/code.md b/examples/code.md index 45101e9..3cb9e15 100644 --- a/examples/code.md +++ b/examples/code.md @@ -6,14 +6,13 @@ theme: background: false --- -Code styling -=== +# Code styling This presentation shows how to: -* Left-align code blocks. -* Have code blocks without background. -* Execute code snippets. +- Left-align code blocks. +- Have code blocks without background. +- Execute code snippets. ```rust pub struct Greeter { @@ -35,10 +34,11 @@ fn main() { } ``` + + -Column layouts -=== +# Column layouts The same code as the one before but split into two columns to split the API definition with its usage: @@ -76,10 +76,11 @@ fn main() { } ``` + + -Snippet execution -=== +# Snippet execution Run code snippets from the presentation and display their output dynamically. @@ -90,10 +91,11 @@ for i in range(0, 5): time.sleep(0.5) ``` + + -Snippet execution - `stderr` -=== +# Snippet execution - `stderr` Output from `stderr` will also be shown as output. @@ -106,3 +108,5 @@ echo "This is a successful command again" sleep 0.5 man # Missing argument ``` + + diff --git a/src/custom.rs b/src/custom.rs index 7d2fffc..e79f280 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -1,8 +1,8 @@ use crate::{ - GraphicsMode, input::user::KeyBinding, media::{emulator::TerminalEmulator, kitty::KittyMode}, processing::code::SnippetLanguage, + GraphicsMode, SpeakerNotesMode, }; use clap::ValueEnum; use schemars::JsonSchema; @@ -123,6 +123,7 @@ pub struct OptionsConfig { /// Whether to be strict about parsing the presentation's front matter. pub strict_front_matter_parsing: Option, + pub speaker_notes_mode: Option, } #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] diff --git a/src/lib.rs b/src/lib.rs index 8d7a7c8..af9ea99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ pub use crate::{ input::source::CommandSource, markdown::parse::MarkdownParser, media::{graphics::GraphicsMode, printer::ImagePrinter, register::ImageRegistry}, - presenter::{PresentMode, Presenter, PresenterOptions}, + presenter::{PresentMode, Presenter, PresenterOptions, SpeakerNotesMode}, processing::builder::{PresentationBuilderOptions, Themes}, render::highlighting::{CodeHighlighter, HighlightThemeSet}, resource::Resources, diff --git a/src/main.rs b/src/main.rs index d0aff1d..67e941d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ -use clap::{CommandFactory, Parser, error::ErrorKind}; +use clap::{error::ErrorKind, CommandFactory, Parser}; use comrak::Arena; use directories::ProjectDirs; use presenterm::{ CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, ImageRegistry, MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet, Presenter, - PresenterOptions, Resources, SnippetExecutor, Themes, ThemesDemo, ThirdPartyConfigs, ThirdPartyRender, - ValidateOverflows, + PresenterOptions, Resources, SnippetExecutor, SpeakerNotesMode, Themes, ThemesDemo, ThirdPartyConfigs, + ThirdPartyRender, ValidateOverflows, }; use std::{ env::{self, current_dir}, @@ -77,6 +77,9 @@ struct Cli { /// The path to the configuration file. #[clap(short, long)] config_file: Option, + + #[clap(short, long)] + speaker_notes_mode: Option, } fn create_splash() -> String { @@ -151,6 +154,7 @@ fn make_builder_options(config: &Config, mode: &PresentMode, force_default_theme strict_front_matter_parsing: config.options.strict_front_matter_parsing.unwrap_or(true), enable_snippet_execution: config.snippet.exec.enable, enable_snippet_execution_replace: config.snippet.exec_replace.enable, + speaker_notes_mode: config.options.speaker_notes_mode.clone(), } } @@ -272,6 +276,7 @@ fn run(mut cli: Cli) -> Result<(), Box> { } else { let commands = CommandSource::new(config.bindings.clone())?; options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. }); + options.speaker_notes_mode = cli.speaker_notes_mode; let options = PresenterOptions { builder_options: options, diff --git a/src/presenter.rs b/src/presenter.rs index f7eef11..51d20d1 100644 --- a/src/presenter.rs +++ b/src/presenter.rs @@ -1,3 +1,7 @@ +use clap::ValueEnum; +use schemars::JsonSchema; +use serde::Deserialize; + use crate::{ custom::KeyBindingsConfig, diff::PresentationDiffer, @@ -37,6 +41,13 @@ pub struct PresenterOptions { pub validate_overflows: bool, } +#[derive(Clone, Debug, Deserialize, ValueEnum, JsonSchema)] +#[serde(rename_all = "kebab-case")] +pub enum SpeakerNotesMode { + Publisher, + Receiver, +} + /// A slideshow presenter. /// /// This type puts everything else together. @@ -194,7 +205,11 @@ impl<'a> Presenter<'a> { }; // If the screen is too small, simply ignore this. Eventually the user will resize the // screen. - if matches!(result, Err(RenderError::TerminalTooSmall)) { Ok(()) } else { result } + if matches!(result, Err(RenderError::TerminalTooSmall)) { + Ok(()) + } else { + result + } } fn apply_command(&mut self, command: Command) -> CommandSideEffect { @@ -264,7 +279,11 @@ impl<'a> Presenter<'a> { panic!("unreachable commands") } }; - if needs_redraw { CommandSideEffect::Redraw } else { CommandSideEffect::None } + if needs_redraw { + CommandSideEffect::Redraw + } else { + CommandSideEffect::None + } } fn try_reload(&mut self, path: &Path, force: bool) { diff --git a/src/processing/builder.rs b/src/processing/builder.rs index 9449445..a037372 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -37,6 +37,7 @@ use crate::{ PresentationThemeSet, }, third_party::{ThirdPartyRender, ThirdPartyRenderError, ThirdPartyRenderRequest}, + SpeakerNotesMode, }; use image::DynamicImage; use serde::Deserialize; @@ -66,6 +67,7 @@ pub struct PresentationBuilderOptions { pub strict_front_matter_parsing: bool, pub enable_snippet_execution: bool, pub enable_snippet_execution_replace: bool, + pub speaker_notes_mode: Option, } impl PresentationBuilderOptions { @@ -98,6 +100,7 @@ impl Default for PresentationBuilderOptions { strict_front_matter_parsing: true, enable_snippet_execution: false, enable_snippet_execution_replace: false, + speaker_notes_mode: None, } } } @@ -237,7 +240,11 @@ impl<'a> PresentationBuilder<'a> { } self.slide_state.needs_enter_column = false; let last_valid = matches!(last, RenderOperation::EnterColumn { .. } | RenderOperation::ExitLayout); - if last_valid { Ok(()) } else { Err(BuildError::NotInsideColumn) } + if last_valid { + Ok(()) + } else { + Err(BuildError::NotInsideColumn) + } } fn push_slide_prelude(&mut self) { @@ -254,6 +261,12 @@ impl<'a> PresentationBuilder<'a> { } fn process_element(&mut self, element: MarkdownElement) -> Result<(), BuildError> { + if matches!(self.options.speaker_notes_mode, Some(SpeakerNotesMode::Receiver)) { + if let MarkdownElement::Comment { comment, source_position } = element { + self.process_comment(comment, source_position)? + } + return Ok(()); + } let should_clear_last = !matches!(element, MarkdownElement::List(_) | MarkdownElement::Comment { .. }); match element { // This one is processed before everything else as it affects how the rest of the @@ -458,6 +471,12 @@ impl<'a> PresentationBuilder<'a> { CommentCommand::NoFooter => { self.slide_state.ignore_footer = true; } + CommentCommand::SpeakerNote(note) => { + if let Some(SpeakerNotesMode::Receiver) = self.options.speaker_notes_mode { + self.push_text(note.into(), ElementType::Paragraph); + self.push_line_break(); + } + } }; // Don't push line breaks for any comments. self.slide_state.ignore_element_line_break = true; @@ -1145,6 +1164,7 @@ enum CommentCommand { JumpToMiddle, IncrementalLists(bool), NoFooter, + SpeakerNote(String), } impl FromStr for CommentCommand { @@ -1561,11 +1581,10 @@ mod test { #[test] fn iterate_list_starting_from_other() { let list = ListIterator::new( - vec![ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered }, ListItem { - depth: 0, - contents: "1".into(), - item_type: ListItemType::Unordered, - }], + vec![ + ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered }, + ListItem { depth: 0, contents: "1".into(), item_type: ListItemType::Unordered }, + ], 3, ); let expected_indexes = [3, 4]; @@ -1652,10 +1671,10 @@ mod test { #[test] fn implicit_slide_ends_with_front_matter() { - let elements = - vec![MarkdownElement::FrontMatter("theme:\n name: light".into()), MarkdownElement::SetexHeading { - text: "hi".into(), - }]; + let elements = vec![ + MarkdownElement::FrontMatter("theme:\n name: light".into()), + MarkdownElement::SetexHeading { text: "hi".into() }, + ]; let options = PresentationBuilderOptions { implicit_slide_ends: true, ..Default::default() }; let slides = build_presentation_with_options(elements, options).into_slides(); assert_eq!(slides.len(), 1); From fd6d57bf524ff7efe3fc3f6f0294cb541bed844e Mon Sep 17 00:00:00 2001 From: dmackdev Date: Tue, 5 Nov 2024 09:18:21 +0000 Subject: [PATCH 02/23] use ipc to navigate to specfic slides in speaker notes presentation when the "publisher" presentation changes slides --- Cargo.lock | 484 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/presentation.rs | 32 +++ src/presenter.rs | 5 + src/processing/builder.rs | 25 +- 5 files changed, 542 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58bdeef..99265f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -112,6 +118,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -170,6 +199,25 @@ dependencies = [ "shlex", ] +[[package]] +name = "cdr" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9617422bf43fde9280707a7e90f8f7494389c182f5c70b0f67592d0f06d41dfa" +dependencies = [ + "byteorder", + "serde", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -189,6 +237,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.20" @@ -217,7 +276,7 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn", @@ -464,6 +523,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +[[package]] +name = "enum-iterator" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c280b9e6b3ae19e152d8e31cf47f18389781e119d4013a2a2bb0180e5facc635" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -569,6 +648,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -587,6 +672,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -610,6 +704,206 @@ dependencies = [ "cc", ] +[[package]] +name = "iceoryx2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ff9bacac005d82f36638bc765d61658e424a21c5cb84d66ead7bd61cc7b373" +dependencies = [ + "cdr", + "iceoryx2-bb-container", + "iceoryx2-bb-derive-macros", + "iceoryx2-bb-elementary", + "iceoryx2-bb-lock-free", + "iceoryx2-bb-log", + "iceoryx2-bb-memory", + "iceoryx2-bb-posix", + "iceoryx2-bb-system-types", + "iceoryx2-cal", + "iceoryx2-pal-concurrency-sync", + "lazy_static", + "serde", + "sha1_smol", + "tiny-fn", + "toml", +] + +[[package]] +name = "iceoryx2-bb-container" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05da563f8303d069990b1a0c351f5431309c20fff0e52dbaf852cdaa6fc809d" +dependencies = [ + "iceoryx2-bb-derive-macros", + "iceoryx2-bb-elementary", + "iceoryx2-bb-log", + "iceoryx2-pal-concurrency-sync", + "serde", +] + +[[package]] +name = "iceoryx2-bb-derive-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efe33eee7e821e439fb5aa9bea95f9d45b90bf9554691908b46570a240c3d798" +dependencies = [ + "iceoryx2-bb-elementary", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "iceoryx2-bb-elementary" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee584a19f03c741f26851fe365fcb034ae745992e4604e2ad0d636a68422fb5" +dependencies = [ + "iceoryx2-pal-concurrency-sync", +] + +[[package]] +name = "iceoryx2-bb-lock-free" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38f033bd95a8e915cc2e2c7a0a834d2eb07ffaa4aef8f3a32792a43c86a5ffc" +dependencies = [ + "iceoryx2-bb-elementary", + "iceoryx2-bb-log", + "iceoryx2-pal-concurrency-sync", + "tiny-fn", +] + +[[package]] +name = "iceoryx2-bb-log" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f7d8442ac92955b5055d4588218631b398ac94818452baf849857d7665959e6" +dependencies = [ + "iceoryx2-pal-concurrency-sync", + "termsize", +] + +[[package]] +name = "iceoryx2-bb-memory" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e07b76d3cfec24ba09237165d846418eabc0b5cc5be8840623089bcb236335" +dependencies = [ + "iceoryx2-bb-elementary", + "iceoryx2-bb-lock-free", + "iceoryx2-bb-log", + "iceoryx2-bb-posix", + "iceoryx2-pal-concurrency-sync", + "lazy_static", + "tiny-fn", +] + +[[package]] +name = "iceoryx2-bb-posix" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581ce58d22bd7ec58fa7f13056b454af33bdfbfecde97c21f013e36317683363" +dependencies = [ + "bitflags 2.6.0", + "enum-iterator", + "iceoryx2-bb-container", + "iceoryx2-bb-elementary", + "iceoryx2-bb-log", + "iceoryx2-bb-system-types", + "iceoryx2-pal-concurrency-sync", + "iceoryx2-pal-configuration", + "iceoryx2-pal-posix", + "lazy_static", + "serde", + "tiny-fn", +] + +[[package]] +name = "iceoryx2-bb-system-types" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b8556076f9c96198465ab95d08e1aae2a54063a110b938651a465a2e075051" +dependencies = [ + "iceoryx2-bb-container", + "iceoryx2-bb-elementary", + "iceoryx2-bb-log", + "iceoryx2-pal-configuration", + "serde", +] + +[[package]] +name = "iceoryx2-bb-testing" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257f890596e5bf7b7f44a281809d294bae430e4eed95620fc69b5770a40b2101" +dependencies = [ + "iceoryx2-pal-configuration", +] + +[[package]] +name = "iceoryx2-bb-threadsafe" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6bdcfaa19bbcdb6ff556b836a9d73bb65563315e4c9cb6e865227e2b12bfd1" +dependencies = [ + "iceoryx2-bb-container", + "iceoryx2-bb-log", + "iceoryx2-bb-posix", +] + +[[package]] +name = "iceoryx2-cal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1aee45b9aee135b9b4a9923613754f1ae30acb44ed4ac9cf85fb3dc269651a7" +dependencies = [ + "cdr", + "iceoryx2-bb-container", + "iceoryx2-bb-elementary", + "iceoryx2-bb-lock-free", + "iceoryx2-bb-log", + "iceoryx2-bb-memory", + "iceoryx2-bb-posix", + "iceoryx2-bb-system-types", + "iceoryx2-bb-testing", + "iceoryx2-bb-threadsafe", + "iceoryx2-pal-concurrency-sync", + "lazy_static", + "once_cell", + "ouroboros", + "serde", + "sha1_smol", + "tiny-fn", + "toml", +] + +[[package]] +name = "iceoryx2-pal-concurrency-sync" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e255bb5b9f5c60968735531ab5797b467aaaf3c5fd53b10ebfbb81d5e436395e" + +[[package]] +name = "iceoryx2-pal-configuration" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b61d9620508c53fc3fa31f821e5784d0c04abda7e46a2407b40a6a92f37ad0" + +[[package]] +name = "iceoryx2-pal-posix" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56672b845179f809997b396dc33267f5b759842e714edcc261d4adcd9d167c2a" +dependencies = [ + "bindgen", + "cc", + "iceoryx2-pal-concurrency-sync", + "iceoryx2-pal-configuration", + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -672,6 +966,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -702,12 +1005,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "libredox" version = "0.1.3" @@ -860,6 +1179,31 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "ouroboros" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" +dependencies = [ + "heck 0.4.1", + "itertools 0.12.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -944,8 +1288,9 @@ dependencies = [ "directories", "flate2", "hex", + "iceoryx2", "image", - "itertools", + "itertools 0.13.0", "libc", "merge-struct", "once_cell", @@ -966,6 +1311,16 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -975,6 +1330,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + [[package]] name = "quick-error" version = "2.0.1" @@ -1131,6 +1499,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1253,6 +1627,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_with" version = "3.11.0" @@ -1296,6 +1679,12 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "shlex" version = "1.3.0" @@ -1378,6 +1767,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -1399,7 +1794,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", @@ -1451,6 +1846,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "termsize" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f11ff5c25c172608d5b85e2fb43ee9a6d683a7f4ab7f96ae07b3d8b590368fd" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.65" @@ -1502,6 +1907,12 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-fn" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fde9a76dac5751480f711f327371c809d7f8a9f036436e6237d67859adbf3bd" + [[package]] name = "tinyvec" version = "1.8.0" @@ -1517,6 +1928,40 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -1568,6 +2013,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -1645,6 +2096,18 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1833,6 +2296,21 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 58fbe9e..2e9df12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ thiserror = "1" unicode-width = "0.2" os_pipe = "1.1.5" libc = "0.2.155" +iceoryx2 = "0.4.1" [dependencies.syntect] version = "5.2" diff --git a/src/presentation.rs b/src/presentation.rs index 27cdbc7..c2860a6 100644 --- a/src/presentation.rs +++ b/src/presentation.rs @@ -6,6 +6,11 @@ use crate::{ style::{Color, Colors}, theme::{Alignment, Margin, PresentationTheme}, }; +use iceoryx2::{ + port::{listener::Listener, notifier::Notifier}, + prelude::EventId, + service::ipc::Service, +}; use serde::Deserialize; use std::{ cell::RefCell, @@ -30,6 +35,12 @@ pub(crate) struct Presentation { pub(crate) state: PresentationState, } +#[derive(Debug)] +pub enum SpeakerNoteChannel { + Notifier(Notifier), + Listener(Listener), +} + impl Presentation { /// Construct a new presentation. pub(crate) fn new(slides: Vec, modals: Modals, state: PresentationState) -> Self { @@ -253,6 +264,10 @@ impl Presentation { false } } + + pub(crate) fn listen_for_speaker_note_evt(&self) -> Option { + self.state.listen_for_speaker_note_evt() + } } impl From> for Presentation { @@ -274,6 +289,7 @@ pub(crate) type AsyncPresentationErrorHolder = Arc, } #[derive(Clone, Debug, Default)] @@ -292,6 +308,22 @@ impl PresentationState { fn set_current_slide_index(&self, value: usize) { self.inner.deref().borrow_mut().current_slide_index = value; + if let Some(SpeakerNoteChannel::Notifier(notifier)) = &self.inner.deref().borrow().channel { + notifier.notify_with_custom_event_id(EventId::new(value)).unwrap(); + } + } + + pub(crate) fn set_channel(&self, speaker_note_channel: SpeakerNoteChannel) { + self.inner.deref().borrow_mut().channel = Some(speaker_note_channel); + } + + fn listen_for_speaker_note_evt(&self) -> Option { + if let Some(SpeakerNoteChannel::Listener(listener)) = &self.inner.deref().borrow().channel { + if let Some(evt) = listener.try_wait_one().unwrap() { + return Some(evt.as_value() + 1); + } + } + None } } diff --git a/src/presenter.rs b/src/presenter.rs index 51d20d1..5e74ac0 100644 --- a/src/presenter.rs +++ b/src/presenter.rs @@ -115,6 +115,11 @@ impl<'a> Presenter<'a> { self.render(&mut drawer)?; loop { + if let Some(idx) = self.state.presentation().listen_for_speaker_note_evt() { + self.apply_command(Command::GoToSlide(idx as u32)); + break; + } + if self.poll_async_renders()? { self.render(&mut drawer)?; } diff --git a/src/processing/builder.rs b/src/processing/builder.rs index a037372..5ddc25d 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -17,7 +17,7 @@ use crate::{ presentation::{ AsRenderOperations, BlockLine, ChunkMutator, ImageProperties, ImageSize, MarginProperties, Modals, Presentation, PresentationMetadata, PresentationState, PresentationThemeMetadata, RenderAsync, RenderOperation, - Slide, SlideBuilder, SlideChunk, + Slide, SlideBuilder, SlideChunk, SpeakerNoteChannel, }, processing::{ code::{CodePreparer, HighlightContext, HighlightMutator, HighlightedLine}, @@ -39,6 +39,7 @@ use crate::{ third_party::{ThirdPartyRender, ThirdPartyRenderError, ThirdPartyRenderRequest}, SpeakerNotesMode, }; +use iceoryx2::{node::NodeBuilder, prelude::ServiceName, service::ipc}; use image::DynamicImage; use serde::Deserialize; use std::{borrow::Cow, cell::RefCell, fmt::Display, iter, mem, path::PathBuf, rc::Rc, str::FromStr}; @@ -142,6 +143,26 @@ impl<'a> PresentationBuilder<'a> { bindings_config: KeyBindingsConfig, options: PresentationBuilderOptions, ) -> Self { + let presentation_state = PresentationState::default(); + + if let Some(mode) = &options.speaker_notes_mode { + let node = NodeBuilder::new().create::().unwrap(); + let service_name: ServiceName = "SpeakerNoteEventService".try_into().unwrap(); + let speaker_note_channel = match mode { + SpeakerNotesMode::Publisher => { + let event = node.service_builder(&service_name).event().open_or_create().unwrap(); + let notifier = event.notifier_builder().create().unwrap(); + SpeakerNoteChannel::Notifier(notifier) + } + SpeakerNotesMode::Receiver => { + let event = node.service_builder(&service_name).event().open_or_create().unwrap(); + let listener = event.listener_builder().create().unwrap(); + SpeakerNoteChannel::Listener(listener) + } + }; + + presentation_state.set_channel(speaker_note_channel); + } Self { slide_chunks: Vec::new(), chunk_operations: Vec::new(), @@ -153,7 +174,7 @@ impl<'a> PresentationBuilder<'a> { resources, third_party, slide_state: Default::default(), - presentation_state: Default::default(), + presentation_state, footer_context: Default::default(), themes, index_builder: Default::default(), From 3151fbdb7c24a96f697b7a1e626b1126b0dc6258 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Tue, 5 Nov 2024 09:31:17 +0000 Subject: [PATCH 03/23] process only speaker notes and end slide comments when in speaker note receiver mode --- src/processing/builder.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/processing/builder.rs b/src/processing/builder.rs index 5ddc25d..f24a722 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -452,6 +452,18 @@ impl<'a> PresentationBuilder<'a> { Ok(comment) => comment, Err(error) => return Err(BuildError::CommandParse { line: source_position.start.line + 1, error }), }; + + if let Some(SpeakerNotesMode::Receiver) = self.options.speaker_notes_mode { + match comment { + CommentCommand::SpeakerNote(note) => { + self.push_text(note.into(), ElementType::Paragraph); + } + CommentCommand::EndSlide => self.terminate_slide(), + _ => {} + } + return Ok(()); + } + match comment { CommentCommand::Pause => self.process_pause(), CommentCommand::EndSlide => self.terminate_slide(), @@ -492,12 +504,7 @@ impl<'a> PresentationBuilder<'a> { CommentCommand::NoFooter => { self.slide_state.ignore_footer = true; } - CommentCommand::SpeakerNote(note) => { - if let Some(SpeakerNotesMode::Receiver) = self.options.speaker_notes_mode { - self.push_text(note.into(), ElementType::Paragraph); - self.push_line_break(); - } - } + CommentCommand::SpeakerNote(_) => {} }; // Don't push line breaks for any comments. self.slide_state.ignore_element_line_break = true; From cbadf07bc0f9cf786eff79115fdac970402fdd7c Mon Sep 17 00:00:00 2001 From: dmackdev Date: Wed, 6 Nov 2024 21:39:39 +0000 Subject: [PATCH 04/23] fix layout on speaker note slides --- src/processing/builder.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/processing/builder.rs b/src/processing/builder.rs index f24a722..06f029c 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -286,6 +286,7 @@ impl<'a> PresentationBuilder<'a> { if let MarkdownElement::Comment { comment, source_position } = element { self.process_comment(comment, source_position)? } + self.slide_state.ignore_element_line_break = true; return Ok(()); } let should_clear_last = !matches!(element, MarkdownElement::List(_) | MarkdownElement::Comment { .. }); @@ -457,6 +458,7 @@ impl<'a> PresentationBuilder<'a> { match comment { CommentCommand::SpeakerNote(note) => { self.push_text(note.into(), ElementType::Paragraph); + self.push_line_break(); } CommentCommand::EndSlide => self.terminate_slide(), _ => {} From 93a0dcba3ec54c5d79e6f7ae21918bb8ffc5f98b Mon Sep 17 00:00:00 2001 From: dmackdev Date: Fri, 8 Nov 2024 17:18:21 +0000 Subject: [PATCH 05/23] move `SpeakerNotesChannel` to `Presenter` from `PresentationStateInner` always notify channel of current slide index after command is applied pass bool flag to builder for rendering speaker notes only --- src/custom.rs | 3 +-- src/lib.rs | 2 +- src/main.rs | 50 ++++++++++++++++++++++++++++++++++----- src/presentation.rs | 32 ------------------------- src/presenter.rs | 32 ++++++++++++++++--------- src/processing/builder.rs | 34 +++++--------------------- 6 files changed, 73 insertions(+), 80 deletions(-) diff --git a/src/custom.rs b/src/custom.rs index e79f280..ffae32d 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -2,7 +2,7 @@ use crate::{ input::user::KeyBinding, media::{emulator::TerminalEmulator, kitty::KittyMode}, processing::code::SnippetLanguage, - GraphicsMode, SpeakerNotesMode, + GraphicsMode, }; use clap::ValueEnum; use schemars::JsonSchema; @@ -123,7 +123,6 @@ pub struct OptionsConfig { /// Whether to be strict about parsing the presentation's front matter. pub strict_front_matter_parsing: Option, - pub speaker_notes_mode: Option, } #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] diff --git a/src/lib.rs b/src/lib.rs index af9ea99..02d81f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ pub use crate::{ input::source::CommandSource, markdown::parse::MarkdownParser, media::{graphics::GraphicsMode, printer::ImagePrinter, register::ImageRegistry}, - presenter::{PresentMode, Presenter, PresenterOptions, SpeakerNotesMode}, + presenter::{PresentMode, Presenter, PresenterOptions, SpeakerNoteChannel}, processing::builder::{PresentationBuilderOptions, Themes}, render::highlighting::{CodeHighlighter, HighlightThemeSet}, resource::Resources, diff --git a/src/main.rs b/src/main.rs index 67e941d..776017b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,15 @@ -use clap::{error::ErrorKind, CommandFactory, Parser}; +use clap::{error::ErrorKind, CommandFactory, Parser, ValueEnum}; use comrak::Arena; use directories::ProjectDirs; +use iceoryx2::{node::NodeBuilder, prelude::ServiceName, service::ipc}; use presenterm::{ CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, ImageRegistry, MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet, Presenter, - PresenterOptions, Resources, SnippetExecutor, SpeakerNotesMode, Themes, ThemesDemo, ThirdPartyConfigs, + PresenterOptions, Resources, SnippetExecutor, SpeakerNoteChannel, Themes, ThemesDemo, ThirdPartyConfigs, ThirdPartyRender, ValidateOverflows, }; +use schemars::JsonSchema; +use serde::Deserialize; use std::{ env::{self, current_dir}, io, @@ -17,6 +20,13 @@ use std::{ const DEFAULT_THEME: &str = "dark"; +#[derive(Clone, Copy, Debug, Deserialize, ValueEnum, JsonSchema)] +#[serde(rename_all = "kebab-case")] +pub enum SpeakerNotesMode { + Publisher, + Receiver, +} + /// Run slideshows from your terminal. #[derive(Parser)] #[command()] @@ -141,7 +151,12 @@ fn display_acknowledgements() { println!("{}", String::from_utf8_lossy(acknowledgements)); } -fn make_builder_options(config: &Config, mode: &PresentMode, force_default_theme: bool) -> PresentationBuilderOptions { +fn make_builder_options( + config: &Config, + mode: &PresentMode, + force_default_theme: bool, + speaker_notes_mode: Option, +) -> PresentationBuilderOptions { PresentationBuilderOptions { allow_mutations: !matches!(mode, PresentMode::Export), implicit_slide_ends: config.options.implicit_slide_ends.unwrap_or_default(), @@ -154,7 +169,7 @@ fn make_builder_options(config: &Config, mode: &PresentMode, force_default_theme strict_front_matter_parsing: config.options.strict_front_matter_parsing.unwrap_or(true), enable_snippet_execution: config.snippet.exec.enable, enable_snippet_execution_replace: config.snippet.exec_replace.enable, - speaker_notes_mode: config.options.speaker_notes_mode.clone(), + render_speaker_notes_only: speaker_notes_mode.is_some_and(|mode| matches!(mode, SpeakerNotesMode::Receiver)), } } @@ -193,6 +208,27 @@ fn overflow_validation(mode: &PresentMode, config: &ValidateOverflows) -> bool { } } +fn create_speaker_notes_channel( + speaker_notes_mode: SpeakerNotesMode, +) -> Result> { + let node = NodeBuilder::new().create::()?; + // TODO: Use a service name that incorporates presenterm and/or the presentation filename/title? + let service_name: ServiceName = "SpeakerNoteEventService".try_into()?; + let speaker_note_channel = match speaker_notes_mode { + SpeakerNotesMode::Publisher => { + let event = node.service_builder(&service_name).event().open_or_create()?; + let notifier = event.notifier_builder().create()?; + SpeakerNoteChannel::Notifier(notifier) + } + SpeakerNotesMode::Receiver => { + let event = node.service_builder(&service_name).event().open_or_create()?; + let listener = event.listener_builder().create()?; + SpeakerNoteChannel::Listener(listener) + } + }; + Ok(speaker_note_channel) +} + fn run(mut cli: Cli) -> Result<(), Box> { if cli.generate_config_file_schema { let schema = schemars::schema_for!(Config); @@ -233,7 +269,7 @@ fn run(mut cli: Cli) -> Result<(), Box> { let parser = MarkdownParser::new(&arena); let validate_overflows = overflow_validation(&mode, &config.defaults.validate_overflows) || cli.validate_overflows; - let mut options = make_builder_options(&config, &mode, force_default_theme); + let mut options = make_builder_options(&config, &mode, force_default_theme, cli.speaker_notes_mode); if cli.enable_snippet_execution { options.enable_snippet_execution = true; } @@ -276,7 +312,8 @@ fn run(mut cli: Cli) -> Result<(), Box> { } else { let commands = CommandSource::new(config.bindings.clone())?; options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. }); - options.speaker_notes_mode = cli.speaker_notes_mode; + + let speaker_notes_channel = cli.speaker_notes_mode.map(create_speaker_notes_channel).transpose()?; let options = PresenterOptions { builder_options: options, @@ -295,6 +332,7 @@ fn run(mut cli: Cli) -> Result<(), Box> { themes, printer, options, + speaker_notes_channel, ); presenter.present(&path)?; } diff --git a/src/presentation.rs b/src/presentation.rs index c2860a6..27cdbc7 100644 --- a/src/presentation.rs +++ b/src/presentation.rs @@ -6,11 +6,6 @@ use crate::{ style::{Color, Colors}, theme::{Alignment, Margin, PresentationTheme}, }; -use iceoryx2::{ - port::{listener::Listener, notifier::Notifier}, - prelude::EventId, - service::ipc::Service, -}; use serde::Deserialize; use std::{ cell::RefCell, @@ -35,12 +30,6 @@ pub(crate) struct Presentation { pub(crate) state: PresentationState, } -#[derive(Debug)] -pub enum SpeakerNoteChannel { - Notifier(Notifier), - Listener(Listener), -} - impl Presentation { /// Construct a new presentation. pub(crate) fn new(slides: Vec, modals: Modals, state: PresentationState) -> Self { @@ -264,10 +253,6 @@ impl Presentation { false } } - - pub(crate) fn listen_for_speaker_note_evt(&self) -> Option { - self.state.listen_for_speaker_note_evt() - } } impl From> for Presentation { @@ -289,7 +274,6 @@ pub(crate) type AsyncPresentationErrorHolder = Arc, } #[derive(Clone, Debug, Default)] @@ -308,22 +292,6 @@ impl PresentationState { fn set_current_slide_index(&self, value: usize) { self.inner.deref().borrow_mut().current_slide_index = value; - if let Some(SpeakerNoteChannel::Notifier(notifier)) = &self.inner.deref().borrow().channel { - notifier.notify_with_custom_event_id(EventId::new(value)).unwrap(); - } - } - - pub(crate) fn set_channel(&self, speaker_note_channel: SpeakerNoteChannel) { - self.inner.deref().borrow_mut().channel = Some(speaker_note_channel); - } - - fn listen_for_speaker_note_evt(&self) -> Option { - if let Some(SpeakerNoteChannel::Listener(listener)) = &self.inner.deref().borrow().channel { - if let Some(evt) = listener.try_wait_one().unwrap() { - return Some(evt.as_value() + 1); - } - } - None } } diff --git a/src/presenter.rs b/src/presenter.rs index 5e74ac0..6b6188d 100644 --- a/src/presenter.rs +++ b/src/presenter.rs @@ -1,6 +1,8 @@ -use clap::ValueEnum; -use schemars::JsonSchema; -use serde::Deserialize; +use iceoryx2::{ + port::{listener::Listener, notifier::Notifier}, + prelude::EventId, + service::ipc::Service, +}; use crate::{ custom::KeyBindingsConfig, @@ -41,11 +43,10 @@ pub struct PresenterOptions { pub validate_overflows: bool, } -#[derive(Clone, Debug, Deserialize, ValueEnum, JsonSchema)] -#[serde(rename_all = "kebab-case")] -pub enum SpeakerNotesMode { - Publisher, - Receiver, +#[derive(Debug)] +pub enum SpeakerNoteChannel { + Notifier(Notifier), + Listener(Listener), } /// A slideshow presenter. @@ -63,6 +64,7 @@ pub struct Presenter<'a> { image_printer: Arc, themes: Themes, options: PresenterOptions, + speaker_notes_channel: Option, } impl<'a> Presenter<'a> { @@ -78,6 +80,7 @@ impl<'a> Presenter<'a> { themes: Themes, image_printer: Arc, options: PresenterOptions, + speaker_notes_channel: Option, ) -> Self { Self { default_theme, @@ -91,6 +94,7 @@ impl<'a> Presenter<'a> { image_printer, themes, options, + speaker_notes_channel, } } @@ -115,9 +119,11 @@ impl<'a> Presenter<'a> { self.render(&mut drawer)?; loop { - if let Some(idx) = self.state.presentation().listen_for_speaker_note_evt() { - self.apply_command(Command::GoToSlide(idx as u32)); - break; + if let Some(SpeakerNoteChannel::Listener(listener)) = self.speaker_notes_channel.as_mut() { + if let Some(evt) = listener.try_wait_one().unwrap() { + self.apply_command(Command::GoToSlide(evt.as_value() as u32)); + break; + } } if self.poll_async_renders()? { @@ -152,6 +158,10 @@ impl<'a> Presenter<'a> { CommandSideEffect::None => (), }; } + if let Some(SpeakerNoteChannel::Notifier(notifier)) = self.speaker_notes_channel.as_mut() { + let current_slide_idx = self.state.presentation().current_slide_index(); + notifier.notify_with_custom_event_id(EventId::new(current_slide_idx + 1)).unwrap(); + } } } diff --git a/src/processing/builder.rs b/src/processing/builder.rs index 06f029c..c361ce3 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -17,7 +17,7 @@ use crate::{ presentation::{ AsRenderOperations, BlockLine, ChunkMutator, ImageProperties, ImageSize, MarginProperties, Modals, Presentation, PresentationMetadata, PresentationState, PresentationThemeMetadata, RenderAsync, RenderOperation, - Slide, SlideBuilder, SlideChunk, SpeakerNoteChannel, + Slide, SlideBuilder, SlideChunk, }, processing::{ code::{CodePreparer, HighlightContext, HighlightMutator, HighlightedLine}, @@ -37,9 +37,7 @@ use crate::{ PresentationThemeSet, }, third_party::{ThirdPartyRender, ThirdPartyRenderError, ThirdPartyRenderRequest}, - SpeakerNotesMode, }; -use iceoryx2::{node::NodeBuilder, prelude::ServiceName, service::ipc}; use image::DynamicImage; use serde::Deserialize; use std::{borrow::Cow, cell::RefCell, fmt::Display, iter, mem, path::PathBuf, rc::Rc, str::FromStr}; @@ -68,7 +66,7 @@ pub struct PresentationBuilderOptions { pub strict_front_matter_parsing: bool, pub enable_snippet_execution: bool, pub enable_snippet_execution_replace: bool, - pub speaker_notes_mode: Option, + pub render_speaker_notes_only: bool, } impl PresentationBuilderOptions { @@ -101,7 +99,7 @@ impl Default for PresentationBuilderOptions { strict_front_matter_parsing: true, enable_snippet_execution: false, enable_snippet_execution_replace: false, - speaker_notes_mode: None, + render_speaker_notes_only: false, } } } @@ -143,26 +141,6 @@ impl<'a> PresentationBuilder<'a> { bindings_config: KeyBindingsConfig, options: PresentationBuilderOptions, ) -> Self { - let presentation_state = PresentationState::default(); - - if let Some(mode) = &options.speaker_notes_mode { - let node = NodeBuilder::new().create::().unwrap(); - let service_name: ServiceName = "SpeakerNoteEventService".try_into().unwrap(); - let speaker_note_channel = match mode { - SpeakerNotesMode::Publisher => { - let event = node.service_builder(&service_name).event().open_or_create().unwrap(); - let notifier = event.notifier_builder().create().unwrap(); - SpeakerNoteChannel::Notifier(notifier) - } - SpeakerNotesMode::Receiver => { - let event = node.service_builder(&service_name).event().open_or_create().unwrap(); - let listener = event.listener_builder().create().unwrap(); - SpeakerNoteChannel::Listener(listener) - } - }; - - presentation_state.set_channel(speaker_note_channel); - } Self { slide_chunks: Vec::new(), chunk_operations: Vec::new(), @@ -174,7 +152,7 @@ impl<'a> PresentationBuilder<'a> { resources, third_party, slide_state: Default::default(), - presentation_state, + presentation_state: Default::default(), footer_context: Default::default(), themes, index_builder: Default::default(), @@ -282,7 +260,7 @@ impl<'a> PresentationBuilder<'a> { } fn process_element(&mut self, element: MarkdownElement) -> Result<(), BuildError> { - if matches!(self.options.speaker_notes_mode, Some(SpeakerNotesMode::Receiver)) { + if self.options.render_speaker_notes_only { if let MarkdownElement::Comment { comment, source_position } = element { self.process_comment(comment, source_position)? } @@ -454,7 +432,7 @@ impl<'a> PresentationBuilder<'a> { Err(error) => return Err(BuildError::CommandParse { line: source_position.start.line + 1, error }), }; - if let Some(SpeakerNotesMode::Receiver) = self.options.speaker_notes_mode { + if self.options.render_speaker_notes_only { match comment { CommentCommand::SpeakerNote(note) => { self.push_text(note.into(), ElementType::Paragraph); From 22d70f79404b9da84e04bfc607391ad8230f844e Mon Sep 17 00:00:00 2001 From: dmackdev Date: Fri, 8 Nov 2024 17:50:28 +0000 Subject: [PATCH 06/23] restore original examples/code.md --- examples/code.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/examples/code.md b/examples/code.md index 3cb9e15..45101e9 100644 --- a/examples/code.md +++ b/examples/code.md @@ -6,13 +6,14 @@ theme: background: false --- -# Code styling +Code styling +=== This presentation shows how to: -- Left-align code blocks. -- Have code blocks without background. -- Execute code snippets. +* Left-align code blocks. +* Have code blocks without background. +* Execute code snippets. ```rust pub struct Greeter { @@ -34,11 +35,10 @@ fn main() { } ``` - - -# Column layouts +Column layouts +=== The same code as the one before but split into two columns to split the API definition with its usage: @@ -76,11 +76,10 @@ fn main() { } ``` - - -# Snippet execution +Snippet execution +=== Run code snippets from the presentation and display their output dynamically. @@ -91,11 +90,10 @@ for i in range(0, 5): time.sleep(0.5) ``` - - -# Snippet execution - `stderr` +Snippet execution - `stderr` +=== Output from `stderr` will also be shown as output. @@ -108,5 +106,3 @@ echo "This is a successful command again" sleep 0.5 man # Missing argument ``` - - From 4c7726ef92e391b449e367c3bb5c4a801ce8b4a8 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Fri, 8 Nov 2024 17:50:45 +0000 Subject: [PATCH 07/23] add speaker notes example presentation --- examples/speaker-notes.md | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/speaker-notes.md diff --git a/examples/speaker-notes.md b/examples/speaker-notes.md new file mode 100644 index 0000000..f579558 --- /dev/null +++ b/examples/speaker-notes.md @@ -0,0 +1,42 @@ +Speaker Notes +=== + +`presenterm` supports speaker notes. + +You can use the following HTML comment throughout your presentation markdown file: + +```markdown + +``` + + + +And you can run a separate instance of `presenterm` to view them. + + + + + +Usage +=== +Run the following two commands in separate terminals. + + + +The `--speaker-notes-mode=publisher` argument will render your actual presentation as normal, without speaker notes: + +``` +presenterm --speaker-notes-mode=publisher examples/speaker-notes.md +``` + +The `--speaker-notes-mode=receiver` argument will render only the speaker notes for the current slide being shown in the actual presentation: + +``` +presenterm --speaker-notes-mode=receiver examples/speaker-notes.md +``` + + + +As you change slides in your actual presentation, the speaker notes presentation slide will automatically navigate to the correct slide. + + From df051d398097157060e3a968652494ac3e55511a Mon Sep 17 00:00:00 2001 From: dmackdev Date: Fri, 8 Nov 2024 18:24:51 +0000 Subject: [PATCH 08/23] show titles in speaker notes mode support implicit slide ends in speaker notes mode --- src/processing/builder.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/processing/builder.rs b/src/processing/builder.rs index c361ce3..9fc6aa2 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -261,9 +261,15 @@ impl<'a> PresentationBuilder<'a> { fn process_element(&mut self, element: MarkdownElement) -> Result<(), BuildError> { if self.options.render_speaker_notes_only { - if let MarkdownElement::Comment { comment, source_position } = element { - self.process_comment(comment, source_position)? + match element { + MarkdownElement::Comment { comment, source_position } => { + self.process_comment(comment, source_position)? + } + MarkdownElement::SetexHeading { text } => self.push_slide_title(text), + _ => {} } + // Allows us to start the next speaker slide when a title is pushed and implicit_slide_ends is enabled. + self.slide_state.last_element = LastElement::Other; self.slide_state.ignore_element_line_break = true; return Ok(()); } From 81cfbbcc2e22acf0524ae17e119486bdc50a0441 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Fri, 8 Nov 2024 19:22:38 +0000 Subject: [PATCH 09/23] remove ipc event service duplication --- src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 776017b..13e0c9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -214,14 +214,13 @@ fn create_speaker_notes_channel( let node = NodeBuilder::new().create::()?; // TODO: Use a service name that incorporates presenterm and/or the presentation filename/title? let service_name: ServiceName = "SpeakerNoteEventService".try_into()?; + let event = node.service_builder(&service_name).event().open_or_create()?; let speaker_note_channel = match speaker_notes_mode { SpeakerNotesMode::Publisher => { - let event = node.service_builder(&service_name).event().open_or_create()?; let notifier = event.notifier_builder().create()?; SpeakerNoteChannel::Notifier(notifier) } SpeakerNotesMode::Receiver => { - let event = node.service_builder(&service_name).event().open_or_create()?; let listener = event.listener_builder().create()?; SpeakerNoteChannel::Listener(listener) } From 4416af81c4c25033f696857e02c0b4256d3b5455 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Mon, 11 Nov 2024 18:04:38 +0000 Subject: [PATCH 10/23] remove SpeakerNoteChannel enum store IPC publisher in Presenter store IPC receiver in CommandSource --- src/input/source.rs | 15 +++++++++++++-- src/lib.rs | 2 +- src/main.rs | 44 +++++++++++++++++++++----------------------- src/presenter.rs | 29 ++++++----------------------- 4 files changed, 41 insertions(+), 49 deletions(-) diff --git a/src/input/source.rs b/src/input/source.rs index 19933ed..78003d2 100644 --- a/src/input/source.rs +++ b/src/input/source.rs @@ -1,5 +1,6 @@ use super::user::{CommandKeyBindings, KeyBindingsValidationError, UserInput}; use crate::custom::KeyBindingsConfig; +use iceoryx2::{port::listener::Listener, service::ipc::Service}; use serde::Deserialize; use std::{io, time::Duration}; use strum::EnumDiscriminants; @@ -10,19 +11,29 @@ use strum::EnumDiscriminants; /// happens. pub struct CommandSource { user_input: UserInput, + speaker_notes_event_receiver: Option>, } impl CommandSource { /// Create a new command source over the given presentation path. - pub fn new(config: KeyBindingsConfig) -> Result { + pub fn new( + config: KeyBindingsConfig, + speaker_notes_event_receiver: Option>, + ) -> Result { let bindings = CommandKeyBindings::try_from(config)?; - Ok(Self { user_input: UserInput::new(bindings) }) + Ok(Self { user_input: UserInput::new(bindings), speaker_notes_event_receiver }) } /// Try to get the next command. /// /// This attempts to get a command and returns `Ok(None)` on timeout. pub(crate) fn try_next_command(&mut self) -> io::Result> { + if let Some(receiver) = self.speaker_notes_event_receiver.as_mut() { + // TODO: Handle Err instead of unwrap. + if let Some(evt) = receiver.try_wait_one().unwrap() { + return Ok(Some(Command::GoToSlide(evt.as_value() as u32))); + } + } match self.user_input.poll_next_command(Duration::from_millis(250))? { Some(command) => Ok(Some(command)), None => Ok(None), diff --git a/src/lib.rs b/src/lib.rs index 02d81f1..8d7a7c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ pub use crate::{ input::source::CommandSource, markdown::parse::MarkdownParser, media::{graphics::GraphicsMode, printer::ImagePrinter, register::ImageRegistry}, - presenter::{PresentMode, Presenter, PresenterOptions, SpeakerNoteChannel}, + presenter::{PresentMode, Presenter, PresenterOptions}, processing::builder::{PresentationBuilderOptions, Themes}, render::highlighting::{CodeHighlighter, HighlightThemeSet}, resource::Resources, diff --git a/src/main.rs b/src/main.rs index 13e0c9c..3f467c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,16 @@ use clap::{error::ErrorKind, CommandFactory, Parser, ValueEnum}; use comrak::Arena; use directories::ProjectDirs; -use iceoryx2::{node::NodeBuilder, prelude::ServiceName, service::ipc}; +use iceoryx2::{ + node::NodeBuilder, + prelude::ServiceName, + service::{ipc::Service, port_factory::event::PortFactory}, +}; use presenterm::{ CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, ImageRegistry, MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet, Presenter, - PresenterOptions, Resources, SnippetExecutor, SpeakerNoteChannel, Themes, ThemesDemo, ThirdPartyConfigs, - ThirdPartyRender, ValidateOverflows, + PresenterOptions, Resources, SnippetExecutor, Themes, ThemesDemo, ThirdPartyConfigs, ThirdPartyRender, + ValidateOverflows, }; use schemars::JsonSchema; use serde::Deserialize; @@ -208,24 +212,10 @@ fn overflow_validation(mode: &PresentMode, config: &ValidateOverflows) -> bool { } } -fn create_speaker_notes_channel( - speaker_notes_mode: SpeakerNotesMode, -) -> Result> { - let node = NodeBuilder::new().create::()?; +fn create_speaker_notes_service() -> Result, Box> { // TODO: Use a service name that incorporates presenterm and/or the presentation filename/title? let service_name: ServiceName = "SpeakerNoteEventService".try_into()?; - let event = node.service_builder(&service_name).event().open_or_create()?; - let speaker_note_channel = match speaker_notes_mode { - SpeakerNotesMode::Publisher => { - let notifier = event.notifier_builder().create()?; - SpeakerNoteChannel::Notifier(notifier) - } - SpeakerNotesMode::Receiver => { - let listener = event.listener_builder().create()?; - SpeakerNoteChannel::Listener(listener) - } - }; - Ok(speaker_note_channel) + Ok(NodeBuilder::new().create::()?.service_builder(&service_name).event().open_or_create()?) } fn run(mut cli: Cli) -> Result<(), Box> { @@ -309,11 +299,19 @@ fn run(mut cli: Cli) -> Result<(), Box> { println!("{}", serde_json::to_string_pretty(&meta)?); } } else { - let commands = CommandSource::new(config.bindings.clone())?; + let speaker_notes_event_receiver = if let Some(SpeakerNotesMode::Receiver) = cli.speaker_notes_mode { + Some(create_speaker_notes_service()?.listener_builder().create()?) + } else { + None + }; + let commands = CommandSource::new(config.bindings.clone(), speaker_notes_event_receiver)?; options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. }); - let speaker_notes_channel = cli.speaker_notes_mode.map(create_speaker_notes_channel).transpose()?; - + let speaker_notes_event_publisher = if let Some(SpeakerNotesMode::Publisher) = cli.speaker_notes_mode { + Some(create_speaker_notes_service()?.notifier_builder().create()?) + } else { + None + }; let options = PresenterOptions { builder_options: options, mode, @@ -331,7 +329,7 @@ fn run(mut cli: Cli) -> Result<(), Box> { themes, printer, options, - speaker_notes_channel, + speaker_notes_event_publisher, ); presenter.present(&path)?; } diff --git a/src/presenter.rs b/src/presenter.rs index 6b6188d..2500279 100644 --- a/src/presenter.rs +++ b/src/presenter.rs @@ -1,8 +1,4 @@ -use iceoryx2::{ - port::{listener::Listener, notifier::Notifier}, - prelude::EventId, - service::ipc::Service, -}; +use iceoryx2::{port::notifier::Notifier, prelude::EventId, service::ipc::Service}; use crate::{ custom::KeyBindingsConfig, @@ -43,12 +39,6 @@ pub struct PresenterOptions { pub validate_overflows: bool, } -#[derive(Debug)] -pub enum SpeakerNoteChannel { - Notifier(Notifier), - Listener(Listener), -} - /// A slideshow presenter. /// /// This type puts everything else together. @@ -64,7 +54,7 @@ pub struct Presenter<'a> { image_printer: Arc, themes: Themes, options: PresenterOptions, - speaker_notes_channel: Option, + speaker_notes_event_publisher: Option>, } impl<'a> Presenter<'a> { @@ -80,7 +70,7 @@ impl<'a> Presenter<'a> { themes: Themes, image_printer: Arc, options: PresenterOptions, - speaker_notes_channel: Option, + speaker_notes_event_publisher: Option>, ) -> Self { Self { default_theme, @@ -94,7 +84,7 @@ impl<'a> Presenter<'a> { image_printer, themes, options, - speaker_notes_channel, + speaker_notes_event_publisher, } } @@ -119,13 +109,6 @@ impl<'a> Presenter<'a> { self.render(&mut drawer)?; loop { - if let Some(SpeakerNoteChannel::Listener(listener)) = self.speaker_notes_channel.as_mut() { - if let Some(evt) = listener.try_wait_one().unwrap() { - self.apply_command(Command::GoToSlide(evt.as_value() as u32)); - break; - } - } - if self.poll_async_renders()? { self.render(&mut drawer)?; } @@ -158,9 +141,9 @@ impl<'a> Presenter<'a> { CommandSideEffect::None => (), }; } - if let Some(SpeakerNoteChannel::Notifier(notifier)) = self.speaker_notes_channel.as_mut() { + if let Some(publisher) = self.speaker_notes_event_publisher.as_mut() { let current_slide_idx = self.state.presentation().current_slide_index(); - notifier.notify_with_custom_event_id(EventId::new(current_slide_idx + 1)).unwrap(); + publisher.notify_with_custom_event_id(EventId::new(current_slide_idx + 1)).unwrap(); } } } From 5f781aca34bb1077f688be693126712f1f0cf8b6 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Mon, 11 Nov 2024 18:05:20 +0000 Subject: [PATCH 11/23] bump CI rust version to 1.75.0 --- .github/workflows/merge.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml index e793108..50862f1 100644 --- a/.github/workflows/merge.yaml +++ b/.github/workflows/merge.yaml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 - name: Install rust toolchain - uses: dtolnay/rust-toolchain@1.74.0 + uses: dtolnay/rust-toolchain@1.75.0 - name: Run cargo check run: cargo check --features sixel @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v4 - name: Install rust toolchain - uses: dtolnay/rust-toolchain@1.74.0 + uses: dtolnay/rust-toolchain@1.75.0 - name: Run cargo test run: cargo test From 0f83b5a595396614f81f066a2c08d55fe6c5fe73 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Mon, 11 Nov 2024 19:29:54 +0000 Subject: [PATCH 12/23] split process_element for speaker notes and presentation modes --- src/processing/builder.rs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/processing/builder.rs b/src/processing/builder.rs index 9fc6aa2..a3244a3 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -181,7 +181,11 @@ impl<'a> PresentationBuilder<'a> { } for element in elements { self.slide_state.ignore_element_line_break = false; - self.process_element(element)?; + if self.options.render_speaker_notes_only { + self.process_element_for_speaker_notes_mode(element)?; + } else { + self.process_element_for_presentation_mode(element)?; + } self.validate_last_operation()?; if !self.slide_state.ignore_element_line_break { self.push_line_break(); @@ -259,20 +263,7 @@ impl<'a> PresentationBuilder<'a> { self.push_line_break(); } - fn process_element(&mut self, element: MarkdownElement) -> Result<(), BuildError> { - if self.options.render_speaker_notes_only { - match element { - MarkdownElement::Comment { comment, source_position } => { - self.process_comment(comment, source_position)? - } - MarkdownElement::SetexHeading { text } => self.push_slide_title(text), - _ => {} - } - // Allows us to start the next speaker slide when a title is pushed and implicit_slide_ends is enabled. - self.slide_state.last_element = LastElement::Other; - self.slide_state.ignore_element_line_break = true; - return Ok(()); - } + fn process_element_for_presentation_mode(&mut self, element: MarkdownElement) -> Result<(), BuildError> { let should_clear_last = !matches!(element, MarkdownElement::List(_) | MarkdownElement::Comment { .. }); match element { // This one is processed before everything else as it affects how the rest of the @@ -297,6 +288,18 @@ impl<'a> PresentationBuilder<'a> { Ok(()) } + fn process_element_for_speaker_notes_mode(&mut self, element: MarkdownElement) -> Result<(), BuildError> { + match element { + MarkdownElement::Comment { comment, source_position } => self.process_comment(comment, source_position)?, + MarkdownElement::SetexHeading { text } => self.push_slide_title(text), + _ => {} + } + // Allows us to start the next speaker slide when a title is pushed and implicit_slide_ends is enabled. + self.slide_state.last_element = LastElement::Other; + self.slide_state.ignore_element_line_break = true; + Ok(()) + } + fn process_front_matter(&mut self, contents: &str) -> Result<(), BuildError> { let metadata = match self.options.strict_front_matter_parsing { true => serde_yaml::from_str::(contents).map(PresentationMetadata::from), From dc0e03927f649530f5c0b6814c6bb536bbab2931 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Sat, 16 Nov 2024 10:52:19 +0000 Subject: [PATCH 13/23] fix formatting --- src/custom.rs | 2 +- src/main.rs | 2 +- src/presenter.rs | 12 ++---------- src/processing/builder.rs | 23 ++++++++++------------- 4 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/custom.rs b/src/custom.rs index ffae32d..7d2fffc 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -1,8 +1,8 @@ use crate::{ + GraphicsMode, input::user::KeyBinding, media::{emulator::TerminalEmulator, kitty::KittyMode}, processing::code::SnippetLanguage, - GraphicsMode, }; use clap::ValueEnum; use schemars::JsonSchema; diff --git a/src/main.rs b/src/main.rs index 3f467c4..9a7cd0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use clap::{error::ErrorKind, CommandFactory, Parser, ValueEnum}; +use clap::{CommandFactory, Parser, ValueEnum, error::ErrorKind}; use comrak::Arena; use directories::ProjectDirs; use iceoryx2::{ diff --git a/src/presenter.rs b/src/presenter.rs index 2500279..750831c 100644 --- a/src/presenter.rs +++ b/src/presenter.rs @@ -203,11 +203,7 @@ impl<'a> Presenter<'a> { }; // If the screen is too small, simply ignore this. Eventually the user will resize the // screen. - if matches!(result, Err(RenderError::TerminalTooSmall)) { - Ok(()) - } else { - result - } + if matches!(result, Err(RenderError::TerminalTooSmall)) { Ok(()) } else { result } } fn apply_command(&mut self, command: Command) -> CommandSideEffect { @@ -277,11 +273,7 @@ impl<'a> Presenter<'a> { panic!("unreachable commands") } }; - if needs_redraw { - CommandSideEffect::Redraw - } else { - CommandSideEffect::None - } + if needs_redraw { CommandSideEffect::Redraw } else { CommandSideEffect::None } } fn try_reload(&mut self, path: &Path, force: bool) { diff --git a/src/processing/builder.rs b/src/processing/builder.rs index a3244a3..268e7be 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -243,11 +243,7 @@ impl<'a> PresentationBuilder<'a> { } self.slide_state.needs_enter_column = false; let last_valid = matches!(last, RenderOperation::EnterColumn { .. } | RenderOperation::ExitLayout); - if last_valid { - Ok(()) - } else { - Err(BuildError::NotInsideColumn) - } + if last_valid { Ok(()) } else { Err(BuildError::NotInsideColumn) } } fn push_slide_prelude(&mut self) { @@ -1598,10 +1594,11 @@ mod test { #[test] fn iterate_list_starting_from_other() { let list = ListIterator::new( - vec![ - ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered }, - ListItem { depth: 0, contents: "1".into(), item_type: ListItemType::Unordered }, - ], + vec![ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered }, ListItem { + depth: 0, + contents: "1".into(), + item_type: ListItemType::Unordered, + }], 3, ); let expected_indexes = [3, 4]; @@ -1688,10 +1685,10 @@ mod test { #[test] fn implicit_slide_ends_with_front_matter() { - let elements = vec![ - MarkdownElement::FrontMatter("theme:\n name: light".into()), - MarkdownElement::SetexHeading { text: "hi".into() }, - ]; + let elements = + vec![MarkdownElement::FrontMatter("theme:\n name: light".into()), MarkdownElement::SetexHeading { + text: "hi".into(), + }]; let options = PresentationBuilderOptions { implicit_slide_ends: true, ..Default::default() }; let slides = build_presentation_with_options(elements, options).into_slides(); assert_eq!(slides.len(), 1); From 27a6151d9a475c47c7a60c3b618e4d13a6c1db74 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Sat, 16 Nov 2024 12:07:04 +0000 Subject: [PATCH 14/23] use pubsub ipc messaging pattern instead of event --- src/input/mod.rs | 1 + src/input/source.rs | 19 +++++++++++++------ src/input/speaker_notes.rs | 5 +++++ src/lib.rs | 2 +- src/main.rs | 22 +++++++++++++++------- src/presenter.rs | 14 +++++++++----- 6 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 src/input/speaker_notes.rs diff --git a/src/input/mod.rs b/src/input/mod.rs index 23060d5..b8954f9 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod source; +pub(crate) mod speaker_notes; pub(crate) mod user; diff --git a/src/input/source.rs b/src/input/source.rs index 78003d2..3324356 100644 --- a/src/input/source.rs +++ b/src/input/source.rs @@ -1,6 +1,9 @@ -use super::user::{CommandKeyBindings, KeyBindingsValidationError, UserInput}; +use super::{ + speaker_notes::SpeakerNotesCommand, + user::{CommandKeyBindings, KeyBindingsValidationError, UserInput}, +}; use crate::custom::KeyBindingsConfig; -use iceoryx2::{port::listener::Listener, service::ipc::Service}; +use iceoryx2::{port::subscriber::Subscriber, service::ipc::Service}; use serde::Deserialize; use std::{io, time::Duration}; use strum::EnumDiscriminants; @@ -11,14 +14,14 @@ use strum::EnumDiscriminants; /// happens. pub struct CommandSource { user_input: UserInput, - speaker_notes_event_receiver: Option>, + speaker_notes_event_receiver: Option>, } impl CommandSource { /// Create a new command source over the given presentation path. pub fn new( config: KeyBindingsConfig, - speaker_notes_event_receiver: Option>, + speaker_notes_event_receiver: Option>, ) -> Result { let bindings = CommandKeyBindings::try_from(config)?; Ok(Self { user_input: UserInput::new(bindings), speaker_notes_event_receiver }) @@ -30,8 +33,12 @@ impl CommandSource { pub(crate) fn try_next_command(&mut self) -> io::Result> { if let Some(receiver) = self.speaker_notes_event_receiver.as_mut() { // TODO: Handle Err instead of unwrap. - if let Some(evt) = receiver.try_wait_one().unwrap() { - return Ok(Some(Command::GoToSlide(evt.as_value() as u32))); + if let Some(msg) = receiver.receive().unwrap() { + match msg.payload() { + SpeakerNotesCommand::GoToSlide(idx) => { + return Ok(Some(Command::GoToSlide(*idx))); + } + } } } match self.user_input.poll_next_command(Duration::from_millis(250))? { diff --git a/src/input/speaker_notes.rs b/src/input/speaker_notes.rs new file mode 100644 index 0000000..d091b6d --- /dev/null +++ b/src/input/speaker_notes.rs @@ -0,0 +1,5 @@ +#[derive(Debug)] +#[repr(C)] +pub enum SpeakerNotesCommand { + GoToSlide(u32), +} diff --git a/src/lib.rs b/src/lib.rs index 8d7a7c8..d5dfe53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ pub use crate::{ demo::ThemesDemo, execute::SnippetExecutor, export::{ExportError, Exporter}, - input::source::CommandSource, + input::{source::CommandSource, speaker_notes::SpeakerNotesCommand}, markdown::parse::MarkdownParser, media::{graphics::GraphicsMode, printer::ImagePrinter, register::ImageRegistry}, presenter::{PresentMode, Presenter, PresenterOptions}, diff --git a/src/main.rs b/src/main.rs index 9a7cd0b..d22cb2b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,13 @@ use directories::ProjectDirs; use iceoryx2::{ node::NodeBuilder, prelude::ServiceName, - service::{ipc::Service, port_factory::event::PortFactory}, + service::{builder::publish_subscribe::Builder, ipc::Service}, }; use presenterm::{ CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, ImageRegistry, MarkdownParser, PresentMode, PresentationBuilderOptions, PresentationTheme, PresentationThemeSet, Presenter, - PresenterOptions, Resources, SnippetExecutor, Themes, ThemesDemo, ThirdPartyConfigs, ThirdPartyRender, - ValidateOverflows, + PresenterOptions, Resources, SnippetExecutor, SpeakerNotesCommand, Themes, ThemesDemo, ThirdPartyConfigs, + ThirdPartyRender, ValidateOverflows, }; use schemars::JsonSchema; use serde::Deserialize; @@ -212,10 +212,16 @@ fn overflow_validation(mode: &PresentMode, config: &ValidateOverflows) -> bool { } } -fn create_speaker_notes_service() -> Result, Box> { +fn create_speaker_notes_service_builder() +-> Result, Box> { // TODO: Use a service name that incorporates presenterm and/or the presentation filename/title? let service_name: ServiceName = "SpeakerNoteEventService".try_into()?; - Ok(NodeBuilder::new().create::()?.service_builder(&service_name).event().open_or_create()?) + let service = NodeBuilder::new() + .create::()? + .service_builder(&service_name) + .publish_subscribe::() + .max_publishers(1); + Ok(service) } fn run(mut cli: Cli) -> Result<(), Box> { @@ -300,7 +306,8 @@ fn run(mut cli: Cli) -> Result<(), Box> { } } else { let speaker_notes_event_receiver = if let Some(SpeakerNotesMode::Receiver) = cli.speaker_notes_mode { - Some(create_speaker_notes_service()?.listener_builder().create()?) + let receiver = create_speaker_notes_service_builder()?.open()?.subscriber_builder().create()?; + Some(receiver) } else { None }; @@ -308,7 +315,8 @@ fn run(mut cli: Cli) -> Result<(), Box> { options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. }); let speaker_notes_event_publisher = if let Some(SpeakerNotesMode::Publisher) = cli.speaker_notes_mode { - Some(create_speaker_notes_service()?.notifier_builder().create()?) + let publisher = create_speaker_notes_service_builder()?.create()?.publisher_builder().create()?; + Some(publisher) } else { None }; diff --git a/src/presenter.rs b/src/presenter.rs index 750831c..66b7711 100644 --- a/src/presenter.rs +++ b/src/presenter.rs @@ -1,6 +1,7 @@ -use iceoryx2::{port::notifier::Notifier, prelude::EventId, service::ipc::Service}; +use iceoryx2::{port::publisher::Publisher, service::ipc::Service}; use crate::{ + SpeakerNotesCommand, custom::KeyBindingsConfig, diff::PresentationDiffer, execute::SnippetExecutor, @@ -54,7 +55,7 @@ pub struct Presenter<'a> { image_printer: Arc, themes: Themes, options: PresenterOptions, - speaker_notes_event_publisher: Option>, + speaker_notes_event_publisher: Option>, } impl<'a> Presenter<'a> { @@ -70,7 +71,7 @@ impl<'a> Presenter<'a> { themes: Themes, image_printer: Arc, options: PresenterOptions, - speaker_notes_event_publisher: Option>, + speaker_notes_event_publisher: Option>, ) -> Self { Self { default_theme, @@ -142,8 +143,11 @@ impl<'a> Presenter<'a> { }; } if let Some(publisher) = self.speaker_notes_event_publisher.as_mut() { - let current_slide_idx = self.state.presentation().current_slide_index(); - publisher.notify_with_custom_event_id(EventId::new(current_slide_idx + 1)).unwrap(); + let current_slide_idx = self.state.presentation().current_slide_index() as u32; + // TODO: Replace unwraps. + let sample = publisher.loan_uninit().unwrap(); + let sample = sample.write_payload(SpeakerNotesCommand::GoToSlide(current_slide_idx + 1)); + sample.send().unwrap(); } } } From 43b44c5b7f0d9e0c0725d587c839b0cafed32541 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Sun, 17 Nov 2024 15:41:36 +0000 Subject: [PATCH 15/23] incorporate presentation file name in ipc service name --- src/main.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index d22cb2b..2d90ebf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ use comrak::Arena; use directories::ProjectDirs; use iceoryx2::{ node::NodeBuilder, - prelude::ServiceName, service::{builder::publish_subscribe::Builder, ipc::Service}, }; use presenterm::{ @@ -212,10 +211,11 @@ fn overflow_validation(mode: &PresentMode, config: &ValidateOverflows) -> bool { } } -fn create_speaker_notes_service_builder() --> Result, Box> { - // TODO: Use a service name that incorporates presenterm and/or the presentation filename/title? - let service_name: ServiceName = "SpeakerNoteEventService".try_into()?; +fn create_speaker_notes_service_builder( + presentation_path: &Path, +) -> Result, Box> { + let file_name = presentation_path.file_name().expect("failed to resolve presentation file name").to_string_lossy(); + let service_name = format!("presenterm/{}", file_name).as_str().try_into()?; let service = NodeBuilder::new() .create::()? .service_builder(&service_name) @@ -306,7 +306,7 @@ fn run(mut cli: Cli) -> Result<(), Box> { } } else { let speaker_notes_event_receiver = if let Some(SpeakerNotesMode::Receiver) = cli.speaker_notes_mode { - let receiver = create_speaker_notes_service_builder()?.open()?.subscriber_builder().create()?; + let receiver = create_speaker_notes_service_builder(&path)?.open()?.subscriber_builder().create()?; Some(receiver) } else { None @@ -315,7 +315,7 @@ fn run(mut cli: Cli) -> Result<(), Box> { options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. }); let speaker_notes_event_publisher = if let Some(SpeakerNotesMode::Publisher) = cli.speaker_notes_mode { - let publisher = create_speaker_notes_service_builder()?.create()?.publisher_builder().create()?; + let publisher = create_speaker_notes_service_builder(&path)?.create()?.publisher_builder().create()?; Some(publisher) } else { None From ddbaf419777d9ce14f76fee3d2dd26e8298ae4be Mon Sep 17 00:00:00 2001 From: dmackdev Date: Tue, 19 Nov 2024 18:55:18 +0000 Subject: [PATCH 16/23] split process comment command for speaker notes mode --- src/processing/builder.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/processing/builder.rs b/src/processing/builder.rs index 268e7be..c8d816e 100644 --- a/src/processing/builder.rs +++ b/src/processing/builder.rs @@ -438,18 +438,14 @@ impl<'a> PresentationBuilder<'a> { }; if self.options.render_speaker_notes_only { - match comment { - CommentCommand::SpeakerNote(note) => { - self.push_text(note.into(), ElementType::Paragraph); - self.push_line_break(); - } - CommentCommand::EndSlide => self.terminate_slide(), - _ => {} - } - return Ok(()); + self.process_comment_command_speaker_notes_mode(comment) + } else { + self.process_comment_command_presentation_mode(comment) } + } - match comment { + fn process_comment_command_presentation_mode(&mut self, comment_command: CommentCommand) -> Result<(), BuildError> { + match comment_command { CommentCommand::Pause => self.process_pause(), CommentCommand::EndSlide => self.terminate_slide(), CommentCommand::NewLine => self.push_line_break(), @@ -496,6 +492,21 @@ impl<'a> PresentationBuilder<'a> { Ok(()) } + fn process_comment_command_speaker_notes_mode( + &mut self, + comment_command: CommentCommand, + ) -> Result<(), BuildError> { + match comment_command { + CommentCommand::SpeakerNote(note) => { + self.push_text(note.into(), ElementType::Paragraph); + self.push_line_break(); + } + CommentCommand::EndSlide => self.terminate_slide(), + _ => {} + } + Ok(()) + } + fn should_ignore_comment(&self, comment: &str) -> bool { if comment.contains('\n') || !comment.starts_with(&self.options.command_prefix) { // Ignore any multi line comment; those are assumed to be user comments From 3114df9de402c8fc07158316b3fe036a7af68c65 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Tue, 19 Nov 2024 19:01:44 +0000 Subject: [PATCH 17/23] propagate iceoryx2 publisher errors --- src/presenter.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/presenter.rs b/src/presenter.rs index 66b7711..f233e9b 100644 --- a/src/presenter.rs +++ b/src/presenter.rs @@ -1,4 +1,7 @@ -use iceoryx2::{port::publisher::Publisher, service::ipc::Service}; +use iceoryx2::{ + port::publisher::{Publisher, PublisherLoanError, PublisherSendError}, + service::ipc::Service, +}; use crate::{ SpeakerNotesCommand, @@ -144,10 +147,9 @@ impl<'a> Presenter<'a> { } if let Some(publisher) = self.speaker_notes_event_publisher.as_mut() { let current_slide_idx = self.state.presentation().current_slide_index() as u32; - // TODO: Replace unwraps. - let sample = publisher.loan_uninit().unwrap(); + let sample = publisher.loan_uninit()?; let sample = sample.write_payload(SpeakerNotesCommand::GoToSlide(current_slide_idx + 1)); - sample.send().unwrap(); + sample.send()?; } } } @@ -494,4 +496,10 @@ pub enum PresentationError { #[error("fatal error: {0}")] Fatal(String), + + #[error(transparent)] + SpeakerNotesPublisher(#[from] PublisherLoanError), + + #[error(transparent)] + SpeakerNotesSend(#[from] PublisherSendError), } From 52d3f04009c0903b41a7ba4a84cbf79ae10fee35 Mon Sep 17 00:00:00 2001 From: dmackdev Date: Tue, 19 Nov 2024 19:13:34 +0000 Subject: [PATCH 18/23] propagate iceoryx2 receiver error --- src/input/source.rs | 9 ++++----- src/presenter.rs | 8 +++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/input/source.rs b/src/input/source.rs index 3324356..42acf6c 100644 --- a/src/input/source.rs +++ b/src/input/source.rs @@ -2,10 +2,10 @@ use super::{ speaker_notes::SpeakerNotesCommand, user::{CommandKeyBindings, KeyBindingsValidationError, UserInput}, }; -use crate::custom::KeyBindingsConfig; +use crate::{custom::KeyBindingsConfig, presenter::PresentationError}; use iceoryx2::{port::subscriber::Subscriber, service::ipc::Service}; use serde::Deserialize; -use std::{io, time::Duration}; +use std::time::Duration; use strum::EnumDiscriminants; /// The source of commands. @@ -30,10 +30,9 @@ impl CommandSource { /// Try to get the next command. /// /// This attempts to get a command and returns `Ok(None)` on timeout. - pub(crate) fn try_next_command(&mut self) -> io::Result> { + pub(crate) fn try_next_command(&mut self) -> Result, PresentationError> { if let Some(receiver) = self.speaker_notes_event_receiver.as_mut() { - // TODO: Handle Err instead of unwrap. - if let Some(msg) = receiver.receive().unwrap() { + if let Some(msg) = receiver.receive()? { match msg.payload() { SpeakerNotesCommand::GoToSlide(idx) => { return Ok(Some(Command::GoToSlide(*idx))); diff --git a/src/presenter.rs b/src/presenter.rs index f233e9b..15f99d0 100644 --- a/src/presenter.rs +++ b/src/presenter.rs @@ -1,5 +1,8 @@ use iceoryx2::{ - port::publisher::{Publisher, PublisherLoanError, PublisherSendError}, + port::{ + publisher::{Publisher, PublisherLoanError, PublisherSendError}, + subscriber::SubscriberReceiveError, + }, service::ipc::Service, }; @@ -502,4 +505,7 @@ pub enum PresentationError { #[error(transparent)] SpeakerNotesSend(#[from] PublisherSendError), + + #[error(transparent)] + SpeakerNotesReceive(#[from] SubscriberReceiveError), } From 13e2d4655ff4a0c57c68e91c8541c5a059ff044e Mon Sep 17 00:00:00 2001 From: dmackdev Date: Thu, 21 Nov 2024 17:04:11 +0000 Subject: [PATCH 19/23] send new SpeakerNotesCommand::Exit ipc message to exit the speaker notes presentation --- src/input/source.rs | 1 + src/input/speaker_notes.rs | 1 + src/presenter.rs | 9 ++++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/input/source.rs b/src/input/source.rs index 42acf6c..9e060be 100644 --- a/src/input/source.rs +++ b/src/input/source.rs @@ -37,6 +37,7 @@ impl CommandSource { SpeakerNotesCommand::GoToSlide(idx) => { return Ok(Some(Command::GoToSlide(*idx))); } + SpeakerNotesCommand::Exit => return Ok(Some(Command::Exit)), } } } diff --git a/src/input/speaker_notes.rs b/src/input/speaker_notes.rs index d091b6d..b01101d 100644 --- a/src/input/speaker_notes.rs +++ b/src/input/speaker_notes.rs @@ -2,4 +2,5 @@ #[repr(C)] pub enum SpeakerNotesCommand { GoToSlide(u32), + Exit, } diff --git a/src/presenter.rs b/src/presenter.rs index 15f99d0..df1539a 100644 --- a/src/presenter.rs +++ b/src/presenter.rs @@ -133,7 +133,14 @@ impl<'a> Presenter<'a> { }, }; match self.apply_command(command) { - CommandSideEffect::Exit => return Ok(()), + CommandSideEffect::Exit => { + if let Some(publisher) = self.speaker_notes_event_publisher.as_mut() { + let sample = publisher.loan_uninit()?; + let sample = sample.write_payload(SpeakerNotesCommand::Exit); + sample.send()?; + } + return Ok(()); + } CommandSideEffect::Suspend => { self.suspend(&mut drawer); break; From 2964ac9397a9553ec4356ed846597013949f54ad Mon Sep 17 00:00:00 2001 From: dmackdev Date: Thu, 21 Nov 2024 17:05:28 +0000 Subject: [PATCH 20/23] use string interpolation for ipc service name --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 2d90ebf..529f897 100644 --- a/src/main.rs +++ b/src/main.rs @@ -215,7 +215,7 @@ fn create_speaker_notes_service_builder( presentation_path: &Path, ) -> Result, Box> { let file_name = presentation_path.file_name().expect("failed to resolve presentation file name").to_string_lossy(); - let service_name = format!("presenterm/{}", file_name).as_str().try_into()?; + let service_name = format!("presenterm/{file_name}").as_str().try_into()?; let service = NodeBuilder::new() .create::()? .service_builder(&service_name) From 209cd10da89fe079e5ca0edd9130aebc2d04f7df Mon Sep 17 00:00:00 2001 From: dmackdev Date: Thu, 21 Nov 2024 17:33:52 +0000 Subject: [PATCH 21/23] propagate error instead of expect --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 529f897..6c76146 100644 --- a/src/main.rs +++ b/src/main.rs @@ -214,7 +214,10 @@ fn overflow_validation(mode: &PresentMode, config: &ValidateOverflows) -> bool { fn create_speaker_notes_service_builder( presentation_path: &Path, ) -> Result, Box> { - let file_name = presentation_path.file_name().expect("failed to resolve presentation file name").to_string_lossy(); + let file_name = presentation_path + .file_name() + .ok_or(Cli::command().error(ErrorKind::InvalidValue, "failed to resolve presentation file name"))? + .to_string_lossy(); let service_name = format!("presenterm/{file_name}").as_str().try_into()?; let service = NodeBuilder::new() .create::()? From 64dcc88d7d35c311fee725cc9d790113438e557e Mon Sep 17 00:00:00 2001 From: dmackdev Date: Wed, 4 Dec 2024 09:33:00 +0000 Subject: [PATCH 22/23] set iceoryx2 log level to error to hide misleading "missing config" warning --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 6c76146..a68a429 100644 --- a/src/main.rs +++ b/src/main.rs @@ -348,6 +348,7 @@ fn run(mut cli: Cli) -> Result<(), Box> { } fn main() { + iceoryx2::prelude::set_log_level(iceoryx2::prelude::LogLevel::Error); let cli = Cli::parse(); if let Err(e) = run(cli) { eprintln!("{e}"); From 792452416b926a706fcf48e989a915b4b0cb6d2c Mon Sep 17 00:00:00 2001 From: dmackdev Date: Wed, 4 Dec 2024 09:36:43 +0000 Subject: [PATCH 23/23] add better error messages for iceoryx2 service open/create errors --- src/main.rs | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index a68a429..597c4e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,10 @@ use comrak::Arena; use directories::ProjectDirs; use iceoryx2::{ node::NodeBuilder, - service::{builder::publish_subscribe::Builder, ipc::Service}, + service::{ + builder::publish_subscribe::{Builder, PublishSubscribeCreateError, PublishSubscribeOpenError}, + ipc::Service, + }, }; use presenterm::{ CommandSource, Config, Exporter, GraphicsMode, HighlightThemeSet, ImagePrinter, ImageProtocol, ImageRegistry, @@ -30,6 +33,34 @@ pub enum SpeakerNotesMode { Receiver, } +#[derive(thiserror::Error, Debug)] +enum IpcServiceError { + #[error("no presenterm process in publisher mode running for presentation")] + ServiceOpenError, + #[error("existing presenterm process in publisher mode already running for presentation")] + ServiceCreateError, + #[error("{0}")] + Other(String), +} + +impl From for IpcServiceError { + fn from(value: PublishSubscribeOpenError) -> Self { + match value { + PublishSubscribeOpenError::DoesNotExist => Self::ServiceOpenError, + _ => Self::Other(value.to_string()), + } + } +} + +impl From for IpcServiceError { + fn from(value: PublishSubscribeCreateError) -> Self { + match value { + PublishSubscribeCreateError::AlreadyExists => Self::ServiceCreateError, + _ => Self::Other(value.to_string()), + } + } +} + /// Run slideshows from your terminal. #[derive(Parser)] #[command()] @@ -309,7 +340,11 @@ fn run(mut cli: Cli) -> Result<(), Box> { } } else { let speaker_notes_event_receiver = if let Some(SpeakerNotesMode::Receiver) = cli.speaker_notes_mode { - let receiver = create_speaker_notes_service_builder(&path)?.open()?.subscriber_builder().create()?; + let receiver = create_speaker_notes_service_builder(&path)? + .open() + .map_err(|err| Cli::command().error(ErrorKind::InvalidValue, IpcServiceError::from(err)))? + .subscriber_builder() + .create()?; Some(receiver) } else { None @@ -318,7 +353,11 @@ fn run(mut cli: Cli) -> Result<(), Box> { options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. }); let speaker_notes_event_publisher = if let Some(SpeakerNotesMode::Publisher) = cli.speaker_notes_mode { - let publisher = create_speaker_notes_service_builder(&path)?.create()?.publisher_builder().create()?; + let publisher = create_speaker_notes_service_builder(&path)? + .create() + .map_err(|err| Cli::command().error(ErrorKind::InvalidValue, IpcServiceError::from(err)))? + .publisher_builder() + .create()?; Some(publisher) } else { None