blob: 6d6e8c2ece61295f778542823bd90d401ea6c6f3 [file] [log] [blame]
// Copyright 2020 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.
#![recursion_limit = "512"]
use anyhow::{Context, Error};
// Include Brightness Control FIDL bindings
use argh::FromArgs;
use fidl_fuchsia_hardware_backlight::{
DeviceMarker as BacklightMarker, DeviceProxy as BacklightProxy,
};
use fidl_fuchsia_ui_brightness::ControlMarker as BrightnessControlMarker;
use fuchsia_async as fasync;
use fuchsia_component::client::connect_to_service;
use fuchsia_syslog::fx_log_info;
const ASSUMED_MAX_BRIGHTNESS: f64 = 250.0;
fn open_backlight() -> Result<BacklightProxy, Error> {
fx_log_info!("Opening backlight");
let (proxy, server) = fidl::endpoints::create_proxy::<BacklightMarker>()
.context("Failed to create backlight proxy")?;
// TODO(kpt): Don't hardcode this path b/138666351
fdio::service_connect("/dev/class/backlight/000", server.into_channel())
.context("Failed to connect built-in service")?;
Ok(proxy)
}
async fn get_max_brightness() -> Result<f32, Error> {
let proxy = open_backlight()?;
let connection_result = proxy.get_max_absolute_brightness().await;
let max_brightness = connection_result.unwrap_or_else(|e| {
fx_log_info!("Didn't connect correctly, got err {}", e);
Ok(ASSUMED_MAX_BRIGHTNESS)
});
Ok(max_brightness.unwrap_or_else(|e| {
fx_log_info!("Didn't get the max_brightness back, got err {}", e);
ASSUMED_MAX_BRIGHTNESS
}) as f32)
}
#[derive(FromArgs, PartialEq, Debug)]
/// Operation: Set or Watch.
struct Operation {
#[argh(subcommand)]
sub_command: MySubCommandEnum,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum MySubCommandEnum {
Set(Set),
Watch(Watch),
}
#[derive(FromArgs, PartialEq, Debug)]
/// Set Operation.
#[argh(subcommand, name = "set")]
struct Set {
#[argh(subcommand)]
brightness: SetBrightness,
}
#[derive(FromArgs, PartialEq, Debug)]
/// Watch Operation.
#[argh(subcommand, name = "watch")]
struct Watch {
#[argh(subcommand)]
brightness: WatchBrightness,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum WatchBrightness {
AutoBrightness(AutoBrightness),
CurrentBrightness(CurrentBrightness),
MaxBrightness(MaxBrightness),
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum SetBrightness {
AutoBrightness(AutoBrightness),
ManualBrightness(ManualBrightness),
}
#[derive(FromArgs, PartialEq, Debug)]
/// Set(Watch) auto brightness mode.
#[argh(subcommand, name = "auto-brightness")]
struct AutoBrightness {}
#[derive(FromArgs, PartialEq, Debug)]
/// Watch max brightness.
#[argh(subcommand, name = "max-brightness")]
struct MaxBrightness {}
#[derive(FromArgs, PartialEq, Debug)]
/// Watch current brightness, return 0-1 by default.
#[argh(subcommand, name = "current-brightness")]
struct CurrentBrightness {
#[argh(switch, short = 'n')]
/// using nits instead of 0-1.
use_nits: bool,
}
#[derive(FromArgs, PartialEq, Debug)]
/// Set manual brightness.
#[argh(subcommand, name = "manual-brightness")]
struct ManualBrightness {
#[argh(option, short = 'v')]
/// set manual to a certain number if value is present, using 0-1 by default.
value: Option<f32>,
#[argh(switch, short = 'n')]
/// using nits instead of 0-1 if nits is present.
use_nits: bool,
}
#[fasync::run_singlethreaded]
async fn main() -> Result<(), Error> {
let brightness = connect_to_service::<BrightnessControlMarker>()
.context("Failed to connect to brightness service")?;
let operation: Operation = argh::from_env();
let max_brightness = get_max_brightness().await.unwrap();
match operation.sub_command {
MySubCommandEnum::Set(set) => match set.brightness {
SetBrightness::AutoBrightness(_auto_brightness) => {
println!("Setting to auto brightness mode");
brightness.set_auto_brightness()?;
}
SetBrightness::ManualBrightness(manual) => {
if manual.value.is_none() {
let auto_on = brightness.watch_auto_brightness().await?;
if auto_on {
println!("Setting to manual brightness mode");
let current = brightness.watch_current_brightness().await?;
brightness.set_manual_brightness(current)?;
let auto = brightness.watch_auto_brightness().await?;
println!("{}", auto);
} else {
println!("Already in manual brightness.");
}
} else {
if manual.use_nits {
let normalized = manual.value.unwrap() / max_brightness;
brightness.set_manual_brightness(normalized)?;
let current = brightness.watch_current_brightness().await?;
println!("{}", current);
} else {
brightness.set_manual_brightness(manual.value.unwrap())?;
let current = brightness.watch_current_brightness().await?;
println!("{}", current);
}
}
}
},
MySubCommandEnum::Watch(watch) => match watch.brightness {
WatchBrightness::AutoBrightness(_auto_brightness) => {
let current = brightness.watch_auto_brightness().await?;
println!("{:?}", current);
}
WatchBrightness::CurrentBrightness(current_brightness) => {
let mut current = brightness.watch_current_brightness().await?;
if current_brightness.use_nits {
current = max_brightness * current;
println!("{:?}", current);
} else {
println!("{:?}", current);
}
}
WatchBrightness::MaxBrightness(_max_brightness) => {
println!("{:?}", max_brightness);
}
},
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_hardware_backlight::{DeviceMarker, DeviceRequest};
use fidl_fuchsia_ui_brightness::{ControlRequest, ControlRequestStream};
use fuchsia_component::client::launcher;
use fuchsia_component::{
client::AppBuilder,
server::{NestedEnvironment, ServiceFs},
};
use futures::lock::Mutex;
use futures::{StreamExt, TryStreamExt};
use std::str::from_utf8;
use std::sync::Arc;
use {
directory_broker,
fidl::endpoints::{create_endpoints, ServerEnd},
fidl_fuchsia_io as fio,
fuchsia_vfs_pseudo_fs_mt::{
directory::entry::DirectoryEntry, execution_scope::ExecutionScope, path,
pseudo_directory,
},
fuchsia_zircon::HandleBased,
};
const BRIGHTNESS_TEST_PKG_URL: &str =
"fuchsia-pkg://fuchsia.com/brightness_command_tests#meta/brightness_command.cmx";
enum IncomingServices {
BrightnessControl(ControlRequestStream),
}
fn run_test_services(
auto_brightness: Arc<Mutex<bool>>,
current_brightness: Arc<Mutex<f32>>,
) -> Result<NestedEnvironment, Error> {
let mut fs = ServiceFs::new();
fs.add_fidl_service(IncomingServices::BrightnessControl);
let env = fs.create_salted_nested_environment("brightness_env");
let auto_brightness = auto_brightness.clone();
let current_brightness = current_brightness.clone();
fasync::Task::spawn(fs.for_each_concurrent(None, move |req| {
let auto_brightness = auto_brightness.clone();
let current_brightness = current_brightness.clone();
async move {
let auto_brightness = auto_brightness.clone();
let current_brightness = current_brightness.clone();
match req {
IncomingServices::BrightnessControl(stream) => {
stream
.err_into::<Error>()
.try_for_each(|request| {
let auto_brightness = auto_brightness.clone();
let current_brightness = current_brightness.clone();
async move {
let auto_brightness = auto_brightness.clone();
let current_brightness = current_brightness.clone();
match request {
ControlRequest::WatchAutoBrightness { responder } => {
let auto_brightness = auto_brightness.lock().await;
responder.send(*auto_brightness)?;
}
ControlRequest::WatchCurrentBrightness { responder } => {
let current_brightness =
current_brightness.lock().await;
responder.send(*current_brightness)?;
}
ControlRequest::SetAutoBrightness { control_handle: _ } => {
let mut auto_brightness = auto_brightness.lock().await;
*auto_brightness = true;
}
ControlRequest::SetManualBrightness {
value,
control_handle: _,
} => {
let mut current_brightness =
current_brightness.lock().await;
*current_brightness = value;
let mut auto_brightness = auto_brightness.lock().await;
*auto_brightness = false;
}
_ => panic!("Not expected request"),
}
Ok(())
}
})
.await
.unwrap();
}
}
}
}))
.detach();
env
}
#[fasync::run_singlethreaded(test)]
async fn test_watch_auto_brightness() -> Result<(), Error> {
let auto_brightness = Arc::new(Mutex::new(false));
let current_brightness = Arc::new(Mutex::new(0.6));
let env = run_test_services(auto_brightness.clone(), current_brightness.clone())?;
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.arg("watch")
.arg("auto-brightness")
.output(&env.launcher())
.unwrap()
.await
.unwrap();
assert_eq!("false\n", from_utf8(&output.stdout).unwrap());
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn test_watch_current_brightness() -> Result<(), Error> {
let auto_brightness = Arc::new(Mutex::new(true));
let current_brightness = Arc::new(Mutex::new(0.6));
let env = run_test_services(auto_brightness.clone(), current_brightness.clone())?;
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.arg("watch")
.arg("current-brightness")
.output(&env.launcher())
.unwrap()
.await
.unwrap();
assert_eq!("0.6\n", from_utf8(&output.stdout).unwrap());
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn test_set_auto_brightness() -> Result<(), Error> {
let auto_brightness = Arc::new(Mutex::new(false));
let current_brightness = Arc::new(Mutex::new(0.6));
let env = run_test_services(auto_brightness.clone(), current_brightness.clone())?;
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.arg("set")
.arg("auto-brightness")
.output(&env.launcher())
.unwrap()
.await
.unwrap();
assert_eq!("Setting to auto brightness mode\n", from_utf8(&output.stdout).unwrap());
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.arg("watch")
.arg("auto-brightness")
.output(&env.launcher())
.unwrap()
.await
.unwrap();
assert_eq!("true\n", from_utf8(&output.stdout).unwrap());
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn test_set_manual_brightness_without_value_auto_on() -> Result<(), Error> {
let auto_brightness = Arc::new(Mutex::new(true));
let current_brightness = Arc::new(Mutex::new(0.6));
let env = run_test_services(auto_brightness.clone(), current_brightness.clone())?;
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.arg("set")
.arg("manual-brightness")
.output(&env.launcher())
.unwrap()
.await
.unwrap();
assert_eq!(
"Setting to manual brightness mode\nfalse\n",
from_utf8(&output.stdout).unwrap()
);
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.arg("watch")
.arg("current-brightness")
.output(&env.launcher())
.unwrap()
.await
.unwrap();
assert_eq!("0.6\n", from_utf8(&output.stdout).unwrap());
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn test_set_manual_brightness_without_value_auto_off() -> Result<(), Error> {
let auto_brightness = Arc::new(Mutex::new(false));
let current_brightness = Arc::new(Mutex::new(0.6));
let env = run_test_services(auto_brightness.clone(), current_brightness.clone())?;
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.arg("set")
.arg("manual-brightness")
.output(&env.launcher())
.unwrap()
.await
.unwrap();
assert_eq!("Already in manual brightness.\n", from_utf8(&output.stdout).unwrap());
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn test_set_manual_brightness_with_value() -> Result<(), Error> {
let auto_brightness = Arc::new(Mutex::new(true));
let current_brightness = Arc::new(Mutex::new(0.6));
let env = run_test_services(auto_brightness.clone(), current_brightness.clone())?;
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.arg("set")
.arg("manual-brightness")
.arg("-v")
.arg("0.1")
.output(&env.launcher())
.unwrap()
.await
.unwrap();
assert_eq!("0.1\n", from_utf8(&output.stdout).unwrap());
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.arg("watch")
.arg("auto-brightness")
.output(&env.launcher())
.unwrap()
.await
.unwrap();
assert_eq!("false\n", from_utf8(&output.stdout).unwrap());
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn test_watch_max_brightness() -> Result<(), Error> {
let (client_end, server_end) = create_endpoints::<fio::NodeMarker>()?;
fasync::Task::spawn(async move {
let fake_dir = pseudo_directory! {
"000" => directory_broker::DirectoryBroker::new(Box::new(|flags, mode, path, server_end| {
let (requests, control_handle) = ServerEnd::<DeviceMarker>::new(server_end.into_channel()).into_stream_and_control_handle().unwrap();
fasync::Task::spawn(async move {
requests
.err_into::<Error>()
.try_for_each(|request| {
async move {
match request {
DeviceRequest::GetMaxAbsoluteBrightness { responder } => {
responder.send(&mut Ok(ASSUMED_MAX_BRIGHTNESS))?;
}
_ => panic!("Not expected request"),
};
Ok(())
}
})
.await
.unwrap();
}).detach();
}))
};
fake_dir.open(
ExecutionScope::new(),
fio::OPEN_RIGHT_READABLE,
fio::MODE_TYPE_DIRECTORY,
path::Path::empty(),
server_end,
);
}).detach();
let output = AppBuilder::new(BRIGHTNESS_TEST_PKG_URL)
.add_handle_to_namespace(
"/dev/class/backlight".to_string(),
client_end.into_channel().into_handle(),
)
.arg("watch")
.arg("max-brightness")
.output(&launcher()?)?
.await
.unwrap();
assert_eq!(format!("{0:.1}\n", ASSUMED_MAX_BRIGHTNESS), from_utf8(&output.stdout).unwrap());
Ok(())
}
}