Compare commits
2 commits
0f3136ca8d
...
680c1f0cec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
680c1f0cec | ||
|
|
3115a4230b |
17 changed files with 146 additions and 136 deletions
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -24,7 +24,7 @@ jobs:
|
||||||
run: cargo build --release --locked
|
run: cargo build --release --locked
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
run: cargo test --release --locked --workspace --lib
|
run: cargo test --release --locked --workspace
|
||||||
|
|
||||||
- name: prepare artifacts
|
- name: prepare artifacts
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -293,7 +293,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bread-cli"
|
name = "bread-cli"
|
||||||
version = "0.6.1"
|
version = "6.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bread-shared",
|
"bread-shared",
|
||||||
|
|
@ -311,7 +311,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bread-shared"
|
name = "bread-shared"
|
||||||
version = "0.6.1"
|
version = "6.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -319,7 +319,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "breadd"
|
name = "breadd"
|
||||||
version = "0.6.1"
|
version = "6.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ tokio = { version = "1.40", features = ["full"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
git2 = "0.18"
|
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
glob = "0.3"
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bread-cli"
|
name = "bread-cli"
|
||||||
version = "0.6.1"
|
version = "6.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,18 @@ async fn stream_events(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut lines = BufReader::new(read_half).lines();
|
let mut lines = BufReader::new(read_half).lines();
|
||||||
|
|
||||||
|
// Consume the subscribe ack before entering the event loop.
|
||||||
|
match lines.next_line().await? {
|
||||||
|
Some(ack) => {
|
||||||
|
let v: Value = serde_json::from_str(&ack)?;
|
||||||
|
if let Some(err) = v.get("error").and_then(Value::as_str) {
|
||||||
|
anyhow::bail!("{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => anyhow::bail!("daemon closed connection during subscribe"),
|
||||||
|
}
|
||||||
|
|
||||||
while let Some(line) = lines.next_line().await? {
|
while let Some(line) = lines.next_line().await? {
|
||||||
let value: Value = serde_json::from_str(&line)?;
|
let value: Value = serde_json::from_str(&line)?;
|
||||||
if raw_json {
|
if raw_json {
|
||||||
|
|
@ -507,23 +519,17 @@ async fn watch_reload(socket: &Path) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn print_doctor(socket: &Path) -> Result<()> {
|
async fn print_doctor(socket: &Path) -> Result<()> {
|
||||||
let stream = match UnixStream::connect(socket).await {
|
if !socket.exists() {
|
||||||
Ok(stream) => stream,
|
println!("bread doctor");
|
||||||
Err(err) => {
|
println!(" daemon ✗ not running");
|
||||||
if err.kind() == io::ErrorKind::NotFound {
|
println!(" socket {} (not found)", socket.display());
|
||||||
println!("bread doctor");
|
println!();
|
||||||
println!(" daemon ✗ not running");
|
println!(" start the daemon: systemctl --user start breadd");
|
||||||
println!(" socket {} (not found)", socket.display());
|
println!(" view logs: journalctl --user -u breadd -f");
|
||||||
println!();
|
return Ok(());
|
||||||
println!(" start the daemon: systemctl --user start breadd");
|
}
|
||||||
println!(" view logs: journalctl --user -u breadd -f");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = send_request_with_stream(stream, "health", json!({})).await?;
|
let response = send_request(socket, "health", json!({})).await?;
|
||||||
render_doctor(&response);
|
render_doctor(&response);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -585,33 +591,6 @@ fn render_doctor(health: &Value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_request_with_stream(
|
|
||||||
stream: UnixStream,
|
|
||||||
method: &str,
|
|
||||||
params: Value,
|
|
||||||
) -> Result<Value> {
|
|
||||||
let (read_half, mut write_half) = stream.into_split();
|
|
||||||
let request = json!({
|
|
||||||
"id": "1",
|
|
||||||
"method": method,
|
|
||||||
"params": params,
|
|
||||||
});
|
|
||||||
|
|
||||||
write_half
|
|
||||||
.write_all(format!("{}\n", serde_json::to_string(&request)?).as_bytes())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut lines = BufReader::new(read_half).lines();
|
|
||||||
let Some(line) = lines.next_line().await? else {
|
|
||||||
anyhow::bail!("daemon closed connection without response");
|
|
||||||
};
|
|
||||||
let response: Value = serde_json::from_str(&line)?;
|
|
||||||
if let Some(error) = response.get("error").and_then(Value::as_str) {
|
|
||||||
anyhow::bail!(error.to_string());
|
|
||||||
}
|
|
||||||
Ok(response.get("result").cloned().unwrap_or_else(|| json!({})))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config_directory() -> PathBuf {
|
fn config_directory() -> PathBuf {
|
||||||
if let Ok(xdg) = env::var("XDG_CONFIG_HOME") {
|
if let Ok(xdg) = env::var("XDG_CONFIG_HOME") {
|
||||||
return Path::new(&xdg).join("bread");
|
return Path::new(&xdg).join("bread");
|
||||||
|
|
|
||||||
|
|
@ -134,9 +134,6 @@ pub fn modules_dir() -> PathBuf {
|
||||||
if let Some(cfg) = dirs::config_dir() {
|
if let Some(cfg) = dirs::config_dir() {
|
||||||
return cfg.join("bread").join("modules");
|
return cfg.join("bread").join("modules");
|
||||||
}
|
}
|
||||||
if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") {
|
|
||||||
return PathBuf::from(xdg).join("bread").join("modules");
|
|
||||||
}
|
|
||||||
if let Ok(home) = std::env::var("HOME") {
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
return PathBuf::from(home)
|
return PathBuf::from(home)
|
||||||
.join(".config")
|
.join(".config")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bread-shared"
|
name = "bread-shared"
|
||||||
version = "0.6.1"
|
version = "6.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "breadd"
|
name = "breadd"
|
||||||
version = "0.6.1"
|
version = "6.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
@ -23,4 +23,4 @@ netlink-packet-core = "0.4"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.13"
|
tempfile.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -76,14 +76,15 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.config.adapters.power.enabled {
|
if self.config.adapters.power.enabled {
|
||||||
// Prefer UPower DBus adapter; fall back to sysfs poller
|
// Prefer UPower D-Bus adapter; fall back to sysfs poller if D-Bus is unavailable.
|
||||||
let upower = power_upower::UPowerAdapter::new();
|
match power_upower::UPowerAdapter::probe().await {
|
||||||
if let Ok(adapter) = upower {
|
Ok(adapter) => self.spawn_adapter(adapter),
|
||||||
self.spawn_adapter(adapter);
|
Err(e) => {
|
||||||
} else {
|
info!("upower unavailable ({e}), falling back to sysfs power poller");
|
||||||
self.spawn_adapter(power::PowerAdapter::new(
|
self.spawn_adapter(power::PowerAdapter::new(
|
||||||
self.config.adapters.power.poll_interval_secs,
|
self.config.adapters.power.poll_interval_secs,
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,9 @@ pub struct RtnetlinkAdapter;
|
||||||
|
|
||||||
impl RtnetlinkAdapter {
|
impl RtnetlinkAdapter {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
// Try to create a connection to validate presence of rtnetlink
|
// Validate that rtnetlink is available; drop the result immediately.
|
||||||
let conn = new_connection();
|
new_connection().map_err(|e| anyhow!(e))?;
|
||||||
match conn {
|
Ok(Self)
|
||||||
Ok((connection, _handle, _messages)) => {
|
|
||||||
// Spawn and immediately drop the connection task; we just validated
|
|
||||||
tokio::spawn(connection);
|
|
||||||
Ok(Self)
|
|
||||||
}
|
|
||||||
Err(e) => Err(anyhow!(e)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@ use super::Adapter;
|
||||||
pub struct UPowerAdapter;
|
pub struct UPowerAdapter;
|
||||||
|
|
||||||
impl UPowerAdapter {
|
impl UPowerAdapter {
|
||||||
pub fn new() -> Result<Self> {
|
/// Try to connect to the D-Bus system bus. Returns Err if D-Bus is unavailable.
|
||||||
// Attempt to connect to system bus to validate availability
|
pub async fn probe() -> Result<Self> {
|
||||||
// We don't actually open the connection here because zbus::Connection::system() is async.
|
let _ = zbus::Connection::system()
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow::anyhow!("D-Bus system bus unavailable: {e}"))?;
|
||||||
Ok(Self)
|
Ok(Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -382,22 +382,39 @@ impl EventNormalizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_network(&self, raw: &RawEvent) -> Vec<BreadEvent> {
|
fn normalize_network(&self, raw: &RawEvent) -> Vec<BreadEvent> {
|
||||||
let online = raw
|
// The sysfs NetworkAdapter puts `online: bool` directly in the payload.
|
||||||
.payload
|
// The rtnetlink adapter omits it; derive connectivity from the event kind instead.
|
||||||
.get("online")
|
let online = if let Some(v) = raw.payload.get("online").and_then(Value::as_bool) {
|
||||||
.and_then(Value::as_bool)
|
v
|
||||||
.unwrap_or(false);
|
} else {
|
||||||
|
match raw.kind.as_str() {
|
||||||
|
"link.up" | "address.added" => true,
|
||||||
|
"link.down" | "address.removed" => false,
|
||||||
|
"route.default.changed" => raw
|
||||||
|
.payload
|
||||||
|
.get("gateway")
|
||||||
|
.map(|v| !v.is_null())
|
||||||
|
.unwrap_or(false),
|
||||||
|
_ => return vec![],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let name = if online {
|
let name = if online {
|
||||||
"bread.network.connected"
|
"bread.network.connected"
|
||||||
} else {
|
} else {
|
||||||
"bread.network.disconnected"
|
"bread.network.disconnected"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut data = raw.payload.clone();
|
||||||
|
if let Some(obj) = data.as_object_mut() {
|
||||||
|
obj.insert("online".to_string(), Value::Bool(online));
|
||||||
|
}
|
||||||
|
|
||||||
vec![BreadEvent {
|
vec![BreadEvent {
|
||||||
event: name.to_string(),
|
event: name.to_string(),
|
||||||
timestamp: raw.timestamp,
|
timestamp: raw.timestamp,
|
||||||
source: AdapterSource::Network,
|
source: AdapterSource::Network,
|
||||||
data: raw.payload.clone(),
|
data,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -315,6 +315,9 @@ async fn handle_command(
|
||||||
let mut guard = state.write().await;
|
let mut guard = state.write().await;
|
||||||
if guard.profile.active != name {
|
if guard.profile.active != name {
|
||||||
let previous = guard.profile.active.clone();
|
let previous = guard.profile.active.clone();
|
||||||
|
if guard.profile.history.len() >= 50 {
|
||||||
|
guard.profile.history.remove(0);
|
||||||
|
}
|
||||||
guard.profile.history.push(previous);
|
guard.profile.history.push(previous);
|
||||||
guard.profile.active = name;
|
guard.profile.active = name;
|
||||||
}
|
}
|
||||||
|
|
@ -450,6 +453,9 @@ fn apply_event_to_state(state: &mut RuntimeState, event: &BreadEvent) {
|
||||||
if let Some(name) = event.data.get("name").and_then(Value::as_str) {
|
if let Some(name) = event.data.get("name").and_then(Value::as_str) {
|
||||||
if state.profile.active != name {
|
if state.profile.active != name {
|
||||||
let previous = state.profile.active.clone();
|
let previous = state.profile.active.clone();
|
||||||
|
if state.profile.history.len() >= 50 {
|
||||||
|
state.profile.history.remove(0);
|
||||||
|
}
|
||||||
state.profile.history.push(previous);
|
state.profile.history.push(previous);
|
||||||
state.profile.active = name.to_string();
|
state.profile.active = name.to_string();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,6 @@ impl SubscriptionTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_pattern(pattern: &str, event_name: &str) -> bool {
|
fn matches_pattern(pattern: &str, event_name: &str) -> bool {
|
||||||
if pattern.ends_with(".*") {
|
|
||||||
let prefix = &pattern[..pattern.len() - 1];
|
|
||||||
return event_name.starts_with(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(prefix) = pattern.strip_suffix(".**") {
|
if let Some(prefix) = pattern.strip_suffix(".**") {
|
||||||
if event_name == prefix {
|
if event_name == prefix {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -150,11 +145,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_segment_wildcard() {
|
fn single_segment_wildcard() {
|
||||||
assert!(matches_pattern(
|
assert!(matches_pattern("bread.device.*", "bread.device.foo"));
|
||||||
|
assert!(!matches_pattern(
|
||||||
"bread.device.*",
|
"bread.device.*",
|
||||||
"bread.device.dock.connected"
|
"bread.device.dock.connected"
|
||||||
));
|
));
|
||||||
assert!(matches_pattern("bread.device.*", "bread.device.foo"));
|
|
||||||
assert!(!matches_pattern("bread.device.*", "bread.device"));
|
assert!(!matches_pattern("bread.device.*", "bread.device"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,14 @@ impl Server {
|
||||||
|
|
||||||
info!(socket = %self.socket_path.display(), "ipc server listening");
|
info!(socket = %self.socket_path.display(), "ipc server listening");
|
||||||
|
|
||||||
|
// Emit the startup event after the socket is bound so that clients
|
||||||
|
// connecting immediately after the socket appears can subscribe and receive it.
|
||||||
|
let _ = self.emit_tx.send(BreadEvent::new(
|
||||||
|
"bread.system.startup",
|
||||||
|
AdapterSource::System,
|
||||||
|
serde_json::json!({}),
|
||||||
|
));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = shutdown_rx.changed() => {
|
_ = shutdown_rx.changed() => {
|
||||||
|
|
@ -124,7 +132,22 @@ impl Server {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let req: IpcRequest = serde_json::from_str(&line)?;
|
let req: IpcRequest = match serde_json::from_str(&line) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
let err_resp = IpcResponse {
|
||||||
|
id: "?".to_string(),
|
||||||
|
result: None,
|
||||||
|
error: Some(format!("parse error: {e}")),
|
||||||
|
};
|
||||||
|
write_half
|
||||||
|
.write_all(
|
||||||
|
format!("{}\n", serde_json::to_string(&err_resp)?).as_bytes(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
if req.method == "events.subscribe" {
|
if req.method == "events.subscribe" {
|
||||||
let filter = req
|
let filter = req
|
||||||
.params
|
.params
|
||||||
|
|
@ -206,12 +229,8 @@ impl Server {
|
||||||
}
|
}
|
||||||
"profile.list" => {
|
"profile.list" => {
|
||||||
let full = self.state_handle.state_dump().await;
|
let full = self.state_handle.state_dump().await;
|
||||||
let profiles = full
|
let profile = full.get("profile").cloned().unwrap_or_else(|| json!({}));
|
||||||
.get("profile")
|
Ok(profile)
|
||||||
.and_then(|v| v.get("profiles"))
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| json!({}));
|
|
||||||
Ok(profiles)
|
|
||||||
}
|
}
|
||||||
"profile.activate" => {
|
"profile.activate" => {
|
||||||
let Some(name) = req.params.get("name").and_then(Value::as_str) else {
|
let Some(name) = req.params.get("name").and_then(Value::as_str) else {
|
||||||
|
|
@ -319,14 +338,8 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_filter(event_name: &str, pattern: &str) -> bool {
|
fn matches_filter(event_name: &str, pattern: &str) -> bool {
|
||||||
// Delegate to the same glob logic used by the subscription table so that
|
// Delegates to the same glob logic as the subscription table:
|
||||||
// `bread events --filter "bread.device.**"` behaves identically to
|
// `*` matches one segment (no dot-crossing), `**` matches any depth.
|
||||||
// `bread.on("bread.device.**", ...)` in Lua.
|
|
||||||
if pattern.ends_with(".*") {
|
|
||||||
let prefix = &pattern[..pattern.len() - 1];
|
|
||||||
return event_name.starts_with(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(prefix) = pattern.strip_suffix(".**") {
|
if let Some(prefix) = pattern.strip_suffix(".**") {
|
||||||
if event_name == prefix || event_name.starts_with(&format!("{prefix}.")) {
|
if event_name == prefix || event_name.starts_with(&format!("{prefix}.")) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -400,7 +413,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn filter_dot_star_matches_one_segment_only() {
|
fn filter_dot_star_matches_one_segment_only() {
|
||||||
assert!(matches_filter("bread.device.connected", "bread.device.*"));
|
assert!(matches_filter("bread.device.connected", "bread.device.*"));
|
||||||
assert!(matches_filter(
|
assert!(!matches_filter(
|
||||||
"bread.device.dock.connected",
|
"bread.device.dock.connected",
|
||||||
"bread.device.*"
|
"bread.device.*"
|
||||||
));
|
));
|
||||||
|
|
@ -442,11 +455,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn filter_dot_star_at_end_acts_as_prefix_match() {
|
fn filter_dot_star_matches_exactly_one_segment() {
|
||||||
// `bread.*` ending the pattern is treated as a prefix match, so
|
|
||||||
// matches everything under `bread.` regardless of depth. This is
|
|
||||||
// consistent with the subscription table's pattern matcher.
|
|
||||||
assert!(matches_filter("bread.alpha", "bread.*"));
|
assert!(matches_filter("bread.alpha", "bread.*"));
|
||||||
assert!(matches_filter("bread.alpha.beta", "bread.*"));
|
assert!(!matches_filter("bread.alpha.beta", "bread.*"));
|
||||||
|
assert!(!matches_filter("bread", "bread.*"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -254,23 +254,23 @@ impl LuaEngine {
|
||||||
self.lua = Lua::new();
|
self.lua = Lua::new();
|
||||||
self.handlers
|
self.handlers
|
||||||
.lock()
|
.lock()
|
||||||
.expect("lua handlers mutex poisoned")
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
.clear();
|
.clear();
|
||||||
self.watch_ids
|
self.watch_ids
|
||||||
.lock()
|
.lock()
|
||||||
.expect("lua watch ids mutex poisoned")
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
.clear();
|
.clear();
|
||||||
self.modules
|
self.modules
|
||||||
.lock()
|
.lock()
|
||||||
.expect("lua modules mutex poisoned")
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
.clear();
|
.clear();
|
||||||
self.module_decls
|
self.module_decls
|
||||||
.lock()
|
.lock()
|
||||||
.expect("lua module decls mutex poisoned")
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
.clear();
|
.clear();
|
||||||
self.module_order
|
self.module_order
|
||||||
.lock()
|
.lock()
|
||||||
.expect("lua module order mutex poisoned")
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
.clear();
|
.clear();
|
||||||
|
|
||||||
self.install_api()?;
|
self.install_api()?;
|
||||||
|
|
@ -1138,7 +1138,7 @@ impl LuaEngine {
|
||||||
let mut decl_map = self
|
let mut decl_map = self
|
||||||
.module_decls
|
.module_decls
|
||||||
.lock()
|
.lock()
|
||||||
.expect("module decls mutex poisoned");
|
.unwrap_or_else(|e| e.into_inner());
|
||||||
decl_map.clear();
|
decl_map.clear();
|
||||||
for decl in &ordered {
|
for decl in &ordered {
|
||||||
decl_map.insert(decl.name.clone(), decl.clone());
|
decl_map.insert(decl.name.clone(), decl.clone());
|
||||||
|
|
@ -1176,7 +1176,7 @@ impl LuaEngine {
|
||||||
*self
|
*self
|
||||||
.module_order
|
.module_order
|
||||||
.lock()
|
.lock()
|
||||||
.expect("module order mutex poisoned") = load_order;
|
.unwrap_or_else(|e| e.into_inner()) = load_order;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -1229,7 +1229,7 @@ impl LuaEngine {
|
||||||
|
|
||||||
fn handle_event(&self, id: SubscriptionId, event: BreadEvent) -> Result<()> {
|
fn handle_event(&self, id: SubscriptionId, event: BreadEvent) -> Result<()> {
|
||||||
let (callback, filter, raw_kind, kind, module) = {
|
let (callback, filter, raw_kind, kind, module) = {
|
||||||
let handlers = self.handlers.lock().expect("lua handlers mutex poisoned");
|
let handlers = self.handlers.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
let Some(entry) = handlers.get(&id) else {
|
let Some(entry) = handlers.get(&id) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
@ -1290,7 +1290,7 @@ impl LuaEngine {
|
||||||
|
|
||||||
fn handle_timer(&self, id: TimerId) -> Result<()> {
|
fn handle_timer(&self, id: TimerId) -> Result<()> {
|
||||||
let (callback, repeating) = {
|
let (callback, repeating) = {
|
||||||
let timers = self.timers.lock().expect("lua timers mutex poisoned");
|
let timers = self.timers.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
let Some(entry) = timers.get(&id) else {
|
let Some(entry) = timers.get(&id) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
@ -1334,7 +1334,7 @@ impl LuaEngine {
|
||||||
let order = self
|
let order = self
|
||||||
.module_order
|
.module_order
|
||||||
.lock()
|
.lock()
|
||||||
.expect("module order mutex poisoned")
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
.clone();
|
.clone();
|
||||||
for name in order {
|
for name in order {
|
||||||
if let Some(hook) = self.get_module_hook(&name, "on_reload") {
|
if let Some(hook) = self.get_module_hook(&name, "on_reload") {
|
||||||
|
|
@ -1356,7 +1356,7 @@ impl LuaEngine {
|
||||||
let order = self
|
let order = self
|
||||||
.module_order
|
.module_order
|
||||||
.lock()
|
.lock()
|
||||||
.expect("module order mutex poisoned")
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
.clone();
|
.clone();
|
||||||
for name in order.into_iter().rev() {
|
for name in order.into_iter().rev() {
|
||||||
if let Some(hook) = self.get_module_hook(&name, "on_unload") {
|
if let Some(hook) = self.get_module_hook(&name, "on_unload") {
|
||||||
|
|
@ -1792,6 +1792,7 @@ fn state_value_to_lua<'lua>(
|
||||||
break g;
|
break g;
|
||||||
}
|
}
|
||||||
std::hint::spin_loop();
|
std::hint::spin_loop();
|
||||||
|
std::thread::yield_now();
|
||||||
};
|
};
|
||||||
let mut value =
|
let mut value =
|
||||||
serde_json::to_value(&*snapshot).map_err(|e| LuaError::external(e.to_string()))?;
|
serde_json::to_value(&*snapshot).map_err(|e| LuaError::external(e.to_string()))?;
|
||||||
|
|
@ -1820,6 +1821,7 @@ fn module_store_get(
|
||||||
break g;
|
break g;
|
||||||
}
|
}
|
||||||
std::hint::spin_loop();
|
std::hint::spin_loop();
|
||||||
|
std::thread::yield_now();
|
||||||
};
|
};
|
||||||
let entry = guard.modules.iter().find(|m| m.name == module)?;
|
let entry = guard.modules.iter().find(|m| m.name == module)?;
|
||||||
entry.store.get(key).cloned()
|
entry.store.get(key).cloned()
|
||||||
|
|
@ -1836,6 +1838,7 @@ fn module_store_set(
|
||||||
break g;
|
break g;
|
||||||
}
|
}
|
||||||
std::hint::spin_loop();
|
std::hint::spin_loop();
|
||||||
|
std::thread::yield_now();
|
||||||
};
|
};
|
||||||
if let Some(entry) = guard.modules.iter_mut().find(|m| m.name == module) {
|
if let Some(entry) = guard.modules.iter_mut().find(|m| m.name == module) {
|
||||||
entry.store.insert(key, value);
|
entry.store.insert(key, value);
|
||||||
|
|
@ -2210,7 +2213,13 @@ fn hyprland_request_socket() -> Result<PathBuf> {
|
||||||
hypr_dir.display()
|
hypr_dir.display()
|
||||||
)),
|
)),
|
||||||
1 => Ok(sockets.remove(0)),
|
1 => Ok(sockets.remove(0)),
|
||||||
_ => Ok(sockets.remove(0)),
|
_ => {
|
||||||
|
warn!(
|
||||||
|
"multiple Hyprland instances found in {}; using the first one",
|
||||||
|
hypr_dir.display()
|
||||||
|
);
|
||||||
|
Ok(sockets.remove(0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2274,11 +2283,17 @@ where
|
||||||
Fut: std::future::Future<Output = ()>,
|
Fut: std::future::Future<Output = ()>,
|
||||||
{
|
{
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
tokio::runtime::Builder::new_current_thread()
|
let rt = match tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.expect("bluetooth action thread")
|
{
|
||||||
.block_on(factory());
|
Ok(rt) => rt,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!(error = %e, "bluetooth action: failed to build tokio runtime");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rt.block_on(factory());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2292,11 +2307,13 @@ where
|
||||||
{
|
{
|
||||||
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let result = tokio::runtime::Builder::new_current_thread()
|
let result = match tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.expect("bluetooth query thread")
|
{
|
||||||
.block_on(factory());
|
Ok(rt) => rt.block_on(factory()),
|
||||||
|
Err(e) => Err(anyhow::anyhow!("bluetooth query: failed to build tokio runtime: {e}")),
|
||||||
|
};
|
||||||
let _ = tx.send(result);
|
let _ = tx.send(result);
|
||||||
});
|
});
|
||||||
rx.recv()
|
rx.recv()
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::sync::atomic::AtomicU64;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bread_shared::{AdapterSource, BreadEvent, RawEvent};
|
use bread_shared::{BreadEvent, RawEvent};
|
||||||
use tokio::sync::{broadcast, mpsc, watch, RwLock};
|
use tokio::sync::{broadcast, mpsc, watch, RwLock};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
@ -105,12 +105,6 @@ async fn main() -> Result<()> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = normalized_tx.send(BreadEvent::new(
|
|
||||||
"bread.system.startup",
|
|
||||||
AdapterSource::System,
|
|
||||||
serde_json::json!({}),
|
|
||||||
));
|
|
||||||
|
|
||||||
let ipc_server = ipc::Server::new(
|
let ipc_server = ipc::Server::new(
|
||||||
config.socket_path(),
|
config.socket_path(),
|
||||||
state_handle,
|
state_handle,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue