blob: ac7f8355c02b58ab3d4636c77df2e76f3c85dc89 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{Context as _, Error};
use argh::FromArgs;
use fidl_fuchsia_hardware_backlight as backlight;
use crate::utils::{self, on_off_to_bool};
/// Obtains a handle to the backlight service at the default hard-coded path.
fn open_backlight() -> Result<backlight::DeviceProxy, Error> {
tracing::trace!("Opening backlight device");
let (proxy, server) = fidl::endpoints::create_proxy::<backlight::DeviceMarker>()
.context("Failed to create fuchsia.hardware.backlight.Device proxy")?;
fdio::service_connect("/dev/class/backlight/000", server.into_channel())
.context("Failed to connect to default backlight service")?;
Ok(proxy)
}
#[derive(Debug)]
struct Backlight {
device: backlight::DeviceProxy,
}
/// Read and change the panel's backlight configuration.
#[derive(FromArgs, Debug, PartialEq)]
#[argh(subcommand, name = "backlight")]
pub struct BacklightCmd {
/// change the panel's brightness - valid values are from 0.0 to 1.0
#[argh(option, long = "brightness")]
set_brightness: Option<f64>,
/// turn the panel's backlight on or off
#[argh(option, long = "power", from_str_fn(on_off_to_bool))]
set_power: Option<bool>,
}
impl Backlight {
pub fn new() -> Result<Backlight, Error> {
let device = open_backlight()?;
Ok(Backlight { device })
}
pub async fn read_and_modify_state(&mut self, args: &BacklightCmd) -> Result<(), Error> {
tracing::trace!("Querying backlight state");
let backlight_state = utils::flatten_zx_error(self.device.get_state_normalized().await)
.context("Failed to get current backlight state")?;
println!("Current panel backlight state: {:?}", backlight_state);
let new_state = backlight::State {
backlight_on: args.set_power.unwrap_or(backlight_state.backlight_on),
brightness: args.set_brightness.unwrap_or(backlight_state.brightness),
};
// Don't issue a FIDL call that wouldn't make any change. This avoids an
// unintuitive race condition where the changes of another brightness
// management agent are discarded.
if args.set_power.is_none() && args.set_brightness.is_none() {
return Ok(());
}
tracing::trace!("Setting new backlight state");
utils::flatten_zx_error(self.device.set_state_normalized(&new_state).await)
.context("Failed to apply new backlight settings")?;
println!("New panel backlight state: {:?}", new_state);
Ok(())
}
}
impl BacklightCmd {
pub async fn exec(&self) -> Result<(), Error> {
let mut backlight = Backlight::new()?;
backlight.read_and_modify_state(self).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use fuchsia_zircon as zx;
use futures::{future, StreamExt};
#[fuchsia::test]
async fn read_and_modify_state_no_changes() {
let (device, mut backlight_request_stream) =
fidl::endpoints::create_proxy_and_stream::<backlight::DeviceMarker>().unwrap();
let mut backlight = Backlight { device };
let test_future = async move {
let args = BacklightCmd { set_brightness: None, set_power: None };
assert_matches!(backlight.read_and_modify_state(&args).await, Ok(()));
};
let service_future = async move {
match backlight_request_stream.next().await.unwrap() {
Ok(backlight::DeviceRequest::GetStateNormalized { responder }) => {
responder
.send(Ok(&backlight::State { backlight_on: true, brightness: 0.5 }))
.unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(test_future, service_future).await;
}
#[fuchsia::test]
async fn read_and_modify_state_power_change() {
let (device, mut backlight_request_stream) =
fidl::endpoints::create_proxy_and_stream::<backlight::DeviceMarker>().unwrap();
let mut backlight = Backlight { device };
let test_future = async move {
let args = BacklightCmd { set_brightness: None, set_power: Some(false) };
assert_matches!(backlight.read_and_modify_state(&args).await, Ok(()));
};
let service_future = async move {
match backlight_request_stream.next().await.unwrap() {
Ok(backlight::DeviceRequest::GetStateNormalized { responder }) => {
responder
.send(Ok(&backlight::State { backlight_on: true, brightness: 0.5 }))
.unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
match backlight_request_stream.next().await.unwrap() {
Ok(backlight::DeviceRequest::SetStateNormalized { state, responder }) => {
assert_matches!(state, backlight::State { backlight_on: false, brightness: _ });
assert_eq!(state.brightness, 0.5);
responder.send(Ok(())).unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(test_future, service_future).await;
}
#[fuchsia::test]
async fn read_and_modify_state_brightness_change() {
let (device, mut backlight_request_stream) =
fidl::endpoints::create_proxy_and_stream::<backlight::DeviceMarker>().unwrap();
let mut backlight = Backlight { device };
let test_future = async move {
let args = BacklightCmd { set_brightness: Some(1.0), set_power: None };
assert_matches!(backlight.read_and_modify_state(&args).await, Ok(()));
};
let service_future = async move {
match backlight_request_stream.next().await.unwrap() {
Ok(backlight::DeviceRequest::GetStateNormalized { responder }) => {
responder
.send(Ok(&backlight::State { backlight_on: true, brightness: 0.5 }))
.unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
match backlight_request_stream.next().await.unwrap() {
Ok(backlight::DeviceRequest::SetStateNormalized { state, responder }) => {
assert_matches!(state, backlight::State { backlight_on: true, brightness: _ });
assert_eq!(state.brightness, 1.0);
responder.send(Ok(())).unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(test_future, service_future).await;
}
#[fuchsia::test]
async fn read_and_modify_state_read_error() {
let (device, mut backlight_request_stream) =
fidl::endpoints::create_proxy_and_stream::<backlight::DeviceMarker>().unwrap();
let mut backlight = Backlight { device };
let test_future = async move {
let args = BacklightCmd { set_brightness: Some(1.0), set_power: None };
let result = backlight.read_and_modify_state(&args).await;
assert_matches!(result, Err(_));
assert_eq!(result.unwrap_err().to_string(), "Failed to get current backlight state");
};
let service_future = async move {
match backlight_request_stream.next().await.unwrap() {
Ok(backlight::DeviceRequest::GetStateNormalized { responder }) => {
responder.send(Err(zx::sys::ZX_ERR_NOT_SUPPORTED)).unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(test_future, service_future).await;
}
#[fuchsia::test]
async fn read_and_modify_state_modify_error() {
let (device, mut backlight_request_stream) =
fidl::endpoints::create_proxy_and_stream::<backlight::DeviceMarker>().unwrap();
let mut backlight = Backlight { device };
let test_future = async move {
let args = BacklightCmd { set_brightness: Some(1.0), set_power: None };
let result = backlight.read_and_modify_state(&args).await;
assert_matches!(result, Err(_));
assert_eq!(result.unwrap_err().to_string(), "Failed to apply new backlight settings");
};
let service_future = async move {
match backlight_request_stream.next().await.unwrap() {
Ok(backlight::DeviceRequest::GetStateNormalized { responder }) => {
responder
.send(Ok(&backlight::State { backlight_on: true, brightness: 0.5 }))
.unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
match backlight_request_stream.next().await.unwrap() {
Ok(backlight::DeviceRequest::SetStateNormalized { responder, .. }) => {
responder.send(Err(zx::sys::ZX_ERR_NOT_SUPPORTED)).unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(test_future, service_future).await;
}
}