1
0
Fork 0
forked from wry/wry

add unix socket ipc

This commit is contained in:
kossLAN 2026-06-08 19:56:17 -04:00
parent dc62d2240f
commit f92c092acc
No known key found for this signature in database
9 changed files with 1550 additions and 2 deletions

View file

@ -13,6 +13,7 @@ use {
cmm::cmm_primaries::Primaries,
format::{Format, XRGB8888},
ifs::wl_output::BlendSpace,
ipc,
tools::tool_client::{Handle, ToolClient, with_tool_client},
tree::Transform,
utils::{errorfmt::ErrorFmt, ordered_float::F64, static_text::StaticText},
@ -501,12 +502,97 @@ pub struct RemoveVirtualOutputArgs {
}
pub fn main(global: GlobalArgs, args: RandrArgs) {
if try_ipc(&global, &args) {
return;
}
with_tool_client(global.log_level, |tc| async move {
let idle = Rc::new(Randr { tc: tc.clone() });
idle.run(&global, args).await;
});
}
fn try_ipc(global: &GlobalArgs, args: &RandrArgs) -> bool {
match &args.command {
None => try_ipc_show(global, &ShowArgs::default()),
Some(RandrCmd::Show(args)) => try_ipc_show(global, args),
Some(RandrCmd::Output(args)) => try_ipc_output(args),
Some(RandrCmd::Card(_)) | Some(RandrCmd::VirtualOutput(_)) => false,
}
}
fn try_ipc_show(global: &GlobalArgs, args: &ShowArgs) -> bool {
let data: ipc::Outputs = match ipc::request(&ipc::Request::OutputsGet) {
Ok(data) => data,
Err(e) if e.can_fallback() => return false,
Err(e) => fatal!("Could not query outputs over IPC: {}", ErrorFmt(e)),
};
if global.json {
show_ipc_json(&data);
} else {
show_ipc_text(&data, args);
}
true
}
fn try_ipc_output(args: &OutputArgs) -> bool {
let request = match &args.command {
OutputCommand::Transform(transform) => ipc::Request::OutputsSetTransform {
output: args.output.clone(),
transform: transform_cmd_text(&transform.command).to_string(),
},
OutputCommand::Scale(scale) => ipc::Request::OutputsSetScale {
output: args.output.clone(),
scale: scale.scale,
round_to_float: scale.round_to_float,
},
OutputCommand::Mode(mode) => ipc::Request::OutputsSetMode {
output: args.output.clone(),
width: mode.width,
height: mode.height,
refresh_rate: mode.refresh_rate,
},
OutputCommand::Position(position) => ipc::Request::OutputsSetPosition {
output: args.output.clone(),
x: position.x,
y: position.y,
},
OutputCommand::Enable => ipc::Request::OutputsSetEnabled {
output: args.output.clone(),
enabled: true,
},
OutputCommand::Disable => ipc::Request::OutputsSetEnabled {
output: args.output.clone(),
enabled: false,
},
OutputCommand::NonDesktop(_)
| OutputCommand::Vrr(_)
| OutputCommand::Tearing(_)
| OutputCommand::Format(_)
| OutputCommand::Colors(_)
| OutputCommand::Brightness(_)
| OutputCommand::BlendSpace(_)
| OutputCommand::UseNativeGamut(_) => return false,
};
match ipc::request_unit(&request) {
Ok(()) => true,
Err(e) if e.can_fallback() => false,
Err(e) => fatal!("Could not modify output over IPC: {}", ErrorFmt(e)),
}
}
fn transform_cmd_text(cmd: &TransformCmd) -> &'static str {
match cmd {
TransformCmd::None => "none",
TransformCmd::Rotate90 => "rotate-90",
TransformCmd::Rotate180 => "rotate-180",
TransformCmd::Rotate270 => "rotate-270",
TransformCmd::Flip => "flip",
TransformCmd::FlipRotate90 => "flip-rotate-90",
TransformCmd::FlipRotate180 => "flip-rotate-180",
TransformCmd::FlipRotate270 => "flip-rotate-270",
}
}
#[derive(Clone, Debug)]
struct Device {
pub id: u64,
@ -1474,3 +1560,310 @@ fn make_json_connector(c: &Connector) -> JsonConnector<'_> {
output,
}
}
fn show_ipc_json(data: &ipc::Outputs) {
let json = JsonRandrData {
drm_devices: data.drm_devices.iter().map(make_ipc_json_device).collect(),
unbound_connectors: data
.unbound_connectors
.iter()
.map(make_ipc_json_connector)
.collect(),
};
jsonl(&json);
}
fn make_ipc_json_device(dev: &ipc::DrmDevice) -> JsonDrmDevice<'_> {
JsonDrmDevice {
devnode: &dev.devnode,
syspath: &dev.syspath,
vendor: dev.vendor,
vendor_name: &dev.vendor_name,
model: dev.model,
model_name: &dev.model_name,
gfx_api: &dev.gfx_api,
render_device: dev.render_device,
connectors: dev.connectors.iter().map(make_ipc_json_connector).collect(),
}
}
fn make_ipc_json_connector(c: &ipc::Connector) -> JsonConnector<'_> {
let output = c.output.as_ref().map(|o| {
let modes = o
.modes
.iter()
.map(|m| JsonMode {
width: m.width,
height: m.height,
refresh_rate_millihz: m.refresh_rate_millihz,
current: m.current,
})
.collect();
let formats = o.formats.iter().map(|f| f.as_str()).collect();
JsonOutput {
product: &o.product,
manufacturer: &o.manufacturer,
serial_number: &o.serial_number,
width_mm: o.width_mm,
height_mm: o.height_mm,
non_desktop: o.non_desktop,
scale: o.scale,
x: o.x,
y: o.y,
width: o.width,
height: o.height,
transform: ipc_transform(&o.transform).text(),
mode: o.mode.map(|m| JsonMode {
width: m.width,
height: m.height,
refresh_rate_millihz: m.refresh_rate_millihz,
current: m.current,
}),
format: o.format.as_deref(),
vrr_capable: o.vrr_capable,
vrr_enabled: o.vrr_enabled,
vrr_mode: JsonVrrMode(VrrMode(o.vrr_mode)),
vrr_cursor_hz: o.vrr_cursor_hz,
tearing_mode: JsonTearingMode(TearingMode(o.tearing_mode)),
flip_margin_ns: o.flip_margin_ns,
supported_color_spaces: o
.supported_color_spaces
.iter()
.map(|s| s.as_str())
.collect(),
current_color_space: o.current_color_space.as_deref(),
supported_eotfs: o.supported_eotfs.iter().map(|s| s.as_str()).collect(),
current_eotf: o.current_eotf.as_deref(),
min_brightness: o.min_brightness,
max_brightness: o.max_brightness,
brightness: o.brightness,
blend_space: o.blend_space.as_deref(),
native_gamut: o.native_gamut.as_ref().map(|p| JsonPrimaries {
r_x: p.r_x,
r_y: p.r_y,
g_x: p.g_x,
g_y: p.g_y,
b_x: p.b_x,
b_y: p.b_y,
w_x: p.w_x,
w_y: p.w_y,
}),
use_native_gamut: o.use_native_gamut,
arbitrary_modes: o.arbitrary_modes,
modes,
formats,
}
});
JsonConnector {
name: &c.name,
enabled: c.enabled,
output,
}
}
fn show_ipc_text(data: &ipc::Outputs, args: &ShowArgs) {
if data.drm_devices.is_not_empty() {
println!("drm devices:");
}
for dev in &data.drm_devices {
print_ipc_drm_device(dev);
println!(" connectors:");
for connector in &dev.connectors {
print_ipc_connector(connector, args.modes, args.formats);
}
}
if data.unbound_connectors.is_not_empty() {
println!("unbound connectors:");
for connector in &data.unbound_connectors {
print_ipc_connector(connector, args.modes, args.formats);
}
}
}
fn print_ipc_drm_device(dev: &ipc::DrmDevice) {
println!(" {}:", dev.devnode);
println!(" model: {} {}", dev.vendor_name, dev.model_name);
println!(" pci-id: {:x}:{:x}", dev.vendor, dev.model);
println!(" syspath: {}", dev.syspath);
println!(" api: {}", dev.gfx_api);
if dev.render_device {
println!(" primary device");
}
}
fn print_ipc_connector(connector: &ipc::Connector, modes: bool, formats: bool) {
println!(" {}:", connector.name);
if !connector.enabled {
println!(" disabled");
}
let Some(o) = &connector.output else {
if connector.enabled {
println!(" disconnected");
}
return;
};
println!(" product: {}", o.product);
println!(" manufacturer: {}", o.manufacturer);
println!(" serial number: {}", o.serial_number);
println!(
" physical size: {}mm x {}mm",
o.width_mm, o.height_mm
);
if o.non_desktop {
if connector.enabled {
println!(" non-desktop");
}
return;
}
println!(" VRR capable: {}", o.vrr_capable);
if o.vrr_capable {
println!(" VRR enabled: {}", o.vrr_enabled);
println!(" VRR mode: {}", vrr_mode_text(o.vrr_mode));
if let Some(hz) = o.vrr_cursor_hz {
println!(" VRR cursor hz: {}", hz);
}
}
println!(
" Tearing mode: {}",
tearing_mode_text(o.tearing_mode)
);
println!(" position: {} x {}", o.x, o.y);
println!(" logical size: {} x {}", o.width, o.height);
if let Some(mode) = &o.mode {
println!(" mode: {}", mode_text(mode));
}
if let Some(format) = &o.format
&& format != XRGB8888.name
{
println!(" format: {format}");
}
if o.scale != 1.0 {
println!(" scale: {}", o.scale);
}
if o.transform != "none" {
println!(" transform: {}", o.transform);
}
if let Some(flip_margin_ns) = o.flip_margin_ns {
println!(
" flip margin: {:?}",
Duration::from_nanos(flip_margin_ns)
);
}
if o.supported_color_spaces.is_not_empty() {
println!(" color spaces:");
print_current_list("default", o.current_color_space.as_deref());
for cs in &o.supported_color_spaces {
print_current_list(cs, o.current_color_space.as_deref());
}
}
if o.supported_eotfs.is_not_empty() {
println!(" eotfs:");
print_current_list("default", o.current_eotf.as_deref());
for eotf in &o.supported_eotfs {
print_current_list(eotf, o.current_eotf.as_deref());
}
}
match (o.min_brightness, o.max_brightness) {
(Some(min), Some(max)) => {
println!(" min brightness: {:>10.4} cd/m^2", min);
println!(" max brightness: {:>10.4} cd/m^2", max);
}
_ => println!(" max brightness: {:>10.4} cd/m^2 (implied)", 80.0),
}
if let Some(lux) = o.brightness {
println!(" brightness: {:>10.4} cd/m^2", lux);
}
if let Some(bs) = &o.blend_space {
println!(" blend space: {bs}");
}
if let Some(p) = &o.native_gamut {
println!(
" native gamut:{}",
fmt::from_fn(|f| {
if o.use_native_gamut {
f.write_str(" (used for default color space)")?;
}
Ok(())
}),
);
println!(
" red: {:.6} {:.6} green: {:.6} {:.6}",
p.r_x, p.r_y, p.g_x, p.g_y
);
println!(
" blue: {:.6} {:.6} white: {:.6} {:.6}",
p.b_x, p.b_y, p.w_x, p.w_y
);
}
if o.arbitrary_modes {
println!(" supports arbitrary modes");
}
if o.modes.is_not_empty() && modes {
println!(" modes:");
for mode in &o.modes {
print!(" {}", mode_text(mode));
if mode.current {
print!(" (current)");
}
println!();
}
}
if o.formats.is_not_empty() && formats {
println!(" formats:");
for format in &o.formats {
println!(" {format}");
}
}
}
fn print_current_list(value: &str, current: Option<&str>) {
let current = match current == Some(value) {
true => " (current)",
false => "",
};
println!(" {value}{current}");
}
fn mode_text(mode: &ipc::Mode) -> String {
format!(
"{} x {} @ {}",
mode.width,
mode.height,
mode.refresh_rate_millihz as f64 / 1000.0
)
}
fn vrr_mode_text(mode: u32) -> String {
match VrrMode(mode) {
VrrMode::NEVER => "never".to_string(),
VrrMode::ALWAYS => "always".to_string(),
VrrMode::VARIANT_1 => "variant1".to_string(),
VrrMode::VARIANT_2 => "variant2".to_string(),
VrrMode::VARIANT_3 => "variant3".to_string(),
_ => format!("unknown ({mode})"),
}
}
fn tearing_mode_text(mode: u32) -> String {
match TearingMode(mode) {
TearingMode::NEVER => "never".to_string(),
TearingMode::ALWAYS => "always".to_string(),
TearingMode::VARIANT_1 => "variant1".to_string(),
TearingMode::VARIANT_2 => "variant2".to_string(),
TearingMode::VARIANT_3 => "variant3".to_string(),
_ => format!("unknown ({mode})"),
}
}
fn ipc_transform(transform: &str) -> Transform {
match transform {
"rotate-90" => Transform::Rotate90,
"rotate-180" => Transform::Rotate180,
"rotate-270" => Transform::Rotate270,
"flip" => Transform::Flip,
"flip-rotate-90" => Transform::FlipRotate90,
"flip-rotate-180" => Transform::FlipRotate180,
"flip-rotate-270" => Transform::FlipRotate270,
_ => Transform::None,
}
}