Will change this commit message to mean something later
Some checks failed
dev release / build (push) Failing after 0s
Some checks failed
dev release / build (push) Failing after 0s
This commit is contained in:
parent
f3905d8114
commit
8d3f55b607
14 changed files with 1491 additions and 40 deletions
|
|
@ -21,6 +21,11 @@ bread reload
|
|||
| `low-battery-warning.lua` | Critical notification once when the battery runs low; resets on AC. | none |
|
||||
| `pause-media-on-headphone-unplug.lua` | Runs `playerctl pause` when a headphone/earbud device disconnects. | none (needs `playerctl`) |
|
||||
| `dock-monitors.lua` | Applies a multi-monitor layout when an external display connects, reverts when removed. | edit output names/resolutions |
|
||||
| `active-window-widget.lua` | Shows the focused window next to the workspace pills in breadbar, via `bread.widget` + `bread.state.watch`. | none |
|
||||
| `cpu-temp-widget.lua` | Live CPU temperature readout in breadbar's stats area, via `bread.widget` + `bread.fs.read` on a timer. | edit `TEMP_PATH` for your hwmon layout |
|
||||
| `bluetooth-toggle-widget.lua` | One-click Bluetooth power toggle in breadbar's tray, via `bread.widget` + a click handler. | none |
|
||||
| `focus-mode-widget.lua` | Click-to-toggle "Focus" profile that mutes audio; a widget as an action launcher, not just a readout, and stays in sync with profile changes triggered elsewhere. | none (needs `wpctl`) |
|
||||
| `workflow-status-widget.lua` | Surfaces `bread.workflow.list()` in breadbar's tray — shows whichever workflow (e.g. `dock-workflow.lua`, below) is currently running or failed, hidden otherwise. | none |
|
||||
|
||||
Each module is the standard skeleton — `bread.module{...}`, an `on_load` that
|
||||
registers subscriptions, `return M` — so they double as references for writing
|
||||
|
|
|
|||
46
examples/modules/active-window-widget.lua
Normal file
46
examples/modules/active-window-widget.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
-- active-window-widget — shows the focused app right next to the
|
||||
-- workspace pills, live-updated via bread.state.watch (no polling, no
|
||||
-- bread.on needed).
|
||||
--
|
||||
-- Demonstrates: WidgetPlacement "right_of_workspaces", a state-watch-driven
|
||||
-- widget (as opposed to a timer or event handler), and calling
|
||||
-- bread.widget.update from inside the watch callback. breadbar's own bar
|
||||
-- doesn't show the focused window anywhere today — this adds that for free.
|
||||
--
|
||||
-- Drop-in: copy into ~/.config/bread/modules/. Zero configuration.
|
||||
|
||||
local M = bread.module({ name = "active-window-widget", version = "1.0.0" })
|
||||
|
||||
local function label_for(window)
|
||||
-- A JSON null (no window focused) doesn't necessarily arrive as Lua nil
|
||||
-- through bread.state.* — mlua's serde bridge can hand back a distinct
|
||||
-- null sentinel instead, which `not window` won't catch. Guard on the
|
||||
-- type directly so any non-string value (nil, the sentinel, ...) falls
|
||||
-- through to the placeholder rather than crashing on #window below.
|
||||
if type(window) ~= "string" or window == "" then
|
||||
return "—"
|
||||
end
|
||||
if #window > 24 then
|
||||
return window:sub(1, 24) .. "…"
|
||||
end
|
||||
return window
|
||||
end
|
||||
|
||||
local function widget_root(window)
|
||||
return { type = "label", text = label_for(window), style = { color = "dim" } }
|
||||
end
|
||||
|
||||
function M.on_load()
|
||||
bread.widget.register({
|
||||
id = "active-window",
|
||||
placement = "right_of_workspaces",
|
||||
tooltip = "Currently focused window",
|
||||
root = widget_root(bread.state.active_window()),
|
||||
})
|
||||
|
||||
bread.state.watch("active_window", function(new_val)
|
||||
bread.widget.update("active-window", { root = widget_root(new_val) })
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
66
examples/modules/bluetooth-toggle-widget.lua
Normal file
66
examples/modules/bluetooth-toggle-widget.lua
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
-- bluetooth-toggle-widget — a real one-click Bluetooth power toggle in the
|
||||
-- hamburger popover's tray section, showing power state and connected
|
||||
-- device count. breadbar's native bar only shows a passive BT icon; this
|
||||
-- adds an actual control surface for it.
|
||||
--
|
||||
-- Demonstrates: WidgetPlacement "tray", a bread.every-polled read of
|
||||
-- bread.bluetooth.devices()/.powered(), and bread.bar.widget_clicked
|
||||
-- driving a real action (bread.bluetooth.power) rather than just display.
|
||||
--
|
||||
-- Drop-in: copy into ~/.config/bread/modules/. Zero configuration.
|
||||
|
||||
local M = bread.module({ name = "bluetooth-toggle-widget", version = "1.0.0" })
|
||||
|
||||
local function widget_root()
|
||||
local powered = bread.bluetooth.powered()
|
||||
local devices = bread.bluetooth.devices() or {}
|
||||
local connected = 0
|
||||
for _, d in ipairs(devices) do
|
||||
if d.connected then
|
||||
connected = connected + 1
|
||||
end
|
||||
end
|
||||
|
||||
local text, style
|
||||
if powered == nil then
|
||||
text, style = "BT n/a", { color = "dim" }
|
||||
elseif not powered then
|
||||
text, style = "BT off", { color = "dim" }
|
||||
elseif connected > 0 then
|
||||
text, style = "BT (" .. connected .. ")", { color = "accent", weight = "bold" }
|
||||
else
|
||||
text, style = "BT on", { color = "fg" }
|
||||
end
|
||||
|
||||
return {
|
||||
type = "label",
|
||||
text = text,
|
||||
style = style,
|
||||
on_click = "toggle",
|
||||
}
|
||||
end
|
||||
|
||||
function M.on_load()
|
||||
bread.widget.register({
|
||||
id = "toggle",
|
||||
placement = "tray",
|
||||
tooltip = "Click to toggle Bluetooth power",
|
||||
root = widget_root(),
|
||||
})
|
||||
|
||||
bread.every(5000, function()
|
||||
bread.widget.update("toggle", { root = widget_root() })
|
||||
end)
|
||||
|
||||
bread.on("bread.bar.widget_clicked", function(e)
|
||||
if e.data.widget_id == "bluetooth-toggle-widget.toggle" and e.data.action == "toggle" then
|
||||
bread.bluetooth.power(not bread.bluetooth.powered())
|
||||
-- Give BlueZ a moment to apply before refreshing the label.
|
||||
bread.after(500, function()
|
||||
bread.widget.update("toggle", { root = widget_root() })
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
64
examples/modules/cpu-temp-widget.lua
Normal file
64
examples/modules/cpu-temp-widget.lua
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
-- cpu-temp-widget — live CPU package temperature, read straight from the
|
||||
-- k10temp hwmon sysfs node via bread.fs.read.
|
||||
--
|
||||
-- Demonstrates: WidgetPlacement "left_of_stats", a bread.every-polled
|
||||
-- widget reading real hardware state (the same category of readout
|
||||
-- breadbar's native CPU%/RAM stats already do in Rust — this shows it's
|
||||
-- just as easy from a drop-in Lua module), and the typed `style` vocabulary
|
||||
-- (color + weight) swapping based on a threshold so the widget visually
|
||||
-- flags when something's hot — no CSS, no guessing which class names the
|
||||
-- rendering app happens to define.
|
||||
--
|
||||
-- Drop-in: copy into ~/.config/bread/modules/. TEMP_PATH is specific to
|
||||
-- this machine (AMD, k10temp) — find yours with:
|
||||
-- grep -l k10temp /sys/class/hwmon/hwmon*/name
|
||||
-- and adjust below; a missing/unreadable path just shows "—" rather than
|
||||
-- erroring, since bread.fs.read returns nil (not an error) for that case.
|
||||
|
||||
local M = bread.module({ name = "cpu-temp-widget", version = "1.0.0" })
|
||||
|
||||
local TEMP_PATH = "/sys/class/hwmon/hwmon6/temp1_input"
|
||||
local HOT_THRESHOLD_C = 80
|
||||
|
||||
local function read_temp_c()
|
||||
local raw = bread.fs.read(TEMP_PATH)
|
||||
if not raw then
|
||||
return nil
|
||||
end
|
||||
return tonumber(raw) / 1000
|
||||
end
|
||||
|
||||
local function widget_root(temp_c)
|
||||
local text = temp_c and string.format("%.0f°C", temp_c) or "—"
|
||||
local hot = temp_c ~= nil and temp_c >= HOT_THRESHOLD_C
|
||||
return {
|
||||
type = "box",
|
||||
children = {
|
||||
{
|
||||
type = "label",
|
||||
text = text,
|
||||
style = hot and { color = "red", weight = "bold" } or { color = "dim" },
|
||||
},
|
||||
{
|
||||
type = "progress",
|
||||
value = temp_c and math.min(temp_c / 100, 1.0) or 0,
|
||||
style = hot and { color = "red" } or nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
function M.on_load()
|
||||
bread.widget.register({
|
||||
id = "cpu-temp",
|
||||
placement = "left_of_stats",
|
||||
tooltip = "CPU package temperature (Tctl)",
|
||||
root = widget_root(read_temp_c()),
|
||||
})
|
||||
|
||||
bread.every(5000, function()
|
||||
bread.widget.update("cpu-temp", { root = widget_root(read_temp_c()) })
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
66
examples/modules/focus-mode-widget.lua
Normal file
66
examples/modules/focus-mode-widget.lua
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
-- focus-mode-widget — a widget that does something, not just shows
|
||||
-- something: click to toggle a "focus" bread profile, which mutes audio
|
||||
-- output as an observable effect. Stays in sync if the profile changes
|
||||
-- from elsewhere too — the CLI (`bread profile-activate default`), another
|
||||
-- module, another widget — not just from its own click, by reacting to
|
||||
-- bread.profile.activated rather than tracking its own local state.
|
||||
--
|
||||
-- Demonstrates: a widget as an action launcher wired to bread's actual
|
||||
-- profile primitive (bread.profile.activate), not just a passive readout;
|
||||
-- staying in sync with state that can change from other sources; combining
|
||||
-- bread.exec with a profile switch for a real, checkable effect.
|
||||
--
|
||||
-- Drop-in: copy into ~/.config/bread/modules/. Needs `wpctl` (pipewire —
|
||||
-- already a dependency of breadbar's own volume slider, so if the bar's
|
||||
-- volume control works, this will too).
|
||||
|
||||
local M = bread.module({ name = "focus-mode-widget", version = "1.0.0" })
|
||||
|
||||
local FOCUS_PROFILE = "focus"
|
||||
local DEFAULT_PROFILE = "default"
|
||||
|
||||
local function is_focused()
|
||||
return bread.state.profile().active == FOCUS_PROFILE
|
||||
end
|
||||
|
||||
local function widget_root()
|
||||
return {
|
||||
type = "label",
|
||||
text = "Focus",
|
||||
style = is_focused() and { color = "accent", weight = "bold" } or { color = "dim" },
|
||||
on_click = "toggle",
|
||||
}
|
||||
end
|
||||
|
||||
function M.on_load()
|
||||
bread.widget.register({
|
||||
id = "toggle",
|
||||
placement = "left_of_clock",
|
||||
tooltip = "Click to toggle Focus mode (mutes audio, activates the 'focus' profile)",
|
||||
root = widget_root(),
|
||||
})
|
||||
|
||||
-- Not just self.click -> self.update: any profile change, from any
|
||||
-- source, is reflected here. Try `bread profile-activate default` from
|
||||
-- a terminal while this is in "focus" state to see it flip on its own.
|
||||
bread.on("bread.profile.activated", function()
|
||||
bread.widget.update("toggle", { root = widget_root() })
|
||||
end)
|
||||
|
||||
bread.on("bread.bar.widget_clicked", function(e)
|
||||
if e.data.widget_id ~= "focus-mode-widget.toggle" or e.data.action ~= "toggle" then
|
||||
return
|
||||
end
|
||||
if is_focused() then
|
||||
bread.profile.activate(DEFAULT_PROFILE)
|
||||
bread.exec("wpctl set-mute @DEFAULT_AUDIO_SINK@ 0")
|
||||
bread.notify("Focus mode off", { title = "bread" })
|
||||
else
|
||||
bread.profile.activate(FOCUS_PROFILE)
|
||||
bread.exec("wpctl set-mute @DEFAULT_AUDIO_SINK@ 1")
|
||||
bread.notify("Focus mode on — audio muted", { title = "bread" })
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
83
examples/modules/workflow-status-widget.lua
Normal file
83
examples/modules/workflow-status-widget.lua
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
-- workflow-status-widget — surfaces bread's workflow engine (bread.workflow,
|
||||
-- see Examples.md's "Example 4" and dock-workflow.lua in this directory) in
|
||||
-- the bar, which had zero visibility anywhere in the UI before this. Shows
|
||||
-- whichever non-done workflow was most recently updated, with its current
|
||||
-- step if it's set one via bread.workflow.step(); hides entirely when
|
||||
-- nothing is running/failed/timed_out, so it stays out of the way until
|
||||
-- there's actually something to look at.
|
||||
--
|
||||
-- Demonstrates: WidgetPlacement "tray", polling an existing bread subsystem
|
||||
-- (bread.workflow.list()) instead of raw hardware or a single module's own
|
||||
-- state, and a widget that disappears (visible = false) rather than
|
||||
-- showing a stale or empty readout.
|
||||
--
|
||||
-- Drop-in: copy into ~/.config/bread/modules/. Zero configuration — it
|
||||
-- reflects whatever workflow any other loaded module starts, including
|
||||
-- dock-workflow.lua in this same directory.
|
||||
|
||||
local M = bread.module({ name = "workflow-status-widget", version = "1.0.0" })
|
||||
|
||||
local function most_relevant()
|
||||
local workflows = bread.workflow.list()
|
||||
local best = nil
|
||||
for _, w in ipairs(workflows) do
|
||||
if w.state ~= "done" and (not best or w.updated_at > best.updated_at) then
|
||||
best = w
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
|
||||
local function widget_update()
|
||||
local w = most_relevant()
|
||||
if not w then
|
||||
-- bread.widget.update leaves an omitted field unchanged, not
|
||||
-- cleared — a Lua table can't distinguish "tooltip = nil" from
|
||||
-- "no tooltip key at all", so an explicit "" is what actually wipes
|
||||
-- the previous tooltip instead of leaving it stale under a hidden
|
||||
-- widget (harmless in practice since GTK won't show a tooltip on
|
||||
-- an invisible widget, but `bread state widgets` would otherwise
|
||||
-- report it forever).
|
||||
return { visible = false, tooltip = "", root = { type = "label", text = "" } }
|
||||
end
|
||||
|
||||
local color = "dim"
|
||||
if w.state == "failed" or w.state == "timed_out" then
|
||||
color = "red"
|
||||
elseif w.state == "running" then
|
||||
color = "accent"
|
||||
end
|
||||
|
||||
local text = w.name
|
||||
if w.step then
|
||||
text = text .. ": " .. w.step
|
||||
end
|
||||
|
||||
return {
|
||||
visible = true,
|
||||
tooltip = "Workflow " .. w.name .. " — " .. w.state,
|
||||
root = { type = "label", text = text, style = { color = color } },
|
||||
}
|
||||
end
|
||||
|
||||
function M.on_load()
|
||||
local u = widget_update()
|
||||
bread.widget.register({
|
||||
id = "status",
|
||||
placement = "tray",
|
||||
visible = u.visible,
|
||||
tooltip = u.tooltip,
|
||||
root = u.root,
|
||||
})
|
||||
|
||||
bread.every(3000, function()
|
||||
local next_u = widget_update()
|
||||
bread.widget.update("status", {
|
||||
visible = next_u.visible,
|
||||
tooltip = next_u.tooltip,
|
||||
root = next_u.root,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
Loading…
Add table
Add a link
Reference in a new issue