Skip to content

Commit 1c23071

Browse files
committed
Fix server name option
1 parent 4f78127 commit 1c23071

5 files changed

Lines changed: 145 additions & 9 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Fix `Client::new` now returns an error when used with the server name
6+
option. Introduced `Client::new_with_server_name` for this use case.
57
- Ports and transport objects now hold strong references to their JACK client and remain
68
usable after `Client` is dropped.
79
- Added `ClientHandle` with an `as_ptr` method. `Port::from_raw` now takes

‎src/client/client_impl.rs‎

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,49 @@ impl Client {
6565
///
6666
/// Although the client may be successful in opening, there still may be some errors minor
6767
/// errors when attempting to opening. To access these, check the returned `ClientStatus`.
68+
///
69+
/// `options` must not contain `SERVER_NAME`, `LOAD_NAME`, `LOAD_INIT`, or `SESSION_ID`, since
70+
/// those require extra arguments that this function does not take. If any are present,
71+
/// `Err(Error::InvalidClientOptions(options))` is returned. To connect to a named server, use
72+
/// [`Client::new_with_server_name`].
6873
pub fn new(client_name: &str, options: ClientOptions) -> Result<(Self, ClientStatus), Error> {
74+
Self::open(client_name, None, options)
75+
}
76+
77+
/// Opens a JACK client connected to the JACK server named `server_name`. Behaves like
78+
/// [`Client::new`] otherwise.
79+
///
80+
/// `ClientOptions::SERVER_NAME` is set automatically, and may also be included in `options`.
81+
/// `options` must not contain `LOAD_NAME`, `LOAD_INIT`, or `SESSION_ID`; if any are present,
82+
/// `Err(Error::InvalidClientOptions(options))` is returned.
83+
pub fn new_with_server_name(
84+
client_name: &str,
85+
server_name: &str,
86+
options: ClientOptions,
87+
) -> Result<(Self, ClientStatus), Error> {
88+
Self::open(client_name, Some(server_name), options)
89+
}
90+
91+
fn open(
92+
client_name: &str,
93+
server_name: Option<&str>,
94+
options: ClientOptions,
95+
) -> Result<(Self, ClientStatus), Error> {
96+
// `jack_client_open` is variadic: these options make libjack read extra arguments via
97+
// `va_arg`. Only the server name argument is ever passed, so any other such option would
98+
// cause libjack to read garbage.
99+
let mut allowed_vararg_options = ClientOptions::empty();
100+
if server_name.is_some() {
101+
allowed_vararg_options |= ClientOptions::SERVER_NAME;
102+
}
103+
if !options
104+
.intersection(ClientOptions::VARARG_OPTIONS)
105+
.difference(allowed_vararg_options)
106+
.is_empty()
107+
{
108+
return Err(Error::InvalidClientOptions(options));
109+
}
110+
69111
let _m = CREATE_OR_DESTROY_CLIENT_MUTEX.lock().ok();
70112

71113
// All of the jack_sys functions below assume the client library is loaded and will panic if
@@ -77,9 +119,22 @@ impl Client {
77119

78120
crate::logging::maybe_init_logging();
79121
let mut status_bits = 0;
80-
let client = unsafe {
81-
let client_name = ffi::CString::new(client_name).unwrap();
82-
j::jack_client_open(client_name.as_ptr(), options.bits(), &mut status_bits)
122+
let client_name = ffi::CString::new(client_name).unwrap();
123+
let client = match server_name {
124+
None => unsafe {
125+
j::jack_client_open(client_name.as_ptr(), options.bits(), &mut status_bits)
126+
},
127+
Some(server_name) => {
128+
let server_name = ffi::CString::new(server_name).unwrap();
129+
unsafe {
130+
j::jack_client_open_with_server_name(
131+
client_name.as_ptr(),
132+
options.bits(),
133+
&mut status_bits,
134+
server_name.as_ptr(),
135+
)
136+
}
137+
}
83138
};
84139
let status = ClientStatus::from_bits(status_bits).unwrap_or_else(ClientStatus::empty);
85140
if client.is_null() {

‎src/client/client_options.rs‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use jack_sys as j;
33

44
bitflags! {
55
/// Option flags for opening a JACK client.
6+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
67
pub struct ClientOptions: j::Enum_JackOptions {
78
/// Do not automatically start the JACK server when it is not already running. This option
89
/// is always selected if `$JACK_NO_START_SERVER` is defined in the calling process
@@ -13,26 +14,36 @@ bitflags! {
1314
/// automatically generates a unique one if needed.
1415
const USE_EXACT_NAME = j::JackUseExactName;
1516

16-
/// Open with optional `server_name` parameter.
17-
///
18-
/// TODO: implement
17+
/// Connect to a named JACK server. Set automatically by `Client::new_with_server_name`;
18+
/// `Client::new` rejects it because it has no server name to pass.
1919
const SERVER_NAME = j::JackServerName;
2020

2121
/// Load internal client from optional `load_name`, otherwise use the `client_name`.
2222
///
23-
/// TODO(wmedrano): Implement `load_name` functionality
23+
/// Not supported when opening a client; `Client::new` returns an error if it is set.
2424
const LOAD_NAME = j::JackLoadName;
2525

2626
/// Pass optional `load_init` to `jack_initialize()` entry point of an internal client.
2727
///
28-
/// TODO: implement
28+
/// Not supported when opening a client; `Client::new` returns an error if it is set.
2929
const LOAD_INIT = j::JackLoadInit;
3030

3131
/// Pass a SessionID token. This allows the session manager to identify the client again.
32+
///
33+
/// Not supported yet; `Client::new` returns an error if it is set.
3234
const SESSION_ID = j::JackSessionID;
3335
}
3436
}
3537

38+
impl ClientOptions {
39+
/// Options that make `jack_client_open` read an extra variadic argument. Passing one without
40+
/// its argument is undefined behavior, so `Client` rejects any it cannot supply.
41+
pub(crate) const VARARG_OPTIONS: ClientOptions = ClientOptions::SERVER_NAME
42+
.union(ClientOptions::LOAD_NAME)
43+
.union(ClientOptions::LOAD_INIT)
44+
.union(ClientOptions::SESSION_ID);
45+
}
46+
3647
impl Default for ClientOptions {
3748
fn default() -> Self {
3849
ClientOptions::NO_START_SERVER

‎src/jack_enums.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::ClientStatus;
1+
use crate::{ClientOptions, ClientStatus};
22

33
/// An error that can occur in JACK.
44
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -10,6 +10,8 @@ pub enum Error {
1010
ClientActivationError,
1111
ClientDeactivationError,
1212
ClientError(ClientStatus),
13+
/// The given `ClientOptions` are not valid for the function they were passed to.
14+
InvalidClientOptions(ClientOptions),
1315
FreewheelError,
1416
InvalidDeactivation,
1517
NotEnoughSpace,
@@ -45,6 +47,9 @@ impl std::fmt::Display for Error {
4547
Error::ClientActivationError => write!(f, "client activation error"),
4648
Error::ClientDeactivationError => write!(f, "client deactivation error"),
4749
Error::ClientError(status) => write!(f, "client error, status is {status:?}"),
50+
Error::InvalidClientOptions(options) => {
51+
write!(f, "invalid client options {options:?}")
52+
}
4853
Error::FreewheelError => write!(f, "freewheel error"),
4954
Error::InvalidDeactivation => write!(f, "invalid deactivation"),
5055
Error::NotEnoughSpace => write!(f, "not enough space"),

‎src/tests/client.rs‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,53 @@ fn client_can_open() {
1414
assert!(cpu_load >= 0.0, "client.cpu_load() = {}", cpu_load);
1515
}
1616

17+
#[test]
18+
fn client_new_rejects_options_that_need_extra_arguments() {
19+
for flag in [
20+
crate::ClientOptions::SERVER_NAME,
21+
crate::ClientOptions::LOAD_NAME,
22+
crate::ClientOptions::LOAD_INIT,
23+
crate::ClientOptions::SESSION_ID,
24+
] {
25+
let options = crate::ClientOptions::default() | flag;
26+
let err = crate::Client::new("invalid options client", options).err();
27+
assert_eq!(err, Some(crate::Error::InvalidClientOptions(options)));
28+
}
29+
}
30+
31+
#[test]
32+
fn client_can_open_with_server_name() {
33+
let (client, _) = crate::Client::new_with_server_name(
34+
"server name client",
35+
"default",
36+
crate::ClientOptions::default(),
37+
)
38+
.unwrap();
39+
assert_eq!(client.name(), "server name client");
40+
41+
// Explicitly passing `SERVER_NAME` is redundant but allowed.
42+
crate::Client::new_with_server_name(
43+
"server name client 2",
44+
"default",
45+
crate::ClientOptions::default() | crate::ClientOptions::SERVER_NAME,
46+
)
47+
.unwrap();
48+
}
49+
50+
#[test]
51+
fn client_with_server_name_rejects_other_options_that_need_extra_arguments() {
52+
for flag in [
53+
crate::ClientOptions::LOAD_NAME,
54+
crate::ClientOptions::LOAD_INIT,
55+
crate::ClientOptions::SESSION_ID,
56+
] {
57+
let options = crate::ClientOptions::default() | flag;
58+
let err =
59+
crate::Client::new_with_server_name("invalid options client", "default", options).err();
60+
assert_eq!(err, Some(crate::Error::InvalidClientOptions(options)));
61+
}
62+
}
63+
1764
#[test]
1865
fn time_is_montonically_increasing() {
1966
let (client, _) = crate::Client::new("", crate::ClientOptions::default()).unwrap();
@@ -114,3 +161,19 @@ fn registering_an_unowned_port_is_an_error() {
114161
Err(crate::Error::PortRegistrationError("p".to_string()))
115162
);
116163
}
164+
165+
#[test]
166+
fn client_with_unknown_server_name_fails_to_open() {
167+
// Proves the server name actually reaches JACK, rather than being dropped.
168+
let err = crate::Client::new_with_server_name(
169+
"unknown server client",
170+
"rust-jack-nonexistent-server",
171+
crate::ClientOptions::NO_START_SERVER,
172+
)
173+
.err();
174+
assert!(
175+
matches!(err, Some(crate::Error::ClientError(_))),
176+
"expected ClientError, got {:?}",
177+
err
178+
);
179+
}

0 commit comments

Comments
 (0)