Skip to content

Commit

Permalink
Merge pull request #25 from acheronfail/next
Browse files Browse the repository at this point in the history
0.12.0
  • Loading branch information
acheronfail authored Jan 8, 2024
2 parents 5f7a6c6 + fae1ba6 commit 830e184
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "i3stat"
version = "0.11.0"
version = "0.12.0"
edition = "2021"
authors = ["acheronfail <[email protected]>"]
description = "A lightweight and batteries-included status_command for i3 and sway"
Expand All @@ -11,6 +11,10 @@ keywords = ["i3", "sway", "status_command", "i3stat", "status"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[[bin]]
name = "i3stat-net"
path = "bin/net.rs"

[[bin]]
name = "i3stat-acpi"
path = "bin/acpi.rs"
Expand Down
2 changes: 1 addition & 1 deletion IDEAS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ There's no guarantee they'll ever be added or implemented, and they'll likely be

## Improvements

* ...
* add an aarch64 build to github releases
9 changes: 8 additions & 1 deletion bin/acpi.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use clap::{ColorChoice, Parser};
use i3stat::error::Result;
use i3stat::util::{local_block_on, netlink_acpi_listen};
use tokio::io::{stdout, AsyncWriteExt};

#[derive(Debug, Parser)]
#[clap(author, version, long_about, name = "i3stat-acpi", color = ColorChoice::Always)]
Expand All @@ -16,7 +17,13 @@ fn main() -> Result<()> {
let (output, _) = local_block_on(async {
let mut acpi = netlink_acpi_listen().await?;
while let Some(event) = acpi.recv().await {
println!("{}", serde_json::to_string(&event)?);
let line = format!("{}", serde_json::to_string(&event)?);

// flush output each time to facilitate common usage patterns, such as
// `i3stat-acpi | while read x; do ... done`, etc.
let mut stdout = stdout();
stdout.write_all(line.as_bytes()).await?;
stdout.flush().await?;
}

Err("unexpected end of acpi event stream".into())
Expand Down
98 changes: 98 additions & 0 deletions bin/net.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use clap::{ColorChoice, Parser, Subcommand};
use futures::future::join_all;
use i3stat::error::Result;
use i3stat::util::route::InterfaceUpdate;
use i3stat::util::{local_block_on, netlink_ipaddr_listen};
use serde_json::json;
use tokio::io::{stdout, AsyncWriteExt};
use tokio::sync::mpsc;

#[derive(Debug, Parser)]
#[clap(author, version, long_about, name = "i3stat-net", color = ColorChoice::Always)]
/// A command which prints network/80211 information gathered from netlink.
///
/// Each line is a JSON array which contains a list of all interfaces reported
/// by netlink. Wireless interfaces also print 80211 information.
struct Cli {
#[command(subcommand)]
command: Command,
}

#[derive(Debug, Copy, Clone, Subcommand)]
enum Command {
/// Print current network interfaces
Info,
/// Watch and print network interfaces whenever a network address change is detected.
Watch,
}

fn main() -> Result<()> {
let args = Cli::parse();

let (output, _) = local_block_on(async {
let (manual_tx, manual_rx) = mpsc::channel(1);
let mut rx = netlink_ipaddr_listen(manual_rx).await?;
manual_tx.send(()).await?;

if let Command::Info = args.command {
match rx.recv().await {
Some(interfaces) => print_interfaces(&interfaces).await?,
None => println!("null"),
}

return Ok(());
}

while let Some(interfaces) = rx.recv().await {
print_interfaces(&interfaces).await?;
}

Err("Unexpected end of netlink subscription".into())
})?;

output
}

async fn print_interfaces(interfaces: &InterfaceUpdate) -> Result<()> {
let s = format!(
"{}\n",
json!(
join_all(interfaces.values().map(|interface| async {
json!({
"index": interface.index,
"name": interface.name,
"mac": interface.mac_address.as_ref().map(|m| m.to_string()),
"ips": interface.ip_addresses.iter().collect::<Vec<_>>(),
"wireless": interface.wireless_info().await.map(|info| json!({
"index": info.index,
"interface": info.interface,
"mac": info.mac_addr.to_string(),
"ssid": info.ssid,
"bssid": info.bssid.as_ref().map(|m| m.to_string()),
"signal": info.signal.as_ref().map(|s| json!({
"dbm": s.dbm,
"link": s.link,
"quality": s.quality()
}))
}))
})
}))
.await
)
);

// flush output each time to facilitate common usage patterns like
// `i3stat-net watch | while read x; do ... done`, etc.
let mut stdout = stdout();
stdout.write_all(s.as_bytes()).await?;
stdout.flush().await?;

Ok(())
}

#[cfg(test)]
#[path = "../src/test_utils.rs"]
mod test_utils;

#[cfg(test)]
crate::gen_manpage!(Cli);
34 changes: 23 additions & 11 deletions src/util/netlink/acpi/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use ::std::os::raw::{c_char, c_uint};
use serde_derive::{Deserialize, Serialize};
use std::any::TypeId;

use crate::error::{Error, Result};

Expand Down Expand Up @@ -50,17 +51,28 @@ impl AcpiGenericNetlinkEvent {
/// Checks a slice of C's chars to ensure they're not signed, needed because C's `char` type could
/// be either signed or unsigned unless specified. See: https://stackoverflow.com/a/2054941/5552584
fn get_u8_bytes(slice: &[c_char]) -> Result<Vec<u8>> {
slice
.into_iter()
.take_while(|c| **c != 0)
.map(|c| -> Result<u8> {
if *c < 0 {
Err(format!("slice contained signed char: {}", c).into())
} else {
Ok(*c as u8)
}
})
.collect::<Result<Vec<_>>>()
// NOTE: on some platforms `c_char` is `i8` and on others it's `u8`. Instead of targeting those
// directly with `#cfg[...]` attributes (because it's not straightforward, have a look at the
// cfg match for `c_char` in the stdlib, it's huge) we instead perform a runtime comparison
// with `TypeId` here.
// According to my tests (with `cargo-show-asm`), this is always optimised out completely since
// `TypeId` returns a constant value, so it's just as good as a compile-time check.
if TypeId::of::<c_char>() == TypeId::of::<i8>() {
slice
.into_iter()
.take_while(|c| **c != 0)
.map(|c| -> Result<u8> {
#[allow(unused_comparisons)]
if *c < 0 {
Err(format!("slice contained signed char: {}", c).into())
} else {
Ok(*c as u8)
}
})
.collect::<Result<Vec<_>>>()
} else {
Ok(slice.iter().map(|&c| c as u8).collect())
}
}

impl<'a> TryFrom<&'a acpi_genl_event> for AcpiGenericNetlinkEvent {
Expand Down

0 comments on commit 830e184

Please sign in to comment.