mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 23:26:34 +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
|
//! Display power management — CEC for TVs, DPMS (wlr-randr/xset) for monitors.
|
||||||
//! via `cec-ctl` subprocess. v4l-utils package provides cec-ctl on Pi5.
|
|
||||||
//!
|
//!
|
||||||
//! Commands:
|
//! Tries CEC first (works for HDMI TVs).
|
||||||
//! - standby: tell TV to sleep
|
//! Falls back to compositor-level output disable for desktop monitors that
|
||||||
//! - image-view-on: wake TV
|
//! don't speak CEC.
|
||||||
//! - is-active-source: query state
|
|
||||||
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
const CEC_DEVICE: &str = "/dev/cec0";
|
const CEC_DEVICE: &str = "/dev/cec0";
|
||||||
|
|
||||||
/// Send CEC standby (sleep) to all connected devices.
|
/// Put the display to sleep — try CEC first, fall back to compositor DPMS.
|
||||||
pub fn standby() -> bool {
|
pub fn standby() {
|
||||||
info!("cec: 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"])
|
run_cec(&["--standby", "--to", "0"])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send CEC image-view-on (wake) to TV.
|
fn cec_wake() -> bool {
|
||||||
pub fn wake() -> bool {
|
|
||||||
info!("cec: wake");
|
|
||||||
run_cec(&["--image-view-on", "--to", "0"])
|
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 {
|
fn run_cec(args: &[&str]) -> bool {
|
||||||
match Command::new("cec-ctl")
|
if !std::path::Path::new(CEC_DEVICE).exists() {
|
||||||
.arg("-d")
|
return false;
|
||||||
.arg(CEC_DEVICE)
|
}
|
||||||
.args(args)
|
match Command::new("cec-ctl").arg("-d").arg(CEC_DEVICE).args(args).output() {
|
||||||
.output()
|
Ok(out) if out.status.success() => {
|
||||||
{
|
info!("cec: ok");
|
||||||
Ok(out) if out.status.success() => true,
|
true
|
||||||
|
}
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
warn!("cec-ctl failed: {}", String::from_utf8_lossy(&out.stderr));
|
warn!("cec failed: {}", String::from_utf8_lossy(&out.stderr));
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Err(e) => {
|
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 bundle = server::fetch_bundle(&server_for_reload, &key_for_reload);
|
||||||
let _ = tx_for_reload.send(WorkerMsg::RenderBundle(bundle));
|
let _ = tx_for_reload.send(WorkerMsg::RenderBundle(bundle));
|
||||||
}
|
}
|
||||||
ServerMsg::Standby => { cec::standby(); }
|
ServerMsg::Standby => cec::standby(),
|
||||||
ServerMsg::Wake => { cec::wake(); }
|
ServerMsg::Wake => cec::wake(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue