Add bread-utils and bread-onnx: shared crates for ecosystem-wide duplication

bread-utils extracts genuinely duplicated logic found across breadbox,
breadclip, breadmon, breadcrumbs, bos-settings, and breadhelp:

- hypr: Hyprland socket1 request/response client (breadbox's
  get_active_workspace + breadclip's position.rs hyprctl_json were
  near-identical), socket2 path resolution (breadmon), and a
  version-tolerant `fullscreen` field parser (Hyprland has shipped both
  bool and int representations across versions).
- singleton: correct flock-based single-instance toggle, replacing the
  TOCTOU-prone read-pid/check-proc/kill/write-pid pattern duplicated
  verbatim between breadbox and breadclip (breadclip's own comment says
  "matches breadbox pattern").
- proc: breadcrumbs' timeout-guarded subprocess runner, promoted verbatim
  as the one implementation in the ecosystem that already got this right.
- atomic + xdg: atomic (temp-then-rename) file writes with an optional
  .bak-before-overwrite variant, and XDG path helpers that never fall back
  to a literal "~/..." string (the exact breadclip-core and
  breadpad-shared bug: PathBuf never expands `~`).
- tomlcfg (feature "toml"): the load_doc/save_doc TOML-editing discipline
  bos-settings and breadhelp both implemented byte-for-byte identically in
  the same fix pass that introduced it.
- gtk_popup (feature "gtk"): layer-shell overlay window setup, visible-row
  navigation, and click-outside-close, deduplicated from breadbox and
  breadclip (~150 duplicated lines, per both apps' own "same as breadbox"
  comments).

bread-onnx extracts the embedding pipeline (tokenize -> tensor build ->
mean-pool -> L2-normalize) duplicated near-verbatim between breadarr and
breadsearch, a shared execution-provider session builder with loud EP-
registration logging, and a model download+integrity helper. Defaults AMD
iGPU acceleration to ort::ep::MIGraphX (not ROCm) per this machine's own
breadsearch-gpu-backends lesson: ROCMExecutionProvider silently no-ops to
CPU on distro ROCm onnxruntime builds compiled with --use_migraphx.

Both crates build and pass their own test suites standalone. Consumer
migrations follow in subsequent commits.
This commit is contained in:
Breadway 2026-07-17 09:15:54 +08:00
parent 394a252f9e
commit 853ee33415
17 changed files with 2503 additions and 5 deletions

View file

@ -0,0 +1,111 @@
//! Shared GTK4 layer-shell popup scaffold: the full-screen transparent
//! overlay window setup, `ListBox` up/down visible-row navigation, and
//! click-outside-to-close gesture were duplicated near-verbatim between
//! `breadbox/src/main.rs` and `breadclip/src/main.rs`:
//!
//! - Layer-shell window setup: `breadbox/src/main.rs:357-365` /
//! `breadclip/src/main.rs:231-239` — identical `init_layer_shell` +
//! namespace + `Layer::Overlay` + `KeyboardMode::Exclusive` + anchor all
//! four edges + zero exclusive zone.
//! - Up/Down navigation loop: `breadbox/src/main.rs:515-546` /
//! `breadclip/src/main.rs:423-454` — byte-for-byte identical "find the
//! next/previous *visible* row" loop (breadclip's own comment even reads
//! `// ---- Keyboard handler (capture phase, same as breadbox) ----`).
//! - Click-outside-close: `breadbox/src/main.rs:566-581` /
//! `breadclip/src/main.rs:474-...` — identical bounds-check against a
//! content widget (breadclip: `// ---- Click outside panel → close (same
//! pattern as breadbox) ----`).
//!
//! Deliberately *not* extracted: the rest of each app's `EventControllerKey`
//! handling (Enter/Delete semantics, filter chips, search) — those differ
//! per app (`do_launch` vs `do_copy`+`Delete`-to-remove) and forcing them
//! into one callback-owning "scaffold" struct would be a leakier
//! abstraction than the ~5 free functions below.
//!
//! Requires the `gtk` feature.
use gtk4::prelude::*;
use gtk4::{ApplicationWindow, GestureClick};
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
/// Build the full-screen transparent overlay window every layer-shell popup
/// in this ecosystem starts from: layered above normal windows, keyboard-
/// exclusive (so Escape/Enter/arrow keys reach the popup instead of the
/// focused client behind it), anchored to all four edges with zero
/// exclusive zone (so it doesn't reserve screen space or push other layer
/// clients around).
pub fn new_overlay_window(app: &gtk4::Application, namespace: &str) -> ApplicationWindow {
let window = ApplicationWindow::builder().application(app).build();
window.init_layer_shell();
window.set_namespace(Some(namespace));
window.set_layer(Layer::Overlay);
window.set_keyboard_mode(KeyboardMode::Exclusive);
for edge in [Edge::Top, Edge::Bottom, Edge::Left, Edge::Right] {
window.set_anchor(edge, true);
}
window.set_exclusive_zone(0);
window
}
/// Select the next *visible* row after the current selection (rows can be
/// hidden by a live search filter — a plain "select index + 1" would land
/// on a filtered-out row). No-op if there is no next visible row.
pub fn select_next_visible(list: &gtk4::ListBox) {
let cur = list.selected_row().map(|r| r.index()).unwrap_or(-1);
let mut i = cur + 1;
loop {
match list.row_at_index(i) {
Some(r) if r.is_visible() => {
list.select_row(Some(&r));
break;
}
Some(_) => i += 1,
None => break,
}
}
}
/// Select the previous *visible* row before the current selection. No-op if
/// there is no previous visible row.
pub fn select_prev_visible(list: &gtk4::ListBox) {
let cur = list.selected_row().map(|r| r.index()).unwrap_or(0);
let mut i = cur - 1;
loop {
if i < 0 {
break;
}
match list.row_at_index(i) {
Some(r) if r.is_visible() => {
list.select_row(Some(&r));
break;
}
Some(_) => i -= 1,
None => break,
}
}
}
/// Attach a click gesture to `window` that calls `on_outside` whenever a
/// click lands outside `content`'s bounds (e.g. clicking the transparent
/// full-screen backdrop around a centered launcher/panel widget).
pub fn close_on_outside_click(
window: &ApplicationWindow,
content: &impl IsA<gtk4::Widget>,
on_outside: impl Fn() + 'static,
) {
let content = content.clone().upcast::<gtk4::Widget>();
let win_ref = window.clone();
let gesture = GestureClick::new();
gesture.connect_pressed(move |_, _, x, y| {
if let Some(b) = content.compute_bounds(&win_ref) {
if x < b.x() as f64
|| x > (b.x() + b.width()) as f64
|| y < b.y() as f64
|| y > (b.y() + b.height()) as f64
{
on_outside();
}
}
});
window.add_controller(gesture);
}