Skip to content

Packages ​

The core gnim package is renderer agnostic: it implements JSX, reactivity and the GObject utilities, but it does not know how to attach objects to each other. That job is done by a renderer package, which provides the render function. There is a renderer for Gtk4, Gtk3 and GNOME Shell, and an additional utility package for common IO operations.

PackagePurpose
@gnim-js/gtk4Gtk4 renderer
@gnim-js/gtk3Gtk3 renderer
@gnim-js/gnome-shellClutter/St renderer for GNOME Shell extensions
@gnim-js/ioTimers, file system, subprocess and web APIs

@gnim-js/gtk4 ​

render ​

Mounts a JSX tree and returns a dispose function that tears down its scope.

ts
function render(element: () => GnimNode, root?: GObject.Object): () => void

Example:

tsx
import Gtk from "gi://Gtk?version=4.0"
import { render } from "@gnim-js/gtk4"

const dispose = render(() => (
  <Gtk.Window visible>
    <Gtk.Label label="Hello" />
  </Gtk.Window>
))

The renderer also defines the slot, css and class JSX attributes.

slot ​

Under the hood, to build the widget tree the renderer uses the Gtk.Buildable interface. The slot attribute specifies the type the child is meant to be.

tsx
<Gtk.CenterBox>
  <Gtk.Box slot="start" />
  <Gtk.Box slot="center" />
  <Gtk.Box slot="end" />
</Gtk.CenterBox>

css ​

An additional css attribute is available on class components that inherit from Gtk.Widget. It is mostly meant to be used as a debugging tool, e.g. with css="border: 1px solid red;".

tsx
<Gtk.Button css="border: 1px solid red;" />

class ​

The class attribute is available on class components that inherit from Gtk.Widget. It is an alternative to the Gtk4 cssClasses property which can take class names in various forms.

tsx
const name: string | Accessor<string> | string[] | Accessor<string[]>

return (
  <Gtk.Button
    class="class1 class2"
    class={name}
    class={["class1 class2", name]}
  />
)

WARNING

Passing cssClasses or class assigns the widget's css-classes property, which means any class name that a widget might have by default will be overwritten. To preserve them, they have to be explicitly specified. Or you might want to imperatively append/remove class names instead.

tsx
return <Gtk.Window class={["background", "MyWindow"]} />

return <Gtk.Window ref={(self) => self.add_css_class("MyWindow")} />

getSlot ​

Reads back the slot attribute that was set on an object in JSX.

ts
function getSlot(object: GObject.Object): string | null

createListItemFactory ​

Creates a Gtk.ListItemFactory for view widgets such as Gtk.ListView and Gtk.GridView, letting you declare list items in JSX.

ts
type Item<T extends GObject.Object> = Gtk.ListItem & {
  item: T | null
}

function createListItemFactory<T extends GObject.Object>(
  factory: (item: Accessor<Item<T> | null>) => GnimNode,
): Gtk.ListItemFactory

The factory function receives an Accessor of the Gtk.ListItem, which is null while the item is not bound to a value.

Example:

tsx
import Gtk from "gi://Gtk?version=4.0"
import { createListItemFactory } from "@gnim-js/gtk4/listItemFactory"

function StringList() {
  const model = Gtk.StringList.new(["one", "two", "three"])

  const factory = createListItemFactory<Gtk.StringObject>((listItem) => (
    <Gtk.Label label={listItem.as((li) => li?.item?.string ?? "")} />
  ))

  return <Gtk.ListView model={Gtk.NoSelection.new(model)} factory={factory} />
}

NOTE

createListItemFactory captures the current reactive scope, so it has to be called from inside one, for example in the body of a function component.

style ​

WARNING

style and keyframes are experimental.

CSS-in-JS: takes an object of Gtk CSS properties and returns a generated class name. Nested selectors are supported with the & prefix and media queries with @media keys.

ts
function style(props: Style): string
function style(producer: () => Style): Accessor<string>

Example:

tsx
import { style } from "@gnim-js/gtk4"

