mirror of
https://github.com/nekename/OpenDeck.git
synced 2026-10-28 09:44:01 +00:00
feat: implement support for the infobar display on the Stream Deck Neo (#253)
Co-authored-by: nekename <63245705+nekename@users.noreply.github.com>
This commit is contained in:
1 parent
b9e47358a7
commit
5719915bf7
12 files changed
+100
-16
No files matched your search
@@ -45,6 +45,13 @@ pub async fn update_image(context: &crate::shared::Context, image: Option<&str>)
|
||||
&ImageRect::from_image_async(image::load_from_memory(&bytes)?.resize(72, 72, image::imageops::FilterType::Nearest))?,
|
||||
)
|
||||
.await?;
|
||||
} else if context.controller == "Infobar" {
|
||||
let img = image::load_from_memory(&bytes)?;
|
||||
let Some(format) = device.kind().lcd_image_format() else {
|
||||
return Err(anyhow::anyhow!("Failed to get LCD image format"));
|
||||
};
|
||||
let data = convert_image_with_format_async(format, img.resize_exact(248, 58, image::imageops::FilterType::Lanczos3))?;
|
||||
device.write_lcd_fill(&data).await?;
|
||||
} else if is_touch_point {
|
||||
let (r, g, b) = extract_average_colour(&image::load_from_memory(&bytes)?);
|
||||
device.set_touchpoint_color(context.position - key_count, r, g, b).await?;
|
||||
@@ -55,6 +62,12 @@ pub async fn update_image(context: &crate::shared::Context, image: Option<&str>)
|
||||
device
|
||||
.write_lcd(context.position as u16 * 200, 0, &ImageRect::from_image_async(image::DynamicImage::new_rgb8(200, 100))?)
|
||||
.await?;
|
||||
} else if context.controller == "Infobar" {
|
||||
let Some(format) = device.kind().lcd_image_format() else {
|
||||
return Err(anyhow::anyhow!("Failed to get LCD image format"));
|
||||
};
|
||||
let data = convert_image_with_format_async(format, image::DynamicImage::new_rgb8(248, 58))?;
|
||||
device.write_lcd_fill(&data).await?;
|
||||
} else if is_touch_point {
|
||||
device.set_touchpoint_color(context.position - key_count, 0, 0, 0).await?;
|
||||
} else {
|
||||
@@ -79,6 +92,10 @@ pub async fn clear_screen(id: &str) -> Result<(), anyhow::Error> {
|
||||
device
|
||||
.write_lcd_fill(&convert_image_with_format_async(device.kind().lcd_image_format().unwrap(), image::DynamicImage::new_rgb8(800, 100))?)
|
||||
.await?;
|
||||
} else if device.kind() == Kind::Neo {
|
||||
device
|
||||
.write_lcd_fill(&convert_image_with_format_async(device.kind().lcd_image_format().unwrap(), image::DynamicImage::new_rgb8(248, 58))?)
|
||||
.await?;
|
||||
}
|
||||
clear_all_touchpoints(device).await;
|
||||
device.flush().await?;
|
||||
@@ -122,6 +139,7 @@ async fn init(device: AsyncStreamDeck, device_id: String) {
|
||||
|
||||
let reader = device.get_reader();
|
||||
ELGATO_DEVICES.write().await.insert(device_id.clone(), device);
|
||||
let _ = clear_screen(&device_id).await;
|
||||
|
||||
crate::events::inbound::devices::register_device(
|
||||
"",
|
||||
@@ -134,6 +152,7 @@ async fn init(device: AsyncStreamDeck, device_id: String) {
|
||||
columns: kind.column_count(),
|
||||
encoders: kind.encoder_count(),
|
||||
touchpoints: kind.touchpoint_count(),
|
||||
infobars: if kind == Kind::Neo { 1 } else { 0 },
|
||||
r#type: device_type,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -52,7 +52,13 @@ pub async fn set_selected_profile(device: String, id: String) -> Result<(), Erro
|
||||
|
||||
if selected_profile != id {
|
||||
let old_profile = &locks.profile_stores.get_profile_store(&DEVICES.get(&device).unwrap(), &selected_profile)?.value;
|
||||
for instance in old_profile.keys.iter().flatten().chain(&mut old_profile.sliders.iter().flatten()) {
|
||||
for instance in old_profile
|
||||
.keys
|
||||
.iter()
|
||||
.flatten()
|
||||
.chain(&mut old_profile.sliders.iter().flatten())
|
||||
.chain(&mut old_profile.infobars.iter().flatten())
|
||||
{
|
||||
if !matches!(instance.action.uuid.as_str(), "opendeck.multiaction" | "opendeck.toggleaction") {
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance, false).await;
|
||||
} else {
|
||||
@@ -67,7 +73,13 @@ pub async fn set_selected_profile(device: String, id: String) -> Result<(), Erro
|
||||
// We must use the mutable version of get_profile_store in order to create the store if it does not exist.
|
||||
let store = locks.profile_stores.get_profile_store_mut(&DEVICES.get(&device).unwrap(), &id).await?;
|
||||
let new_profile = &store.value;
|
||||
for instance in new_profile.keys.iter().flatten().chain(&mut new_profile.sliders.iter().flatten()) {
|
||||
for instance in new_profile
|
||||
.keys
|
||||
.iter()
|
||||
.flatten()
|
||||
.chain(&mut new_profile.sliders.iter().flatten())
|
||||
.chain(&mut new_profile.infobars.iter().flatten())
|
||||
{
|
||||
if !matches!(instance.action.uuid.as_str(), "opendeck.multiaction" | "opendeck.toggleaction") {
|
||||
let _ = crate::events::outbound::will_appear::will_appear(instance).await;
|
||||
} else {
|
||||
|
||||
@@ -27,7 +27,14 @@ pub async fn register_device(uuid: &str, mut event: PayloadEvent<crate::shared::
|
||||
let mut locks = crate::store::profiles::acquire_locks_mut().await;
|
||||
let selected_profile = locks.device_stores.get_selected_profile(&event.payload.id)?;
|
||||
let profile = locks.profile_stores.get_profile_store(&DEVICES.get(&event.payload.id).unwrap(), &selected_profile)?;
|
||||
for instance in profile.value.keys.iter().flatten().chain(profile.value.sliders.iter().flatten()) {
|
||||
for instance in profile
|
||||
.value
|
||||
.keys
|
||||
.iter()
|
||||
.flatten()
|
||||
.chain(profile.value.sliders.iter().flatten())
|
||||
.chain(profile.value.infobars.iter().flatten())
|
||||
{
|
||||
let _ = crate::events::outbound::will_appear::will_appear(instance).await;
|
||||
}
|
||||
|
||||
@@ -53,7 +60,14 @@ pub async fn deregister_device(uuid: &str, event: PayloadEvent<String>) -> Resul
|
||||
|
||||
let selected_profile = locks.device_stores.get_selected_profile(&event.payload)?;
|
||||
let profile = locks.profile_stores.get_profile_store(&DEVICES.get(&event.payload).unwrap(), &selected_profile)?;
|
||||
for instance in profile.value.keys.iter().flatten().chain(profile.value.sliders.iter().flatten()) {
|
||||
for instance in profile
|
||||
.value
|
||||
.keys
|
||||
.iter()
|
||||
.flatten()
|
||||
.chain(profile.value.sliders.iter().flatten())
|
||||
.chain(profile.value.infobars.iter().flatten())
|
||||
{
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance, false).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ pub struct DeviceInfo {
|
||||
pub encoders: u8,
|
||||
#[serde_inline_default(0)]
|
||||
pub touchpoints: u8,
|
||||
#[serde_inline_default(0)]
|
||||
pub infobars: u8,
|
||||
pub r#type: u8,
|
||||
}
|
||||
|
||||
@@ -312,6 +314,8 @@ pub struct Profile {
|
||||
pub id: String,
|
||||
pub keys: Vec<Option<ActionInstance>>,
|
||||
pub sliders: Vec<Option<ActionInstance>>,
|
||||
#[serde(default)]
|
||||
pub infobars: Vec<Option<ActionInstance>>,
|
||||
}
|
||||
|
||||
/// A map of category names to a list of actions in that category.
|
||||
|
||||
@@ -39,11 +39,13 @@ impl ProfileStores {
|
||||
id: id.to_owned(),
|
||||
keys: Vec::new(),
|
||||
sliders: Vec::new(),
|
||||
infobars: Vec::new(),
|
||||
};
|
||||
|
||||
let mut store = Store::new(&canonical_id, &config_dir().join("profiles"), default).context(format!("Failed to create store for profile {}", canonical_id))?;
|
||||
store.value.keys.resize((device.rows * device.columns + device.touchpoints) as usize, None);
|
||||
store.value.sliders.resize(device.encoders as usize, None);
|
||||
store.value.infobars.resize(device.infobars as usize, None);
|
||||
|
||||
let categories = crate::shared::CATEGORIES.read().await;
|
||||
let actions = categories.values().flat_map(|v| v.actions.iter()).collect::<Vec<_>>();
|
||||
@@ -53,7 +55,7 @@ impl ProfileStores {
|
||||
instance.action.plugin == "opendeck"
|
||||
|| (plugins_dir.join(&instance.action.plugin).exists() && (!registered.contains(&instance.action.plugin) || actions.iter().any(|v| v.uuid == instance.action.uuid)))
|
||||
};
|
||||
for slot in store.value.keys.iter_mut().chain(store.value.sliders.iter_mut()) {
|
||||
for slot in store.value.keys.iter_mut().chain(store.value.sliders.iter_mut()).chain(store.value.infobars.iter_mut()) {
|
||||
if let Some(instance) = slot {
|
||||
if !keep_instance(instance) {
|
||||
*slot = None;
|
||||
@@ -157,7 +159,7 @@ impl ProfileStores {
|
||||
pub fn all_from_plugin(&self, plugin: &str) -> Vec<crate::shared::ActionContext> {
|
||||
let mut all = vec![];
|
||||
for store in self.stores.values() {
|
||||
for instance in store.value.keys.iter().chain(&store.value.sliders).flatten() {
|
||||
for instance in store.value.keys.iter().chain(&store.value.sliders).chain(&store.value.infobars).flatten() {
|
||||
if instance.action.plugin == plugin {
|
||||
all.push(instance.context.clone());
|
||||
} else if let Some(children) = &instance.children {
|
||||
@@ -301,6 +303,7 @@ pub async fn get_slot<'a>(context: &crate::shared::Context, locks: &'a Locks<'_>
|
||||
|
||||
let configured = match &context.controller[..] {
|
||||
"Encoder" => store.value.sliders.get(context.position as usize).ok_or_else(|| anyhow!("index out of bounds"))?,
|
||||
"Infobar" => store.value.infobars.get(context.position as usize).ok_or_else(|| anyhow!("index out of bounds"))?,
|
||||
_ => store.value.keys.get(context.position as usize).ok_or_else(|| anyhow!("index out of bounds"))?,
|
||||
};
|
||||
|
||||
@@ -313,6 +316,7 @@ pub async fn get_slot_mut<'a>(context: &crate::shared::Context, locks: &'a mut L
|
||||
|
||||
let configured = match &context.controller[..] {
|
||||
"Encoder" => store.value.sliders.get_mut(context.position as usize).ok_or_else(|| anyhow!("index out of bounds"))?,
|
||||
"Infobar" => store.value.infobars.get_mut(context.position as usize).ok_or_else(|| anyhow!("index out of bounds"))?,
|
||||
_ => store.value.keys.get_mut(context.position as usize).ok_or_else(|| anyhow!("index out of bounds"))?,
|
||||
};
|
||||
|
||||
|
||||
@@ -190,6 +190,8 @@ impl DiskActionInstance {
|
||||
pub struct DiskProfile {
|
||||
pub keys: Vec<Option<DiskActionInstance>>,
|
||||
pub sliders: Vec<Option<DiskActionInstance>>,
|
||||
#[serde(default)]
|
||||
pub infobars: Vec<Option<DiskActionInstance>>,
|
||||
}
|
||||
|
||||
impl From<&Profile> for DiskProfile {
|
||||
@@ -197,6 +199,7 @@ impl From<&Profile> for DiskProfile {
|
||||
Self {
|
||||
keys: value.keys.clone().into_iter().map(|x| x.map(|v| v.into())).collect(),
|
||||
sliders: value.sliders.clone().into_iter().map(|x| x.map(|v| v.into())).collect(),
|
||||
infobars: value.infobars.clone().into_iter().map(|x| x.map(|v| v.into())).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,6 +215,7 @@ impl DiskProfile {
|
||||
id,
|
||||
keys: self.keys.into_iter().map(|x| x.map(|v| v.into_action_instance(path))).collect(),
|
||||
sliders: self.sliders.into_iter().map(|x| x.map(|v| v.into_action_instance(path))).collect(),
|
||||
infobars: self.infobars.into_iter().map(|x| x.map(|v| v.into_action_instance(path))).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user