diff --git a/Cargo.lock b/Cargo.lock index 08ded537..2263f214 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -609,6 +609,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "jzon" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ab85f84ca42c5ec520e6f3c9966ba1fd62909ce260f8837e248857d2560509" + [[package]] name = "keyframe" version = "1.1.1" @@ -1103,6 +1109,7 @@ dependencies = [ "fast_image_resize", "fastrand", "image", + "jzon", "resvg", "rustix", ] diff --git a/client/Cargo.toml b/client/Cargo.toml index abd6b968..0b3ca0a6 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -29,6 +29,7 @@ image = { version = "0.25", default-features = false, features = [ "tiff", "webp", ] } +jzon = { version = "0.12", default-features = false } resvg = { version = "0.45" } rustix = { version = "1.1", default-features = false, features = [ "fs" ] } fast_image_resize = "5.3" diff --git a/client/src/cli.rs b/client/src/cli.rs index c1de931f..3073ba1f 100644 --- a/client/src/cli.rs +++ b/client/src/cli.rs @@ -265,6 +265,10 @@ pub struct Query { #[arg(short, long, default_value = "false")] pub all: bool, + /// Print the information in `json` format + #[arg(short, long, default_value = "false")] + pub json: bool, + /// The daemon's namespace. /// /// The resulting namespace will be 'swww-daemon' appended to what you pass in this argument. diff --git a/client/src/main.rs b/client/src/main.rs index 99c3297d..c24cf65c 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -2,7 +2,7 @@ use std::{str::FromStr, time::Duration}; use clap::Parser; use common::cache; -use common::ipc::{self, Answer, Client, IpcSocket, RequestSend}; +use common::ipc::{self, Answer, BgInfo, Client, IpcSocket, RequestSend}; use common::mmap::Mmap; mod imgproc; @@ -42,8 +42,9 @@ fn main() -> Result<(), String> { } }; - for namespace in namespaces { - let socket = IpcSocket::connect(&namespace).map_err(|err| err.to_string())?; + let mut infos = Vec::new(); + for namespace in &namespaces { + let socket = IpcSocket::connect(namespace).map_err(|err| err.to_string())?; loop { RequestSend::Ping.send(&socket)?; let bytes = socket.recv().map_err(|err| err.to_string())?; @@ -58,22 +59,66 @@ fn main() -> Result<(), String> { std::thread::sleep(Duration::from_millis(1)); } - process_swww_args(&swww, &namespace)?; + if let Some(info) = process_swww_args(&swww, namespace)? { + infos.push(info); + } + } + + if !infos.is_empty() { + if let Swww::Query(query) = swww + && query.json + { + use jzon::{JsonValue, object, stringify_pretty}; + let mut buf = String::new(); + for (namespace, infos) in namespaces.iter().zip(infos) { + let mut arr = JsonValue::new_array(); + for info in infos { + let displaying = match info.img { + ipc::BgImg::Color(color) => { + object! { color: format!("#{:x}", u32::from_ne_bytes(color)) } + } + ipc::BgImg::Img(img) => { + object! { image: img.as_ref() } + } + }; + _ = arr.push(object! { + name: info.name.as_ref(), + width: info.dim.0, + height: info.dim.1, + scale: info.scale_factor.to_f32(), + displaying: displaying + }); + } + buf = format!("{buf}\n\"{namespace}\": {},", stringify_pretty(arr, 4)); + } + buf.pop(); // delete trailing comma + println!("{{{buf}\n}}"); + } else { + for (namespace, infos) in namespaces.iter().zip(infos) { + for info in infos { + println!("{namespace}: {info}"); + } + } + } } Ok(()) } -fn process_swww_args(args: &Swww, namespace: &str) -> Result<(), String> { +fn process_swww_args(args: &Swww, namespace: &str) -> Result>, String> { let request = match make_request(args, namespace)? { Some(request) => request, - None => return Ok(()), + None => return Ok(None), }; let socket = IpcSocket::connect(namespace).map_err(|err| err.to_string())?; request.send(&socket)?; let bytes = socket.recv().map_err(|err| err.to_string())?; drop(socket); match Answer::receive(bytes) { - Answer::Info(info) => info.iter().for_each(|i| println!("{namespace}: {i}")), + Answer::Info(infos) => { + if let Swww::Query(_) = args { + return Ok(Some(infos)); + } + } Answer::Ok => { if let Swww::Kill(_) = args { #[cfg(debug_assertions)] @@ -83,7 +128,7 @@ fn process_swww_args(args: &Swww, namespace: &str) -> Result<(), String> { let path = IpcSocket::::path(namespace); for _ in 0..tries { if rustix::fs::access(&path, rustix::fs::Access::EXISTS).is_err() { - return Ok(()); + return Ok(None); } std::thread::sleep(Duration::from_millis(100)); } @@ -94,10 +139,10 @@ fn process_swww_args(args: &Swww, namespace: &str) -> Result<(), String> { } } Answer::Ping(_) => { - return Ok(()); + return Ok(None); } } - Ok(()) + Ok(None) } fn make_request(args: &Swww, namespace: &str) -> Result, String> { @@ -405,5 +450,6 @@ fn restore_output(output: &str, namespace: &str) -> Result<(), String> { transition_wave: (0.0, 0.0), }), namespace, - ) + )?; + Ok(()) } diff --git a/common/src/ipc/types.rs b/common/src/ipc/types.rs index f2ad2ea4..56a77655 100644 --- a/common/src/ipc/types.rs +++ b/common/src/ipc/types.rs @@ -177,6 +177,14 @@ impl Scale { } } } + + #[must_use] + pub fn to_f32(&self) -> f32 { + match self { + Scale::Output(i) | Scale::Preferred(i) => i.get() as f32, + Scale::Fractional(f) => f.get() as f32 / 120.0, + } + } } impl PartialEq for Scale { @@ -193,14 +201,7 @@ impl PartialEq for Scale { impl fmt::Display for Scale { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Scale::Output(i) | Scale::Preferred(i) => i.get() as f32, - Scale::Fractional(f) => f.get() as f32 / 120.0, - } - ) + write!(f, "{}", self.to_f32()) } } diff --git a/doc/swww-query.1.scd b/doc/swww-query.1.scd index 2628d141..d211731c 100644 --- a/doc/swww-query.1.scd +++ b/doc/swww-query.1.scd @@ -11,6 +11,9 @@ swww-query *-a*,*--all* Send this command to all active *swww-daemon* namespaces. +*-j*,*--j* + Print the information in *json* format. + *-n*,*--namespace* Which wayland namespace to send this command to.