| // 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; |
| } |
| } |