mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 22:26:33 +00:00
feat: DPMS fallback via wlr-randr for non-CEC desktop monitors
This commit is contained in:
parent
cbb1683c5d
commit
c0704be343
2 changed files with 80 additions and 28 deletions
104
kiosk/src/cec.rs
104
kiosk/src/cec.rs
|
|
@ -1,44 +1,49 @@
|
|||
//! CEC (HDMI Consumer Electronics Control) — manages display power state
|
||||
//! via `cec-ctl` subprocess. v4l-utils package provides cec-ctl on Pi5.
|
||||
//! Display power management — CEC for TVs, DPMS (wlr-randr/xset) for monitors.
|
||||
//!
|
||||
//! Commands:
|
||||
//! - standby: tell TV to sleep
|
||||
//! - image-view-on: wake TV
|
||||
//! - is-active-source: query state
|
||||
//! Tries CEC first (works for HDMI TVs).
|
||||
//! Falls back to compositor-level output disable for desktop monitors that
|
||||
//! don't speak CEC.
|
||||
|
||||
use std::process::Command;
|
||||
use tracing::{info, warn};
|
||||
|
||||
const CEC_DEVICE: &str = "/dev/cec0";
|
||||
|
||||
/// Send CEC standby (sleep) to all connected devices.
|
||||
pub fn standby() -> bool {
|
||||
info!("cec: standby");
|
||||
/// Put the display to sleep — try CEC first, fall back to compositor DPMS.
|
||||
pub fn standby() {
|
||||
info!("power: standby");
|
||||
if !cec_standby() {
|
||||
wlr_output_off();
|
||||
}
|
||||
}
|
||||
|
||||
/// Wake the display — try CEC first, fall back to compositor DPMS.
|
||||
pub fn wake() {
|
||||
info!("power: wake");
|
||||
if !cec_wake() {
|
||||
wlr_output_on();
|
||||
}
|
||||
}
|
||||
|
||||
fn cec_standby() -> bool {
|
||||
run_cec(&["--standby", "--to", "0"])
|
||||
}
|
||||
|
||||
/// Send CEC image-view-on (wake) to TV.
|
||||
pub fn wake() -> bool {
|
||||
info!("cec: wake");
|
||||
fn cec_wake() -> bool {
|
||||
run_cec(&["--image-view-on", "--to", "0"])
|
||||
}
|
||||
|
||||
/// Switch HDMI input on TV to this device.
|
||||
pub fn become_active_source() -> bool {
|
||||
info!("cec: become active source");
|
||||
run_cec(&["--active-source", "phys-addr=0.0.0.0"])
|
||||
}
|
||||
|
||||
fn run_cec(args: &[&str]) -> bool {
|
||||
match Command::new("cec-ctl")
|
||||
.arg("-d")
|
||||
.arg(CEC_DEVICE)
|
||||
.args(args)
|
||||
.output()
|
||||
{
|
||||
Ok(out) if out.status.success() => true,
|
||||
if !std::path::Path::new(CEC_DEVICE).exists() {
|
||||
return false;
|
||||
}
|
||||
match Command::new("cec-ctl").arg("-d").arg(CEC_DEVICE).args(args).output() {
|
||||
Ok(out) if out.status.success() => {
|
||||
info!("cec: ok");
|
||||
true
|
||||
}
|
||||
Ok(out) => {
|
||||
warn!("cec-ctl failed: {}", String::from_utf8_lossy(&out.stderr));
|
||||
warn!("cec failed: {}", String::from_utf8_lossy(&out.stderr));
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
@ -47,3 +52,50 @@ fn run_cec(args: &[&str]) -> bool {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn off all outputs via wlr-randr (Wayland compositors: labwc, wayfire, sway).
|
||||
fn wlr_output_off() {
|
||||
// Get list of outputs
|
||||
let outputs = list_outputs();
|
||||
if outputs.is_empty() {
|
||||
warn!("dpms: no outputs found");
|
||||
return;
|
||||
}
|
||||
for output in outputs {
|
||||
match Command::new("wlr-randr")
|
||||
.args(["--output", &output, "--off"])
|
||||
.output()
|
||||
{
|
||||
Ok(out) if out.status.success() => info!("dpms: {output} off"),
|
||||
Ok(out) => warn!("dpms off {output} failed: {}", String::from_utf8_lossy(&out.stderr)),
|
||||
Err(e) => warn!("wlr-randr unavailable: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wlr_output_on() {
|
||||
let outputs = list_outputs();
|
||||
for output in outputs {
|
||||
match Command::new("wlr-randr")
|
||||
.args(["--output", &output, "--on"])
|
||||
.output()
|
||||
{
|
||||
Ok(out) if out.status.success() => info!("dpms: {output} on"),
|
||||
Ok(out) => warn!("dpms on {output} failed: {}", String::from_utf8_lossy(&out.stderr)),
|
||||
Err(e) => warn!("wlr-randr unavailable: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list_outputs() -> Vec<String> {
|
||||
let out = match Command::new("wlr-randr").output() {
|
||||
Ok(out) => out,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
text.lines()
|
||||
.filter(|l| !l.starts_with(' ') && !l.is_empty())
|
||||
.map(|l| l.split_whitespace().next().unwrap_or("").to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ fn activate(app: &Application) {
|
|||
let bundle = server::fetch_bundle(&server_for_reload, &key_for_reload);
|
||||
let _ = tx_for_reload.send(WorkerMsg::RenderBundle(bundle));
|
||||
}
|
||||
ServerMsg::Standby => { cec::standby(); }
|
||||
ServerMsg::Wake => { cec::wake(); }
|
||||
ServerMsg::Standby => cec::standby(),
|
||||
ServerMsg::Wake => cec::wake(),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue