aboutsummaryrefslogtreecommitdiff
path: root/Sources
diff options
context:
space:
mode:
authorTommaso Negri <me@tommasongr.com>2026-08-09 23:58:42 +0200
committerTommaso Negri <me@tommasongr.com>2026-08-09 23:58:44 +0200
commitd59b22e557fa5d69ea6be725a7e5bcd829588225 (patch)
tree618e20ef2b648e46eac3d51e14b8e0fcc1faba3c /Sources
parent62ce97dcb63d61c8db739c1b32bd387042ca66a0 (diff)
downloadbar-d59b22e557fa5d69ea6be725a7e5bcd829588225.tar.gz
bar-d59b22e557fa5d69ea6be725a7e5bcd829588225.zip
rename project and add documentation guides
Diffstat (limited to 'Sources')
-rw-r--r--Sources/bar/Config.swift (renamed from Sources/phbar/Config.swift)21
-rw-r--r--Sources/bar/Models/AppDelegate.swift (renamed from Sources/phbar/Models/AppDelegate.swift)0
-rw-r--r--Sources/bar/Models/Block.swift (renamed from Sources/phbar/Models/Block.swift)4
-rw-r--r--Sources/bar/Models/Cell.swift (renamed from Sources/phbar/Models/Cell.swift)17
-rw-r--r--Sources/bar/Models/HEX.swift (renamed from Sources/phbar/Models/HEX.swift)0
-rw-r--r--Sources/bar/Models/Notifier.swift (renamed from Sources/phbar/Models/Notifier.swift)10
-rw-r--r--Sources/bar/Models/Runner.swift (renamed from Sources/phbar/Models/Runner.swift)0
-rw-r--r--Sources/bar/Models/Screen.swift (renamed from Sources/phbar/Models/Screen.swift)8
-rw-r--r--Sources/bar/Views/BarView.swift (renamed from Sources/phbar/Views/BarView.swift)0
-rw-r--r--Sources/bar/Views/BarWindow.swift (renamed from Sources/phbar/Views/BarWindow.swift)0
-rw-r--r--Sources/bar/Views/BlockView.swift (renamed from Sources/phbar/Views/BlockView.swift)0
-rw-r--r--Sources/bar/Views/CellView.swift (renamed from Sources/phbar/Views/CellView.swift)0
-rw-r--r--Sources/bar/bar.docc/bar.md44
-rw-r--r--Sources/bar/bar.docc/commands.md50
-rw-r--r--Sources/bar/bar.docc/customisation.md45
-rw-r--r--Sources/bar/bar.docc/installation.md95
-rw-r--r--Sources/bar/bar.docc/manual-updates.md28
-rw-r--r--Sources/bar/bar.docc/philosophy.md34
-rw-r--r--Sources/bar/main.swift (renamed from Sources/phbar/main.swift)4
19 files changed, 328 insertions, 32 deletions
diff --git a/Sources/phbar/Config.swift b/Sources/bar/Config.swift
index e948b30..2d9859b 100644
--- a/Sources/phbar/Config.swift
+++ b/Sources/bar/Config.swift
@@ -3,13 +3,12 @@ import SwiftUI
/// The top-level configuration for the bar.
///
/// Set appearance, define blocks, and choose the bar position here.
-/// This is the primary file a user edits to customise phbar.
+/// This is the primary file a user edits to customise *bar*.
enum Config {
/// The bar height in points (pt).
///
- /// Tip: Set this value to 30 to match the default macOS
- /// menu bar height.
- static let height: CGFloat = 30
+ /// > Tip: Set this value to 30 to match the default macOS menu bar height.
+ static let height: Double = 30
/// The bar position on the screen.
static let position: Position = .top
@@ -52,9 +51,9 @@ enum Config {
/// and runs a shell command, optionally on a repeating interval.
/// See ``Config-swift.enum/Block`` for the full configuration options.
static let blocks: [Block] = [
- Block(name: "version", placement: .left, command: "echo '&!1 phbar v1.0.0 '"),
+ Block(name: "version", placement: .left, command: "echo '&!1 bar v1.0.0 '"),
Block(name: "ph", placement: .left, command: "echo ' by Panini House'"),
- Block(name: "memory", placement: .right, command: "echo \"􀟱 Hi $USER, welcome to phbar! | \""),
+ Block(name: "memory", placement: .right, command: "echo \"􀟱 Hi $USER, welcome to bar! | \""),
Block(name: "clock", placement: .right, command: "echo \"􀐬 $(date '+%a %d, %H:%M:%S') \"", interval: 1),
]
}
@@ -74,7 +73,7 @@ extension Config {
/// A block defined in the configuration.
///
/// Represents one unit of the bar. Each block is a named command that
- /// produces output on an optional timer. See ``phbar/Block`` for the
+ /// produces output on an optional timer. See ``bar/Block`` for the
/// runtime counterpart that manages execution and cell publishing.
struct Block: Sendable {
/// The block identifier used for manual updates.
@@ -83,10 +82,10 @@ extension Config {
/// interval. You might want instead to react to specific
/// events or "manually" update them.
/// In such cases, you can send a standard system
- /// notification that tells phbar to trigger an update
+ /// notification that tells *bar* to trigger an update
/// for the block identified by the name.
///
- /// E.g: `notifyutil -p house.panini.phbar.touch.<name>`
+ /// E.g: `notifyutil -p bar.touch.<name>`
///
/// Be aware that nothing stops you from having multiple
/// blocks with the same name. In such case, triggering
@@ -104,8 +103,8 @@ extension Config {
/// script somewhere on the system.
///
/// The command process will inherit the environment
- /// of the process that executed phbar in the first place.
- /// This means that running phbar from your regular shell
+ /// of the process that executed `bar` in the first place.
+ /// This means that running `bar` from your regular shell
/// or from launchctl might result in different behaviours.
/// Make sure to test both scenarios properly.
let command: String
diff --git a/Sources/phbar/Models/AppDelegate.swift b/Sources/bar/Models/AppDelegate.swift
index a7ecb5a..a7ecb5a 100644
--- a/Sources/phbar/Models/AppDelegate.swift
+++ b/Sources/bar/Models/AppDelegate.swift
diff --git a/Sources/phbar/Models/Block.swift b/Sources/bar/Models/Block.swift
index fe575e4..cb09a4b 100644
--- a/Sources/phbar/Models/Block.swift
+++ b/Sources/bar/Models/Block.swift
@@ -27,7 +27,7 @@ final class Block: ObservableObject, Identifiable {
/// The shell command to execute for producing the cells.
///
/// Can be an inline expression or a path to an executable
- /// script. Inherits the environment of the phbar process.
+ /// script. Inherits the environment of the `bar` process.
let command: String
/// Refresh interval in seconds, or `nil` for a one-shot block.
@@ -101,7 +101,7 @@ final class Block: ObservableObject, Identifiable {
extension Config.Block {
@MainActor
- var resolved: phbar.Block {
+ var resolved: bar.Block {
.init(name: name, placement: placement, command: command, interval: interval)
}
}
diff --git a/Sources/phbar/Models/Cell.swift b/Sources/bar/Models/Cell.swift
index 0ad8e51..9e0d46f 100644
--- a/Sources/phbar/Models/Cell.swift
+++ b/Sources/bar/Models/Cell.swift
@@ -5,19 +5,24 @@ import SwiftUI
/// A thin wrapper around a single text line captured
/// by a block.
///
-/// When a block executes the command it captures stdout
-/// and splits each line into a cell. Each cell is then
-/// laid out horizontally respecting the order.
+/// When a ``Block`` executes the command it captures `stdout`
+/// and splits each line into a ``Cell``. Each ``Cell``
+/// is then laid out horizontally respecting the order.
///
+/// ### Highlight
/// Besides the underlying text value, a cell holds an
/// highlight level (as Int) that, if true, applies the
-/// special background color defined in `Config.swift`.
+/// special background color defined in ``Config``.
/// You can set the highlight at runtime by prefixing the
/// text line with the special notation `&!<level>`, where
/// `<level>` is a positive integer that can be used as an
-/// index to pick an highlight (count starts from 1).
+/// index to pick an highlight.
///
-/// E.g: `echo "&!1 $(date "+%a %d, %H:%M:%S") "`
+/// ```shell
+/// echo "&!1 $(date "+%a %d, %H:%M:%S") "
+/// ```
+///
+/// > Note: Count starts from 1.
///
/// Highlight colors can have multiple use cases, but they
/// were imagined specifically for distinguishing cells
diff --git a/Sources/phbar/Models/HEX.swift b/Sources/bar/Models/HEX.swift
index a262630..a262630 100644
--- a/Sources/phbar/Models/HEX.swift
+++ b/Sources/bar/Models/HEX.swift
diff --git a/Sources/phbar/Models/Notifier.swift b/Sources/bar/Models/Notifier.swift
index a428c49..cdfe19e 100644
--- a/Sources/phbar/Models/Notifier.swift
+++ b/Sources/bar/Models/Notifier.swift
@@ -20,12 +20,12 @@ private final class NotifyBox {
/// notifyutil -p <prefix><name>
/// ```
///
-/// Each category of events uses a different ``prefix``
+/// Each category of events uses a different `prefix`
/// to avoid collisions. For example, block refresh
/// notifications use ``touchPrefix``:
///
/// ```
-/// notifyutil -p house.panini.phbar.touch.<name>
+/// notifyutil -p bar.touch.<name>
/// ```
///
/// > Note: `notify.h` isn't part of Swift's Darwin module,
@@ -37,9 +37,9 @@ private final class NotifyBox {
enum Notifier {
/// Prefix for block refresh notifications.
///
- /// Use `notifyutil -p house.panini.phbar.touch.<name>` to trigger
+ /// Use `notifyutil -p bar.touch.<name>` to trigger
/// a block update from the shell.
- static let touchPrefix = "house.panini.phbar.touch."
+ static let touchPrefix = "bar.touch."
/// Register `handler` to fire whenever the notification is posted.
///
@@ -80,7 +80,7 @@ enum Notifier {
/// Remove and release a registration.
///
- /// Pass the token returned by ``Notifier/register(_:handler:)``.
+ /// Pass the token returned by ``Notifier/register(_:prefix:handler:)``.
/// Optional: process exit clears all pending registrations anyway.
static func cancel(_ token: UnsafeMutableRawPointer) {
let center = CFNotificationCenterGetDarwinNotifyCenter()
diff --git a/Sources/phbar/Models/Runner.swift b/Sources/bar/Models/Runner.swift
index be4dbff..be4dbff 100644
--- a/Sources/phbar/Models/Runner.swift
+++ b/Sources/bar/Models/Runner.swift
diff --git a/Sources/phbar/Models/Screen.swift b/Sources/bar/Models/Screen.swift
index 870d3b6..8029db4 100644
--- a/Sources/phbar/Models/Screen.swift
+++ b/Sources/bar/Models/Screen.swift
@@ -1,10 +1,6 @@
import AppKit
+import Foundation
-/// Helpers for multi-screen geometry.
-///
-/// Maps a bar configuration (position, height) into concrete
-/// screen coordinates so the window can be placed correctly
-/// on each display.
enum Screen {
/// Frame of the bar within a screen, in global display coordinates.
///
@@ -14,7 +10,7 @@ enum Screen {
/// of the screen.
/// - height: The bar height in points (see ``Config/height``).
///
- /// - Returns: An ``NSRect`` in screen coordinates. The bar spans
+ /// - Returns: An NSRect in screen coordinates. The bar spans
/// the full width of the screen and is anchored at the top
/// or bottom edge.
static func barFrame(_ screen: NSScreen, position: Config.Position, height: CGFloat) -> NSRect {
diff --git a/Sources/phbar/Views/BarView.swift b/Sources/bar/Views/BarView.swift
index 7ad7c4c..7ad7c4c 100644
--- a/Sources/phbar/Views/BarView.swift
+++ b/Sources/bar/Views/BarView.swift
diff --git a/Sources/phbar/Views/BarWindow.swift b/Sources/bar/Views/BarWindow.swift
index ce65432..ce65432 100644
--- a/Sources/phbar/Views/BarWindow.swift
+++ b/Sources/bar/Views/BarWindow.swift
diff --git a/Sources/phbar/Views/BlockView.swift b/Sources/bar/Views/BlockView.swift
index 85d6e6e..85d6e6e 100644
--- a/Sources/phbar/Views/BlockView.swift
+++ b/Sources/bar/Views/BlockView.swift
diff --git a/Sources/phbar/Views/CellView.swift b/Sources/bar/Views/CellView.swift
index 6b39eb1..6b39eb1 100644
--- a/Sources/phbar/Views/CellView.swift
+++ b/Sources/bar/Views/CellView.swift
diff --git a/Sources/bar/bar.docc/bar.md b/Sources/bar/bar.docc/bar.md
new file mode 100644
index 0000000..3d8b2ee
--- /dev/null
+++ b/Sources/bar/bar.docc/bar.md
@@ -0,0 +1,44 @@
+# ``bar``
+
+Modular status bar for macOS written in Swift.
+
+## Overview
+
+*bar* draws a status bar on each screen connected at runtime.
+The bar can be configured to have a specific ``Config-swift.enum/height``, ``Config-swift.enum/position``, and ``Config-swift.enum/background`` color.
+You can also customize the ``Config-swift.enum/font`` and ``Config-swift.enum/foreground`` color used by all the texts.
+
+Inside the bar, you can display one or more ``Block``, each defined by a name, placement, shell command, and optional refresh interval.
+At runtime each block runs the command, reads from stdout, and builds one or more ``Cell`` from the output.
+If you specify the interval, the block will re-run the command with such frequency.
+
+## Topics
+
+### Getting started
+
+- <doc:installation>
+- <doc:customisation>
+- ``Config``
+- ``HEX``
+
+### Blocks
+
+- <doc:commands>
+- <doc:manual-updates>
+- ``Block``
+- ``Cell``
+- ``Runner``
+- ``Notifier``
+
+### Design
+
+- <doc:philosophy>
+
+### Views
+
+- ``AppDelegate``
+- ``Screen``
+- ``BarWindow``
+- ``BarView``
+- ``BlockView``
+- ``CellView``
diff --git a/Sources/bar/bar.docc/commands.md b/Sources/bar/bar.docc/commands.md
new file mode 100644
index 0000000..4c00bfe
--- /dev/null
+++ b/Sources/bar/bar.docc/commands.md
@@ -0,0 +1,50 @@
+# Commands
+
+How a block runs its command and what you can do with it.
+
+Each block runs its ``Config-swift.enum/Block/command`` as a shell script via `/bin/sh -c`.
+The standard output is captured and **each line becomes one ``Cell``** in the bar.
+Cells are laid out horizontally in the order they appear.
+
+```shell
+# A single-line command produces one cell
+echo "Hello, world"
+
+# Multi-line output produces multiple cells
+echo "first cell"
+echo "second cell"
+```
+
+## Highlighting cells
+
+Prefix a line with `&!<level>` to apply a highlight background color.
+The highlight level is a positive integer that indexes into ``Config-swift.enum/highlights`` (1-based).
+
+```shell
+echo "&!1 workspace 1" # highlighted with Config.highlights[0]
+echo "&!2 workspace 2" # highlighted with Config.highlights[1]
+echo "workspace 3" # no highlight
+```
+
+Highlight colors were imagined specifically for distinguishing cells within a group. E.g: showing window manager workspaces and which one is active.
+
+## Environment variables
+
+Each command inherits the `bar` process environment, plus these extras:
+
+| Variable | What holds | When set | Example value |
+|---|---|---|---|
+| `BLOCK` | The name of the block | Always | `"clock"` |
+| `BUTTON` | The mouse button number | On mouse click inside the block | `"0"` (left), `"1"` (right), `"2"` (middle) |
+| `SCROLL` | The scroll direction | On scroll over the block | `"UP"`, `"DOWN"`, `"LEFT"`, `"RIGHT"` |
+
+This lets a single script adapt its behaviour based on which block called it or what interaction just happened:
+
+```shell
+#!/bin/sh
+if [ "$BLOCK" = "clock" ]; then
+ date '+%a %d, %H:%M:%S'
+elif [ -n "$BUTTON" ]; then
+ echo "clicked button $BUTTON"
+fi
+```
diff --git a/Sources/bar/bar.docc/customisation.md b/Sources/bar/bar.docc/customisation.md
new file mode 100644
index 0000000..cc692ce
--- /dev/null
+++ b/Sources/bar/bar.docc/customisation.md
@@ -0,0 +1,45 @@
+# Customisation
+
+How to customise the program.
+
+The program can be customised by editing the static properties inside the ``Config`` enum.
+
+```swift
+// Sources/bar/Config.swift
+
+enum Config {
+ static let height: Double = 30
+ static let position: Position = .top
+
+ static let font: Font = .custom("My Awesome Font", fixedSize: 14)
+
+ static let foreground: Color = .hex("#FFFFFF")
+ static let background: Color = .hex("#000000").opacity(0.75)
+ static let highlights: [Color] = [.blue, .purple]
+
+ static let blocks: [Block] = [
+ Block(name: "workspaces", placement: .left, command: "~/scripts/workspaces"),
+ Block(name: "clock", placement: .right, command: "date '+%a %d, %H:%M:%S'", interval: 1),
+ ]
+}
+```
+
+> Tip: While testing the configuration you might want to use the `make run` target.
+> It will build and execute the program right away, without copying the binaries.
+
+## HEX Colors
+
+You can use any standard SwiftUI color. For convenience, we also provide the ``HEX`` type for defining colors by hex code directly in source:
+
+```swift
+static let foreground: Color = .hex("#CDD6F4")
+```
+
+Supported formats (the leading `#` is optional): `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`.
+
+## Spacing
+
+*bar* does not offer an option to customise the spacing between blocks.
+We personally think that adding a space character at the end or beginning of a block is plenty enough and eliminates a bunch of possible edge cases.
+
+> Tip: If you don't like the default space of your font you can try one of the [alternatives from the Unicode specification](https://spaces.mau.fi/).
diff --git a/Sources/bar/bar.docc/installation.md b/Sources/bar/bar.docc/installation.md
new file mode 100644
index 0000000..a96f79f
--- /dev/null
+++ b/Sources/bar/bar.docc/installation.md
@@ -0,0 +1,95 @@
+# Installation
+
+How to install and build the program.
+
+**Building the program from source is an integral part of the philosophy and design of the software**.
+For this reason we do not ship pre-built binaries nor you'll be able to find it on Homebrew or any other package manager.
+
+## Requirements
+
+- **macOS 26** or later
+- **Swift 6.3** (bundled with Xcode or installed via [swift.org](https://swift.org))
+
+## Download
+
+Clone the official repository somewhere on you system:
+
+```shell
+git clone https://github.com/paninihouse/bar.git
+```
+
+## Build
+
+From within the *bar* directory, run the install target:
+
+```shell
+make install
+```
+
+> Tip: By default, the install target builds a release binary and copies it to `/usr/local/bin/bar`.
+> You can override the install location by setting `PREFIX`:
+>
+> ```shell
+> make install PREFIX=~/.local/bin
+> ```
+
+### Without make
+
+If you don't have `make`, build directly with the Swift Package Manager:
+
+```shell
+swift build -c release
+cp .build/release/bar /usr/local/bin/
+```
+
+## Run at login
+
+It is fine to run `bar` manually while you're editing and customising the source code.
+Although, as soon as you're happy with your build is a good idea to create a launch agent that can start the program for you.
+
+Copy the example down below in a `plist` file inside the `~/Library/LaunchAgents/` directory.
+
+> Tip: It is best practice to use [reverse domain name notation](https://en.wikipedia.org/wiki/Reverse_domain_name_notation) for the filename.
+
+Adjust the example as you see fit.
+For the full file specification run:
+
+```shell
+man launchd.plist
+```
+
+You can than register the launch agent like so:
+
+```shell
+launchctl load ~/Library/LaunchAgents/house.panini.bar.plist
+```
+
+### Example launch agent
+
+```plist
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>house.panini.bar</string>
+
+ <key>ProgramArguments</key>
+ <array>
+ <string>/usr/local/bin/bar</string>
+ </array>
+
+ <key>RunAtLoad</key>
+ <true/>
+
+ <key>KeepAlive</key>
+ <true/>
+
+ <key>StandardOutPath</key>
+ <string>/tmp/bar/bar.log</string>
+
+ <key>StandardErrorPath</key>
+ <string>/tmp/bar/bar.err</string>
+</dict>
+</plist>
+```
diff --git a/Sources/bar/bar.docc/manual-updates.md b/Sources/bar/bar.docc/manual-updates.md
new file mode 100644
index 0000000..60520ca
--- /dev/null
+++ b/Sources/bar/bar.docc/manual-updates.md
@@ -0,0 +1,28 @@
+# Manual refreshes
+
+How to trigger a block update on-demand.
+
+*bar* implements a very basic way to manually signal a block to refresh its content.
+This is done by posting a [Darwin notification](https://developer.apple.com/documentation/notifications) from the shell.
+
+## Triggering a refresh
+
+Use `notifyutil` (bundled with macOS) to post a notification:
+
+```shell
+notifyutil -p bar.touch.<name>
+```
+
+Replace `<name>` with the block's name (e.g. `clock`, `battery`).
+If multiple blocks share the same name, a single notification refreshes them all.
+
+## Use cases
+
+Manual updates are useful for blocks that react to external events:
+
+- **Window managers** - refresh when the workspace or focused app changes.
+- **Volume indicator** — refresh when the sound output changes.
+- **Network monitor** — poll only when the interface state flips.
+- **Now playing** — update when the current track changes.
+
+Combine with a launch agent or a shell hook that posts the notification whenever the relevant system state changes.
diff --git a/Sources/bar/bar.docc/philosophy.md b/Sources/bar/bar.docc/philosophy.md
new file mode 100644
index 0000000..6c9764b
--- /dev/null
+++ b/Sources/bar/bar.docc/philosophy.md
@@ -0,0 +1,34 @@
+# Design philosophy
+
+*bar* design is heavily inspired by the suite of [suckless tools](https://suckless.org/) for Linux.
+These tools are very different from what you typically have on macOS.
+They do not have configuration files, extraneous features, or a plugin/extension system.
+Instead, they are focused on simplicity, frugality, and a DIY approach.
+
+We deliberately designed *bar* to be extremely limited, specific, and easy to understand, like a suckless tool.
+This is not software you install, run, and forget about.
+You're supposed to fully understand how it works, make it your own, and ultimately give back your enhancements to the community.
+
+## How customization works
+
+*bar* does not read a configuration file somewhere on your system.
+Instead, configuration happens directly in the source code by editing ``Config`` and rebuilding the software.
+This approach allows us to keep the software extremely minimal and reliable, since all the possible errors are caught at build time.
+
+## No pre-built binaries
+
+Since the configuration requires you to modify the source code, it doesn't make sense to ship pre-built binaries of *bar*.
+Therefore, you won't find it in Homebrew or in any other package manager.
+The way to install *bar* is simply to download or clone the repository on your system and run <doc:installation>.
+
+## How to extend the functionality
+
+*bar* already does a lot out of the box, but it is deliberately more limited compared to other solutions like [SketchyBar](https://felixkratz.github.io/SketchyBar/).
+What if you need a specific feature that is not available?
+Well, you can build it or patch it yourself.
+
+*bar* aims to stay focused and minimal, so it can be read through and understood in roughly half an hour.
+You're more than encouraged to apply patches to extend the software, write your own features, and ultimately publish them for the entire community.
+
+This approach is far more powerful than a plugin/extension ecosystem because you're not limited to what we, as the main developers, decide you can customize or change.
+You own the source code, therefore you are free to do whatever you want.
diff --git a/Sources/phbar/main.swift b/Sources/bar/main.swift
index 6322d69..e965fa5 100644
--- a/Sources/phbar/main.swift
+++ b/Sources/bar/main.swift
@@ -1,8 +1,8 @@
import AppKit
// Entry point. Top-level code runs on the main actor.
-let app = NSApplication.shared
-let delegate = AppDelegate()
+private let app = NSApplication.shared
+private let delegate = AppDelegate()
app.delegate = delegate
app.setActivationPolicy(.accessory) // no Dock icon, no menu
app.run()