Macotron

macotron.menubar

Add your own items to the menu bar next to the Macotron icon, give them menus with HTML rows and button rows, and add rows to the Macotron menu itself.

// A countdown in the menu bar with a menu of actions.
let until = 0;

function paint() {
  const left = Math.max(0, until - Date.now());
  macotron.menubar.status("timer", {
    title: left ? new Date(left).toISOString().slice(14, 19) : "",
    sfSymbol: "timer",
    color: left ? "green" : null,
    minWidth: left ? 72 : undefined,
    menu: [
      { title: left ? "Running" : "Stopped", dimmed: true },
      "-",
      { title: "Start 25 minutes", onClick: () => { until = Date.now() + 25 * 60000; paint(); } },
      { title: "Stop", onClick: () => { until = 0; paint(); } },
    ],
  });
}

paint();
macotron.every(1000, paint);

Status items

A status item is a separate item in the menu bar, to the left of the Macotron icon. Each plugin can add as many as it likes, keyed by id.

status(id: string, opts: StatusOptions): void

Creates the item, or repaints it when an item with that id exists. Call it again whenever the content changes; a call that changes nothing costs almost nothing, so painting on a timer is fine. macOS remembers each item's position by its id.

A left-click runs onClick. With a menu as well, right-click or Control-click opens the menu. With a menu and no onClick, any click opens the menu. Clicks act on mouse-down, like the system items.

The tooltip is the title, followed by the subtitle when there is one.

OptionTypeWhat it does
titlestringThe text. When you leave it out, the item shows its id, so pass "" for an icon-only item.
subtitlestringA second line under the title. Both lines then draw at a smaller size to fit the bar.
secondarybooleanDraws the subtitle smaller and dimmer than the title. Off by default, so both lines share one size and color.
colorstringText color: red, orange, yellow, green, blue, purple, pink, gray, white, black, or #RGB / #RRGGBB. In an icon-only item it tints the SF Symbol instead.
subtitleColorstringSubtitle color, in the same forms. Defaults to color.
bold, italicbooleanFont style. With secondary, the subtitle is never bold.
minWidthnumberMinimum width in points. Use it for a changing number, so proportional digits do not push the neighboring items around.
sfSymbolstringAn SF Symbol name, such as "timer", drawn before the text. icon is an alias.
imagestringPath to an image file, scaled to 18 points high. It replaces sfSymbol. A file name that ends in Template (for example meterTemplate.png) is treated as a mask that the bar tints.
svgstringSVG markup to draw as the icon. It replaces image and sfSymbol. Its own colors are kept unless template is true.
templatebooleanTreats svg as a mask that the menu bar tints to suit its background. Off by default.
sparklineobject{ values, width?, height?, color? }: a line graph of values, scaled between their minimum and maximum. Size defaults to 36 x 18 points. color is #RRGGBB; without it the line is a mask the bar tints. It replaces svg, image and sfSymbol.
onClick() => voidRuns on a left-click.
onHover(hovering: boolean) => voidRuns with true when the pointer enters the item and false when it leaves.
menuMenuBarMenuItem[]The item's menu. See Menus.
requiredbooleanDefault true. When the user Command-drags a required item out of the menu bar, Settings warns and offers a Restore button. Pass false for an item that is fine to hide.

Pick colors for a non-template svg with isDark(), not macotron.system.darkMode(). The menu bar follows the wallpaper, so it can draw light text while the system is in Light Mode. Repaint on the menubar:appearance event.

isVisible(id: string): boolean

Whether a status item is on screen. It is false when the user dragged the item out of the menu bar, when the bar has no room to draw it (usually because of the notch), and for an unknown id. Settings already warns about both cases, so few plugins need to ask.

remove(id: string): void

Removes the status item with this id, and the Macotron menu row with this id if there is one.

Status items stay on screen while plugins reload. After the reload, Macotron removes every item that no plugin painted again.

A menu is an array. Each element is a MenuBarMenuItem object, the string "-" for a separator, or any other string for a plain text row.

FieldTypeWhat it does
titlestringThe row's text.
iconstringAn SF Symbol name drawn before the title. A one- or two-character string, such as an emoji, goes in front of the title text instead.
onClick() => voidRuns when the row is chosen. The menu closes. A row without onClick still draws in full color, so you can use it as a label.
dimmedbooleanDraws the title in the secondary label color but keeps the row clickable. Use it for rows such as past events.
menuMenuBarMenuItem[]A submenu. A row with a submenu ignores its own onClick.
htmlstringShows a web page as the row instead of a title. See below.
width, heightnumberSize of an html row in points, 260 x 160 by default. Width is limited to 60–800 and height to 24–600. For a buttons row, width is the starting width.
buttonsMenuBarMenuItem[]Draws these items as buttons across one row. See below.

HTML rows

An html row is a small web view. Your markup goes into a page with a transparent background, the 13px system font, and light and dark color schemes. The page keeps running while the menu is open, so CSS animations and images work. When the menu is wider than the row, the page is centered.

The row reloads only when the markup changes, so repainting the menu every second does not restart the page.

Menu tracking takes mouse events before the page sees them. Treat an HTML row as a display. Put clickable things in ordinary rows or a buttons row.

Button rows

A row with buttons draws each item's title as a button, spread evenly across the menu width. Clicking a button runs its onClick and leaves the menu open, so you can page or step through content without closing the menu. Repaint the menu in the handler to show the new state. Button icons are not drawn.

// From the mini-calendar plugin: page through months without closing the menu.
menu: [
  { title: monthName },
  { html: grid(cells), width: 220, height: rows * 24 + 6 },
  { buttons: [
    { title: "‹", onClick: () => move(-1) },
    { title: "Today", onClick: () => move(-offset) },
    { title: "›", onClick: () => move(1) },
  ] },
  "-",
  { title: "Open Calendar…", onClick: () => macotron.app.launch("com.apple.iCal") },
]

The Macotron menu

These calls add rows to the menu under the Macotron icon, above the built-in rows.

add(id: string, opts: { title, icon?, section?, onClick?, menu? }): void

Adds a row, or replaces the row with that id. icon works as in a menu item. onClick runs when the row is chosen. With a menu, the row opens that submenu instead.

Rows without a section come first. Rows with a section are grouped under a header with that name, and the sections are sorted by name.

macotron.menubar.add("system-metrics-menu", {
  title: "CPU " + cpu + "%",
  icon: "cpu",
  section: "System",
});

update(id: string, opts: { title?, icon? }): void

Changes the title or icon of an existing row and keeps everything else. Does nothing for an unknown id.

remove(id: string): void

Removes the row. It also removes a status item with the same id.

The Macotron icon

setIcon(sfSymbolName: string): void

Replaces the Macotron glyph with an SF Symbol.

setIconColor(color?: string | null): void

Tints the Macotron icon. Accepts the same color names and hex forms as status. Pass null to go back to the normal menu bar color. The tint is cleared on reload unless a plugin sets it again.

setTitle(text: string): void

Shows text next to the Macotron icon. Pass "" to clear it.

isDark(): boolean

Whether the menu bar draws light text right now. It follows the wallpaper as well as the system theme.

Events

Fires when the menu bar switches between light and dark text. There is no payload; call isDark().

macotron.on("menubar:appearance", paint);

See also