AphrodyBun GitHub

Runtime docs · Runtime · Interop & Tooling

COSMIC

Use the COSMIC desktop from JavaScript with bun:cosmic

bun:cosmic gives you the parts of the COSMIC desktop (Pop!_OS, Alpine and other Linux distributions) that an app needs: text shaping with cosmic-text, the freedesktop application index, cosmic-config settings, libcosmic windows and desktop notifications. The module loads on first import, so it costs nothing at startup.

import { apps, config, isSupported, text } from "bun:cosmic";

if (isSupported) {
  const { width, height } = text.layout("Hello", { fontSize: 24 });
  console.log(width, height, apps.list().length);
}

const comp = config.open("com.system76.CosmicComp", 1);
console.log(comp.get("autotile"));

config and ron work on every platform. On other platforms isSupported is false, and the text, application, window and notification calls throw an error with code ERR_BUN_COSMIC_UNSUPPORTED.


Text

text.layout shapes and wraps text, with font fallback, bidirectional text and ligatures. It returns the size of the block and the position of every glyph.

const layout = text.layout("Bun on COSMIC", { fontSize: 18, width: 120, family: "monospace" });

for (const line of layout.lines) {
  console.log(line.baseline, line.glyphs.length);
}

text.render takes the same options plus color, background, imageWidth and imageHeight. It returns an RGBA image with 4 bytes per pixel.

const { width, height, data } = text.render("42", { fontSize: 32, color: "#ffffff", background: "#000000" });

The font database holds the system fonts. text.loadFont adds a TrueType or OpenType font from a path or a buffer and returns the families it added. text.fonts lists every face.


Applications

apps.list reads the .desktop files under applications in $XDG_DATA_HOME and $XDG_DATA_DIRS. Names and comments come in the language of LC_ALL, LC_MESSAGES or LANG.

const editors = apps.list().filter(app => app.mimeTypes?.includes("text/plain") && !app.noDisplay);

Pass dirs or locales to read other directories or languages.

apps.launch starts an application from its entry or its id. It splits the Exec value into arguments as the Desktop Entry specification describes, expands the field codes and calls Bun.spawn without a shell.

const proc = apps.launch("org.gnome.TextEditor", { files: ["/tmp/notes.txt"] });
proc.unref();

files fill %f and %F, and urls fill %u and %U. A file or URL code with nothing to fill removes its argument. action runs one of the entry's desktop actions. For an entry with Terminal=true, terminal is the command placed in front, such as ["cosmic-term", "-e"].

apps.expandExec returns the arguments without starting anything. An unknown field code or an unterminated quote throws an error with code ERR_BUN_COSMIC_INVALID_EXEC.

apps.expandExec('"/opt/my app/bin" --open %F %i', { files: ["a.txt", "b.txt"], icon: "my-app" });
// ["/opt/my app/bin", "--open", "a.txt", "b.txt", "--icon", "my-app"]

Settings

COSMIC stores each setting as a RON file in $XDG_CONFIG_HOME/cosmic/<name>/v<version>/<key>. config.open gives you the same view as cosmic-config.

const theme = config.open("com.system76.CosmicTheme.Mode", 1);

theme.get("is_dark"); // the user value, else the system default
theme.set("is_dark", true);

const watcher = theme.watch(key => console.log("changed", key));

get falls back to the system default from $XDG_DATA_DIRS. getLocal and getDefault read one side only. set writes the file atomically, and running COSMIC apps pick up the change. state: true opens $XDG_STATE_HOME instead.

ron.parse and ron.stringify convert between RON and JavaScript. None is null and maps are Maps. ron.some, ron.tuple and ron.variant build Some(…), tuples and enum variants.

import { ron } from "bun:cosmic";

ron.stringify(ron.variant("Rgba", [1, 0.5, 0, 1])); // "Rgba(1, 0.5, 0, 1)"

Windows and notifications

Windows and notifications run in the bun-cosmic helper process, so the GUI never blocks JavaScript. Bun looks for the helper in BUN_COSMIC_HELPER, then in PATH, then next to the bun executable. On Alpine, install the bun-cosmic package.

openWindow opens a libcosmic dialog. closed resolves with the button the user chose, or null when the window was closed another way.

import { openWindow } from "bun:cosmic";

const win = openWindow({ title: "Update", body: "Restart now?", buttons: ["Restart", "Later"] });
const choice = await win.closed;
console.log(choice?.label);

notify sends a notification over D-Bus. With actions, the promise waits for the user to pick one or close the notification.

import { notify } from "bun:cosmic";

const { action } = await notify({
  summary: "Build finished",
  body: "3 warnings",
  actions: { open: "Open log" },
});

When the helper is missing, both calls throw an error with code ERR_BUN_COSMIC_HELPER_NOT_FOUND.