Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: bind/publish to different addresses in non linux #425

Merged
merged 1 commit into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ version = "0.9.0"
edition = "2021"

[dependencies]
anyhow = "1"
ansi-parser = "0.9"
base64 = "0.22"
bincode = "1.3"
Expand Down
46 changes: 43 additions & 3 deletions src/commands/speaker_notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ impl SpeakerNotesEventPublisher {
// ignore unrelated events.
let envelope = SpeakerNotesEventEnvelope { event, presentation_path: self.presentation_path.clone() };
let data = serde_json::to_string(&envelope).expect("serialization failed");
self.socket.send(data.as_bytes())?;
Ok(())
match self.socket.send(data.as_bytes()) {
Ok(_) => Ok(()),
Err(e) if e.kind() == io::ErrorKind::ConnectionRefused => Ok(()),
Err(e) => Err(e),
}
}
}

Expand All @@ -38,6 +41,7 @@ impl SpeakerNotesEventListener {
pub fn new(address: SocketAddr, presentation_path: PathBuf) -> io::Result<Self> {
let s = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
// Use SO_REUSEADDR so we can have multiple listeners on the same port.
#[cfg(not(target_os = "macos"))]
s.set_reuse_address(true)?;
// Don't block so we can listen to the keyboard and this socket at the same time.
s.set_nonblocking(true)?;
Expand All @@ -61,7 +65,7 @@ impl SpeakerNotesEventListener {
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "command")]
pub(crate) enum SpeakerNotesEvent {
GoToSlide { slide: u32 },
Expand All @@ -73,3 +77,39 @@ struct SpeakerNotesEventEnvelope {
presentation_path: PathBuf,
event: SpeakerNotesEvent,
}

#[cfg(not(target_os = "macos"))]
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{default_speaker_notes_listen_address, default_speaker_notes_publish_address};
use std::{thread::sleep, time::Duration};

fn make_listener(path: PathBuf) -> SpeakerNotesEventListener {
SpeakerNotesEventListener::new(default_speaker_notes_listen_address(), path).expect("building listener")
}

fn make_publisher(path: PathBuf) -> SpeakerNotesEventPublisher {
SpeakerNotesEventPublisher::new(default_speaker_notes_publish_address(), path).expect("building publisher")
}

#[test]
fn bind_multiple() {
let _l1 = make_listener("".into());
let _l2 = make_listener("".into());
}

#[test]
fn multicast() {
let path = PathBuf::from("/tmp/test.md");
let l1 = make_listener(path.clone());
let l2 = make_listener(path.clone());
let publisher = make_publisher(path);
let event = SpeakerNotesEvent::Exit;
publisher.send(event.clone()).expect("send failed");
sleep(Duration::from_millis(100));

assert_eq!(l1.try_recv().expect("recv first failed"), Some(event.clone()));
assert_eq!(l2.try_recv().expect("recv second failed"), Some(event));
}
}
22 changes: 16 additions & 6 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,11 @@ impl Default for KeyBindingsConfig {
#[serde(deny_unknown_fields)]
pub struct SpeakerNotesConfig {
/// The address in which to listen for speaker note events.
#[serde(default = "default_speaker_notes_address")]
#[serde(default = "default_speaker_notes_listen_address")]
pub listen_address: SocketAddr,

/// The address in which to publish speaker notes events.
#[serde(default = "default_speaker_notes_address")]
#[serde(default = "default_speaker_notes_publish_address")]
pub publish_address: SocketAddr,

/// Whether to always publish speaker notes.
Expand All @@ -408,8 +408,8 @@ pub struct SpeakerNotesConfig {
impl Default for SpeakerNotesConfig {
fn default() -> Self {
Self {
listen_address: default_speaker_notes_address(),
publish_address: default_speaker_notes_address(),
listen_address: default_speaker_notes_listen_address(),
publish_address: default_speaker_notes_publish_address(),
always_publish: false,
}
}
Expand Down Expand Up @@ -480,12 +480,22 @@ fn default_suspend_bindings() -> Vec<KeyBinding> {
}

#[cfg(target_os = "linux")]
fn default_speaker_notes_address() -> SocketAddr {
pub(crate) fn default_speaker_notes_listen_address() -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 255, 255, 255)), 59418)
}

#[cfg(not(target_os = "linux"))]
fn default_speaker_notes_address() -> SocketAddr {
pub(crate) fn default_speaker_notes_listen_address() -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 59418)
}

#[cfg(not(target_os = "macos"))]
pub(crate) fn default_speaker_notes_publish_address() -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 255, 255, 255)), 59418)
}

#[cfg(target_os = "macos")]
pub(crate) fn default_speaker_notes_publish_address() -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 59418)
}

Expand Down
7 changes: 5 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::{
theme::{PresentationTheme, PresentationThemeSet},
third_party::{ThirdPartyConfigs, ThirdPartyRender},
};
use anyhow::anyhow;
use clap::{CommandFactory, Parser, error::ErrorKind};
use commands::speaker_notes::{SpeakerNotesEventListener, SpeakerNotesEventPublisher};
use comrak::Arena;
Expand Down Expand Up @@ -320,11 +321,13 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
.then(|| {
SpeakerNotesEventPublisher::new(config.speaker_notes.publish_address, full_presentation_path.clone())
})
.transpose()?;
.transpose()
.map_err(|e| anyhow!("failed to create speaker notes publisher: {e}"))?;
let events_listener = cli
.listen_speaker_notes
.then(|| SpeakerNotesEventListener::new(config.speaker_notes.listen_address, full_presentation_path))
.transpose()?;
.transpose()
.map_err(|e| anyhow!("failed to create speaker notes listener: {e}"))?;
let command_listener = CommandListener::new(config.bindings.clone(), events_listener)?;

options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. });
Expand Down
Loading