Skip to content

Commit 08653ce

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 08653ce

File tree

8 files changed

+314
-7
lines changed

8 files changed

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

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)