feat: 支持预览灯条排序效果。
This commit is contained in:
parent
958a422672
commit
4e75aa4307
114
src-tauri/Cargo.lock
generated
114
src-tauri/Cargo.lock
generated
@ -38,6 +38,17 @@ version = "1.0.69"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atk"
|
name = "atk"
|
||||||
version = "0.15.1"
|
version = "0.15.1"
|
||||||
@ -230,6 +241,15 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmake"
|
||||||
|
version = "0.1.49"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cocoa"
|
name = "cocoa"
|
||||||
version = "0.24.1"
|
version = "0.24.1"
|
||||||
@ -283,6 +303,15 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concurrent-queue"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -583,6 +612,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "2.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@ -664,6 +699,21 @@ dependencies = [
|
|||||||
"new_debug_unreachable",
|
"new_debug_unreachable",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.27"
|
version = "0.3.27"
|
||||||
@ -671,6 +721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac"
|
checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -707,21 +758,37 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.27"
|
version = "0.3.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879"
|
checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-timer"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.27"
|
version = "0.3.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
|
checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
@ -1572,12 +1639,51 @@ dependencies = [
|
|||||||
"pathdiff",
|
"pathdiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paho-mqtt"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6a19171f5b405f350373e32b6c2c4b47c225afccc837c11d2e7e22ba1749c62"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"futures",
|
||||||
|
"futures-timer",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"paho-mqtt-sys",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paho-mqtt-sys"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1782b5e75d712f951a2a4c7d3175a2ef37d93ddb3ad8656b37092f3f05464bc9"
|
||||||
|
dependencies = [
|
||||||
|
"cmake",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.15.10"
|
version = "0.15.10"
|
||||||
@ -2653,12 +2759,14 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"hex",
|
"hex",
|
||||||
"log",
|
"log",
|
||||||
|
"paho-mqtt",
|
||||||
"paris",
|
"paris",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.7.3",
|
"toml 0.7.3",
|
||||||
"url-build-parse",
|
"url-build-parse",
|
||||||
@ -2974,6 +3082,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.0.11"
|
version = "0.0.11"
|
||||||
|
@ -28,6 +28,8 @@ url-build-parse = "9.0.0"
|
|||||||
color_space = "0.5.3"
|
color_space = "0.5.3"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
toml = "0.7.3"
|
toml = "0.7.3"
|
||||||
|
paho-mqtt = "0.12.1"
|
||||||
|
time = {version="0.3.20", features= ["formatting"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||||
|
@ -3,7 +3,10 @@ use std::env::current_dir;
|
|||||||
use paris::{error, info};
|
use paris::{error, info};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::api::path::config_dir;
|
use tauri::api::path::config_dir;
|
||||||
use tokio::sync::OnceCell;
|
|
||||||
|
use crate::screenshot::{self, LedSamplePoints};
|
||||||
|
|
||||||
|
const CONFIG_FILE_NAME: &str = "cc.ivanli.ambient_light/led_strip_config.toml";
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub enum Border {
|
pub enum Border {
|
||||||
@ -32,23 +35,16 @@ pub struct LedStripConfig {
|
|||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
pub struct LedStripConfigGroup {
|
pub struct LedStripConfigGroup {
|
||||||
pub items: Vec<LedStripConfig>,
|
pub strips: Vec<LedStripConfig>,
|
||||||
|
pub mappers: Vec<SamplePointMapper>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LedStripConfigGroup {
|
||||||
impl LedStripConfig {
|
pub async fn read_config() -> anyhow::Result<Self> {
|
||||||
pub async fn global() -> &'static Vec<LedStripConfig> {
|
|
||||||
static LED_STRIP_CONFIGS_GLOBAL: OnceCell<Vec<LedStripConfig>> = OnceCell::const_new();
|
|
||||||
LED_STRIP_CONFIGS_GLOBAL
|
|
||||||
.get_or_init(|| async { Self::read_config().await.unwrap() })
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn read_config() -> anyhow::Result<Vec<Self>> {
|
|
||||||
// config path
|
// config path
|
||||||
let path = config_dir()
|
let path = config_dir()
|
||||||
.unwrap_or(current_dir().unwrap())
|
.unwrap_or(current_dir().unwrap())
|
||||||
.join("led_strip_config.toml");
|
.join(CONFIG_FILE_NAME);
|
||||||
|
|
||||||
let exists = tokio::fs::try_exists(path.clone())
|
let exists = tokio::fs::try_exists(path.clone())
|
||||||
.await
|
.await
|
||||||
@ -60,42 +56,44 @@ impl LedStripConfig {
|
|||||||
let config: LedStripConfigGroup = toml::from_str(&config)
|
let config: LedStripConfigGroup = toml::from_str(&config)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to parse config file: {}", e))?;
|
.map_err(|e| anyhow::anyhow!("Failed to parse config file: {}", e))?;
|
||||||
|
|
||||||
Ok(config.items)
|
Ok(config)
|
||||||
} else {
|
} else {
|
||||||
info!("config file not exist, fallback to default config");
|
info!("config file not exist, fallback to default config");
|
||||||
Ok(Self::get_default_config().await?)
|
Ok(Self::get_default_config().await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write_config(configs: &Vec<Self>) -> anyhow::Result<()> {
|
pub async fn write_config(configs: &Self) -> anyhow::Result<()> {
|
||||||
let path = config_dir()
|
let path = config_dir()
|
||||||
.unwrap_or(current_dir().unwrap())
|
.unwrap_or(current_dir().unwrap())
|
||||||
.join("led_strip_config.toml");
|
.join(CONFIG_FILE_NAME);
|
||||||
|
|
||||||
let configs = LedStripConfigGroup { items: configs.clone() };
|
tokio::fs::create_dir_all(path.parent().unwrap()).await?;
|
||||||
|
|
||||||
let config = toml::to_string(&configs).map_err(|e| {
|
let config_text = toml::to_string(&configs).map_err(|e| {
|
||||||
anyhow::anyhow!("Failed to parse config file: {}. configs: {:?}", e, configs)
|
anyhow::anyhow!("Failed to parse config file: {}. configs: {:?}", e, configs)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
tokio::fs::write(&path, config).await.map_err(|e| {
|
tokio::fs::write (&path, config_text).await.map_err(|e| {
|
||||||
anyhow::anyhow!("Failed to write config file: {}. path: {:?}", e, &path)
|
anyhow::anyhow!("Failed to write config file: {}. path: {:?}", e, &path)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_default_config() -> anyhow::Result<Vec<Self>> {
|
pub async fn get_default_config() -> anyhow::Result<Self> {
|
||||||
let displays = display_info::DisplayInfo::all().map_err(|e| {
|
let displays = display_info::DisplayInfo::all().map_err(|e| {
|
||||||
error!("can not list display info: {}", e);
|
error!("can not list display info: {}", e);
|
||||||
anyhow::anyhow!("can not list display info: {}", e)
|
anyhow::anyhow!("can not list display info: {}", e)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut configs = Vec::new();
|
let mut strips = Vec::new();
|
||||||
|
let mut mappers = Vec::new();
|
||||||
for (i, display) in displays.iter().enumerate() {
|
for (i, display) in displays.iter().enumerate() {
|
||||||
|
let mut configs = Vec::new();
|
||||||
for j in 0..4 {
|
for j in 0..4 {
|
||||||
let config = Self {
|
let item = LedStripConfig {
|
||||||
index: j + i * 4 * 30,
|
index: j + i * 4,
|
||||||
display_id: display.id,
|
display_id: display.id,
|
||||||
border: match j {
|
border: match j {
|
||||||
0 => Border::Top,
|
0 => Border::Top,
|
||||||
@ -104,14 +102,18 @@ impl LedStripConfig {
|
|||||||
3 => Border::Right,
|
3 => Border::Right,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
start_pos: 0,
|
start_pos: j + i * 4 * 30,
|
||||||
len: 30,
|
len: 30,
|
||||||
};
|
};
|
||||||
configs.push(config);
|
configs.push(item);
|
||||||
|
strips.push(item);
|
||||||
|
mappers.push(SamplePointMapper {
|
||||||
|
start: (j + i * 4) * 30,
|
||||||
|
end: (j + i * 4 + 1) * 30,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(Self { strips, mappers })
|
||||||
Ok(configs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +222,7 @@ impl LedStripConfigOfDisplays {
|
|||||||
start_pos: i * 4 * 30 + 90,
|
start_pos: i * 4 * 30 + 90,
|
||||||
len: 30,
|
len: 30,
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
configs.push(config);
|
configs.push(config);
|
||||||
}
|
}
|
||||||
@ -228,3 +230,15 @@ impl LedStripConfigOfDisplays {
|
|||||||
Ok(configs[0])
|
Ok(configs[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SamplePointMapper {
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SamplePointConfig {
|
||||||
|
pub display_id: u32,
|
||||||
|
pub points: Vec<LedSamplePoints>,
|
||||||
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use std::{sync::Arc, borrow::Borrow};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tauri::async_runtime::RwLock;
|
use tauri::async_runtime::RwLock;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
use crate::ambient_light::{config, LedStripConfig};
|
use crate::ambient_light::{config, LedStripConfigGroup};
|
||||||
|
|
||||||
use super::Border;
|
use super::Border;
|
||||||
|
|
||||||
pub struct ConfigManager {
|
pub struct ConfigManager {
|
||||||
configs: Arc<RwLock<Vec<LedStripConfig>>>,
|
config: Arc<RwLock<LedStripConfigGroup>>,
|
||||||
config_update_receiver: tokio::sync::watch::Receiver<Vec<LedStripConfig>>,
|
config_update_receiver: tokio::sync::watch::Receiver<LedStripConfigGroup>,
|
||||||
config_update_sender: tokio::sync::watch::Sender<Vec<LedStripConfig>>,
|
config_update_sender: tokio::sync::watch::Sender<LedStripConfigGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigManager {
|
impl ConfigManager {
|
||||||
@ -18,11 +18,11 @@ impl ConfigManager {
|
|||||||
static CONFIG_MANAGER_GLOBAL: OnceCell<ConfigManager> = OnceCell::const_new();
|
static CONFIG_MANAGER_GLOBAL: OnceCell<ConfigManager> = OnceCell::const_new();
|
||||||
CONFIG_MANAGER_GLOBAL
|
CONFIG_MANAGER_GLOBAL
|
||||||
.get_or_init(|| async {
|
.get_or_init(|| async {
|
||||||
let configs = LedStripConfig::read_config().await.unwrap();
|
let configs = LedStripConfigGroup::read_config().await.unwrap();
|
||||||
let (config_update_sender, config_update_receiver) =
|
let (config_update_sender, config_update_receiver) =
|
||||||
tokio::sync::watch::channel(configs.clone());
|
tokio::sync::watch::channel(configs.clone());
|
||||||
ConfigManager {
|
ConfigManager {
|
||||||
configs: Arc::new(RwLock::new(configs)),
|
config: Arc::new(RwLock::new(configs)),
|
||||||
config_update_receiver,
|
config_update_receiver,
|
||||||
config_update_sender,
|
config_update_sender,
|
||||||
}
|
}
|
||||||
@ -31,45 +31,71 @@ impl ConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reload(&self) -> anyhow::Result<()> {
|
pub async fn reload(&self) -> anyhow::Result<()> {
|
||||||
let mut configs = self.configs.write().await;
|
let mut configs = self.config.write().await;
|
||||||
*configs = LedStripConfig::read_config().await?;
|
*configs = LedStripConfigGroup::read_config().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&self, configs: &Vec<LedStripConfig>) -> anyhow::Result<()> {
|
pub async fn update(&self, configs: &LedStripConfigGroup) -> anyhow::Result<()> {
|
||||||
LedStripConfig::write_config(configs).await?;
|
LedStripConfigGroup::write_config(configs).await?;
|
||||||
self.reload().await?;
|
self.reload().await?;
|
||||||
|
|
||||||
self.config_update_sender.send(configs.clone()).map_err(|e| {
|
self.config_update_sender
|
||||||
anyhow::anyhow!("Failed to send config update: {}", e)
|
.send(configs.clone())
|
||||||
})?;
|
.map_err(|e| anyhow::anyhow!("Failed to send config update: {}", e))?;
|
||||||
|
|
||||||
log::info!("config updated: {:?}", configs);
|
log::info!("config updated: {:?}", configs);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn configs(&self) -> Vec<LedStripConfig> {
|
pub async fn configs(&self) -> LedStripConfigGroup {
|
||||||
self.configs.read().await.clone()
|
self.config.read().await.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn patch_led_strip_len(&self, display_id: u32, border: Border, delta_len: i8) -> anyhow::Result<()> {
|
pub async fn patch_led_strip_len(
|
||||||
let mut configs = self.configs.write().await;
|
&self,
|
||||||
|
display_id: u32,
|
||||||
|
border: Border,
|
||||||
|
delta_len: i8,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut config = self.config.write().await;
|
||||||
|
|
||||||
for config in configs.iter_mut() {
|
for config in config.strips.iter_mut() {
|
||||||
if config.display_id == display_id && config.border == border {
|
if config.display_id == display_id && config.border == border {
|
||||||
let target = config.len as i64 + delta_len as i64;
|
let target = config.len as i64 + delta_len as i64;
|
||||||
if target < 0 || target > 1000 {
|
if target < 0 || target > 1000 {
|
||||||
return Err(anyhow::anyhow!("Overflow. range: 0-1000, current: {}", target));
|
return Err(anyhow::anyhow!(
|
||||||
|
"Overflow. range: 0-1000, current: {}",
|
||||||
|
target
|
||||||
|
));
|
||||||
}
|
}
|
||||||
config.len = target as usize;
|
config.len = target as usize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cloned_config = configs.clone();
|
let cloned_config = config.clone();
|
||||||
|
|
||||||
drop(configs);
|
drop(config);
|
||||||
|
|
||||||
|
self.update(&cloned_config).await?;
|
||||||
|
|
||||||
|
self.config_update_sender
|
||||||
|
.send(cloned_config)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to send config update: {}", e))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_items(&self, items: Vec<config::LedStripConfig>) -> anyhow::Result<()> {
|
||||||
|
let mut config = self.config.write().await;
|
||||||
|
|
||||||
|
config.strips = items;
|
||||||
|
|
||||||
|
let cloned_config = config.clone();
|
||||||
|
|
||||||
|
drop(config);
|
||||||
|
|
||||||
self.update(&cloned_config).await?;
|
self.update(&cloned_config).await?;
|
||||||
|
|
||||||
@ -82,7 +108,7 @@ impl ConfigManager {
|
|||||||
|
|
||||||
pub fn clone_config_update_receiver(
|
pub fn clone_config_update_receiver(
|
||||||
&self,
|
&self,
|
||||||
) -> tokio::sync::watch::Receiver<Vec<LedStripConfig>> {
|
) -> tokio::sync::watch::Receiver<LedStripConfigGroup> {
|
||||||
self.config_update_receiver.clone()
|
self.config_update_receiver.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
mod config;
|
mod config;
|
||||||
mod config_manager;
|
mod config_manager;
|
||||||
|
mod publisher;
|
||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use config_manager::*;
|
pub use config_manager::*;
|
||||||
|
pub use publisher::*;
|
||||||
|
105
src-tauri/src/ambient_light/publisher.rs
Normal file
105
src-tauri/src/ambient_light/publisher.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use paris::warn;
|
||||||
|
use tauri::async_runtime::RwLock;
|
||||||
|
use tokio::sync::watch;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
ambient_light::{config, ConfigManager},
|
||||||
|
rpc::MqttRpc,
|
||||||
|
screenshot_manager::ScreenshotManager,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct LedColorsPublisher {
|
||||||
|
rx: Arc<RwLock<watch::Receiver<Vec<u8>>>>,
|
||||||
|
tx: Arc<RwLock<watch::Sender<Vec<u8>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LedColorsPublisher {
|
||||||
|
pub async fn global() -> &'static Self {
|
||||||
|
static LED_COLORS_PUBLISHER_GLOBAL: tokio::sync::OnceCell<LedColorsPublisher> =
|
||||||
|
tokio::sync::OnceCell::const_new();
|
||||||
|
|
||||||
|
let (tx, rx) = watch::channel(Vec::new());
|
||||||
|
|
||||||
|
LED_COLORS_PUBLISHER_GLOBAL
|
||||||
|
.get_or_init(|| async {
|
||||||
|
LedColorsPublisher {
|
||||||
|
rx: Arc::new(RwLock::new(rx)),
|
||||||
|
tx: Arc::new(RwLock::new(tx)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&self) -> anyhow::Result<()> {
|
||||||
|
let tx = self.tx.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let tx = tx.write().await;
|
||||||
|
|
||||||
|
let screenshot_manager = ScreenshotManager::global().await;
|
||||||
|
let config_manager = ConfigManager::global().await;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let configs = config_manager.configs().await;
|
||||||
|
let channels = screenshot_manager.channels.read().await;
|
||||||
|
|
||||||
|
let mut colors_configs = Vec::new();
|
||||||
|
|
||||||
|
for (display_id, rx) in channels.iter() {
|
||||||
|
let led_strip_configs: Vec<_> = configs
|
||||||
|
.strips
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.display_id == *display_id)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if led_strip_configs.len() == 0 {
|
||||||
|
warn!("no led strip config for display_id: {}", display_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rx = rx.clone();
|
||||||
|
|
||||||
|
if rx.changed().await.is_ok() {
|
||||||
|
let screenshot = rx.borrow().clone();
|
||||||
|
// log::info!("screenshot updated: {:?}", display_id);
|
||||||
|
|
||||||
|
let points: Vec<_> = led_strip_configs
|
||||||
|
.iter()
|
||||||
|
.map(|config| screenshot.get_sample_points(&config))
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let colors_config = config::SamplePointConfig {
|
||||||
|
display_id: *display_id,
|
||||||
|
points,
|
||||||
|
};
|
||||||
|
|
||||||
|
colors_configs.push(colors_config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let colors = screenshot_manager.get_all_colors(&colors_configs, &configs.mappers, &channels).await;
|
||||||
|
match tx.send(colors) {
|
||||||
|
Ok(_) => {
|
||||||
|
// log::info!("colors updated");
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
warn!("colors update failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_colors(payload: Vec<u8>) -> anyhow::Result<()> {
|
||||||
|
let mqtt = MqttRpc::global().await;
|
||||||
|
|
||||||
|
mqtt.publish_led_sub_pixels(payload).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clone_receiver(&self) -> watch::Receiver<Vec<u8>> {
|
||||||
|
self.rx.read().await.clone()
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,11 @@
|
|||||||
mod ambient_light;
|
mod ambient_light;
|
||||||
mod display;
|
mod display;
|
||||||
mod led_color;
|
mod led_color;
|
||||||
|
mod rpc;
|
||||||
pub mod screenshot;
|
pub mod screenshot;
|
||||||
mod screenshot_manager;
|
mod screenshot_manager;
|
||||||
|
|
||||||
use ambient_light::{Border, LedStripConfig};
|
use ambient_light::{Border, LedColorsPublisher, LedStripConfig, LedStripConfigGroup};
|
||||||
use core_graphics::display::{
|
use core_graphics::display::{
|
||||||
kCGNullWindowID, kCGWindowImageDefault, kCGWindowListOptionOnScreenOnly, CGDisplay,
|
kCGNullWindowID, kCGWindowImageDefault, kCGWindowListOptionOnScreenOnly, CGDisplay,
|
||||||
};
|
};
|
||||||
@ -72,23 +73,23 @@ async fn subscribe_encoded_screenshot_updated(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn read_led_strip_configs() -> Result<Vec<ambient_light::LedStripConfig>, String> {
|
async fn read_led_strip_configs() -> Result<LedStripConfigGroup, String> {
|
||||||
let configs = ambient_light::LedStripConfig::read_config()
|
let config = ambient_light::LedStripConfigGroup::read_config()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("can not read led strip configs: {}", e);
|
error!("can not read led strip configs: {}", e);
|
||||||
e.to_string()
|
e.to_string()
|
||||||
})?;
|
})?;
|
||||||
Ok(configs)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn write_led_strip_configs(
|
async fn write_led_strip_configs(
|
||||||
configs: Vec<ambient_light::LedStripConfig>,
|
configs: Vec<ambient_light::LedStripConfig>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
ambient_light::LedStripConfig::write_config(&configs)
|
let config_manager = ambient_light::ConfigManager::global().await;
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
config_manager.set_items(configs).await.map_err(|e| {
|
||||||
error!("can not write led strip configs: {}", e);
|
error!("can not write led strip configs: {}", e);
|
||||||
e.to_string()
|
e.to_string()
|
||||||
})
|
})
|
||||||
@ -103,9 +104,7 @@ async fn get_led_strips_sample_points(
|
|||||||
if let Some(rx) = channels.get(&config.display_id) {
|
if let Some(rx) = channels.get(&config.display_id) {
|
||||||
let rx = rx.clone();
|
let rx = rx.clone();
|
||||||
let screenshot = rx.borrow().clone();
|
let screenshot = rx.borrow().clone();
|
||||||
let width = screenshot.width;
|
let sample_points = screenshot.get_sample_points(&config);
|
||||||
let height = screenshot.height;
|
|
||||||
let sample_points = Screenshot::get_sample_point(&config, width as usize, height as usize);
|
|
||||||
Ok(sample_points)
|
Ok(sample_points)
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("display not found: {}", config.display_id));
|
return Err(format!("display not found: {}", config.display_id));
|
||||||
@ -131,9 +130,27 @@ async fn get_one_edge_colors(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn get_all_colors(
|
||||||
|
configs: Vec<ambient_light::SamplePointConfig>,
|
||||||
|
mappers: Vec<ambient_light::SamplePointMapper>,
|
||||||
|
) -> Result<Vec<u8>, String> {
|
||||||
|
let screenshot_manager = ScreenshotManager::global().await;
|
||||||
|
|
||||||
|
let channels = screenshot_manager.channels.to_owned();
|
||||||
|
let channels = channels.read().await;
|
||||||
|
|
||||||
|
Ok(screenshot_manager
|
||||||
|
.get_all_colors(&configs, &mappers, &channels)
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn patch_led_strip_len(display_id: u32, border: Border, delta_len: i8) -> Result<(), String> {
|
async fn patch_led_strip_len(display_id: u32, border: Border, delta_len: i8) -> Result<(), String> {
|
||||||
info!("patch_led_strip_len: {} {:?} {}", display_id, border, delta_len);
|
info!(
|
||||||
|
"patch_led_strip_len: {} {:?} {}",
|
||||||
|
display_id, border, delta_len
|
||||||
|
);
|
||||||
let config_manager = ambient_light::ConfigManager::global().await;
|
let config_manager = ambient_light::ConfigManager::global().await;
|
||||||
config_manager
|
config_manager
|
||||||
.patch_led_strip_len(display_id, border, delta_len)
|
.patch_led_strip_len(display_id, border, delta_len)
|
||||||
@ -147,12 +164,26 @@ async fn patch_led_strip_len(display_id: u32, border: Border, delta_len: i8) ->
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn send_colors(buffer: Vec<u8>) -> Result<(), String> {
|
||||||
|
ambient_light::LedColorsPublisher::send_colors(buffer)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("can not send colors: {}", e);
|
||||||
|
e.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let screenshot_manager = ScreenshotManager::global().await;
|
let screenshot_manager = ScreenshotManager::global().await;
|
||||||
screenshot_manager.start().unwrap();
|
screenshot_manager.start().unwrap();
|
||||||
|
|
||||||
|
let led_color_publisher = ambient_light::LedColorsPublisher::global().await;
|
||||||
|
led_color_publisher.start().unwrap();
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
greet,
|
greet,
|
||||||
@ -162,7 +193,9 @@ async fn main() {
|
|||||||
write_led_strip_configs,
|
write_led_strip_configs,
|
||||||
get_led_strips_sample_points,
|
get_led_strips_sample_points,
|
||||||
get_one_edge_colors,
|
get_one_edge_colors,
|
||||||
patch_led_strip_len
|
patch_led_strip_len,
|
||||||
|
send_colors,
|
||||||
|
get_all_colors
|
||||||
])
|
])
|
||||||
.register_uri_scheme_protocol("ambient-light", move |_app, request| {
|
.register_uri_scheme_protocol("ambient-light", move |_app, request| {
|
||||||
let response = ResponseBuilder::new().header("Access-Control-Allow-Origin", "*");
|
let response = ResponseBuilder::new().header("Access-Control-Allow-Origin", "*");
|
||||||
@ -311,6 +344,22 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let app_handle = app.handle().clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let publisher = ambient_light::LedColorsPublisher::global().await;
|
||||||
|
let mut publisher_update_receiver = publisher.clone_receiver().await;
|
||||||
|
loop {
|
||||||
|
if let Err(err) = publisher_update_receiver.changed().await {
|
||||||
|
error!("publisher update receiver changed error: {}", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let publisher = publisher_update_receiver.borrow().clone();
|
||||||
|
|
||||||
|
app_handle.emit_all("led_colors_changed", publisher).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
3
src-tauri/src/rpc/mod.rs
Normal file
3
src-tauri/src/rpc/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mod mqtt;
|
||||||
|
|
||||||
|
pub use mqtt::*;
|
241
src-tauri/src/rpc/mqtt.rs
Normal file
241
src-tauri/src/rpc/mqtt.rs
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
use paho_mqtt as mqtt;
|
||||||
|
use paris::{error, info, warn};
|
||||||
|
use serde_json::json;
|
||||||
|
use std::time::Duration;
|
||||||
|
use time::{format_description, OffsetDateTime};
|
||||||
|
use tokio::{sync::OnceCell, task};
|
||||||
|
|
||||||
|
const DISPLAY_TOPIC: &'static str = "display-ambient-light/display";
|
||||||
|
const DESKTOP_TOPIC: &'static str = "display-ambient-light/desktop";
|
||||||
|
const DISPLAY_BRIGHTNESS_TOPIC: &'static str = "display-ambient-light/board/brightness";
|
||||||
|
const BOARD_SEND_CMD: &'static str = "display-ambient-light/board/cmd";
|
||||||
|
|
||||||
|
pub struct MqttRpc {
|
||||||
|
client: mqtt::AsyncClient,
|
||||||
|
// change_display_brightness_tx: broadcast::Sender<display::DisplayBrightness>,
|
||||||
|
// message_tx: broadcast::Sender<models::CmdMqMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MqttRpc {
|
||||||
|
pub async fn global() -> &'static Self {
|
||||||
|
static MQTT_RPC: OnceCell<MqttRpc> = OnceCell::const_new();
|
||||||
|
|
||||||
|
MQTT_RPC
|
||||||
|
.get_or_init(|| async {
|
||||||
|
let mqtt_rpc = MqttRpc::new().await.unwrap();
|
||||||
|
mqtt_rpc.initialize().await.unwrap();
|
||||||
|
mqtt_rpc
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new() -> anyhow::Result<Self> {
|
||||||
|
let client = mqtt::AsyncClient::new("tcp://192.168.31.11:1883")
|
||||||
|
.map_err(|err| anyhow::anyhow!("can not create MQTT client. {:?}", err))?;
|
||||||
|
|
||||||
|
client.set_connected_callback(|client| {
|
||||||
|
info!("MQTT server connected.");
|
||||||
|
|
||||||
|
client.subscribe("display-ambient-light/board/#", mqtt::QOS_1);
|
||||||
|
|
||||||
|
client.subscribe(format!("{}/#", DISPLAY_TOPIC), mqtt::QOS_1);
|
||||||
|
});
|
||||||
|
client.set_connection_lost_callback(|client| {
|
||||||
|
info!("MQTT server connection lost.");
|
||||||
|
});
|
||||||
|
client.set_disconnected_callback(|_, a1, a2| {
|
||||||
|
info!("MQTT server disconnected. {:?} {:?}", a1, a2);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut last_will_payload = serde_json::Map::new();
|
||||||
|
last_will_payload.insert("message".to_string(), json!("offline"));
|
||||||
|
last_will_payload.insert(
|
||||||
|
"time".to_string(),
|
||||||
|
serde_json::Value::String(
|
||||||
|
OffsetDateTime::now_utc()
|
||||||
|
.format(&time::format_description::well_known::iso8601::Iso8601::DEFAULT)
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let last_will = mqtt::Message::new(
|
||||||
|
format!("{}/status", DESKTOP_TOPIC),
|
||||||
|
serde_json::to_string(&last_will_payload)
|
||||||
|
.unwrap()
|
||||||
|
.as_bytes(),
|
||||||
|
mqtt::QOS_1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let connect_options = mqtt::ConnectOptionsBuilder::new()
|
||||||
|
.keep_alive_interval(Duration::from_secs(5))
|
||||||
|
.will_message(last_will)
|
||||||
|
.automatic_reconnect(Duration::from_secs(1), Duration::from_secs(5))
|
||||||
|
.finalize();
|
||||||
|
|
||||||
|
let token = client.connect(connect_options);
|
||||||
|
|
||||||
|
token.await.map_err(|err| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"can not connect MQTT server. wait for connect token failed. {:?}",
|
||||||
|
err
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// let (change_display_brightness_tx, _) =
|
||||||
|
// broadcast::channel::<display::DisplayBrightness>(16);
|
||||||
|
// let (message_tx, _) = broadcast::channel::<models::CmdMqMessage>(32);
|
||||||
|
Ok(Self { client })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn listen(&self) {
|
||||||
|
// let change_display_brightness_tx2 = self.change_display_brightness_tx.clone();
|
||||||
|
// let message_tx_cloned = self.message_tx.clone();
|
||||||
|
|
||||||
|
// let mut stream = self.client.to_owned().get_stream(100);
|
||||||
|
|
||||||
|
// while let Some(notification) = stream.next().await {
|
||||||
|
// match notification {
|
||||||
|
// Some(notification) => match notification.topic() {
|
||||||
|
// DISPLAY_BRIGHTNESS_TOPIC => {
|
||||||
|
// let payload_text = String::from_utf8(notification.payload().to_vec());
|
||||||
|
// match payload_text {
|
||||||
|
// Ok(payload_text) => {
|
||||||
|
// let display_brightness: Result<display::DisplayBrightness, _> =
|
||||||
|
// serde_json::from_str(payload_text.as_str());
|
||||||
|
// match display_brightness {
|
||||||
|
// Ok(display_brightness) => {
|
||||||
|
// match change_display_brightness_tx2.send(display_brightness)
|
||||||
|
// {
|
||||||
|
// Ok(_) => {}
|
||||||
|
// Err(err) => {
|
||||||
|
// warn!(
|
||||||
|
// "can not send display brightness to channel. {:?}",
|
||||||
|
// err
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Err(err) => {
|
||||||
|
// warn!(
|
||||||
|
// "can not parse display brightness from payload. {:?}",
|
||||||
|
// err
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Err(err) => {
|
||||||
|
// warn!("can not parse display brightness from payload. {:?}", err);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// BOARD_SEND_CMD => {
|
||||||
|
// let payload_text = String::from_utf8(notification.payload().to_vec());
|
||||||
|
// match payload_text {
|
||||||
|
// Ok(payload_text) => {
|
||||||
|
// let message: Result<models::CmdMqMessage, _> =
|
||||||
|
// serde_json::from_str(payload_text.as_str());
|
||||||
|
// match message {
|
||||||
|
// Ok(message) => match message_tx_cloned.send(message) {
|
||||||
|
// Ok(_) => {}
|
||||||
|
// Err(err) => {
|
||||||
|
// warn!("can not send message to channel. {:?}", err);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// Err(err) => {
|
||||||
|
// warn!("can not parse message from payload. {:?}", err);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Err(err) => {
|
||||||
|
// warn!("can not parse message from payload. {:?}", err);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// _ => {}
|
||||||
|
// },
|
||||||
|
// _ => {
|
||||||
|
// warn!("can not get notification from MQTT server.");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn initialize(&self) -> anyhow::Result<()> {
|
||||||
|
// self.subscribe_board()?;
|
||||||
|
// self.subscribe_display()?;
|
||||||
|
self.broadcast_desktop_online();
|
||||||
|
anyhow::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscribe_board(&self) -> anyhow::Result<()> {
|
||||||
|
self.client
|
||||||
|
.subscribe("display-ambient-light/board/#", mqtt::QOS_1)
|
||||||
|
.wait()
|
||||||
|
.map_err(|err| anyhow::anyhow!("subscribe board failed. {:?}", err))
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
fn subscribe_display(&self) -> anyhow::Result<()> {
|
||||||
|
self.client
|
||||||
|
.subscribe(format!("{}/#", DISPLAY_TOPIC), mqtt::QOS_1)
|
||||||
|
.wait()
|
||||||
|
.map_err(|err| anyhow::anyhow!("subscribe board failed. {:?}", err))
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn broadcast_desktop_online(&self) {
|
||||||
|
let client = self.client.to_owned();
|
||||||
|
task::spawn(async move {
|
||||||
|
loop {
|
||||||
|
match OffsetDateTime::now_utc()
|
||||||
|
.format(&format_description::well_known::Iso8601::DEFAULT)
|
||||||
|
{
|
||||||
|
Ok(now_str) => {
|
||||||
|
let msg = mqtt::Message::new(
|
||||||
|
"display-ambient-light/desktop/online",
|
||||||
|
now_str.as_bytes(),
|
||||||
|
mqtt::QOS_0,
|
||||||
|
);
|
||||||
|
match client.publish(msg).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(error) => {
|
||||||
|
warn!("can not publish last online time. {}", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
warn!("can not get time for now. {}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn publish_led_sub_pixels(&self, payload: Vec<u8>) -> anyhow::Result<()> {
|
||||||
|
self.client
|
||||||
|
.publish(mqtt::Message::new(
|
||||||
|
"display-ambient-light/desktop/colors",
|
||||||
|
payload,
|
||||||
|
mqtt::QOS_1,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.map_err(|error| anyhow::anyhow!("mqtt publish failed. {}", error))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn subscribe_change_display_brightness_rx(
|
||||||
|
// &self,
|
||||||
|
// ) -> broadcast::Receiver<display::DisplayBrightness> {
|
||||||
|
// self.change_display_brightness_tx.subscribe()
|
||||||
|
// }
|
||||||
|
pub async fn publish_desktop_cmd(&self, field: &str, payload: Vec<u8>) -> anyhow::Result<()> {
|
||||||
|
self.client
|
||||||
|
.publish(mqtt::Message::new(
|
||||||
|
format!("{}/{}", DESKTOP_TOPIC, field),
|
||||||
|
payload,
|
||||||
|
mqtt::QOS_1,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.map_err(|error| anyhow::anyhow!("mqtt publish failed. {}", error))
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tauri::async_runtime::RwLock;
|
use tauri::async_runtime::RwLock;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ambient_light::{LedStripConfigOfDisplays, LedStripConfig},
|
ambient_light::{LedStripConfig, LedStripConfigOfDisplays},
|
||||||
led_color::LedColor,
|
led_color::LedColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,17 +41,19 @@ impl Screenshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sample_point(
|
pub fn get_sample_points(
|
||||||
|
&self,
|
||||||
config: &LedStripConfig,
|
config: &LedStripConfig,
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
) -> Vec<LedSamplePoints> {
|
) -> Vec<LedSamplePoints> {
|
||||||
|
let height = self.height as usize;
|
||||||
|
let width = self.width as usize;
|
||||||
|
|
||||||
match config.border {
|
match config.border {
|
||||||
crate::ambient_light::Border::Top => {
|
crate::ambient_light::Border::Top => {
|
||||||
Self::get_one_edge_sample_points(height / 8, width, config.len, 5)
|
Self::get_one_edge_sample_points(height / 8, width, config.len, 5)
|
||||||
}
|
}
|
||||||
crate::ambient_light::Border::Bottom => {
|
crate::ambient_light::Border::Bottom => {
|
||||||
let points = Self::get_one_edge_sample_points(height / 9, width, config.len, 1);
|
let points = Self::get_one_edge_sample_points(height / 9, width, config.len, 5);
|
||||||
points
|
points
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|groups| -> Vec<Point> {
|
.map(|groups| -> Vec<Point> {
|
||||||
@ -60,7 +62,7 @@ impl Screenshot {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
crate::ambient_light::Border::Left => {
|
crate::ambient_light::Border::Left => {
|
||||||
let points = Self::get_one_edge_sample_points(width / 16, height, config.len, 1);
|
let points = Self::get_one_edge_sample_points(width / 16, height, config.len, 5);
|
||||||
points
|
points
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|groups| -> Vec<Point> {
|
.map(|groups| -> Vec<Point> {
|
||||||
@ -69,7 +71,7 @@ impl Screenshot {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
crate::ambient_light::Border::Right => {
|
crate::ambient_light::Border::Right => {
|
||||||
let points = Self::get_one_edge_sample_points(width / 16, height, config.len, 1);
|
let points = Self::get_one_edge_sample_points(width / 16, height, config.len, 5);
|
||||||
points
|
points
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|groups| -> Vec<Point> {
|
.map(|groups| -> Vec<Point> {
|
||||||
@ -261,6 +263,12 @@ impl Screenshot {
|
|||||||
}
|
}
|
||||||
colors
|
colors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_colors_by_sample_points(&self, points: &Vec<LedSamplePoints>) -> Vec<LedColor> {
|
||||||
|
let bytes = self.bytes.read().await;
|
||||||
|
|
||||||
|
Self::get_one_edge_colors(points, &bytes, self.bytes_per_row)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
type Point = (usize, usize);
|
type Point = (usize, usize);
|
||||||
pub type LedSamplePoints = Vec<Point>;
|
pub type LedSamplePoints = Vec<Point>;
|
||||||
|
@ -4,10 +4,14 @@ use core_graphics::display::{
|
|||||||
kCGNullWindowID, kCGWindowImageDefault, kCGWindowListOptionOnScreenOnly, CGDisplay,
|
kCGNullWindowID, kCGWindowImageDefault, kCGWindowListOptionOnScreenOnly, CGDisplay,
|
||||||
};
|
};
|
||||||
use paris::{error, info, warn};
|
use paris::{error, info, warn};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{async_runtime::RwLock, Window};
|
use tauri::{async_runtime::RwLock, Window};
|
||||||
use tokio::sync::{watch, OnceCell};
|
use tokio::sync::{watch, OnceCell};
|
||||||
|
|
||||||
use crate::screenshot::{Screenshot, ScreenshotPayload, ScreenSamplePoints};
|
use crate::{
|
||||||
|
ambient_light::{SamplePointConfig, SamplePointMapper},
|
||||||
|
screenshot::{LedSamplePoints, ScreenSamplePoints, Screenshot, ScreenshotPayload},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn take_screenshot(display_id: u32, scale_factor: f32) -> anyhow::Result<Screenshot> {
|
pub fn take_screenshot(display_id: u32, scale_factor: f32) -> anyhow::Result<Screenshot> {
|
||||||
log::debug!("take_screenshot");
|
log::debug!("take_screenshot");
|
||||||
@ -39,7 +43,12 @@ pub fn take_screenshot(display_id: u32, scale_factor: f32) -> anyhow::Result<Scr
|
|||||||
bytes_per_row,
|
bytes_per_row,
|
||||||
bytes,
|
bytes,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
ScreenSamplePoints { top: vec![], bottom: vec![], left: vec![], right: vec![] }
|
ScreenSamplePoints {
|
||||||
|
top: vec![],
|
||||||
|
bottom: vec![],
|
||||||
|
left: vec![],
|
||||||
|
right: vec![],
|
||||||
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,4 +214,59 @@ impl ScreenshotManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_colors(
|
||||||
|
&self,
|
||||||
|
configs: &Vec<SamplePointConfig>,
|
||||||
|
mappers: &Vec<SamplePointMapper>,
|
||||||
|
channels: &HashMap<u32, watch::Receiver<Screenshot>>,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
let total_leds = configs
|
||||||
|
.iter()
|
||||||
|
.fold(0, |acc, config| acc + config.points.len());
|
||||||
|
|
||||||
|
let mut global_colors = vec![0u8; total_leds * 3];
|
||||||
|
let mut all_colors = vec![];
|
||||||
|
for config in configs {
|
||||||
|
let rx = channels.get(&config.display_id);
|
||||||
|
if rx.is_none() {
|
||||||
|
error!(
|
||||||
|
"get_all_colors: can not find display_id {}",
|
||||||
|
config.display_id
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let rx = rx.unwrap();
|
||||||
|
let screenshot = rx.borrow().clone();
|
||||||
|
let mut colors = screenshot.get_colors_by_sample_points(&config.points).await;
|
||||||
|
|
||||||
|
all_colors.append(&mut colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut color_index = 0;
|
||||||
|
mappers.iter().for_each(|group| {
|
||||||
|
if group.end >= all_colors.len() || group.start >= all_colors.len() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if group.end > group.start {
|
||||||
|
for i in group.start..group.end - 1 {
|
||||||
|
let rgb = all_colors[color_index].get_rgb();
|
||||||
|
color_index += 1;
|
||||||
|
|
||||||
|
global_colors[i * 3] = rgb[0];
|
||||||
|
global_colors[i * 3 + 1] = rgb[1];
|
||||||
|
global_colors[i * 3 + 2] = rgb[2];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i in (group.end..group.start - 1).rev() {
|
||||||
|
let rgb = all_colors[color_index].get_rgb();
|
||||||
|
color_index += 1;
|
||||||
|
|
||||||
|
global_colors[i * 3] = rgb[0];
|
||||||
|
global_colors[i * 3 + 1] = rgb[1];
|
||||||
|
global_colors[i * 3 + 2] = rgb[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
global_colors
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
32
src/App.tsx
32
src/App.tsx
@ -3,9 +3,10 @@ import { invoke } from '@tauri-apps/api/tauri';
|
|||||||
import { DisplayView } from './components/display-view';
|
import { DisplayView } from './components/display-view';
|
||||||
import { DisplayListContainer } from './components/display-list-container';
|
import { DisplayListContainer } from './components/display-list-container';
|
||||||
import { displayStore, setDisplayStore } from './stores/display.store';
|
import { displayStore, setDisplayStore } from './stores/display.store';
|
||||||
import { LedStripConfig } from './models/led-strip-config';
|
import { LedStripConfigContainer } from './models/led-strip-config';
|
||||||
import { setLedStripStore } from './stores/led-strip.store';
|
import { setLedStripStore } from './stores/led-strip.store';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
|
import { LedStripPartsSorter } from './components/led-strip-parts-sorter';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@ -14,19 +15,35 @@ function App() {
|
|||||||
displays: JSON.parse(displays),
|
displays: JSON.parse(displays),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
invoke<LedStripConfig[]>('read_led_strip_configs').then((strips) => {
|
invoke<LedStripConfigContainer>('read_led_strip_configs').then((configs) => {
|
||||||
setLedStripStore({
|
console.log(configs);
|
||||||
strips,
|
setLedStripStore(configs);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// register tauri event listeners
|
// listen to config_changed event
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const unlisten = listen('config_changed', (event) => {
|
const unlisten = listen('config_changed', (event) => {
|
||||||
const strips = event.payload as LedStripConfig[];
|
const { strips, mappers } = event.payload as LedStripConfigContainer;
|
||||||
|
console.log(event.payload);
|
||||||
setLedStripStore({
|
setLedStripStore({
|
||||||
strips,
|
strips,
|
||||||
|
mappers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
unlisten.then((unlisten) => unlisten());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// listen to led_colors_changed event
|
||||||
|
createEffect(() => {
|
||||||
|
const unlisten = listen<Uint8ClampedArray>('led_colors_changed', (event) => {
|
||||||
|
const colors = event.payload;
|
||||||
|
|
||||||
|
setLedStripStore({
|
||||||
|
colors,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -37,6 +54,7 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<LedStripPartsSorter />
|
||||||
<DisplayListContainer>
|
<DisplayListContainer>
|
||||||
{displayStore.displays.map((display) => {
|
{displayStore.displays.map((display) => {
|
||||||
return <DisplayView display={display} />;
|
return <DisplayView display={display} />;
|
||||||
|
79
src/components/led-strip-parts-sorter.tsx
Normal file
79
src/components/led-strip-parts-sorter.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
createContext,
|
||||||
|
createEffect,
|
||||||
|
createMemo,
|
||||||
|
createSignal,
|
||||||
|
For,
|
||||||
|
JSX,
|
||||||
|
onCleanup,
|
||||||
|
} from 'solid-js';
|
||||||
|
import { LedStripConfig, LedStripPixelMapper } from '../models/led-strip-config';
|
||||||
|
import { ledStripStore } from '../stores/led-strip.store';
|
||||||
|
|
||||||
|
const SorterItem: Component<{ mapper: LedStripPixelMapper; strip: LedStripConfig }> = (
|
||||||
|
props,
|
||||||
|
) => {
|
||||||
|
const [fullLeds, setFullLeds] = createSignal<string[]>([]);
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
let stopped = false;
|
||||||
|
const frame = () => {
|
||||||
|
const strips = ledStripStore.strips;
|
||||||
|
const totalLedCount = strips.reduce((acc, strip) => acc + strip.len, 0);
|
||||||
|
|
||||||
|
const fullLeds = new Array(totalLedCount).fill('rgba(255,255,255,0.5)');
|
||||||
|
|
||||||
|
for (let i = props.mapper.start, j = 0; i < props.mapper.end; i++, j++) {
|
||||||
|
fullLeds[i] = `rgb(${ledStripStore.colors[i * 3]}, ${
|
||||||
|
ledStripStore.colors[i * 3 + 1]
|
||||||
|
}, ${ledStripStore.colors[i * 3 + 2]})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFullLeds(fullLeds);
|
||||||
|
|
||||||
|
if (!stopped) {
|
||||||
|
requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
frame();
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
stopped = true;
|
||||||
|
console.timeEnd('frame');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex h-2 m-2">
|
||||||
|
<For each={fullLeds()}>
|
||||||
|
{(it) => (
|
||||||
|
<div
|
||||||
|
class="flex-auto flex h-full w-full justify-center items-center relative"
|
||||||
|
title={it}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute top-1/2 -translate-y-1/2 h-2.5 w-2.5 rounded-full ring-1 ring-stone-300"
|
||||||
|
style={{ background: it }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LedStripPartsSorter: Component = () => {
|
||||||
|
const context = createContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<For each={ledStripStore.strips}>
|
||||||
|
{(strip, index) => (
|
||||||
|
<SorterItem strip={strip} mapper={ledStripStore.mappers[index()]} />
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,10 +1,19 @@
|
|||||||
import { Borders } from '../constants/border';
|
import { Borders } from '../constants/border';
|
||||||
|
|
||||||
|
export type LedStripPixelMapper = {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LedStripConfigContainer = {
|
||||||
|
strips: LedStripConfig[];
|
||||||
|
mappers: LedStripPixelMapper[];
|
||||||
|
};
|
||||||
|
|
||||||
export class LedStripConfig {
|
export class LedStripConfig {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly display_id: number,
|
public readonly display_id: number,
|
||||||
public readonly border: Borders,
|
public readonly border: Borders,
|
||||||
public start_pos: number,
|
|
||||||
public len: number,
|
public len: number,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { createStore } from 'solid-js/store';
|
import { createStore } from 'solid-js/store';
|
||||||
import { DisplayConfig } from '../models/display-config';
|
import { DisplayConfig } from '../models/display-config';
|
||||||
import { LedStripConfig } from '../models/led-strip-config';
|
import { LedStripConfig, LedStripPixelMapper } from '../models/led-strip-config';
|
||||||
|
|
||||||
export const [ledStripStore, setLedStripStore] = createStore({
|
export const [ledStripStore, setLedStripStore] = createStore({
|
||||||
displays: new Array<DisplayConfig>(),
|
displays: new Array<DisplayConfig>(),
|
||||||
strips: new Array<LedStripConfig>(),
|
strips: new Array<LedStripConfig>(),
|
||||||
|
mappers: new Array<LedStripPixelMapper>(),
|
||||||
|
colors: new Uint8ClampedArray(),
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user