diff --git a/backend-process/src/database/operations.rs b/backend-process/src/database/operations.rs index 4f8bd0c..a2c0f81 100644 --- a/backend-process/src/database/operations.rs +++ b/backend-process/src/database/operations.rs @@ -1,7 +1,9 @@ use crate::database::mappers::{ImageMappingStorageFormat, InputMappingStorageFormat}; use crate::database::models::{ImageMapping, InputMapping}; use crate::database::sqlite::SqLite; -use rusqlite::Connection; +use firmware_api::display_zones::DisplayZones; +use rusqlite::fallible_streaming_iterator::FallibleStreamingIterator; +use rusqlite::{Connection, params}; use std::io::{Error, ErrorKind}; pub struct Operations { @@ -9,22 +11,33 @@ pub struct Operations { } impl Operations { + /// Creates a new Operations instance and initializes required database tables. + /// Panics if table creation fails. pub fn new(db: SqLite) -> Self { let instance = Operations { database: db }; - instance.create_input_mapping_table().ok(); - instance.create_image_mapping_table().ok(); + instance + .create_input_mapping_table() + .expect("Failed to create input_mapping table"); + instance + .create_image_mapping_table() + .expect("Failed to create image_mapping table"); + instance + .create_config_mapping_table() + .expect("Failed to create config_mapping table"); instance } + /// Returns a reference to the database connection. fn open_connection(&self) -> Result<&Connection, String> { self.database .connection() .ok_or(String::from("Operations not initialized")) } + /// Creates input_mapping table (button_id, actions) if it doesn't exist. fn create_input_mapping_table(&self) -> Result<(), String> { - pub const CREATE_INPUT_MAPPING_TABLE: &str = " + const CREATE_INPUT_MAPPING_TABLE: &str = " CREATE TABLE IF NOT EXISTS input_mapping ( button_id INTEGER PRIMARY KEY, actions TEXT NOT NULL @@ -37,8 +50,9 @@ impl Operations { Ok(()) } + /// Creates image_mapping table (display_zone_id, image_path) if it doesn't exist. fn create_image_mapping_table(&self) -> Result<(), String> { - pub const CREATE_IMAGE_MAPPING_TABLE: &str = " + const CREATE_IMAGE_MAPPING_TABLE: &str = " CREATE TABLE IF NOT EXISTS image_mapping ( display_zone_id INTEGER PRIMARY KEY, image_path TEXT NOT NULL @@ -51,6 +65,23 @@ impl Operations { Ok(()) } + /// Creates config_mapping table (id, brightness) as singleton if it doesn't exist. + fn create_config_mapping_table(&self) -> Result<(), String> { + // Extend this with any other params if required + const CREATE_CONFIG_MAPPING_TABLE: &str = " + CREATE TABLE IF NOT EXISTS config_mapping ( + id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1), + brightness INTEGER) + "; + + self.open_connection()? + .execute(CREATE_CONFIG_MAPPING_TABLE, ()) + .ok(); + + Ok(()) + } + + /// Sets or updates button-to-action mapping using UPSERT. pub fn set_mapping_for_input(&self, input_mapping: InputMapping) -> Result { let input_mapping: InputMappingStorageFormat = input_mapping.try_into()?; const SET_INPUT_MAPPING: &str = "INSERT INTO input_mapping (button_id, actions) VALUES (?1, ?2) \ @@ -64,6 +95,7 @@ impl Operations { .map_err(|e| e.to_string()) } + /// Sets or updates display zone image mapping using UPSERT. pub fn set_image_for_display_zone(&self, image_mapping: ImageMapping) -> Result { let input_mapping: ImageMappingStorageFormat = image_mapping.into(); const SET_INPUT_MAPPING: &str = "INSERT INTO image_mapping (display_zone_id, image_path) VALUES (?1, ?2) \ @@ -78,7 +110,65 @@ impl Operations { .map_err(Error::other) } - #[allow(dead_code)] + /// Deletes image mapping for specified display zone. + pub fn clear_image_for_display_zone( + &self, + display_zones: DisplayZones, + ) -> Result { + const REMOVE_IMAGE_FOR_DISPLAY_ZONES: &str = + "DELETE FROM image_mapping WHERE display_zone_id = ?"; + + let int_value = u8::from(display_zones); + + self.open_connection() + .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))? + .execute(REMOVE_IMAGE_FOR_DISPLAY_ZONES, params![int_value]) + .map_err(Error::other) + } + + /// Sets or updates brightness config value using UPSERT. + pub fn set_brightness(&self, brightness: u8) -> Result { + const SET_BRIGHTNESS: &str = "INSERT INTO config_mapping (id, brightness) VALUES (1, ?1)\ + ON CONFLICT(id) DO UPDATE SET brightness=?1"; + self.open_connection() + .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))? + .execute(SET_BRIGHTNESS, params![brightness]) + .map_err(Error::other) + } + + /// Gets stored brightness value, returns None if not set. + pub fn get_stored_brightness(&self) -> Result, Error> { + const GET_BRIGHTNESS_VALUE: &str = "SELECT brightness FROM config_mapping WHERE id = 1"; + + let conn = self + .open_connection() + .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))?; + let mut stmt = conn.prepare(GET_BRIGHTNESS_VALUE).map_err(Error::other)?; + + let mut rows = stmt.query(params![]).map_err(Error::other)?; + let singleton_row = match rows.nth(0) { + Ok(row) => row, + _ => return Ok(None), + }; + if let Some(row) = singleton_row { + let brightness: u8 = row.get(0).map_err(Error::other)?; + return Ok(Some(brightness)); + } + + Ok(None) + } + + /// Deletes all image mappings from database. + pub fn clear_all_display_zone_images(&self) -> Result { + const CLEAR_ALL_DISPLAY_ZONE_IMAGES: &str = "DELETE FROM image_mapping"; + + self.open_connection() + .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))? + .execute(CLEAR_ALL_DISPLAY_ZONE_IMAGES, ()) + .map_err(Error::other) + } + + /// Returns all image mappings from database. pub fn get_all_image_mappings(&self) -> Result, String> { const GET_ALL_IMAGE_MAPPINGS: &str = "SELECT * FROM image_mapping"; @@ -102,6 +192,7 @@ impl Operations { .collect() } + /// Returns all input mappings from database. pub fn get_all_input_mappings(&self) -> Result, String> { const GET_ALL_INPUT_MAPPINGS: &str = "SELECT * FROM input_mapping"; @@ -176,7 +267,7 @@ mod tests { } #[test] - fn allows_setting_display_zone_images() { + fn allows_setting_and_clearing_display_zone_images() { let sqlite = SqLite::new(false); let operations = Operations::new(sqlite.unwrap()); @@ -225,5 +316,63 @@ mod tests { // Should still contain binding to button 1 assert!(new_rows.contains(&to_add[1])); + + // Test single clear + operations + .clear_image_for_display_zone(DisplayZones::Touchscreen3) + .unwrap(); + + let new_rows = operations.get_all_image_mappings().unwrap(); + assert_eq!(new_rows.len(), 1); + } + + #[test] + fn allows_clearing_all_display_zone_images() { + let sqlite = SqLite::new(false); + let operations = Operations::new(sqlite.unwrap()); + + let to_add = &[ + ImageMapping { + display_zone: DisplayZones::Touchscreen3, + image_path: String::from("foo.jpg"), + }, + ImageMapping { + display_zone: DisplayZones::Button3, + image_path: String::from("fat.jpg"), + }, + ]; + + operations.create_input_mapping_table().unwrap(); + + for item in to_add.iter() { + operations.set_image_for_display_zone(item.clone()).unwrap(); + } + + operations.clear_all_display_zone_images().unwrap(); + let new_rows = operations.get_all_image_mappings().unwrap(); + + assert_eq!(new_rows.len(), 0); + } + + #[test] + fn allows_setting_brightness_value() { + let sqlite = SqLite::new(false); + let operations = Operations::new(sqlite.unwrap()); + + // When there is no stored brightness + let brightness = operations.get_stored_brightness().unwrap(); + + assert_eq!(brightness, None); + + operations.set_brightness(69).unwrap(); + + let brightness = operations.get_stored_brightness().unwrap().unwrap(); + + assert_eq!(brightness, 69); + + operations.set_brightness(20).unwrap(); + let brightness = operations.get_stored_brightness().unwrap().unwrap(); + + assert_eq!(brightness, 20); } } diff --git a/backend-process/src/device_management.rs b/backend-process/src/device_management.rs index a8e634b..3e141b7 100644 --- a/backend-process/src/device_management.rs +++ b/backend-process/src/device_management.rs @@ -1,4 +1,4 @@ -use hidapi::HidDevice; +use hidapi::{HidApi, HidDevice}; use std::thread::sleep; use std::time::Duration; @@ -12,25 +12,38 @@ const AJAZZ_LAUNCHPAD: DeviceIdentifier = DeviceIdentifier { pid: 0x3004, }; -pub fn scan_for_launchpad() -> HidDevice { - let mut hid_api = hidapi::HidApi::new_without_enumerate().unwrap(); +pub struct DeviceManagement { + hid_api: HidApi, +} + +impl DeviceManagement { + pub fn new() -> Self { + Self { + hid_api: HidApi::new_without_enumerate().unwrap(), + } + } - loop { - hid_api.reset_devices().unwrap(); - hid_api + fn refresh_launchpad_filter(&mut self) { + self.hid_api.reset_devices().unwrap(); + self.hid_api .add_devices(AJAZZ_LAUNCHPAD.vid, AJAZZ_LAUNCHPAD.pid) .unwrap(); + } + pub fn scan_for_launchpad(&mut self) -> HidDevice { + loop { + self.refresh_launchpad_filter(); - // Bit of a hack, there are 3 identified devices with the given vid/pid, so need to find the one that works - for device in hid_api.device_list() { - // Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages - let launchpad = hid_api.open_path(device.path()); + // Bit of a hack, there are 3 identified devices with the given vid/pid, so need to find the one that works + for device in self.hid_api.device_list() { + // Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages + let launchpad = self.hid_api.open_path(device.path()); - if let Ok(device) = launchpad { - return device; + if let Ok(device) = launchpad { + return device; + } } - } - sleep(Duration::from_millis(500)); + sleep(Duration::from_millis(500)); + } } } diff --git a/backend-process/src/main.rs b/backend-process/src/main.rs index 5b92866..f4db275 100644 --- a/backend-process/src/main.rs +++ b/backend-process/src/main.rs @@ -5,13 +5,13 @@ mod protobuf_conversion; mod socket; use crate::database::operations::Operations; +use crate::device_management::DeviceManagement; use crate::input_handler::{ EnigoKeyActionHandler, InputMapping, KeyActionExecutor, LaunchpadInputHandler, }; use crate::socket::commands::IncomingCommands; use firmware_api::device; -use firmware_api::device::HidDeviceWrapper; -use log::info; +use log::{debug, error, info}; use std::fs::File; #[derive(Clone)] @@ -20,6 +20,7 @@ enum States { ReadClientMessages, HandleDeviceInput, PruneConnections, + InitialiseDevice, } struct StateMachine { @@ -29,18 +30,26 @@ struct StateMachine { impl StateMachine { fn new() -> Self { Self { - current_state: States::EstablishConnection, + current_state: States::InitialiseDevice, } } - fn next_state(&mut self, current_connections: u8) { + fn next_state(&mut self, current_connections: u8, device_is_connected: bool) { match self.current_state { + States::InitialiseDevice => { + self.current_state = States::PruneConnections; + } States::EstablishConnection => match current_connections { connections if connections > 0 => self.current_state = States::ReadClientMessages, _ => self.current_state = States::HandleDeviceInput, }, - States::HandleDeviceInput => match current_connections { - connections if connections > 0 => self.current_state = States::ReadClientMessages, - _ => self.current_state = States::EstablishConnection, + States::HandleDeviceInput => match device_is_connected { + true => match current_connections { + connections if connections > 0 => { + self.current_state = States::ReadClientMessages + } + _ => self.current_state = States::EstablishConnection, + }, + false => self.current_state = States::InitialiseDevice, }, States::ReadClientMessages => { self.current_state = States::PruneConnections; @@ -59,75 +68,123 @@ impl StateMachine { fn main() { env_logger::init(); let mut state_machine = StateMachine::new(); - let db = Operations::new(database::sqlite::SqLite::new(true).unwrap()); - let mut default_mappings = InputMapping::default(); - default_mappings.override_config(db.get_all_input_mappings().unwrap().into()); - let mut server = socket::connection::ServerHandler::new(&db).expect("Failed to create server"); - + let mut device: Option> = None; let key_action_handler: Box = Box::new(EnigoKeyActionHandler::default()); - let input_handler = LaunchpadInputHandler::new(default_mappings, key_action_handler.as_ref()); - let hid_device = device_management::scan_for_launchpad(); - let mut device = device::Device::new(HidDeviceWrapper::new(&hid_device, false), input_handler); - device.refresh().unwrap(); - loop { let current_state = state_machine.current_state(); - + let mut device_disconnected_during_read = false; match current_state { + States::InitialiseDevice => { + let mut device_management = DeviceManagement::new(); + let hid_device = device_management.scan_for_launchpad(); + // Image config fetching + let default_images = db.get_all_image_mappings().unwrap(); + + // Input config fetching + let mut default_mappings = InputMapping::default(); + default_mappings.override_config(db.get_all_input_mappings().unwrap().into()); + let input_handler = + LaunchpadInputHandler::new(default_mappings, key_action_handler.as_ref()); + + // Brightness config fetching + let stored_brightness = db.get_stored_brightness().unwrap(); + + let new_device = device::Device::new( + device::HidDeviceWrapper::new(hid_device, false), // No borrowing here + input_handler, + ); + new_device.refresh().unwrap(); + + // Stop showing background image + new_device.clear_all_images().ok(); + + for default_mapping in default_images { + match File::open(default_mapping.image_path) { + Ok(image) => { + new_device + .set_display_zone_image(default_mapping.display_zone, image) + .ok(); + } + Err(e) => { + error!("Failed to process image {}", e) + } + } + } + if let Some(brightness) = stored_brightness { + new_device.set_brightness(brightness).ok(); + } + device = Some(new_device); + } States::EstablishConnection => match server.add_new_connection_if_exists() { Ok(_) => { - info!("New connection added"); + debug!("New connection added"); } Err(e) => { - info!("New connection could not be added: {}", e); + debug!("New connection could not be added: {}", e); } }, - States::ReadClientMessages => match server.handle_command_and_persist_config() { - Ok(message_type) => match message_type { - IncomingCommands::SetKeyConfig(mapping) => { - let input_handler = LaunchpadInputHandler::new( - device.handler().new_updated_mappings(mapping), - key_action_handler.as_ref(), - ); - device.update_handler(input_handler); - } - IncomingCommands::SetDisplayZoneImage(mapping) => { - if let Ok(image) = File::open(mapping.image_path) { - device - .set_display_zone_image(mapping.display_zone, image) - .ok(); - } - } - IncomingCommands::SetBrightness(brightness) => { - device.set_brightness(brightness).ok(); - } - IncomingCommands::ClearDisplayZoneImage(display_zone) => { - device.clear_display_zone_image(display_zone).ok(); - } - IncomingCommands::SetBootLogo(file_path) => { - if let Ok(image) = File::open(file_path) { - device.set_background_image(image).ok(); + States::ReadClientMessages => { + if let Some(ref mut dev) = device { + match server.handle_command_and_persist_config() { + Ok(message_type) => match message_type { + IncomingCommands::SetKeyConfig(mapping) => { + let input_handler = LaunchpadInputHandler::new( + dev.handler().new_updated_mappings(mapping), + key_action_handler.as_ref(), + ); + dev.update_handler(input_handler); + } + IncomingCommands::SetDisplayZoneImage(mapping) => { + if let Ok(image) = File::open(mapping.image_path) { + dev.set_display_zone_image(mapping.display_zone, image).ok(); + } + } + IncomingCommands::SetBrightness(brightness) => { + dev.set_brightness(brightness).ok(); + } + IncomingCommands::ClearDisplayZoneImage(display_zone) => { + dev.clear_display_zone_image(display_zone).ok(); + } + IncomingCommands::SetBootLogo(file_path) => { + if let Ok(image) = File::open(file_path) { + dev.set_background_image(image).ok(); + } + } + IncomingCommands::ClearAllDisplayZoneImages => { + dev.clear_all_images().ok(); + } + }, + Err(e) => { + info!("No known command was handled: {}", e); } } - IncomingCommands::ClearAllDisplayZoneImages => { - device.clear_all_images().ok(); - } - }, - Err(e) => { - info!("No known command was handled: {}", e); } - }, + } States::HandleDeviceInput => { - device.read_input().ok(); + if let Some(ref mut dev) = device { + dev.read_input().unwrap_or_else(|e| { + debug!("Error reading input: {}", e); + if e.to_string() == "hidapi error: hid_read_timeout: device disconnected" { + info!("Disconnected from device"); + device_disconnected_during_read = true; + } + }); + } } - States::PruneConnections => { server.prune_connections().ok(); } } - state_machine.next_state(server.server().connected_clients() as u8); + let device_is_connected = device.is_some() && !device_disconnected_during_read; + if device_disconnected_during_read { + device = None; + } + state_machine.next_state( + server.server().connected_clients() as u8, + device_is_connected, + ); } } diff --git a/backend-process/src/socket/connection.rs b/backend-process/src/socket/connection.rs index 8a1d844..470ec42 100644 --- a/backend-process/src/socket/connection.rs +++ b/backend-process/src/socket/connection.rs @@ -60,9 +60,21 @@ impl<'a> ServerHandler<'a> { return Ok(IncomingCommands::SetBootLogo(command.image_path)); } Command::SetBrightnessCommand(command) => { - return Ok(IncomingCommands::SetBrightness( - command.brightness_value as u8, - )); + return match command.brightness_value { + 0..=100 => { + self.operations + .set_brightness(command.brightness_value as u8) + .ok(); + Ok(IncomingCommands::SetBrightness( + command.brightness_value as u8, + )) + } + + _ => Err(Error::new( + ErrorKind::InvalidInput, + "Brightness value was not in the range 0 to 100!", + )), + }; } Command::SetDisplayZoneImageCommand(command) => { if let Ok(display_zone_image_model) = command.try_into() { @@ -74,7 +86,10 @@ impl<'a> ServerHandler<'a> { return Ok(IncomingCommands::SetDisplayZoneImage(database_copy)); } } - Command::ClearAllDisplayZoneImagesCommand(_) => { + Command::ClearAllDisplayZoneImagesCommand(command) => { + if command.unpersist_images { + self.operations.clear_all_display_zone_images().ok(); + } return Ok(IncomingCommands::ClearAllDisplayZoneImages); } Command::ClearDisplayZoneImageCommand(command) => { @@ -82,6 +97,9 @@ impl<'a> ServerHandler<'a> { && let Ok(display_zone_wrapper) = DisplayZoneWrapper::try_from(protobuf_enum) { + self.operations + .clear_image_for_display_zone(display_zone_wrapper.display_zone()) + .ok(); return Ok(IncomingCommands::ClearDisplayZoneImage( display_zone_wrapper.display_zone(), )); diff --git a/firmware-api/examples/set_background_image.rs b/firmware-api/examples/set_background_image.rs index be80378..5066a45 100644 --- a/firmware-api/examples/set_background_image.rs +++ b/firmware-api/examples/set_background_image.rs @@ -13,7 +13,7 @@ fn main() { .unwrap_or_else(|e| panic!("Failed to open device: {}", e)); let device = Device::new( - HidDeviceWrapper::new(&hid_device, false), + HidDeviceWrapper::new(hid_device, false), FunctionHandler::new(|action| println!("{:?}", action)), ); diff --git a/firmware-api/examples/set_button_and_touchscreen_image.rs b/firmware-api/examples/set_button_and_touchscreen_image.rs index 880506c..28b63b7 100644 --- a/firmware-api/examples/set_button_and_touchscreen_image.rs +++ b/firmware-api/examples/set_button_and_touchscreen_image.rs @@ -33,7 +33,7 @@ fn main() { .unwrap_or_else(|e| panic!("Failed to open device: {}", e)); let device = Device::new( - HidDeviceWrapper::new(&hid_device, false), + HidDeviceWrapper::new(hid_device, false), FunctionHandler::new(|action| println!("{:?}", action)), ); diff --git a/firmware-api/examples/simple_brightness_command.rs b/firmware-api/examples/simple_brightness_command.rs index c56a582..22272ea 100644 --- a/firmware-api/examples/simple_brightness_command.rs +++ b/firmware-api/examples/simple_brightness_command.rs @@ -11,7 +11,7 @@ fn main() { .unwrap_or_else(|e| panic!("Failed to open device: {}", e)); let device = Device::new( - HidDeviceWrapper::new(&hid_device, false), + HidDeviceWrapper::new(hid_device, false), FunctionHandler::new(|_| {}), ); device diff --git a/firmware-api/examples/simple_connect_and_read.rs b/firmware-api/examples/simple_connect_and_read.rs index 3392c86..e2d984b 100644 --- a/firmware-api/examples/simple_connect_and_read.rs +++ b/firmware-api/examples/simple_connect_and_read.rs @@ -9,7 +9,7 @@ fn main() { .unwrap_or_else(|e| panic!("Failed to open device: {}", e)); let device = Device::new( - HidDeviceWrapper::new(&hid_device, false), + HidDeviceWrapper::new(hid_device, false), FunctionHandler::new(|action| println!("{:?}", action)), ); diff --git a/firmware-api/src/device.rs b/firmware-api/src/device.rs index a54d025..fc18729 100644 --- a/firmware-api/src/device.rs +++ b/firmware-api/src/device.rs @@ -18,19 +18,19 @@ pub trait HidDeviceOperations { fn write(&self, data: &[u8]) -> HidResult; } -pub struct HidDeviceWrapper<'a> { - device: &'a hidapi::HidDevice, +pub struct HidDeviceWrapper { + device: hidapi::HidDevice, } /// Warning: this device will read in non-blocking mode -impl<'a> HidDeviceWrapper<'a> { - pub fn new(device: &'a hidapi::HidDevice, blocking_read: bool) -> Self { +impl HidDeviceWrapper { + pub fn new(device: hidapi::HidDevice, blocking_read: bool) -> Self { device.set_blocking_mode(blocking_read).ok(); Self { device } } } -impl HidDeviceOperations for HidDeviceWrapper<'_> { +impl HidDeviceOperations for HidDeviceWrapper { fn read(&self, buffer: &mut [u8]) -> HidResult { self.device.read(buffer) } @@ -170,9 +170,9 @@ impl Device { } } -impl<'a> Device, FunctionHandler> { +impl Device { pub fn from_hid_device( - hid_device: &'a hidapi::HidDevice, + hid_device: hidapi::HidDevice, handler: FunctionHandler, blocking_read: bool, ) -> Self { diff --git a/messaging/examples/client_sending_key_config_to_server.rs b/messaging/examples/client_sending_key_config_to_server.rs index e9ccc28..d1320d6 100644 --- a/messaging/examples/client_sending_key_config_to_server.rs +++ b/messaging/examples/client_sending_key_config_to_server.rs @@ -87,7 +87,7 @@ fn main() { ) .unwrap(), 4 => handler.set_brightness(2).unwrap(), - 5 => handler.clear_all_images().unwrap(), + 5 => handler.clear_all_images(false).unwrap(), 6 => handler .clear_display_zone_image(DisplayZone::BUTTON_2) .unwrap(), diff --git a/messaging/protobufs/commands/display_zone_image.proto b/messaging/protobufs/commands/display_zone_image.proto index da03675..85b901a 100644 --- a/messaging/protobufs/commands/display_zone_image.proto +++ b/messaging/protobufs/commands/display_zone_image.proto @@ -13,4 +13,6 @@ message ClearDisplayZoneImage { DisplayZone display_zone = 1; } -message ClearAllDisplayZoneImages {} +message ClearAllDisplayZoneImages { + bool unpersist_images = 1; +} diff --git a/messaging/src/client_wrapper.rs b/messaging/src/client_wrapper.rs index 2ca7ec7..1232f5a 100644 --- a/messaging/src/client_wrapper.rs +++ b/messaging/src/client_wrapper.rs @@ -46,7 +46,9 @@ pub trait ClientCommands { ) -> Result<(), Error>; /// Clears all images from all display zones, resetting them to default/blank state - fn clear_all_images(&mut self) -> Result<(), Error>; + /// + /// * `unpersist_image` whether the stored images should not show again on the next boot + fn clear_all_images(&mut self, unpersist_image: bool) -> Result<(), Error>; /// Clears the image from a specific display zone, resetting it to default/blank state /// @@ -113,10 +115,11 @@ impl ClientCommands for ClientWrapper { ) } - fn clear_all_images(&mut self) -> Result<(), Error> { + fn clear_all_images(&mut self, unpersist_images: bool) -> Result<(), Error> { self.client.send_message( create_command(Command::ClearAllDisplayZoneImagesCommand( ClearAllDisplayZoneImages { + unpersist_images, ..ClearAllDisplayZoneImages::default() }, )) diff --git a/messaging/src/protos/display_zone_image.rs b/messaging/src/protos/display_zone_image.rs index 7495719..6411022 100644 --- a/messaging/src/protos/display_zone_image.rs +++ b/messaging/src/protos/display_zone_image.rs @@ -289,6 +289,9 @@ impl ::protobuf::reflect::ProtobufValue for ClearDisplayZoneImage { // @@protoc_insertion_point(message:display_zone_image.ClearAllDisplayZoneImages) #[derive(PartialEq,Clone,Default,Debug)] pub struct ClearAllDisplayZoneImages { + // message fields + // @@protoc_insertion_point(field:display_zone_image.ClearAllDisplayZoneImages.unpersist_images) + pub unpersist_images: bool, // special fields // @@protoc_insertion_point(special_field:display_zone_image.ClearAllDisplayZoneImages.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -306,8 +309,13 @@ impl ClearAllDisplayZoneImages { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(0); + let mut fields = ::std::vec::Vec::with_capacity(1); let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "unpersist_images", + |m: &ClearAllDisplayZoneImages| { &m.unpersist_images }, + |m: &mut ClearAllDisplayZoneImages| { &mut m.unpersist_images }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "ClearAllDisplayZoneImages", fields, @@ -326,6 +334,9 @@ impl ::protobuf::Message for ClearAllDisplayZoneImages { fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { while let Some(tag) = is.read_raw_tag_or_eof()? { match tag { + 8 => { + self.unpersist_images = is.read_bool()?; + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -338,12 +349,18 @@ impl ::protobuf::Message for ClearAllDisplayZoneImages { #[allow(unused_variables)] fn compute_size(&self) -> u64 { let mut my_size = 0; + if self.unpersist_images != false { + my_size += 1 + 1; + } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size } fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if self.unpersist_images != false { + os.write_bool(1, self.unpersist_images)?; + } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -361,11 +378,13 @@ impl ::protobuf::Message for ClearAllDisplayZoneImages { } fn clear(&mut self) { + self.unpersist_images = false; self.special_fields.clear(); } fn default_instance() -> &'static ClearAllDisplayZoneImages { static instance: ClearAllDisplayZoneImages = ClearAllDisplayZoneImages { + unpersist_images: false, special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -395,7 +414,8 @@ static file_descriptor_proto_data: &'static [u8] = b"\ play_zone\x18\x01\x20\x01(\x0e2\x0c.DisplayZoneR\x0bdisplayZone\x12\x1d\ \n\nimage_path\x18\x02\x20\x01(\tR\timagePath\"H\n\x15ClearDisplayZoneIm\ age\x12/\n\x0cdisplay_zone\x18\x01\x20\x01(\x0e2\x0c.DisplayZoneR\x0bdis\ - playZone\"\x1b\n\x19ClearAllDisplayZoneImagesb\x06proto3\ + playZone\"F\n\x19ClearAllDisplayZoneImages\x12)\n\x10unpersist_images\ + \x18\x01\x20\x01(\x08R\x0funpersistImagesb\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file