Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/scripts.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
local function volume()
local function display_device(widgets, offset, device, device_type)
if device and device.Connected then
if device then
table.insert(widgets, Widget.Text {
text = device.Name,
scrolling = true,
Expand Down
3 changes: 3 additions & 0 deletions omni-led-api/proto/plugin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ message EventResponse {}

message Field {
oneof field {
None f_none = 8;
bool f_bool = 1;
int64 f_integer = 2;
double f_float = 3;
Expand Down Expand Up @@ -87,3 +88,5 @@ enum LogLevelFilter {
LOG_LEVEL_FILTER_DEBUG = 5;
LOG_LEVEL_FILTER_TRACE = 6;
}

message None {}
3 changes: 3 additions & 0 deletions omni-led-api/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ macro_rules! into_field {
};
}

// None
into_field!(None, field::Field::FNone);

// Boolean values
into_field!(bool, field::Field::FBool);

Expand Down
11 changes: 5 additions & 6 deletions omni-led-applications/audio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,18 @@ Audio application sends `AUDIO` events in two forms:

1. Full update for both devices on startup and on main input/output device change
`AUDIO`: table
- `Input`: table
- `Connected`: bool
- `Input`: table | none
- `IsMuted`: bool
- `Volume`: integer
- `Name`: string
- `Output`: table
- `Connected`: bool
- `Output`: table | none
- `IsMuted`: bool
- `Volume`: integer
- `Name`: string

> `Connected` field tells if the device is actually connected or not. If `Connected` is `false` then rest of the data
> for the relevant device type is filled with dummy values.
> `Input` and `Output` fields are only sent if the devices are found. If the device is disconnected during the
lifetime of the application the fields will be set with value `none` so that they are cleaned up in the scripting
environment.

2. Partial update on main input/output device's volume change
`AUDIO`: table
Expand Down
49 changes: 32 additions & 17 deletions omni-led-applications/audio/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use audio::Audio;
use clap::Parser;
use log::debug;
use omni_led_api::plugin::Plugin;
use omni_led_api::types::Table;
use omni_led_derive::IntoProto;
use std::error::Error;
use tokio::runtime::Handle;
use tokio::sync::mpsc;
use tokio::sync::mpsc::{Receiver, Sender};
Expand All @@ -13,9 +13,9 @@ mod audio;
const NAME: &str = "AUDIO";

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
async fn main() {
let options = Options::parse();
let mut plugin = Plugin::new(NAME, &options.address).await?;
let mut plugin = Plugin::new(NAME, &options.address).await.unwrap();

let (tx, mut rx): (
Sender<(DeviceData, DeviceType)>,
Expand All @@ -33,21 +33,23 @@ async fn main() -> Result<(), Box<dyn Error>> {
);
}

let event = match device_type {
DeviceType::Input => AudioEvent {
input: Some(data),
output: None,
},
DeviceType::Output => AudioEvent {
input: None,
output: Some(data),
},
let event_data = if data.connected {
Some(EventData {
is_muted: data.is_muted,
volume: data.volume,
name: data.name,
})
} else {
None
};

let event: Table = match device_type {
DeviceType::Input => InputAudioEvent { input: event_data }.into(),
DeviceType::Output => OutputAudioEvent { output: event_data }.into(),
};

plugin.update(event.into()).await.unwrap();
}

Ok(())
}

#[derive(Copy, Clone, Debug)]
Expand All @@ -58,13 +60,26 @@ pub enum DeviceType {

#[derive(IntoProto)]
#[proto(rename_all = PascalCase)]
struct AudioEvent {
input: Option<DeviceData>,
output: Option<DeviceData>,
struct InputAudioEvent {
#[proto(strong_none)]
input: Option<EventData>,
}

#[derive(IntoProto)]
#[proto(rename_all = PascalCase)]
struct OutputAudioEvent {
#[proto(strong_none)]
output: Option<EventData>,
}

#[derive(IntoProto)]
#[proto(rename_all = PascalCase)]
struct EventData {
is_muted: bool,
volume: i32,
name: Option<String>,
}

struct DeviceData {
connected: bool,
is_muted: bool,
Expand Down
32 changes: 24 additions & 8 deletions omni-led-derive/src/into_proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::{Attribute, Data, DeriveInput};

use crate::common::{get_attribute, is_option, parse_attributes};
use crate::common::{get_attribute, get_attribute_with_default_value, is_option, parse_attributes};

pub fn expand_into_proto_derive(input: DeriveInput) -> proc_macro::TokenStream {
let name = input.ident;
Expand Down Expand Up @@ -47,10 +47,11 @@ fn generate_assignments(data: &Data, struct_attrs: &StructAttributes) -> TokenSt
None => field_name,
};

let is_option = is_option(&field.ty);

let attrs = get_field_attributes(&field.attrs);

let is_option = is_option(&field.ty);
let propagate_none = attrs.strong_none.is_some();

let value_accessor = if is_option {
quote! { value }
} else {
Expand All @@ -65,17 +66,30 @@ fn generate_assignments(data: &Data, struct_attrs: &StructAttributes) -> TokenSt
};

let insertion = quote! {
table.items.insert(#renamed.to_string(), #transformed.into());
table.items.insert(#renamed.to_string(), #transformed.into())
};

let none_insertion = quote! {
table.items.insert(#renamed.to_string(), omni_led_api::types::None{}.into())
};

if is_option {
quote! {
if let Some(value) = self.#field_identifier {
#insertion
if propagate_none {
quote! {
match self.#field_identifier {
Some(value) => #insertion,
None => #none_insertion,
};
}
} else {
quote! {
if let Some(value) = self.#field_identifier {
#insertion;
}
}
}
} else {
insertion
quote! { #insertion; }
}
});
quote! { #(#assignments)* }
Expand All @@ -99,13 +113,15 @@ fn get_struct_attributes(attributes: &Vec<Attribute>) -> StructAttributes {
}

struct FieldAttributes {
strong_none: Option<TokenStream>,
transform: Option<TokenStream>,
}

fn get_field_attributes(attributes: &Vec<Attribute>) -> FieldAttributes {
let mut attributes = parse_attributes("proto", attributes);

FieldAttributes {
strong_none: get_attribute_with_default_value(&mut attributes, "strong_none", quote! {}),
transform: get_attribute(&mut attributes, "transform"),
}
}
Expand Down
1 change: 1 addition & 0 deletions omni-led-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ libc = "0.2"
windres = "0.2"

[dev-dependencies]
omni-led-derive = { path = "../omni-led-derive", features = ["into-proto"] }
test-case = "3.3.1"

[features]
Expand Down
57 changes: 0 additions & 57 deletions omni-led-lib/src/common/common.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
use mlua::{Lua, Table, Value, chunk};
use omni_led_api::types::Field;
use omni_led_api::types::field::Field as FieldEntry;
use std::hash::{DefaultHasher, Hash, Hasher};

use crate::script_handler::script_data_types::ImageData;

#[macro_export]
macro_rules! create_table {
Expand Down Expand Up @@ -51,8 +46,6 @@ macro_rules! create_table_with_defaults {
}};
}

pub const KEY_VAL_TABLE: &str = "key-val-table";

pub fn load_internal_functions(lua: &Lua) {
let dump = lua
.create_function(|_, value: Value| {
Expand Down Expand Up @@ -90,53 +83,3 @@ pub fn load_internal_functions(lua: &Lua) {
)
.unwrap();
}

pub fn proto_to_lua_value(lua: &Lua, field: Field) -> mlua::Result<Value> {
match field.field {
None => Ok(mlua::Nil),
Some(FieldEntry::FBool(bool)) => Ok(Value::Boolean(bool)),
Some(FieldEntry::FInteger(integer)) => Ok(Value::Integer(integer)),
Some(FieldEntry::FFloat(float)) => Ok(Value::Number(float)),
Some(FieldEntry::FString(string)) => {
let string = lua.create_string(string)?;
Ok(Value::String(string))
}
Some(FieldEntry::FArray(array)) => {
let size = array.items.len();
let table = lua.create_table_with_capacity(size, 0)?;
for value in array.items {
table.push(proto_to_lua_value(lua, value)?)?;
}
Ok(Value::Table(table))
}
Some(FieldEntry::FTable(map)) => {
let size = map.items.len();
let table = lua.create_table_with_capacity(0, size)?;
for (key, value) in map.items {
table.set(key, proto_to_lua_value(lua, value)?)?;
}

let meta = lua.create_table_with_capacity(0, 1)?;
meta.set(KEY_VAL_TABLE, true)?;
_ = table.set_metatable(Some(meta));

Ok(Value::Table(table))
}
Some(FieldEntry::FImageData(image)) => {
let hash = hash(&image.data);
let image_data = ImageData {
format: image.format().try_into().map_err(mlua::Error::external)?,
bytes: image.data,
hash: Some(hash),
};
let user_data = lua.create_any_userdata(image_data)?;
Ok(Value::UserData(user_data))
}
}
}

pub fn hash<T: Hash>(t: &T) -> u64 {
let mut s = DefaultHasher::new();
t.hash(&mut s);
s.finish()
}
Loading