Skip to content

Commit 3aec8f3

Browse files
committed
✨ zb: Add helper for IBus connection creation
Implement `connection::Builder::ibus()` for connecting with IBus. The IBus address is fetched from the `ibus address` command, handled similarly to how we handle the `launchd` address on macOS. Fixes #964
1 parent d436729 commit 3aec8f3

File tree

7 files changed

+253
-7
lines changed

7 files changed

+253
-7
lines changed

zbus/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,11 @@ uds_windows.workspace = true
9898
[target.'cfg(unix)'.dependencies]
9999
rustix.workspace = true
100100
libc.workspace = true
101-
102-
[target.'cfg(any(target_os = "macos", windows))'.dependencies]
103101
async-recursion.workspace = true
104102

103+
[target.'cfg(windows)'.dependencies.async-recursion]
104+
workspace = true
105+
105106
[dev-dependencies]
106107
zbus_xml.workspace = true
107108

zbus/src/abstractions/process.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[cfg(not(feature = "tokio"))]
22
use async_process::{Child, unix::CommandExt};
3-
#[cfg(target_os = "macos")]
3+
#[cfg(unix)]
44
use std::process::Output;
55
use std::{ffi::OsStr, io::Error, process::Stdio};
66
#[cfg(feature = "tokio")]
@@ -63,7 +63,7 @@ impl Command {
6363

6464
/// Executes the command as a child process, waiting for it to finish and
6565
/// collecting all of its output.
66-
#[cfg(target_os = "macos")]
66+
#[cfg(unix)]
6767
pub async fn output(&mut self) -> Result<Output, Error> {
6868
self.0.output().await
6969
}
@@ -93,7 +93,7 @@ impl Command {
9393
}
9494

9595
/// An asynchronous wrapper around running and getting command output
96-
#[cfg(target_os = "macos")]
96+
#[cfg(unix)]
9797
pub async fn run<I, S>(program: S, args: I) -> Result<Output, Error>
9898
where
9999
I: IntoIterator<Item = S>,

zbus/src/address/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,11 @@ mod tests {
293293
Address::from_str("launchd:env=my_cool_env_key").unwrap(),
294294
Transport::Launchd(Launchd::new("my_cool_env_key")).into(),
295295
);
296+
#[cfg(unix)]
297+
assert_eq!(
298+
Address::from_str("ibus:").unwrap(),
299+
Transport::Ibus(crate::address::transport::Ibus::new()).into(),
300+
);
296301

297302
#[cfg(all(feature = "vsock", feature = "p2p", not(feature = "tokio")))]
298303
{
@@ -391,6 +396,11 @@ mod tests {
391396
Address::from(Transport::Launchd(Launchd::new("my_cool_key"))).to_string(),
392397
"launchd:env=my_cool_key"
393398
);
399+
#[cfg(unix)]
400+
assert_eq!(
401+
Address::from(Transport::Ibus(crate::address::transport::Ibus::new())).to_string(),
402+
"ibus:"
403+
);
394404

395405
#[cfg(all(feature = "vsock", feature = "p2p", not(feature = "tokio")))]
396406
{

zbus/src/address/transport/ibus.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use crate::{Address, Result, process::run};
2+
3+
#[derive(Clone, Debug, PartialEq, Eq)]
4+
#[non_exhaustive]
5+
/// The transport properties of an IBus D-Bus address.
6+
///
7+
/// This transport type queries the IBus daemon for its D-Bus address using the `ibus address`
8+
/// command. IBus (Intelligent Input Bus) is an input method framework used primarily on Linux
9+
/// systems for entering text in various languages.
10+
///
11+
/// # Platform Support
12+
///
13+
/// This transport is available on Unix-like systems where IBus is installed.
14+
///
15+
/// # Example
16+
///
17+
/// ```no_run
18+
/// # use zbus::address::transport::{Transport, Ibus};
19+
/// # use zbus::Address;
20+
/// #
21+
/// // Create an IBus transport
22+
/// let ibus = Ibus::new();
23+
/// let _addr = Transport::Ibus(ibus);
24+
///
25+
/// // Or use it directly as an address
26+
/// let _addr = Address::from(Transport::Ibus(Ibus::new()));
27+
/// ```
28+
pub struct Ibus;
29+
30+
impl Ibus {
31+
/// Create a new IBus transport.
32+
///
33+
/// This will query the IBus daemon for its D-Bus address when the connection is established.
34+
#[must_use]
35+
pub fn new() -> Self {
36+
Self
37+
}
38+
39+
/// Determine the actual transport details behind an IBus address.
40+
///
41+
/// This method executes the `ibus address` command to retrieve the D-Bus address from the
42+
/// running IBus daemon, then parses and returns the underlying transport.
43+
///
44+
/// # Errors
45+
///
46+
/// Returns an error if:
47+
/// - The `ibus` command is not found or fails to execute
48+
/// - The IBus daemon is not running
49+
/// - The command output cannot be parsed as a valid D-Bus address
50+
/// - The command output is not valid UTF-8
51+
///
52+
/// # Example
53+
///
54+
/// ```no_run
55+
/// # use zbus::connection::Builder;
56+
/// # use zbus::block_on;
57+
/// #
58+
/// # block_on(async {
59+
/// // This method is used internally by the connection builder
60+
/// let _conn = Builder::ibus()?.build().await?;
61+
/// # Ok::<(), zbus::Error>(())
62+
/// # }).unwrap();
63+
/// ```
64+
pub(super) async fn bus_address(&self) -> Result<Address> {
65+
let output = run("ibus", ["address"])
66+
.await
67+
.expect("failed to wait on ibus output");
68+
69+
if !output.status.success() {
70+
return Err(crate::Error::Address(format!(
71+
"ibus terminated with code: {}",
72+
output.status
73+
)));
74+
}
75+
76+
let addr = String::from_utf8(output.stdout).map_err(|e| {
77+
crate::Error::Address(format!("Unable to parse ibus output as UTF-8: {e}"))
78+
})?;
79+
80+
addr.trim().parse()
81+
}
82+
83+
/// Parse IBus transport from D-Bus address options.
84+
///
85+
/// The IBus transport type does not require any options, so this method will succeed
86+
/// as long as the transport type is specified as "ibus".
87+
///
88+
/// # Errors
89+
///
90+
/// This method does not return errors for the IBus transport, but the signature is kept
91+
/// consistent with other transport types.
92+
pub(super) fn from_options(_opts: std::collections::HashMap<&str, &str>) -> Result<Self> {
93+
Ok(Self)
94+
}
95+
}
96+
97+
impl Default for Ibus {
98+
fn default() -> Self {
99+
Self::new()
100+
}
101+
}
102+
103+
impl std::fmt::Display for Ibus {
104+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105+
write!(f, "ibus:")
106+
}
107+
}
108+
109+
#[cfg(test)]
110+
mod tests {
111+
use super::*;
112+
113+
#[test]
114+
fn test_ibus_new() {
115+
let ibus = Ibus::new();
116+
assert_eq!(ibus.to_string(), "ibus:");
117+
}
118+
119+
#[test]
120+
fn test_ibus_default() {
121+
let ibus = Ibus::default();
122+
assert_eq!(ibus.to_string(), "ibus:");
123+
}
124+
125+
#[test]
126+
fn test_ibus_from_options() {
127+
let options = std::collections::HashMap::new();
128+
let ibus = Ibus::from_options(options).unwrap();
129+
assert_eq!(ibus, Ibus::new());
130+
}
131+
132+
#[test]
133+
fn test_ibus_display() {
134+
let ibus = Ibus::new();
135+
assert_eq!(format!("{}", ibus), "ibus:");
136+
}
137+
}

zbus/src/address/transport/mod.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ pub use autolaunch::{Autolaunch, AutolaunchScope};
4444
mod launchd;
4545
#[cfg(target_os = "macos")]
4646
pub use launchd::Launchd;
47+
#[cfg(unix)]
48+
mod ibus;
49+
#[cfg(unix)]
50+
pub use ibus::Ibus;
4751
#[cfg(any(
4852
all(feature = "vsock", not(feature = "tokio")),
4953
feature = "tokio-vsock"
@@ -73,6 +77,12 @@ pub enum Transport {
7377
/// A launchd D-Bus address.
7478
#[cfg(target_os = "macos")]
7579
Launchd(Launchd),
80+
/// An IBus D-Bus address.
81+
///
82+
/// IBus (Intelligent Input Bus) is an input method framework. This transport queries the
83+
/// IBus daemon for its D-Bus address using the `ibus address` command.
84+
#[cfg(unix)]
85+
Ibus(Ibus),
7686
#[cfg(any(
7787
all(feature = "vsock", not(feature = "tokio")),
7888
feature = "tokio-vsock"
@@ -89,7 +99,7 @@ pub enum Transport {
8999
}
90100

91101
impl Transport {
92-
#[cfg_attr(any(target_os = "macos", windows), async_recursion::async_recursion)]
102+
#[cfg_attr(any(unix, windows), async_recursion::async_recursion)]
93103
pub(super) async fn connect(self) -> Result<Stream> {
94104
match self {
95105
Transport::Unix(unix) => {
@@ -217,6 +227,12 @@ impl Transport {
217227
let addr = launchd.bus_address().await?;
218228
addr.connect().await
219229
}
230+
231+
#[cfg(unix)]
232+
Transport::Ibus(ibus) => {
233+
let addr = ibus.bus_address().await?;
234+
addr.connect().await
235+
}
220236
}
221237
}
222238

@@ -237,6 +253,8 @@ impl Transport {
237253
"autolaunch" => Autolaunch::from_options(options).map(Self::Autolaunch),
238254
#[cfg(target_os = "macos")]
239255
"launchd" => Launchd::from_options(options).map(Self::Launchd),
256+
#[cfg(unix)]
257+
"ibus" => Ibus::from_options(options).map(Self::Ibus),
240258

241259
_ => Err(Error::Address(format!(
242260
"unsupported transport '{transport}'"
@@ -364,6 +382,8 @@ impl Display for Transport {
364382
Self::Autolaunch(autolaunch) => write!(f, "{autolaunch}")?,
365383
#[cfg(target_os = "macos")]
366384
Self::Launchd(launchd) => write!(f, "{launchd}")?,
385+
#[cfg(unix)]
386+
Self::Ibus(ibus) => write!(f, "{ibus}")?,
367387
}
368388

369389
Ok(())

zbus/src/blocking/connection/builder.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,40 @@ impl<'a> Builder<'a> {
3535
crate::connection::Builder::system().map(Self)
3636
}
3737

38+
/// Create a builder for an IBus connection.
39+
///
40+
/// IBus (Intelligent Input Bus) is an input method framework. This method creates a builder
41+
/// that will query the IBus daemon for its D-Bus address using the `ibus address` command.
42+
///
43+
/// # Platform Support
44+
///
45+
/// This method is available on Unix-like systems where IBus is installed.
46+
///
47+
/// # Errors
48+
///
49+
/// Returns an error if:
50+
/// - The `ibus` command is not found or fails to execute
51+
/// - The IBus daemon is not running
52+
/// - The command output cannot be parsed as a valid D-Bus address
53+
///
54+
/// # Example
55+
///
56+
/// ```no_run
57+
/// # use std::error::Error;
58+
/// # use zbus::blocking::connection;
59+
/// #
60+
/// let conn = connection::Builder::ibus()?
61+
/// .build()?;
62+
///
63+
/// // Use the connection to interact with IBus services
64+
/// # drop(conn);
65+
/// # Ok::<_, Box<dyn Error + Send + Sync>>(())
66+
/// ```
67+
#[cfg(unix)]
68+
pub fn ibus() -> Result<Self> {
69+
crate::connection::Builder::ibus().map(Self)
70+
}
71+
3872
/// Create a builder for a connection that will use the given [D-Bus bus address].
3973
///
4074
/// [D-Bus bus address]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses

zbus/src/connection/builder.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,49 @@ impl<'a> Builder<'a> {
9898
Ok(Self::new(Target::Address(Address::system()?)))
9999
}
100100

101+
/// Create a builder for an IBus connection.
102+
///
103+
/// IBus (Intelligent Input Bus) is an input method framework. This method creates a builder
104+
/// that will query the IBus daemon for its D-Bus address using the `ibus address` command.
105+
///
106+
/// # Platform Support
107+
///
108+
/// This method is available on Unix-like systems where IBus is installed.
109+
///
110+
/// # Errors
111+
///
112+
/// Returns an error if:
113+
/// - The `ibus` command is not found or fails to execute
114+
/// - The IBus daemon is not running
115+
/// - The command output cannot be parsed as a valid D-Bus address
116+
///
117+
/// # Example
118+
///
119+
/// ```no_run
120+
/// # use std::error::Error;
121+
/// # use zbus::connection::Builder;
122+
/// # use zbus::block_on;
123+
/// #
124+
/// # block_on(async {
125+
/// let conn = Builder::ibus()?
126+
/// .build()
127+
/// .await?;
128+
///
129+
/// // Use the connection to interact with IBus services
130+
/// # drop(conn);
131+
/// # Ok::<(), zbus::Error>(())
132+
/// # }).unwrap();
133+
/// #
134+
/// # Ok::<_, Box<dyn Error + Send + Sync>>(())
135+
/// ```
136+
#[cfg(unix)]
137+
pub fn ibus() -> Result<Self> {
138+
use crate::address::transport::{Ibus, Transport};
139+
Ok(Self::new(Target::Address(Address::from(Transport::Ibus(
140+
Ibus::new(),
141+
)))))
142+
}
143+
101144
/// Create a builder for a connection that will use the given [D-Bus bus address].
102145
///
103146
/// # Example
@@ -126,7 +169,8 @@ impl<'a> Builder<'a> {
126169
/// ```
127170
///
128171
/// **Note:** The IBus address is different for each session. You can find the address for your
129-
/// current session using `ibus address` command.
172+
/// current session using `ibus address` command. For a more convenient way to connect to IBus,
173+
/// see [`Builder::ibus`].
130174
///
131175
/// [D-Bus bus address]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
132176
pub fn address<A>(address: A) -> Result<Self>

0 commit comments

Comments
 (0)