const className = style({
  "color": "red",
  "&:hover": {
    color: "blue",
  },
})

return <Gtk.Label class={className} label="styled" />

Identical style objects are deduplicated into a single stylesheet, and stylesheets are removed from the display when every scope using them is disposed. The producer form returns an Accessor that recomputes the stylesheet when its dependencies change.

keyframes ​

WARNING

style and keyframes are experimental.

Defines a CSS @keyframes animation and returns its generated name. Keys are either from/to or percentages.

ts
type Keyframes =
  | { from: CssProperties; to: CssProperties }
  | { [percentage: number]: CssProperties }

function keyframes(keyframes: Keyframes): string
function keyframes(keyframes: () => Keyframes): Accessor<string>

Example:

tsx
import { keyframes, style } from "@gnim-js/gtk4"

const spin = keyframes({
  from: { transform: "rotate(0turn)" },
  to: { transform: "rotate(1turn)" },
})

const className = style({
  animation: `${spin} 1s linear infinite`,
})

@gnim-js/gtk3 ​

Exports render with the same signature as the Gtk4 package and defines the same slot, css and class JSX attributes.

Differences from Gtk4:

  • Every constructed object gets visible: true by default, since in Gtk3 widgets are hidden unless shown explicitly.
  • Gtk3 has no cssClasses property; the class JSX attribute manipulates style classes through the widget's Gtk.StyleContext.

@gnim-js/gnome-shell ​

WARNING

GNOME Shell support is experimental.

Renderer for GNOME Shell extensions. Instead of Gtk widgets it renders Clutter/St actors: text nodes become St.Label, Clutter.Actor children are added with add_child, and Clutter.Action, Clutter.Constraint and Clutter.LayoutManager children are attached through their respective APIs. The Gtk-specific css, class and slot JSX attributes are not available.

tsx
import { render } from "@gnim-js/gnome-shell"
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js"
import PanelButton from "./PanelButton"

export default class extends Extension {
  enable(): void {
    this.disable = render(() => <PanelButton />)
  }
}

The package ships .gir files for the GNOME Shell libraries (Clutter, Cogl, Meta, Mtk, St, Shell, Gvc) under gir-1.0/gnome<version>, which can be fed to the CLI to generate types:

sh
gnim types -d node_modules/@gnim-js/gnome-shell/gir-1.0/gnome50 --alias

TIP

The quickest way to get a working extension setup is the gnome-shell template:

sh
npm create gnim@beta -- --template gnome-shell

@gnim-js/io ​

Utilities for timers, files, subprocesses and web APIs. The package has four subpath exports and no root export.

@gnim-js/io/timer ​

IMPORTANT

Timer exists mostly for legacy reasons, newly written code might prefer setTimeout and setInterval.

Timer ​

A GObject wrapping GLib timeout sources. It emits a now signal on each tick and a cancelled signal when cancelled with cancel().

ts
const timer = Timer.interval(1000, () => console.log("every second"))
const once = Timer.timeout(1000, () => console.log("after a second"))
const idle = Timer.idle(() => console.log("when the loop is idle"))

timer.cancel()

interval, timeout and idle are also exported as standalone functions.

  • interval(interval, callback?) runs the callback immediately, then repeatedly at a fixed interval in milliseconds.
  • timeout(interval, callback?) runs the callback once after a delay.
  • idle(callback?) runs the callback once when there is nothing else to do on the event loop.

createPoll ​

Creates an Accessor that polls at a fixed interval, either by running a function or by executing a command.

ts
function createPoll(
  init: string,
  interval: number,
  exec: string | string[],
): Accessor<string>

function createPoll<T>(
  init: T,
  interval: number,
  exec: string | string[],
  transform: (stdout: string, prev: T) => T | Promise<T>,
): Accessor<T>

function createPoll<T>(
  init: T,
  interval: number,
  fn: (prev: T) => T | Promise<T>,
): Accessor<T>

Example:

ts
import { createPoll } from "@gnim-js/io/timer"

const date = createPoll("", 1000, "date")

