breadhelp 0.2.0: live guided tour overlay replacing static onboarding
Replaces the old in-window onboarding wizard with a real screen-wide tour: dim + spotlight cutout around the actual on-screen component (breadbar, breadbox), floating callout teaching the shortcut, and event-driven confirmation via real Hyprland/breadd signals instead of click-through fakery.
This commit is contained in:
parent
fea8f83204
commit
29b2445573
20 changed files with 1077 additions and 298 deletions
|
|
@ -1,3 +1,4 @@
|
|||
use serde::Deserialize;
|
||||
use std::process::Command;
|
||||
|
||||
/// Runtime-only rebind via `hyprctl keyword bind`: takes effect immediately
|
||||
|
|
@ -13,3 +14,83 @@ pub fn rebind_temp(bind_value: &str) {
|
|||
eprintln!("breadhelp: hyprctl rebind failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Client {
|
||||
pub class: String,
|
||||
pub at: (i32, i32),
|
||||
pub size: (i32, i32),
|
||||
/// Numeric monitor id — join against `monitors()` by `Monitor::id` for a name.
|
||||
pub monitor: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Monitor {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub focused: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LayerClient {
|
||||
pub namespace: String,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: i32,
|
||||
pub h: i32,
|
||||
pub monitor: String,
|
||||
}
|
||||
|
||||
fn query_json(cmd: &str) -> Option<serde_json::Value> {
|
||||
let output = Command::new("hyprctl").args(["-j", cmd]).output().ok()?;
|
||||
output.status.success().then(|| serde_json::from_slice(&output.stdout).ok()).flatten()
|
||||
}
|
||||
|
||||
pub fn clients() -> Vec<Client> {
|
||||
query_json("clients").and_then(|v| serde_json::from_value(v).ok()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn monitors() -> Vec<Monitor> {
|
||||
query_json("monitors").and_then(|v| serde_json::from_value(v).ok()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// `hyprctl -j layers` nests surfaces as `{monitor_name: {levels: {level: [surface]}}}`
|
||||
/// with no fixed schema the `hyprland` crate or any sibling repo has a typed struct
|
||||
/// for, so this walks the `serde_json::Value` by hand instead of deriving `Deserialize`.
|
||||
pub fn layers() -> Vec<LayerClient> {
|
||||
let Some(serde_json::Value::Object(by_monitor)) = query_json("layers") else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
for (monitor_name, monitor_val) in by_monitor {
|
||||
let Some(levels) = monitor_val.get("levels").and_then(|v| v.as_object()) else {
|
||||
continue;
|
||||
};
|
||||
for entries in levels.values().filter_map(|v| v.as_array()) {
|
||||
for entry in entries {
|
||||
let fields = (
|
||||
entry.get("namespace").and_then(|v| v.as_str()),
|
||||
entry.get("x").and_then(|v| v.as_i64()),
|
||||
entry.get("y").and_then(|v| v.as_i64()),
|
||||
entry.get("w").and_then(|v| v.as_i64()),
|
||||
entry.get("h").and_then(|v| v.as_i64()),
|
||||
);
|
||||
if let (Some(namespace), Some(x), Some(y), Some(w), Some(h)) = fields {
|
||||
out.push(LayerClient {
|
||||
namespace: namespace.to_string(),
|
||||
x: x as i32,
|
||||
y: y as i32,
|
||||
w: w as i32,
|
||||
h: h as i32,
|
||||
monitor: monitor_name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue