Barista — a macOS menu-bar application framework in Common Lisp.

BARISTA ASDF System Details

Barista is a macOS menu-bar application framework written in Common Lisp. It lets you display live information — text, emojis, icons, or dynamically rendered images — in the macOS status bar.

Plugins run as threads inside a single Lisp process and communicate with AppKit via Grand Central Dispatch (GCD). Each plugin gets its own NSStatusItem with an icon and a dropdown menu.

This is similar to XBar and TextBar, but with key advantages:

Feature

XBar

Barista

Plugin execution

Subprocess per refresh

Threads in one Lisp process

State persistence

External storage

All in memory

Plugin language

Any (mostly Bash)

Common Lisp

Menu API

Text-based

Programmatic DSL + ObjC interop

Debugging

None

Live REPL via SLYNK/SWANK

Because plugins are Lisp code running in the same process, you can connect your IDE (SLIME/SLY) to the running Barista instance and debug plugins interactively.

Introduction

Barista turns the macOS menu bar into a live Lisp-powered dashboard.

A plugin is a Common Lisp class defined with the defplugin macro. Each plugin can:

How it works

Barista loads user plugins from ~/.config/barista/plugins/ at startup, then initializes AppKit on the macOS main thread and enters the [NSApp run] event loop. Worker threads update NSStatusItem objects via GCD (dispatch_async on the main queue), ensuring all UI mutations happen on the main thread.

Key features

Installation

From a pre-built release

Download the latest Barista-x.y.z.dmg from the GitHub Releases page, open it and drag Barista.app to your Applications folder.

Because the app is not signed with an Apple Developer certificate, macOS will attach a quarantine flag to it when downloaded from the internet and refuse to open it with a "Barista is damaged and can't be opened" message.

To remove the quarantine flag, run the following command in Terminal after copying the app to /Applications:

xattr -dr com.apple.quarantine /Applications/Barista.app

Then open the app normally.

Build from source

Barista uses Qlot for dependency management and Roswell for building.

git clone https://github.com/40ants/barista.git
cd barista
qlot install
./build.sh

This produces dist/Barista.app. To also copy it to /Applications:

INSTALL=1 ./build.sh

Running with a REPL

You can connect your IDE to a running Barista instance for live debugging:

# From the built executable:
./dist/Barista.app/Contents/MacOS/barista --slynk
./dist/Barista.app/Contents/MacOS/barista --slynk --port 4005

# From source via Roswell:
qlot exec ros app.ros --slynk

Connect from SLY:

M-x sly-connect RET 127.0.0.1 RET 5007 RET

SLIME is also supported via the --swank flag. Default port is 5007. Logs are written to /tmp/barista.log.

Available Plugins

Barista ships with several bundled plugins. Enable or disable them from the menu-bar icon → Plugins submenu (visible when no other plugin is active, or via Alt-click on any plugin icon).

Pomodoro

A Pomodoro Technique timer. Shows a tomato emoji (🍅) when idle and a countdown when running. Choose 5, 15, or 25 minute intervals from the dropdown menu. The countdown turns red in the last 3 minutes. A macOS notification fires when the timer starts and stops.

Clipboard

A clipboard history manager. Polls the system pasteboard every second and keeps the last 10 copied items. Click the icon (📋) to see the history; click any item to copy it back to the clipboard. Includes a "Clear history" option.

Currency

Displays USD and EUR exchange rates from the Central Bank of Russia daily XML feed. The status-bar label flips between $ and rates every 5 seconds. Rates are fetched on startup and refreshed every 30 minutes. The dropdown menu shows both rates side by side.

System Monitor

Shows real-time CPU, GPU, and memory usage as a colored bar-chart icon in the status bar (rendered dynamically as a PNG). The dropdown menu displays numeric percentages for each metric. Colors change from green to orange to red as usage increases. Metrics are read directly from Mach/IOKit APIs via CFFI — no external commands.

Community Plugins

Writing Custom Plugins

Plugins live as .lisp files in ~/.config/barista/plugins/. Each file is loaded at startup before AppKit initialises.

Minimal text plugin

The simplest plugin shows a text label and updates it on a schedule:

(defpackage #:my-plugins/clock
  (:use #:cl)
  (:import-from #:barista/plugin
                #:defplugin
                #:get-title)
  (:import-from #:barista/vars
                #:*plugin*))
(in-package #:my-plugins/clock)

(defplugin clock ()
  (:title "🕐")
  (:every :minute
    (setf (get-title *plugin*)
          (local-time:format-timestring nil (local-time:now)
                                        :format '(:hour ":" (:min 2))))))

defplugin options

The defplugin macro accepts the following options:

You can define custom slots on the plugin class by listing them after the plugin name, just like defclass:

(defplugin my-plugin
    ((counter :initform 0 :accessor get-counter))
  (:title "0")
  (:every :second
    (incf (get-counter *plugin*))
    (setf (get-title *plugin*)
          (format nil "~D" (get-counter *plugin*)))))

Image icon plugin

(defpackage #:my-plugins/status
  (:use #:cl)
  (:import-from #:barista/plugin
                #:defplugin
                #:get-image)
  (:import-from #:barista/vars
                #:*plugin*))
(in-package #:my-plugins/status)

;; Colour icon — pixels render as-is (default).
(defplugin status-colour ()
  (:image #p"~/.config/barista/icons/my-icon.png" :size 18))

;; Monochrome/symbolic icon — macOS recolours it for dark/light mode.
(defplugin status-mono ()
  (:image #p"~/.config/barista/icons/mono-icon.png" :size 18 :template t))

You can also update the icon at runtime from a worker thread:

(:every (30 :seconds)
  (let ((icon-path (render-current-state-to-png)))
    (setf (get-image *plugin*) icon-path)))

setf get-image accepts a pathname, a namestring, or an NSImage pointer.

There are two ways to define a dropdown menu.

Declarativedefmenu (compile-time, static structure):

(defmenu start
    (("Start 5 min"  :callback (lambda () (start 5)))
     ("Start 15 min" :callback (lambda () (start 15)))
     ("---")
     ("Help"         :url "https://example.com")))

Item spec: (title &key callback submenu url state disabled). * "---" adds a separator. * :callback is a zero-argument function called on click. * :submenu is a symbol naming another defmenu. * :url opens a URL in the default browser. * :state t shows a native checkmark. * :disabled t greys out the item.

Imperativebuild-menu / add-item (runtime, dynamic structure):

(defun build-my-menu (plugin)
  (build-menu
    (dolist (item (get-items plugin))
      (add-item item :callback (make-item-callback item)))
    (add-separator)
    (add-item "Clear" :callback (lambda () (clear plugin)))))

Use build-menu when the menu structure depends on runtime data.

Dynamic menu wiring

When a plugin needs a dynamic menu (built at runtime), wire up the menu-thunk on the first worker tick instead of using the :menu option:

(defplugin my-plugin
    ((menu-ready :initform nil :accessor get-menu-ready))
  (:title "Data")
  (:every (5 :seconds)
    (unless (get-menu-ready *plugin*)
      (setf (barista/classes:get-menu-thunk
             (barista/plugin:get-status-item *plugin*))
            (let ((p *plugin*))
              (lambda () (build-dynamic-menu p))))
      (setf (get-menu-ready *plugin*) t))
    (update-data *plugin*)))

This pattern is used by the clipboard, currency, and system-monitor plugins.

The plugin variable

barista/vars:*plugin* is the central context variable. It is bound inside worker threads, menu thunks, and menu callbacks automatically. Always use it to access the current plugin instance:

(setf (get-title *plugin*) "Updated!")
(setf (get-image *plugin*) #p"/path/to/icon.png")

Switching menus at runtime

Use replace-menu to swap the dropdown menu of a running plugin:

(barista/plugin:replace-menu start-menu-symbol stop-menu-symbol)

This replaces the menu-thunk on the status item.

Enabling plugins

On first launch Barista shows a system icon in the menu bar. Click it and choose Plugins to enable the plugins you want. The selection is saved to ~/.config/barista/settings.lisp and restored on every subsequent launch.

You can also Alt-click any plugin icon to access a maintenance menu with options to restart all plugins or quit Barista.

API

BARISTA/CLASSES

Classes

STATUS-ITEM

Wraps an NSStatusItem for one Barista plugin.

Readers

Integer pointer address of the NSStatusBarButton, captured at initialisation time and used for click-table keying and cleanup. Storing it avoids calling (send ns-item "button") on a potentially-released object during hide.

Nullary function that builds and returns an NSMenu pointer.

Raw CFFI pointer to the AppKit NSStatusItem.

When T, this is the system plugin item. The click handler skips appending the Settings/Quit section to its menu because it already is the Settings menu.

Accessors

Integer pointer address of the NSStatusBarButton, captured at initialisation time and used for click-table keying and cleanup. Storing it avoids calling (send ns-item "button") on a potentially-released object during hide.

Nullary function that builds and returns an NSMenu pointer.

Raw CFFI pointer to the AppKit NSStatusItem.

When T, this is the system plugin item. The click handler skips appending the Settings/Quit section to its menu because it already is the Settings menu.

Generics

Return the current status-bar icon of PLUGIN.

Functions

Return a compile-time form that evaluates to an NSString or NSAttributedString. Called by defmenu/build-menu macros to process user-supplied title values.

Concatenate PARTS into a single NSMutableAttributedString. Each part may be a plain string, a (text :color ... :font ... :size ...) list, or an existing NSAttributedString pointer.

function
text &key font color (size +default-font-size+)

Create an NSAttributedString from TEXT with optional FONT, COLOR, and SIZE.

Return the standard menu font at SIZE points.

function
name &key (size +default-font-size+)

Return an NSFont for NAME at SIZE points, or NIL if the font is unknown.

function
path &key (size nil) (template nil)

Load an NSImage from PATH (pathname or string). PATH may be a CL pathname or a namestring.

Keyword arguments: SIZE -- when non-NIL, a number; calls setSize: with SIZE x SIZE points. TEMPLATE -- when T, marks the image as a template image so macOS adapts it to the current appearance (dark/light mode). Use T only for monochrome/symbolic icons. For colour images leave NIL (the default) so pixels render as-is.

Returns the NSImage pointer, or NIL if the file could not be loaded.

BARISTA/CONFIG

Functions

Return a list of plugin name keywords that are enabled in the config. Only keys explicitly set to T are included.

Return T if PLUGIN-NAME is enabled in the configuration. Defaults to NIL (disabled) when no value is stored -- this ensures a clean first-run experience where the system plugin is shown instead.

Load Barista configuration from disk. If the file does not exist (first launch), bootstraps an empty storage and points storage-pathname at the target path so that subsequent (setf (value ...)) calls persist to the correct file.

Note: when handler-case catches no-storage-file, ubiquitous leaves storage-pathname pointing at its default global path and does NOT set it to the requested path. We must set both storage-pathname and storage explicitly to make offload work correctly later.

Persist the enabled/disabled state for PLUGIN-NAME. ubiquitous automatically writes to disk after each (setf value).

BARISTA/MAIN

Functions

Load all user plugin files from ~/.config/barista/plugins/.

Start the Barista menu-bar application.

Loads user plugins, initialises AppKit on macOS thread 0 via trivial-main-thread, and enters the AppKit event loop (never returns normally).

Instantiate and start all registered plugins regardless of config. Must be called on the AppKit main thread. NOTE: Prefer start-enabled-plugins for normal startup.

Stop all currently running plugins.

BARISTA/MENU

Generics

Remove ITEM from the status bar and release the NSStatusItem.

Functions

function
title &key callback submenu url state disabled

Add an item to the menu currently being built by build-menu. Must be called inside a build-menu body. STATE: when T sets the native macOS checkmark (NSControlStateValueOn). DISABLED: when T greys out the item and makes it non-interactive.

Add a native NSMenuItem separator to the menu currently being built. Must be called inside a build-menu body.

Create the AppKit NSStatusItem for the barista/classes:status-item ITEM. Must be called on the AppKit main thread.

function
ITEM IMAGE-PATH &KEY (SIZE 18.0d0) TEMPLATE

Like initialize-status-item but uses an image file instead of a text title. IMAGE-PATH is a CL pathname or namestring to a PNG/ICNS file. SIZE -- desired icon size in points (default 18); passed to make-ns-image. TEMPLATE -- when T, marks the image as a template (monochrome/symbolic icons that should adapt to dark/light mode). Leave NIL (default) for colour images so pixels are rendered as-is.

Return an NSMenu pointer for NAME-OR-MENU. NAME-OR-MENU may be a symbol (looked up in menu-constructors or as a function), or an already-built NSMenu CFFI pointer.

Macros

Evaluate BODY with current-menu bound to a fresh NSMenu, then return it.

macro
name (&rest items)

Define a named menu constructor and register it in menu-constructors.

Example: (defmenu my-menu (("Item one" :callback #'handler) ("Item two" :url "https://example.com")))

BARISTA/PLUGIN

Generics

Return the current status-bar icon of PLUGIN.

Return the menu-thunk of PLUGIN's status item.

Return the current status-bar label of PLUGIN.

Stop PLUGIN, dispatching to the AppKit main thread via GCD.

Functions

Start only the plugins that are enabled in the configuration. Must be called directly on the AppKit main thread (not via GCD) so that all plugins are registered in running-plugins before the caller checks visibility (e.g. ensure-system-plugin).

Start CLASS-NAME, dispatching to the AppKit main thread via GCD. Safe to call from any thread. Returns immediately.

Macros

macro
name (&rest slots) &body options

Define a Barista plugin class with background workers and a menu-bar item.

Options: (:title EXPR) -- initial status-bar title (text or attributed) (:image PATH &key size template) -- status-bar icon from an image file. PATH is a pathname or string evaluated at plugin start time. SIZE (default 18) scales the icon. TEMPLATE T for monochrome/symbolic icons that should adapt to dark/light mode; leave NIL (default) for colour images. (:menu SYMBOL) -- name of a defmenu to show on click (:every PERIOD FORMS) -- background worker: evaluate FORMS every PERIOD

:title and :image are mutually exclusive; :image takes precedence when both are supplied.

Run BODY with barista/vars:*plugin* bound to the named running plugin. Useful for interactive debugging.

BARISTA/SYSTEM-PLUGIN

Functions

Call after start-enabled-plugins to show the system plugin when needed. Must be called on the AppKit main thread.

Hide the system plugin status item from the menu bar. No-op if not visible.

Build the Settings > Plugins submenu dynamically. Each item shows the plugin name with a native checkmark if currently enabled.

Show the system plugin status item in the menu bar. Creates it on first call; no-op if already visible.

Show the system plugin if no user plugins are running; hide it otherwise. Must be called from the AppKit main thread (via on-main-thread when in doubt).

BARISTA/UTILS

Functions

function
duration &optional stream

Open URL in the default browser via a background thread.

Macros

Schedule ACTIONS to run on the AppKit main thread via GCD. Returns immediately (fire-and-forget). Safe to call from any thread.

BARISTA/VARS

Variables

If True, then debugger will be invoked on any error in the periodic threads.

Default font size in points for status-bar text.

variable
(:aliceblue :antiquewhite :antiquewhite1 :antiquewhite2 :antiquewhite3 :antiquewhite4 :aquamarine :aquamarine1 :aquamarine2 :aquamarine3 :aquamarine4 :azure :azure1 :azure2 :azure3 :azure4 :beige :bisque :bisque1 :bisque2 :bisque3 :bisque4 :black :blanchedalmond :blue :blue1 :blue2 :blue3 :blue4 :blueviolet :brown :brown1 :brown2 :brown3 :brown4 :burlywood :burlywood1 :burlywood2 :burlywood3 :burlywood4 :cadetblue :cadetblue1 :cadetblue2 :cadetblue3 :cadetblue4 :chartreuse :chartreuse1 :chartreuse2 :chartreuse3 :chartreuse4 :chocolate :chocolate1 :chocolate2 :chocolate3 :chocolate4 :coral :coral1 :coral2 :coral3 :coral4 :cornflowerblue :cornsilk :cornsilk1 :cornsilk2 :cornsilk3 :cornsilk4 :cyan :cyan1 :cyan2 :cyan3 :cyan4 :darkblue :darkcyan :darkgoldenrod :darkgoldenrod1 :darkgoldenrod2 :darkgoldenrod3 :darkgoldenrod4 :darkgray :darkgreen :darkgrey :darkkhaki :darkmagenta :darkolivegreen :darkolivegreen1 :darkolivegreen2 :darkolivegreen3 :darkolivegreen4 :darkorange :darkorange1 :darkorange2 :darkorange3 :darkorange4 :darkorchid :darkorchid1 :darkorchid2 :darkorchid3 :darkorchid4 :darkred :darksalmon :darkseagreen :darkseagreen1 :darkseagreen2 :darkseagreen3 :darkseagreen4 :darkslateblue :darkslategray :darkslategray1 :darkslategray2 :darkslategray3 :darkslategray4 :darkslategrey :darkturquoise :darkviolet :debianred :deeppink :deeppink1 :deeppink2 :deeppink3 :deeppink4 :deepskyblue :deepskyblue1 :deepskyblue2 :deepskyblue3 :deepskyblue4 :dimgray :dimgrey :dodgerblue :dodgerblue1 :dodgerblue2 :dodgerblue3 :dodgerblue4 :firebrick :firebrick1 :firebrick2 :firebrick3 :firebrick4 :floralwhite :forestgreen :gainsboro :ghostwhite :gold :gold1 :gold2 :gold3 :gold4 :goldenrod :goldenrod1 :goldenrod2 :goldenrod3 :goldenrod4 :gray :gray0 :gray1 :gray10 :gray100 :gray11 :gray12 :gray13 :gray14 :gray15 :gray16 :gray17 :gray18 :gray19 :gray2 :gray20 :gray21 :gray22 :gray23 :gray24 :gray25 :gray26 :gray27 :gray28 :gray29 :gray3 :gray30 :gray31 :gray32 :gray33 :gray34 :gray35 :gray36 :gray37 :gray38 :gray39 :gray4 :gray40 :gray41 :gray42 :gray43 :gray44 :gray45 :gray46 :gray47 :gray48 :gray49 :gray5 :gray50 :gray51 :gray52 :gray53 :gray54 :gray55 :gray56 :gray57 :gray58 :gray59 :gray6 :gray60 :gray61 :gray62 :gray63 :gray64 :gray65 :gray66 :gray67 :gray68 :gray69 :gray7 :gray70 :gray71 :gray72 :gray73 :gray74 :gray75 :gray76 :gray77 :gray78 :gray79 :gray8 :gray80 :gray81 :gray82 :gray83 :gray84 :gray85 :gray86 :gray87 :gray88 :gray89 :gray9 :gray90 :gray91 :gray92 :gray93 :gray94 :gray95 :gray96 :gray97 :gray98 :gray99 :green :green1 :green2 :green3 :green4 :greenyellow :grey :grey0 :grey1 :grey10 :grey100 :grey11 :grey12 :grey13 :grey14 :grey15 :grey16 :grey17 :grey18 :grey19 :grey2 :grey20 :grey21 :grey22 :grey23 :grey24 :grey25 :grey26 :grey27 :grey28 :grey29 :grey3 :grey30 :grey31 :grey32 :grey33 :grey34 :grey35 :grey36 :grey37 :grey38 :grey39 :grey4 :grey40 :grey41 :grey42 :grey43 :grey44 :grey45 :grey46 :grey47 :grey48 :grey49 :grey5 :grey50 :grey51 :grey52 :grey53 :grey54 :grey55 :grey56 :grey57 :grey58 :grey59 :grey6 :grey60 :grey61 :grey62 :grey63 :grey64 :grey65 :grey66 :grey67 :grey68 :grey69 :grey7 :grey70 :grey71 :grey72 :grey73 :grey74 :grey75 :grey76 :grey77 :grey78 :grey79 :grey8 :grey80 :grey81 :grey82 :grey83 :grey84 :grey85 :grey86 :grey87 :grey88 :grey89 :grey9 :grey90 :grey91 :grey92 :grey93 :grey94 :grey95 :grey96 :grey97 :grey98 :grey99 :honeydew :honeydew1 :honeydew2 :honeydew3 :honeydew4 :hotpink :hotpink1 :hotpink2 :hotpink3 :hotpink4 :indianred :indianred1 :indianred2 :indianred3 :indianred4 :ivory :ivory1 :ivory2 :ivory3 :ivory4 :khaki :khaki1 :khaki2 :khaki3 :khaki4 :lavender :lavenderblush :lavenderblush1 :lavenderblush2 :lavenderblush3 :lavenderblush4 :lawngreen :lemonchiffon :lemonchiffon1 :lemonchiffon2 :lemonchiffon3 :lemonchiffon4 :lightblue :lightblue1 :lightblue2 :lightblue3 :lightblue4 :lightcoral :lightcyan :lightcyan1 :lightcyan2 :lightcyan3 :lightcyan4 :lightgoldenrod :lightgoldenrod1 :lightgoldenrod2 :lightgoldenrod3 :lightgoldenrod4 :lightgoldenrodyellow :lightgray :lightgreen :lightgrey :lightpink :lightpink1 :lightpink2 :lightpink3 :lightpink4 :lightsalmon :lightsalmon1 :lightsalmon2 :lightsalmon3 :lightsalmon4 :lightseagreen :lightskyblue :lightskyblue1 :lightskyblue2 :lightskyblue3 :lightskyblue4 :lightslateblue :lightslategray :lightslategrey :lightsteelblue :lightsteelblue1 :lightsteelblue2 :lightsteelblue3 :lightsteelblue4 :lightyellow :lightyellow1 :lightyellow2 :lightyellow3 :lightyellow4 :limegreen :linen :magenta :magenta1 :magenta2 :magenta3 :magenta4 :maroon :maroon1 :maroon2 :maroon3 :maroon4 :mediumaquamarine :mediumblue :mediumorchid :mediumorchid1 :mediumorchid2 :mediumorchid3 :mediumorchid4 :mediumpurple :mediumpurple1 :mediumpurple2 :mediumpurple3 :mediumpurple4 :mediumseagreen :mediumslateblue :mediumspringgreen :mediumturquoise :mediumvioletred :midnightblue :mintcream :mistyrose :mistyrose1 :mistyrose2 :mistyrose3 :mistyrose4 :moccasin :navajowhite :navajowhite1 :navajowhite2 :navajowhite3 :navajowhite4 :navy :navyblue :oldlace :olivedrab :olivedrab1 :olivedrab2 :olivedrab3 :olivedrab4 :orange :orange1 :orange2 :orange3 :orange4 :orangered :orangered1 :orangered2 :orangered3 :orangered4 :orchid :orchid1 :orchid2 :orchid3 :orchid4 :palegoldenrod :palegreen :palegreen1 :palegreen2 :palegreen3 :palegreen4 :paleturquoise :paleturquoise1 :paleturquoise2 :paleturquoise3 :paleturquoise4 :palevioletred :palevioletred1 :palevioletred2 :palevioletred3 :palevioletred4 :papayawhip :peachpuff :peachpuff1 :peachpuff2 :peachpuff3 :peachpuff4 :peru :pink :pink1 :pink2 :pink3 :pink4 :plum :plum1 :plum2 :plum3 :plum4 :powderblue :purple :purple1 :purple2 :purple3 :purple4 :red :red1 :red2 :red3 :red4 :rosybrown :rosybrown1 :rosybrown2 :rosybrown3 :rosybrown4 :royalblue :royalblue1 :royalblue2 :royalblue3 :royalblue4 :saddlebrown :salmon :salmon1 :salmon2 :salmon3 :salmon4 :sandybrown :seagreen :seagreen1 :seagreen2 :seagreen3 :seagreen4 :seashell :seashell1 :seashell2 :seashell3 :seashell4 :sienna :sienna1 :sienna2 :sienna3 :sienna4 :skyblue :skyblue1 :skyblue2 :skyblue3 :skyblue4 :slateblue :slateblue1 :slateblue2 :slateblue3 :slateblue4 :slategray :slategray1 :slategray2 :slategray3 :slategray4 :slategrey :snow :snow1 :snow2 :snow3 :snow4 :springgreen :springgreen1 :springgreen2 :springgreen3 :springgreen4 :steelblue :steelblue1 :steelblue2 :steelblue3 :steelblue4 :tan :tan1 :tan2 :tan3 :tan4 :thistle :thistle1 :thistle2 :thistle3 :thistle4 :tomato :tomato1 :tomato2 :tomato3 :tomato4 :turquoise :turquoise1 :turquoise2 :turquoise3 :turquoise4 :violet :violetred :violetred1 :violetred2 :violetred3 :violetred4 :wheat :wheat1 :wheat2 :wheat3 :wheat4 :white :whitesmoke :yellow :yellow1 :yellow2 :yellow3 :yellow4 :yellowgreen)

A list of supported color names.