effect(() => {
  console.log(date())
})

NOTE

Polling is lazy: it starts when the first subscriber appears and stops when the number of subscribers drops to zero. Until then the accessor holds the init placeholder value.

@gnim-js/io/fs ​

readFile, readFileAsync ​

Read the contents of a file as a UTF-8 string.

ts
function readFile(file: string | Gio.File): string
function readFileAsync(file: string | Gio.File): Promise<string>

writeFile, writeFileAsync ​

Replace the contents of a file, creating parent directories when they do not exist.

ts
function writeFile(file: string | Gio.File, content: string): Gio.File
function writeFileAsync(
  file: string | Gio.File,
  content: string,
): Promise<Gio.File>

monitorFile ​

Monitors a file, or a directory and all of its subdirectories recursively. Newly created directories are monitored automatically, and the monitor cancels itself when the watched path is deleted.

ts
function monitorFile(
  path: string,
  callback: (filePath: string, event: Gio.FileMonitorEvent) => void,
): Gio.FileMonitor

Example:

ts
import { monitorFile } from "@gnim-js/io/fs"

const monitor = monitorFile("/path/to/dir", (path, event) => {
  console.log(path, event)
})

monitor.cancel()

@gnim-js/io/process ​

Process ​

A GObject abstraction over Gio.Subprocess with piped stdin, stdout and stderr. It emits stdout(line) and stderr(line) signals for each line of output and an exit(code, signaled) signal when the process ends.

ts
const proc = new Process({ argv: ["command", "arg"] })

proc.connect("stdout", (_, line) => console.log(line))
proc.connect("exit", (_, code) => console.log("exited with", code))

await proc.write("input\n")
proc.signal(15)
proc.kill()

subprocess ​

Starts a long-running child process and wires up the output callbacks, which default to print and printerr.

ts
function subprocess(args: {
  cmd: string | string[]
  out?: (stdout: string) => void
  err?: (stderr: string) => void
}): Process

function subprocess(
  cmd: string | string[],
  onOut?: (stdout: string) => void,
  onErr?: (stderr: string) => void,
): Process

exec, execAsync ​

Execute a command and return its trimmed stdout. When the command exits unsuccessfully, its stderr is thrown as an Error. String commands are parsed with GLib.shell_parse_argv; array commands are executed as-is.

ts
function exec(cmd: string | string[]): string
function execAsync(cmd: string | string[]): Promise<string>

Example:

ts
import { execAsync } from "@gnim-js/io/process"

try {
  const stdout = await execAsync("uname -a")
  console.log(stdout)
} catch (error) {
  console.error(error)
}

IMPORTANT

Commands are not run in a shell: expansions like $VAR, ~ or pipes will not work. To use shell features spawn one explicitly:

ts
execAsync(["bash", "-c", "command | other"])

createSubprocess ​

Creates an Accessor fed by the stdout of a long-running subprocess. Like createPoll, it is lazy: the process is spawned when the first subscriber appears and killed when the number of subscribers drops to zero.

ts
function createSubprocess(
  init: string,
  exec: string | string[],
): Accessor<string>

function createSubprocess<T>(
  init: T,
  exec: string | string[],
  transform: (stdout: string, prev: T) => T | Promise<T>,
): Accessor<T>

Example:

ts
import { createSubprocess } from "@gnim-js/io/process"

const line = createSubprocess("", ["journalctl", "-f"])

effect(() => {
  console.log(line())
})

@gnim-js/io/web ​

GJS does not implement some common web APIs that you would expect from a JavaScript runtime. See this gjs issue for context.

fetch ​

A basic implementation of the fetch API using Soup, along with Headers, Response, URL and URLSearchParams.

Example:

ts
import { fetch, URL } from "@gnim-js/io/web"

const url = new URL("https://some-site.com/api")
url.searchParams.set("hello", "world")

const res = await fetch(url, {
  method: "POST",
  body: JSON.stringify({ hello: "world" }),
  headers: {
    "Content-Type": "application/json",
  },
})

const json = await res.json()