summaryrefslogtreecommitdiff
path: root/docs/guide
diff options
context:
space:
mode:
Diffstat (limited to 'docs/guide')
-rw-r--r--docs/guide/ags/cli-app.md131
-rw-r--r--docs/guide/ags/faq.md290
-rw-r--r--docs/guide/ags/first-widgets.md400
-rw-r--r--docs/guide/ags/installation.md65
-rw-r--r--docs/guide/ags/theming.md169
-rw-r--r--docs/guide/ags/utilities.md174
-rw-r--r--docs/guide/ags/variable.md141
-rw-r--r--docs/guide/ags/widget.md227
-rw-r--r--docs/guide/getting-started/installation.md69
-rw-r--r--docs/guide/getting-started/introduction.md23
-rw-r--r--docs/guide/getting-started/nix.md154
-rw-r--r--docs/guide/getting-started/supported-languages.md65
-rw-r--r--docs/guide/libraries/apps.md113
-rw-r--r--docs/guide/libraries/auth.md118
-rw-r--r--docs/guide/libraries/battery.md97
-rw-r--r--docs/guide/libraries/bluetooth.md104
-rw-r--r--docs/guide/libraries/hyprland.md97
-rw-r--r--docs/guide/libraries/mpris.md100
-rw-r--r--docs/guide/libraries/network.md94
-rw-r--r--docs/guide/libraries/notifd.md106
-rw-r--r--docs/guide/libraries/powerprofiles.md97
-rw-r--r--docs/guide/libraries/references.md44
-rw-r--r--docs/guide/libraries/river.md97
-rw-r--r--docs/guide/libraries/tray.md97
-rw-r--r--docs/guide/libraries/wireplumber.md94
25 files changed, 3166 insertions, 0 deletions
diff --git a/docs/guide/ags/cli-app.md b/docs/guide/ags/cli-app.md
new file mode 100644
index 0000000..ceed56a
--- /dev/null
+++ b/docs/guide/ags/cli-app.md
@@ -0,0 +1,131 @@
+# CLI and App
+
+`App` is a singleton **instance** of [Astal.Application](https://aylur.github.io/libastal/class.Application.html).
+
+```ts
+import { App } from "astal"
+```
+
+## Entry point
+
+:::code-group
+
+```ts [app.ts]
+App.start({
+ main() {
+ // setup anything
+ // instantiate widgets
+ },
+})
+```
+
+:::
+
+:::warning
+You can not instantiate widgets outside of the main function.
+:::
+
+## Instance identifier
+
+You can run multiple instance by defining a unique instance name.
+
+```ts
+App.start({
+ instanceName: "my-instance", // defaults to "astal"
+ main() {},
+})
+```
+
+## Messaging from CLI
+
+If you want to interact with an instance from the cli, you can do so by sending a message.
+
+```ts
+App.start({
+ main() {},
+ requestHandler(request: string, res: (response: any) => void) {
+ if (request == "say hi") {
+ res("hi cli")
+ }
+ res("unknown command")
+ },
+})
+```
+
+:::code-group
+
+```sh [ags]
+ags -m "say hi"
+# hi cli
+```
+
+```sh [astal]
+astal say hi
+# hi cli
+```
+
+:::
+
+If you want to run arbitrary JavaScript from cli, you can use `App.eval`.
+It will evaluate the passed string as the body of an `async` function.
+
+```ts
+App.start({
+ main() {},
+ requestHandler(js: string, res) {
+ App.eval(js).then(res).catch(res)
+ },
+})
+```
+
+If the string does not contain a semicolon, a single expression is assumed and returned implicity.
+
+```sh
+ags -m "'hello'"
+# hello
+```
+
+If the string contains a semicolon, you have to return explicitly
+
+```sh
+ags -m "'hello';"
+# undefined
+
+ags -m "return 'hello';"
+# hello
+```
+
+## App without AGS
+
+As mentioned before AGS is only a scaffolding tool. You can setup
+a dev environment and a bundler yourself. In which case you won't be using
+the ags cli to run the bundled scripts. The produced script can run as the main instance
+and a "client" instance.
+
+The first time you run your bundled script the `main` function gets executed.
+While that instance is running any subsequent execution of the script will call
+the `client` function.
+
+:::code-group
+
+```ts [main.ts]
+App.start({
+ // main instance
+ main(...args: Array<string>) {
+ print(...args)
+ },
+
+ // every subsequent calls
+ client(message: (msg: string) => string, ...args: Array<string>) {
+ const res = message("you can message the main instance")
+ console.log(res)
+ },
+
+ // this runs in the main instance
+ requestHandler(request: string, res: (response: any) => void) {
+ res("response from main")
+ },
+})
+```
+
+:::
diff --git a/docs/guide/ags/faq.md b/docs/guide/ags/faq.md
new file mode 100644
index 0000000..6edc250
--- /dev/null
+++ b/docs/guide/ags/faq.md
@@ -0,0 +1,290 @@
+# Frequently asked question, common issues, tips and tricks
+
+## Monitor id does not match compositor
+
+The monitor property that windows expect is mapped by Gdk, which is not always
+the same as the compositor. Instead use the `gdkmonitor` property which expects
+a `Gdk.Monitor` object which you can get from compositor libraries.
+
+Example with Hyprland
+
+```tsx
+import Hyprland from "gi://AstalHyprland"
+
+function Bar(gdkmonitor) {
+ return <window gdkmonitor={gdkmonitor} />
+}
+
+function main() {
+ for (const m of Hyprland.get_default().get_monitors()) {
+ Bar(m.gdk_monitor)
+ }
+}
+
+App.start({ main })
+```
+
+## Environment variables
+
+JavaScript is **not** an bash.
+
+```ts
+const HOME = exec("echo $HOME") // does not work
+```
+
+`exec` and `execAsync` runs the passed program as is, its **not** run in a
+shell environment, so the above example just passes `$HOME` as a string literal
+to the `echo` program.
+
+:::danger Please don't do this
+You could pass it to bash, but that is a horrible approach.
+
+```ts
+const HOME = exec("bash -c 'echo $HOME'")
+```
+
+:::
+
+You can read environment variables with [GLib.getenv](https://gjs-docs.gnome.org/glib20~2.0/glib.getenv).
+
+```ts
+import GLib from "gi://GLib"
+
+const HOME = GLib.getenv("HOME")
+```
+
+## Custom svg symbolic icons
+
+Put the svgs in a directory, named `<icon-name>-symbolic.svg`
+and use `App.add_icons` or `icons` parameter in `App.start`
+
+:::code-group
+
+```ts [app.ts]
+App.start({
+ icons: `${SRC}/icons`,
+ main() {
+ Widget.Icon({
+ icon: "custom-symbolic", // custom-symbolic.svg
+ css: "color: green;", // can be colored, like other named icons
+ })
+ },
+})
+```
+
+:::
+
+:::info
+If there is a name clash with an icon from your current icon pack
+the icon pack will take precedence
+:::
+
+## Logging
+
+The `console` API in gjs uses glib logging functions.
+If you just want to print some text as is to stdout
+use the globally available `print` function or `printerr` for stderr.
+
+```ts
+print("print this line to stdout")
+printerr("print this line to stderr")
+```
+
+## Binding custom structures
+
+The `bind` function can take two types of objects.
+
+```ts
+interface Subscribable<T = unknown> {
+ subscribe(callback: (value: T) => void): () => void
+ get(): T
+}
+
+interface Connectable {
+ connect(signal: string, callback: (...args: any[]) => unknown): number
+ disconnect(id: number): void
+}
+```
+
+`Connectable` is for mostly gobjects, while `Subscribable` is for `Variables`
+and custom objects.
+
+For example you can compose `Variables` in using a class.
+
+```ts
+type MyVariableValue = {
+ number: number
+ string: string
+}
+
+class MyVariable {
+ number = Variable(0)
+ string = Variable("")
+
+ get(): MyVariableValue {
+ return {
+ number: this.number.get(),
+ string: this.string.get(),
+ }
+ }
+
+ subscribe(callback: (v: MyVariableValue) => void) {
+ const unsub1 = this.number.subscribe((value) => {
+ callback({ string: value, number: this.number.get() })
+ })
+
+ const unsub2 = this.string.subscribe((value) => {
+ callback({ number: value, string: this.string.get() })
+ })
+
+ return () => {
+ unsub1()
+ unsub2()
+ }
+ }
+}
+```
+
+Then it can be used with `bind`.
+
+```tsx
+function MyWidget() {
+ const myvar = new MyVariable()
+ const label = bind(myvar).as(({ string, number }) => {
+ return `${string} ${number}`
+ })
+
+ return <label label={label} />
+}
+```
+
+## Populate the global scope with frequently accessed variables
+
+It might be annoying to always import Gtk only for `Gtk.Align` enums.
+
+:::code-group
+
+```ts [globals.ts]
+import Gtk from "gi://Gtk"
+
+declare global {
+ const START: number
+ const CENTER: number
+ const END: number
+ const FILL: number
+}
+
+Object.assign(globalThis, {
+ START: Gtk.Align.START,
+ CENTER: Gtk.Align.CENTER,
+ END: Gtk.Align.END,
+ FILL: Gtk.Align.FILL,
+})
+```
+
+:::
+
+:::code-group
+
+```tsx [Bar.tsx]
+export default function Bar() {
+ return <window>
+ <box halign={START} />
+ </window>
+}
+```
+
+:::
+
+:::code-group
+
+```ts [app.ts]
+import "./globals"
+import Bar from "./Bar"
+
+App.start({
+ main: Bar
+})
+```
+
+:::
+
+:::info
+It is considered bad practice to populate the global scope, but its your code, not a public library.
+:::
+
+## Auto create Window for each Monitor
+
+To have Window widgets appear on a monitor when its plugged in, listen to `App.monitor_added`.
+
+:::code-group
+
+```tsx [Bar.tsx]
+export default function Bar(gdkmonitor: Gdk.Monitor) {
+ return <window gdkmonitor={gdkmonitor} />
+}
+```
+
+:::
+
+:::code-group
+
+```ts [app.ts]
+import { Gdk, Gtk } from "astal"
+import Bar from "./Bar"
+
+function main() {
+ const bars = new Map<Gdk.Monitor, Gtk.Widget>()
+
+ // initialize
+ for (const gdkmonitor of App.get_monitors()) {
+ bars.set(gdkmonitor, Bar(gdkmonitor))
+ }
+
+ App.connect("monitor-added", (_, gdkmonitor) => {
+ bars.set(gdkmonitor, Bar(gdkmonitor))
+ })
+
+ App.connect("monitor-removed", (_, gdkmonitor) => {
+ bars.get(gdkmonitor)?.destroy()
+ bars.delete(gdkmonitor)
+ })
+}
+
+App.start({ main })
+```
+
+:::
+
+## Error: Can't convert non-null pointer to JS value
+
+These happen when accessing list type properties. Gjs fails to correctly bind
+`List` and other array like types of Vala as a property.
+
+```ts
+import Notifd from "gi://AstalNotifd"
+const notifd = Notifd.get_default()
+
+notifd.notifications // ❌ // [!code error]
+
+notifd.get_notifications() // ✅
+```
+
+## How to create regular floating windows
+
+Use `Gtk.Window` with [Widget.astalify](/guide/ags/widget#how-to-use-non-builtin-gtk-widgets).
+
+By default `Gtk.Window` is destroyed on close. To prevent this add a handler for `delete-event`.
+
+```tsx {4-7}
+const RegularWindow = Widget.astalify(Gtk.Window)
+
+return <RegularWindow
+ onDeleteEvent={(self) => {
+ self.hide()
+ return true
+ }}
+>
+ {child}
+</RegularWindow>
+```
diff --git a/docs/guide/ags/first-widgets.md b/docs/guide/ags/first-widgets.md
new file mode 100644
index 0000000..2c64732
--- /dev/null
+++ b/docs/guide/ags/first-widgets.md
@@ -0,0 +1,400 @@
+# First Widgets
+
+AGS is the predecessor of Astal, which was written purely in TypeScript and so only supported
+JavaScript/TypeScript. Now it serves as a scaffolding tool for Astal projects in TypeScript.
+While what made AGS what it is, is now part of the Astal project, for simplicity we will
+refer to the Astal TypeScript lib as AGS.
+
+:::tip
+If you are not familiar with the JavaScript syntax [MDN](https://developer.mozilla.org/en-US/)
+and [javascript.info](https://javascript.info/) have great references.
+:::
+
+## Getting Started
+
+Start by initializing a project
+
+```sh
+ags --init
+```
+
+then run `ags` in the terminal
+
+```sh
+ags
+```
+
+Done! You have now a custom written bar using Gtk.
+
+:::tip
+AGS will transpile every `.ts`, `.jsx` and `.tsx` files into regular javascript then
+it will bundle everything into a single javascript file which then GJS can execute.
+The bundler used is [esbuild](https://esbuild.github.io/).
+:::
+
+## Root of every shell component: Window
+
+Astal apps are composed of widgets. A widget is a piece of UI that has its own logic and style.
+A widget can be as small as a button or an entire bar.
+The top level widget is always a [Window](https://aylur.github.io/libastal/class.Window.html) which will hold all widgets.
+
+::: code-group
+
+```tsx [widget/Bar.tsx]
+function Bar(monitor = 0) {
+ return <window className="Bar" monitor={monitor}>
+ <box>Content of the widget</box>
+ </window>
+}
+```
+
+:::
+
+::: code-group
+
+```ts [app.ts]
+import Bar from "./widget/Bar"
+
+App.start({
+ main() {
+ Bar(0)
+ Bar(1) // instantiate for each monitor
+ },
+})
+```
+
+:::
+
+## Creating and nesting widgets
+
+Widgets are JavaScript functions which return Gtk widgets,
+either by using JSX or using a widget constructor.
+
+:::code-group
+
+```tsx [MyButton.tsx]
+function MyButton(): JSX.Element {
+ return <button onClicked="echo hello">
+ Clicke Me!
+ </button>
+}
+```
+
+```ts [MyButton.ts]
+import { Widget } from "astal"
+
+function MyButton(): Widget.Button {
+ return Widget.Button({
+ onClicked: "echo hello",
+ label: "Click Me!",
+ })
+}
+```
+
+:::
+
+:::info
+The only difference between the two is the return type.
+Using markup the return type is always `Gtk.Widget` (globally available as `JSX.Element`),
+while using constructors the return type is the type of the widget.
+It is rare to need the actual return type, so most if not all of the time, you can use markup.
+:::
+
+Now that you have declared `MyButton`, you can nest it into another component.
+
+```tsx
+function MyBar() {
+ return <window>
+ <box>
+ Click The button
+ <MyButton />
+ </box>
+ </window>
+}
+```
+
+Notice that widgets you defined start with a capital letter `<MyButton />`.
+Lowercase tags are builtin widgets, while capital letter is for custom widgets.
+
+## Displaying Data
+
+JSX lets you put markup into JavaScript.
+Curly braces let you “escape back” into JavaScript so that you can embed some variable
+from your code and display it.
+
+```tsx
+function MyWidget() {
+ const label = "hello"
+
+ return <button>{label}</button>
+}
+```
+
+You can also pass JavaScript to markup attributes
+
+```tsx
+function MyWidget() {
+ const label = "hello"
+
+ return <button label={label} />
+}
+```
+
+## Conditional Rendering
+
+You can use the same techniques as you use when writing regular JavaScript code.
+For example, you can use an if statement to conditionally include JSX:
+
+```tsx
+function MyWidget() {
+ let content
+
+ if (condition) {
+ content = <True />
+ } else {
+ content = <False />
+ }
+
+ return <box>{content}</box>
+}
+```
+
+You can also inline a [conditional `?`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_operator) (ternary) expression.
+
+```tsx
+function MyWidget() {
+ return <box>{condition ? <True /> : <False />}</box>
+}
+```
+
+When you don’t need the `else` branch, you can also use a shorter [logical && syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND#short-circuit_evaluation):
+
+```tsx
+function MyWidget() {
+ return <box>{condition && <True />}</box>
+}
+```
+
+:::info
+As you can guess from the above snippet, [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) values are not rendered.
+:::
+
+## Rendering lists
+
+You can use [`for` loops](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for) or [array `map()` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
+
+```tsx
+function MyWidget() {
+ const labels = [
+ "label1"
+ "label2"
+ "label3"
+ ]
+
+ return <box>
+ {labels.map(label => (
+ <label label={label} />
+ ))}
+ </box>
+}
+```
+
+## Widget signal handlers
+
+You can respond to events by declaring event handler functions inside your widget:
+
+```tsx
+function MyButton() {
+ function onClicked(self: Widget.Button, ...args) {
+ console.log(self, "was clicked")
+ }
+
+ return <button onClicked={onClicked} />
+}
+```
+
+The handler can also be a string, which will get executed in a subprocess asynchronously.
+
+```tsx
+function MyButton() {
+ return <button onClicked="echo hello" />
+}
+```
+
+:::info
+Attributes prefixed with `on` will connect to a `signal` of the widget.
+Their types are not generated, but written by hand, which means not all of them are typed.
+Refer to the Gtk and Astal docs to have a full list of them.
+:::
+
+## How properties are passed
+
+Using JSX, a custom widget will always have a single object as its parameter.
+
+```ts
+type Props = {
+ myprop: string
+ child?: JSX.Element // when only one child is passed
+ children?: Array<JSX.Element> // when multiple children are passed
+}
+
+function MyWidget({ myprop, child, children }: Props) {
+ //
+}
+```
+
+```tsx
+// child prop of MyWidget is the box
+return <MyWidget myprop="hello">
+ <box />
+</MyWidget>
+```
+
+```tsx
+// children prop of MyWidget is [box, box, box]
+return <MyWidget myprop="hello">
+ <box />
+ <box />
+ <box />
+</MyWidget>
+```
+
+## State management
+
+The state of widgets are handled with Bindings. A `Binding` lets you
+connect the state of one [GObject](https://docs.gtk.org/gobject/class.Object.html) to another, in our case it is used to
+rerender part of a widget based on the state of a `GObject`.
+A `GObject` can be a [Variable](./variable) or it can be from a [Library](../libraries/references).
+
+We use the `bind` function to create a `Binding` object from a `Variable` or
+a regular GObject and one of its properties.
+
+Here is an example of a Counter widget that uses a `Variable` as its state:
+
+```tsx
+import { Variable, bind } from "astal"
+
+function Counter() {
+ const count = Variable(0)
+
+ function increment() {
+ count.set(count.get() + 1)
+ }
+
+ return <box>
+ <label label={bind(count).as(num => num.toString())} />
+ <button onClicked={increment}>
+ Click to increment
+ <button>
+ </box>
+}
+```
+
+:::info
+Bindings have an `.as()` method which lets you transform the assigned value.
+In the case of a Label, its label property expects a string, so it needs to be
+turned to a string first.
+:::
+
+:::tip
+`Variables` have a shorthand for `bind(variable).as(transform)`
+
+```tsx
+const v = Variable(0)
+const transform = (v) => v.toString()
+
+return <box>
+ {/* these two are equivalent */}
+ <label label={bind(v).as(transform)} />
+ <label label={v(transform)} />
+</box>
+```
+
+:::
+
+Here is an example of a battery percent label that binds the `percentage`
+property of the Battery object from the [Battery Library](/guide/libraries/battery):
+
+```tsx
+import Battery from "gi://AstalBattery"
+import { bind } from "astal"
+
+function BatteryPercentage() {
+ const bat = Battery.get_default()
+
+ return <label label={bind(bat, "percentage").as((p) => p * 100 + " %")} />
+}
+```
+
+## Dynamic children
+
+You can also use a `Binding` for `child` and `children` properties.
+
+```tsx
+const child = Variable(<box />)
+
+return <box>{child}</box>
+```
+
+```tsx
+const num = Variable(3)
+const range = (n) => [...Array(n).keys()]
+
+return <box>
+ {num(n => range(n).map(i => (
+ <button>
+ {i.toString()}
+ <button/>
+ )))}
+<box>
+```
+
+:::warning
+Only bind children of the `box` or the `stack` widget. Gtk does not cleanup widgets by default,
+they have to be explicitly destroyed. The box widget is a special container that
+will implicitly call `.destroy()` on its removed child widgets.
+You can disable this behavior by setting the `noImplicityDestroy` property.
+:::
+
+:::info
+The above example destroys and recreates every widget in the list everytime
+the value of the `Variable` changes. There might be cases where you would
+want to handle child creation yourself, because you don't want to lose the
+inner state of widgets that does not need to be recreated.
+:::
+
+When there is at least one `Binding` passed as a child, the `children`
+parameter will always be a flattened `Binding<Array<JSX.Element>>`
+
+```tsx
+function MyContainer({ children }) {
+ // children is a Binding over an Array of widgets
+}
+
+return <MyContainer>
+ <box />
+ {num(n => range(n).map(i => (
+ <button>
+ {i.toString()}
+ <button/>
+ )))}
+ [
+ [
+ <button />
+ ]
+ <button />
+ ]
+</MyContainer>
+```
+
+:::info
+You can pass the followings as children:
+
+- widgets
+- deeply nested arrays of widgets
+- bindings of widgets,
+- bindings of deeply nested arrays of widgets
+
+[falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) values are not rendered and anything not from this list
+will be coerced into a string and rendered as a label
+:::
diff --git a/docs/guide/ags/installation.md b/docs/guide/ags/installation.md
new file mode 100644
index 0000000..0adcf68
--- /dev/null
+++ b/docs/guide/ags/installation.md
@@ -0,0 +1,65 @@
+# Installation
+
+## Nix
+
+maintainer: [@Aylur](https://github.com/Aylur)
+
+Read more about it on the [nix page](../getting-started/nix#ags)
+
+You can try without installing.
+
+<!--TODO: remove v2 after merge-->
+```sh
+nix run github:aylur/ags/v2 -- --help
+```
+
+## Bulding AGS from source
+
+1. [Install Astal](../getting-started/installation.md) if you have not already
+
+2. Install the following dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu go npm gjs
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install golang npm gjs
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install golang-go npm gjs
+```
+
+:::
+
+3. Clone the repo and Install
+
+<!--TODO: remove v2 after merge-->
+```sh
+git clone https://github.com/aylur/ags.git
+cd ags/src
+git checkout v2 # https://github.com/Aylur/ags/pull/504
+go install
+```
+
+:::info
+If you have installed Astal **not** in `/usr` make sure to set its path.
+
+```sh
+go install -ldflags "-X main.astalGjs=$(pkg-config --variable prefix astal-0.1)/share/astal/gjs"
+```
+
+:::
+
+:::tip
+`go install` installs the `ags` binary to `$GOPATH/bin` so make sure its in your `$PATH`.
+You can move it to another directory if you like. For example
+
+```sh
+mv $GOPATH/bin/ags ~/.local/bin/ags
+```
+
+:::
diff --git a/docs/guide/ags/theming.md b/docs/guide/ags/theming.md
new file mode 100644
index 0000000..ea83e35
--- /dev/null
+++ b/docs/guide/ags/theming.md
@@ -0,0 +1,169 @@
+# Theming
+
+Since the widget toolkit is **GTK3** theming is done with **CSS**.
+
+- [CSS tutorial](https://www.w3schools.com/css/)
+- [GTK CSS Overview wiki](https://docs.gtk.org/gtk3/css-overview.html)
+- [GTK CSS Properties Overview wiki](https://docs.gtk.org/gtk3/css-properties.html)
+
+:::warning GTK is not the web
+While most features are implemented in GTK,
+you can't assume anything that works on the web will work with GTK.
+Refer to the [GTK docs](https://docs.gtk.org/gtk3/css-overview.html)
+to see what is available.
+:::
+
+So far every widget you made used your default GTK3 theme.
+To make them more custom, you can apply stylesheets to them.
+
+## From file at startup
+
+You can pass a path to a file or css as a string in `App.start`
+
+:::code-group
+
+```ts [app.ts]
+const inlineCss = `
+window {
+ background-color: transparent;
+}
+`
+
+App.start({
+ css: "./style.css",
+ css: `${SRC}/style.css'`,
+ css: inlineCss,
+})
+```
+
+:::
+
+:::info
+The global `SRC` will point to the directory `app.ts` is in.
+AGS will set the current working directory to `--config`, so `./style.css` also works.
+:::
+
+## Css Property on Widgets
+
+```ts
+Widget.Label({
+ css: "color: blue; padding: 1em;",
+ label: "hello",
+})
+```
+
+:::info
+The `css` property of a widget will not cascade to its children.
+:::
+
+## Apply Stylesheets at Runtime
+
+You can apply additional styles at runtime.
+
+```ts
+App.apply_css("/path/to/file.css")
+```
+
+```ts
+App.apply_css(`
+window {
+ background-color: transparent;
+}
+`)
+```
+
+```ts
+App.reset_css() // reset if need
+```
+
+:::warning
+`App.apply_css` will apply on top of other stylesheets applied before.
+You can reset stylesheets with `App.resetCss`
+:::
+
+## Inspector
+
+If you are not sure about the widget hierarchy or any CSS selector,
+you can use the [GTK inspector](https://wiki.gnome.org/Projects/GTK/Inspector)
+
+```sh
+# to bring up the inspector run
+ags --inspector
+```
+
+## Using SCSS
+
+Gtk's CSS only supports a subset of what the web offers.
+Most notably nested selectors are unsupported by Gtk, but this can be
+workaround by using preprocessors like [SCSS](https://sass-lang.com/).
+
+:::code-group
+
+```sh [Arch]
+sudo pacman -Syu dart-sass
+```
+
+```sh [Fedora]
+npm install -g sass # not packaged on Fedora
+```
+
+```sh [Alpine]
+sudo apk add dart-sass
+```
+
+```sh [Ubuntu]
+npm install -g sass # not packaged on Ubuntu
+```
+
+```sh [openSUSE]
+sudo zypper install dart-sass
+```
+
+:::
+
+Importing `scss` files will simply return transpiled css.
+
+:::code-group
+
+```ts [app.ts]
+import style from "./style.scss"
+
+App.start({
+ css: style,
+ main() {},
+})
+```
+
+:::
+
+:::tip
+If you for example want to set scss varibles from JS,
+You can inline import, compose, and transpile yourself.
+
+```ts [app.ts]
+import style1 from "inline:./style1.scss"
+import style2 from "inline:./style2.scss"
+
+const tmpscss = "/tmp/style.scss"
+const target = "/tmp/style.css"
+
+writeFile(tmpscss, `
+ $var1: red;
+ $var1: blue;
+ ${style1}
+ ${style1}
+`)
+
+exec(`sass ${tmpscss} ${target}`)
+
+App.start({
+ css: target,
+})
+
+```
+
+:::
+
+:::info
+If you want other preprocessors support builtin open an Issue.
+:::
diff --git a/docs/guide/ags/utilities.md b/docs/guide/ags/utilities.md
new file mode 100644
index 0000000..42589d3
--- /dev/null
+++ b/docs/guide/ags/utilities.md
@@ -0,0 +1,174 @@
+# Utilities
+
+## File functions
+
+Import them from `astal` or `astal/file`
+
+```ts
+import {
+ readFile,
+ readFileAsync,
+ writeFile,
+ writeFileAsync,
+ monitorFile,
+} from "astal"
+```
+
+### Reading files
+
+```ts
+function readFile(path: string): string
+function readFileAsync(path: string): Promise<string>
+```
+
+### Writing files
+
+```ts
+function writeFile(path: string, content: string): void
+function writeFileAsync(path: string, content: string): Promise<void>
+```
+
+### Monitoring files
+
+```ts
+function monitorFile(
+ path: string,
+ callback: (file: string, event: Gio.FileMonitorEvent) => void,
+): Gio.FileMonitor
+```
+
+## Timeouts and Intervals
+
+Import them from `astal` or `astal/time`
+
+```ts
+import { interval, timeout, idle } from "astal"
+```
+
+You can use javascript native `setTimeout` or `setInterval`
+they return a [GLib.Source](https://docs.gtk.org/glib/struct.Source.html) instance.
+Alternatively you can use these functions provided by Astal,
+which return an [Astal.Time](https://aylur.github.io/libastal/class.Time.html) instance.
+
+`Astal.Time` has a `now` signal and a `cancelled` signal.
+
+### Interval
+
+Will immediately execute the function and every `interval` millisecond.
+
+```ts
+function interval(interval: number, callback?: () => void): Astal.Time
+```
+
+### Timeout
+
+Will execute the `callback` after `timeout` millisecond.
+
+```ts
+function timeout(timeout: number, callback?: () => void): Astal.Time
+```
+
+### Idle
+
+Executes `callback` whenever there are no higher priority events pending.
+
+```ts
+function idle(callback?: () => void): Astal.Time
+```
+
+Example:
+
+```ts
+const timer = interval(1000, () => {
+ console.log("optional callback")
+})
+
+timer.connect("now", () => {
+ console.log("tick")
+})
+
+timer.connect("cancelled", () => {
+ console.log("cancelled")
+})
+
+timer.cancel()
+```
+
+## Process functions
+
+Import them from `astal` or `astal/proc`
+
+```ts
+import { subprocess, exec, execAsync } from "astal"
+```
+
+### Subprocess
+
+You can start a subprocess and run callback functions whenever it outputs to
+stdout or stderr. [Astal.Process](https://aylur.github.io/libastal/class.Process.html) has a `stdout` and `stderr` signal.
+
+```ts
+function subprocess(args: {
+ cmd: string | string[]
+ out?: (stdout: string) => void
+ err?: (stderr: string) => void
+}): Astal.Process
+
+function subprocess(
+ cmd: string | string[],
+ onOut?: (stdout: string) => void,
+ onErr?: (stderr: string) => void,
+): Astal.Process
+```
+
+Example:
+
+```ts
+const proc = subprocess(
+ "some-command",
+ (out) => console.log(out), // optional
+ (err) => console.error(out), // optional
+)
+
+// or with signals
+const proc = subprocess("some-command")
+proc.connect("stdout", (out) => console.log(out))
+proc.connect("stderr", (err) => console.error(err))
+```
+
+### Executing external commands and scripts
+
+```ts
+function exec(cmd: string | string[]): string
+function execAsync(cmd: string | string[]): Promise<string>
+```
+
+Example:
+
+```ts
+try {
+ const out = exec("/path/to/script")
+ console.log(out)
+} catch (err) {
+ console.error(err)
+}
+
+execAsync(["bash", "-c", "/path/to/script.sh"])
+ .then((out) => console.log(out))
+ .catch((err) => console.error(err))
+```
+
+:::warning
+`subprocess`, `exec`, and `execAsync` executes the passed executable as is.
+They are **not** executed in a shell environment,
+they do **not** expand env variables like `$HOME`,
+and they do **not** handle logical operators like `&&` and `||`.
+
+If you want bash, run them with bash.
+
+```ts
+exec(["bash", "-c", "command $VAR && command"])
+exec("bash -c 'command $VAR' && command")
+```
+
+:::
diff --git a/docs/guide/ags/variable.md b/docs/guide/ags/variable.md
new file mode 100644
index 0000000..96e8d38
--- /dev/null
+++ b/docs/guide/ags/variable.md
@@ -0,0 +1,141 @@
+# Variable
+
+```js
+import { Variable } from "astal"
+```
+
+Variable is just a simple `GObject` that holds a value.
+And has shortcuts for hooking up subprocesses.
+
+:::info
+The `Variable` object imported from the `"astal"` package is **not** [Astal.Variable](https://aylur.github.io/libastal/class.Variable.html).
+:::
+
+## Variable as state
+
+```typescript
+const myvar = Variable<string>("initial-value")
+
+// whenever its value changes, callback will be executed
+myvar.subscribe((value: string) => {
+ console.log(value)
+})
+
+// settings its value
+myvar.set("new value")
+
+// getting its value
+const value = myvar.get()
+
+// binding them to widgets
+Widget.Label({
+ label: bind(myvar).as((value) => `transformed ${value}`),
+ label: myvar((value) => `transformed ${value}`), // shorthand for the above
+})
+```
+
+:::warning
+Make sure to make the transform functions pure. The `.get()` function can be called
+anytime by `astal` especially when `deriving`, so make sure there are no sideeffects.
+:::
+
+## Composing variables
+
+Using `Variable.derive` we can compose both Variables and Bindings.
+
+```typescript
+const v1: Variable<number> = Variable(2)
+const v2: Variable<number> = Variable(3)
+
+// first argument is a list of dependencies
+// second argument is a transform function,
+// where the parameters are the values of the dependencies in the order they were passed
+const v3: Variable<number> = Variable.derive([v1, v2], (v1, v2) => {
+ return v1 * v2
+})
+
+const b1: Binding<string> = bind(obj, "prop")
+const b2: Binding<string> = bind(obj, "prop")
+
+const b3: Variable<string> = Variable.derive([b1, b2], (b1, b2) => {
+ return `${b1}-${b2}`
+})
+```
+
+## Subprocess shorthands
+
+Using `.poll` and `.watch` we can start subprocess and capture their
+output in `Variables`. They can poll and watch at the same time, but they
+can only poll/watch one subprocess.
+
+:::warning
+The command parameter is passed to [execAsync](/guide/ags/utilities#executing-external-commands-and-scripts)
+which means they are **not** executed in a shell environment,
+they do **not** expand env variables like `$HOME`,
+and they do **not** handle logical operators like `&&` and `||`.
+
+If you want bash, run them with bash.
+
+```js
+Variable("").poll(1000, ["bash", "-c", "command $VAR && command"])
+```
+
+:::
+
+```typescript
+const myVar = Variable<number>(0)
+ .poll(1000, "command", (out: string, prev: number) => parseInt(out))
+ .poll(1000, ["bash", "-c", "command"], (out, prev) => parseInt(out))
+ .poll(1000, (prev) => prev + 1)
+```
+
+```typescript
+const myVar = Variable<number>(0)
+ .watch("command", (out: string, prev: number) => parseInt(out))
+ .watch(["bash", "-c", "command"], (out, prev) => parseInt(out))
+```
+
+You can temporarily stop them and restart them whenever.
+
+```js
+myvar.stopWatch() // this kills the subprocess
+myvar.stopPoll()
+
+myvar.startListen() // launches the subprocess again
+myvar.startPoll()
+
+console.log(myvar.isListening())
+console.log(myvar.isPolling())
+```
+
+## Gobject connection shorthands
+
+Using `.observe` you can connect gobject signals and capture their value.
+
+```typescript
+const myvar = Variable("")
+ .observe(obj1, "signal", () => "")
+ .observe(obj2, "signal", () => "")
+```
+
+## Dispose if no longer needed
+
+This will stop the interval and force exit the subprocess and disconnect gobjects.
+
+```js
+myVar.drop()
+```
+
+:::warning
+Don't forget to drop them when they are defined inside widgets
+with either `.poll`, `.watch` or `.observe`
+
+```tsx
+function MyWidget() {
+ const myvar = Variable().poll()
+
+ return <box onDestroy={() => myvar.drop()} />
+}
+```
+
+:::
diff --git a/docs/guide/ags/widget.md b/docs/guide/ags/widget.md
new file mode 100644
index 0000000..1fe755f
--- /dev/null
+++ b/docs/guide/ags/widget.md
@@ -0,0 +1,227 @@
+# Widget
+
+## AGS widget properties
+
+These are properties that Astal.js additionally adds to Gtk.Widgets
+
+- className: `string` - List of class CSS selectors separated by white space.
+- css: `string` - Inline CSS. e.g `label { color: white; }`. If no selector is specified `*` will be assumed. e.g `color: white;` will be inferred as `* { color: white; }`.
+- cursor: `string` - Cursor style when hovering over widgets that have hover states, e.g it won't work on labels. [list of valid values](https://docs.gtk.org/gdk3/ctor.Cursor.new_from_name.html).
+- clickThrough: `boolean` - Lets click events through.
+
+To have a full list of available properties, reference the documentation of the widget.
+
+- [Astal widgets](https://aylur.github.io/libastal/index.html#classes)
+- [Gtk widgets](https://docs.gtk.org/gtk3/#classes)
+
+## AGS widget methods
+
+Additional methods that Astal.js adds to Gtk.Widget instances
+
+### setup
+
+`setup` is a convenience prop to not have predefine widgets before returning them
+
+without `setup`
+
+```tsx
+function MyWidget() {
+ const button = Widget.Button()
+ // setup button
+ return button
+}
+```
+
+using `setup`
+
+```tsx
+function MyWidget() {
+ function setup(button: Widget.Button) {
+ // setup button
+ }
+
+ return <buttons setup={setup} />
+}
+```
+
+### hook
+
+Shorthand for connection and disconnecting to gobjects.
+
+without `hook`
+
+```tsx
+function MyWidget() {
+ const id = gobject.connect("signal", callback)
+
+ return <box
+ onDestroy={() => {
+ gobject.disconnect(id)
+ }}
+ />
+}
+```
+
+with `hook`
+
+```tsx
+function MyWidget() {
+ return <box
+ setup={(self) => {
+ self.hook(gobject, "signal", callback)
+ }}
+ />
+}
+```
+
+### toggleClassName
+
+Toggle classNames based on a condition
+
+```tsx
+function MyWidget() {
+ return <box
+ setup={(self) => {
+ self.toggleClassName("classname", someCondition)
+ }}
+ />
+}
+```
+
+## How to use non builtin Gtk widgets
+
+Using `Widget.astalify` you can setup widget constructors to behave like builtin widgets.
+The `astalify` function will apply the following:
+
+- set `visible` to true by default (Gtk3 widgets are invisible by default)
+- make gobject properties accept and consume `Binding` objects
+- add properties and methods listed above
+- proxify the constructor so the `new` keyword is not needed
+- sets up signal handlers that are passed as props prefixed with `on`
+
+```tsx
+import { Widget, Gtk } from "astal"
+
+// define its props, constructor and type
+export type ColorButtonProps = Widget.ConstructProps<
+ Gtk.ColorButton,
+ Gtk.ColorButton.ConstructorProps,
+ { onColorSet: [] }
+>
+export const ColorButton = Widget.astalify<
+ typeof Gtk.ColorButton,
+ ColorButtonProps,
+ "ColorButton"
+>(Gtk.ColorButton)
+export type ColorButton = ReturnType<typeof ColorButton>
+
+function MyWidget() {
+ function setup(button: ColorButton) {}
+
+ return <ColorButton
+ setup={setup}
+ useAlpha
+ rgba={new Gdk.RGBA({
+ red: 1,
+ green: 0,
+ blue: 0,
+ alpha: 0.5,
+ })}
+ onColorSet={(self) => {
+ console.log(self.rgba)
+ }}
+ />
+}
+```
+
+:::info
+Signal properties have to be annotated manually for TypeScript.
+You can reference [Gtk3](https://gjs-docs.gnome.org/gtk30~3.0/)
+and [Astal](https://aylur.github.io/libastal/index.html#classes) for available signals.
+:::
+
+:::tip
+
+As stated before children are passed as either `child` or `children` property,
+when passing a container widget with `Widget.astalify` these rules still apply.
+While subclasses of `Gtk.Bin` *can* take a `child` property in gjs, you might notice
+a warning that it is deprecated. You can workaround this with a simple wrapper function.
+
+```tsx
+const GtkFrame = Widget.astalify<
+ typeof Gtk.Frame,
+ FrameProps,
+ "Frame"
+>(Gtk.Frame)
+
+export function Frame({ child, ...props }: FrameProps) {
+ const frame = GtkFrame(props)
+ frame.add(child) // use the widget's child adding function
+ return frame
+}
+```
+
+:::
+
+## TypeScript
+
+Type of widgets are available through `Widget`.
+Here is an example Widget that takes in and handles a possibly `Binding` prop.
+
+```tsx
+import { Binding, Variable, Widget } from "astal"
+
+export interface ToggleButtonProps extends Widget.ButtonProps {
+ onToggled?: (self: Widget.Button, on: boolean) => void
+ state?: Binding<boolean> | boolean
+ child?: JSX.Element
+}
+
+export default function ToggleButton(btnprops: ToggleButtonProps) {
+ const { state = false, onToggled, setup, child, ...props } = btnprops
+ const innerState = Variable(state instanceof Binding ? state.get() : state)
+
+ return <button
+ {...props}
+ setup={self => {
+ setup?.(self)
+
+ self.toggleClassName("active", innerState.get())
+ self.hook(innerState, () => self.toggleClassName("active", innerState.get()))
+
+ if (state instanceof Binding) {
+ self.hook(state, () => innerState.set(state.get()))
+ }
+ }}
+ onClicked={self => {
+ onToggled?.(self, !innerState.get())
+ }}
+ >
+ {child}
+ </button>
+}
+```
+
+## Builtin Widgets
+
+You can check the [source code](https://github.com/aylur/astal/blob/main/core/gjs/src/widgets.ts) to have a full list of builtin widgets.
+
+These widgets are available by default in JSX.
+
+- box: [Astal.Box](https://aylur.github.io/libastal/class.Box.html)
+- button: [Astal.Button](https://aylur.github.io/libastal/class.Button.html)
+- centerbox: [Astal.CenterBox](https://aylur.github.io/libastal/class.CenterBox.html)
+- circularprogress: [Astal.CircularProgress](https://aylur.github.io/libastal/class.CircularProgress.html)
+- drawingarea: [Gtk.DrawingArea](https://docs.gtk.org/gtk3/class.DrawingArea.html)
+- entry: [Gtk.Entry](https://docs.gtk.org/gtk3/class.Entry.html)
+- eventbox: [Astal.EventBox](https://aylur.github.io/libastal/class.EventBox.html)
+- icon: [Astal.Icon](https://aylur.github.io/libastal/class.Icon.html)
+- label: [Astal.Label](https://aylur.github.io/libastal/class.Label.html)
+- levelbar: [Astal.LevelBar](https://aylur.github.io/libastal/class.LevelBar.html)
+- overlay: [Astal.Overlay](https://aylur.github.io/libastal/class.Overlay.html)
+- revealer: [Gtk.Revealer](https://docs.gtk.org/gtk3/class.Revealer.html)
+- scrollable: [Astal.Scrollable](https://aylur.github.io/libastal/class.Scrollable.html)
+- slider: [Astal.Slider](https://aylur.github.io/libastal/class.Slider.html)
+- stack: [Astal.Stack](https://aylur.github.io/libastal/class.Stack.html)
+- switch: [Gtk.Switch](https://docs.gtk.org/gtk3/class.Switch.html)
+- window: [Astal.Window](https://aylur.github.io/libastal/class.Window.html)
diff --git a/docs/guide/getting-started/installation.md b/docs/guide/getting-started/installation.md
new file mode 100644
index 0000000..ef5c6e8
--- /dev/null
+++ b/docs/guide/getting-started/installation.md
@@ -0,0 +1,69 @@
+# Installation
+
+## Nix
+
+maintainer: [@Aylur](https://github.com/Aylur)
+
+Read more about it on the [nix page](./nix#astal)
+
+## Arch
+
+maintainer: [@kotontrion](https://github.com/kotontrion)
+
+:::code-group
+
+```sh [Core Library]
+yay -S libastal-git
+```
+
+```sh [Every Library]
+yay -S libastal-meta
+```
+
+:::
+
+## Bulding libastal from source
+
+1. Clone the repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/core
+```
+
+2. Install the following dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala gtk3 gtk-layer-shell gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac gtk3-devel gtk-layer-shell-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson valac libgtk3-dev libgtk-layer-shell-dev gobject-introspection
+```
+
+:::
+
+3. Build and install with `meson`
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+meson install -C build
+```
+
+:::
diff --git a/docs/guide/getting-started/introduction.md b/docs/guide/getting-started/introduction.md
new file mode 100644
index 0000000..92d2ff1
--- /dev/null
+++ b/docs/guide/getting-started/introduction.md
@@ -0,0 +1,23 @@
+# Introduction
+
+## What is Astal?
+
+Astal (_meaning "desk"_) is a bundle of libraries built using [GLib](https://docs.gtk.org/glib/) in Vala and C.
+The core library [libastal](https://aylur.github.io/libastal) has some Gtk widgets that come packaged,
+the most important one is the [Window](https://aylur.github.io/libastal/class.Window.html) which is the main toplevel component using [gtk-layer-shell](https://github.com/wmww/gtk-layer-shell).
+This is what allows us to use Gtk as shell components on Wayland.
+libastal also comes with some utility functions such as running external processes,
+reading, writing and monitoring files.
+
+## Why Astal?
+
+What makes Astal convenient to use is not the core library, as it could easily be replaced
+by the standard library of any of your favorite language that has bindings to Gtk, it is the
+accompanying libraries (_formerly known as "services" in AGS_)
+
+Have you ever wanted to write a custom bar, custom notification popups
+or an applauncher, but gave up because writing a workspace widget,
+implementing the notification daemon or handling a search filter was too much of a hassle?
+
+Astal libraries have you [covered](/guide/libraries/references), you don't have to worry about these,
+you just define the layout, style it with CSS and that's it.
diff --git a/docs/guide/getting-started/nix.md b/docs/guide/getting-started/nix.md
new file mode 100644
index 0000000..2b04bdc
--- /dev/null
+++ b/docs/guide/getting-started/nix.md
@@ -0,0 +1,154 @@
+# Nix
+
+## Astal
+
+Using Astal on Nix will require you to package your project.
+
+:::code-group
+
+```nix [<i class="devicon-lua-plain"></i> Lua]
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+ astal = {
+ url = "github:aylur/astal";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs = { self, nixpkgs, astal }: let
+ system = "x86_64-linux";
+ pkgs = nixpkgs.legacyPackages.${system};
+ in {
+ packages.${system}.default = astal.lib.mkLuaPacakge {
+ inherit pkgs;
+ src = ./path/to/project; # should contain init.lua
+
+ # add extra glib packages or binaries
+ extraPackages = [
+ astal.packages.${system}.battery
+ pkgs.dart-sass
+ ];
+ };
+ };
+}
+```
+
+```nix [<i class="devicon-python-plain"></i> Python]
+# Not documented yet
+```
+
+```nix [<i class="devicon-vala-plain"></i> Vala]
+# keep in mind that this is just the nix derivation
+# and you still have to use some build tool like meson
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+ astal.url = "github:aylur/astal";
+ };
+
+ outputs = { self, nixpkgs, astal }: let
+ system = "x86_64-linux";
+ pkgs = nixpkgs.legacyPackages.${system};
+ in {
+ packages.${system} = {
+ default = pkgs.stdenv.mkDerivation {
+ name = "my-shell";
+ src = ./.;
+
+ nativeBuildInputs = with pkgs; [
+ meson
+ ninja
+ pkg-config
+ vala
+ gobject-introspection
+ ];
+
+ # add extra packages
+ buildInputs = [
+ astal.packages.${system}.astal
+ ];
+ };
+ };
+ };
+}
+```
+
+:::
+
+## AGS
+
+The recommended way to use AGS on NixOS is through the home-manager module.
+
+Example content of a `flake.nix` file that contains your `homeConfigurations`.
+
+<!--TODO: remove v2 after merge-->
+
+:::code-group
+
+```nix [<i class="devicon-nixos-plain"></i> flake.nix]
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+ home-manager = {
+ url = "github:nix-community/home-manager";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ # add ags https://github.com/Aylur/ags/pull/504
+ ags.url = "github:aylur/ags/v2";
+ };
+
+ outputs = { home-manager, nixpkgs, ... }@inputs:
+ let
+ system = "x86_64-linux";
+ in
+ {
+ homeConfigurations."${username}" = home-manager.lib.homeManagerConfiguration {
+ pkgs = import nixpkgs { inherit system; };
+
+ # pass inputs as specialArgs
+ extraSpecialArgs = { inherit inputs; };
+
+ # import your home.nix
+ modules = [ ./home-manager/home.nix ];
+ };
+ };
+}
+```
+
+:::
+
+Example content of `home.nix` file
+
+:::code-group
+
+```nix [<i class="devicon-nixos-plain"></i> home.nix]
+{ inputs, pkgs, ... }:
+{
+ # add the home manager module
+ imports = [ inputs.ags.homeManagerModules.default ];
+
+ programs.ags = {
+ enable = true;
+ configDir = ../ags;
+
+ # additional packages to add to gjs's runtime
+ extraPackages = with pkgs; [
+ inputs.ags.packages.${pkgs.system}.battery
+ fzf
+ ];
+ };
+}
+```
+
+:::
+
+AGS by default only includes the core `libastal` library.
+If you want to include any other [library](../libraries/references) you have to add them to `extraPackages`.
+You can also add binaries which will be added to `$PATH`.
+
+:::warning
+The `configDir` option symlinks the given path to `~/.config/ags`.
+If you already have your source code there leave it as `null`.
+:::
diff --git a/docs/guide/getting-started/supported-languages.md b/docs/guide/getting-started/supported-languages.md
new file mode 100644
index 0000000..634bad0
--- /dev/null
+++ b/docs/guide/getting-started/supported-languages.md
@@ -0,0 +1,65 @@
+# Supported Languages
+
+## JavaScript
+
+The main intended usage of Astal is in TypeScript with [AGS](/guide/ags/first-widgets).
+It supports JSX and has a state management solution similar to web frameworks.
+Only a minimal knowledge of JavaScript's syntax is needed to get started.
+
+:::info
+The runtime is [GJS](https://gitlab.gnome.org/GNOME/gjs) and **not** nodejs
+:::
+
+Examples:
+
+- [Simple Bar](https://github.com/Aylur/astal/tree/main/examples/js/simple-bar)
+![simple-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b)
+
+## Lua
+
+Similar to how there is a [TypeScript](https://github.com/Aylur/astal/tree/main/core/gjs) lib for GJS, there is also an accompanying library for [Lua](https://github.com/Aylur/astal/tree/main/core/lua).
+<!--TODO: open issue and link performance issue-->
+Unfortunately, I have encountered very heavy [performance issues](https://github.com/aylur/astal) with [lgi](https://github.com/lgi-devs/lgi),
+and so currently I don't recommend using Lua for full desktop shells, but only for static
+components that don't render child nodes dynamically, bars and panels for example.
+
+Examples:
+
+- [Simple Bar](https://github.com/Aylur/astal/tree/main/examples/lua/simple-bar)
+![simple-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b)
+
+## Python
+
+There was a WIP [library for python](https://github.com/aylur/astal/tree/feat/python), to make it behave similar to the above two
+but I don't plan on finishing it, because I'm not a fan of python.
+If you are interested in picking it up, feel free to open a PR.
+Nonetheless you can still use python the OOP way [pygobject](https://pygobject.gnome.org/tutorials/gobject/subclassing.html) intended it.
+
+Examples:
+
+- [Starter Bar](https://github.com/Aylur/astal/tree/main/examples/py/starter-bar)
+
+## Vala
+
+Vala is a language that simply put uses C# syntax and compiles to C.
+It is the language most of Astal is written in. I would still recommend
+using TypeScript or Lua over Vala as they don't need a build step.
+
+Examples:
+
+- [Simple Bar](https://github.com/Aylur/astal/tree/main/examples/vala/simple-bar)
+![simple-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b)
+
+## C
+
+I don't recommend using C as it requires quite a lot of boilerplate, both for
+build step and code.
+
+Examples:
+
+- TODO
+
+## Other languages
+
+There a few more that supports gobject-introspection, most notably Haskell, Rust and C++.
+If you are interested and feel like contributing, PRs are welcome for bindings, and examples.
diff --git a/docs/guide/libraries/apps.md b/docs/guide/libraries/apps.md
new file mode 100644
index 0000000..c53daf0
--- /dev/null
+++ b/docs/guide/libraries/apps.md
@@ -0,0 +1,113 @@
+# Apps
+
+Library and CLI tool for querying and launching
+applications that have a corresponding `.desktop` file.
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala json-glib gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac json-glib-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson valac libjson-glib-dev gobject-introspection
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/apps
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Apps reference](https://aylur.github.io/libastal/apps).
+
+### CLI
+
+```sh
+astal-apps --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Apps from "gi://AstalApps"
+
+const apps = new Apps.Apps({
+ includeEntry: true,
+ includeExecutable: true,
+})
+
+for (const app of apps.fuzzy_query("spotify")) {
+ print(app.name)
+}
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalApps as Apps
+
+apps = Apps.Apps(
+ include_entry=True,
+ include_executable=True,
+)
+
+for app in apps.fuzzy_query("obsidian"):
+ print(app.get_name())
+
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Apps = require("lgi").require("AstalApps")
+
+local apps = Apps.Apps({
+ include_entry = true,
+ include_executable = true,
+})
+
+for _, app in ipairs(apps:fuzzy_query("lutris")) do
+ print(app.name)
+end
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented, contributions are appreciated
+```
+
+:::
+
+:::info
+The fuzzy query uses [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance). I am not a mathematician, but if you know how to reimplement
+the logic of [fzf](https://github.com/junegunn/fzf) to make it better feel free to open PRs.
+:::
diff --git a/docs/guide/libraries/auth.md b/docs/guide/libraries/auth.md
new file mode 100644
index 0000000..1f07a17
--- /dev/null
+++ b/docs/guide/libraries/auth.md
@@ -0,0 +1,118 @@
+# Auth
+
+Library and CLI tool for authentication using [pam](https://github.com/linux-pam/linux-pam).
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson pam gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson pam-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+# Not yet documented
+```
+
+:::
+
+::: warning On NixOS you have to add `astal-auth` to `security.pam`.
+::: code-group
+
+```nix [configuration.nix]
+{
+ security.pam.services.astal-auth = {}
+}
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/auth
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Auth reference](https://aylur.github.io/libastal/auth).
+
+### CLI
+
+```sh
+astal-auth --password my-password
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Auth from "gi://AstalAuth"
+
+Auth.Pam.authenticate("password", (_, task) => {
+ try {
+ AstalAuth.Pam.authenticate_finish(task)
+ print("authentication sucessful")
+ } catch (error) {
+ print(error)
+ }
+})
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalAuth as Auth
+
+def callback(_, task) -> None:
+ try:
+ Auth.Pam.authenticate_finish(task)
+ print("success")
+ except Exception as e:
+ print(e)
+
+Auth.Pam.authenticate("password", callback)
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Auth = require("lgi").require("AstalAuth")
+
+Auth.Pam.authenticate("password", function(_, task)
+ local status, err = Auth.Pam.authenticate_finish(task)
+ if err ~= nil then
+ print(err)
+ else
+ print("success")
+ end
+end)
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/battery.md b/docs/guide/libraries/battery.md
new file mode 100644
index 0000000..b42d747
--- /dev/null
+++ b/docs/guide/libraries/battery.md
@@ -0,0 +1,97 @@
+# Battery
+
+Library and CLI tool for monitoring [upowerd](https://upower.freedesktop.org/) devices.
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala json-glib gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac json-glib-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson valac libjson-glib-dev gobject-introspection
+```
+
+:::
+
+::: info
+Although UPower is not a direct build dependency,
+it should be self-explanatory that the daemon is required to be available at runtime.
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/battery
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Battery reference](https://aylur.github.io/libastal/battery).
+
+### CLI
+
+```sh
+astal-battery --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Battery from "gi://AstalBattery"
+
+const battery = Battery.get_default()
+
+print(battery.percentage)
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalBattery as Battery
+
+battery = Battery.get_default()
+
+print(battery.get_percentage())
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Battery = require("lgi").require("AstalBattery")
+
+local battery = Battery.get_default()
+
+print(battery.percentage)
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/bluetooth.md b/docs/guide/libraries/bluetooth.md
new file mode 100644
index 0000000..04d9db2
--- /dev/null
+++ b/docs/guide/libraries/bluetooth.md
@@ -0,0 +1,104 @@
+# Bluetooth
+
+Library for monitoring [bluez](https://www.bluez.org/) over dbus.
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson valac gobject-introspection
+```
+
+:::
+
+::: info
+Although bluez is not a direct build dependency,
+it should be self-explanatory that the daemon is required to be available at runtime.
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/bluetooth
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Bluetooth reference](https://aylur.github.io/libastal/bluetooth).
+
+### CLI
+
+There is no CLI for this library, use the one provided by bluez.
+
+```sh
+bluetoothctl --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Bluetooth from "gi://AstalBluetooth"
+
+const bluetooth = Bluetooth.get_default()
+
+for (const device of bluetooth.get_devices()) {
+ print(device.name)
+}
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalBluetooth as Bluetooth
+
+bluetooth = Bluetooth.get_default()
+
+for device in bluetooth.get_devices():
+ print(device.get_name())
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Bluetooth = require("lgi").require("AstalBluetooth")
+
+local bluetooth = Bluetooth.get_default()
+
+for _, d in ipairs(bluetooth.devices) do
+ print(d.name)
+end
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/hyprland.md b/docs/guide/libraries/hyprland.md
new file mode 100644
index 0000000..faf9e50
--- /dev/null
+++ b/docs/guide/libraries/hyprland.md
@@ -0,0 +1,97 @@
+# Hyprland
+
+Library and CLI tool for monitoring the [Hyprland socket](https://wiki.hyprland.org/IPC/).
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala json-glib gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac json-glib-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson valac libjson-glib-dev gobject-introspection
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/hyprland
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Hyprland reference](https://aylur.github.io/libastal/hyprland).
+
+### CLI
+
+```sh
+astal-hyprland # starts monitoring
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Hyprland from "gi://AstalHyprland"
+
+const hyprland = Hyprland.get_default()
+
+for (const client of hyprland.get_clients()) {
+ print(client.title)
+}
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalHyprland as Hyprland
+
+hyprland = Hyprland.get_default()
+
+for client in hyprland.get_clients():
+ print(client.get_title())
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Hyprland = require("lgi").require("AstalHyprland")
+
+local hyprland = Hyprland.get_default()
+
+for _, c in ipairs(hyprland.clients) do
+ print(c.title)
+end
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/mpris.md b/docs/guide/libraries/mpris.md
new file mode 100644
index 0000000..dfe7956
--- /dev/null
+++ b/docs/guide/libraries/mpris.md
@@ -0,0 +1,100 @@
+# Mpris
+
+Library and CLI tool for interacting and monitoring media players
+exposing an mpris interface through dbus.
+
+An alternative for [playerctl](https://github.com/altdesktop/playerctl) that better integrates
+with astal.
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala json-glib gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac json-glib-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson valac libjson-glib-dev gobject-introspection
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/mpris
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Mpris reference](https://aylur.github.io/libastal/mpris).
+
+### CLI
+
+```sh
+astal-mpris --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Mpris from "gi://AstalMpris"
+
+const spotify = Mpris.Player.new("spotify")
+
+if (spotify.available)
+ print(spotify.title)
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalMpris as Mpris
+
+spotify = Mpris.Player.new("spotify")
+
+if spotify.get_available():
+ print(spotify.get_title())
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Mpris = require("lgi").require("AstalMpris")
+
+local spotify = Mpris.Player.new("spotify")
+
+if spotify.available then
+ print(spotify.title)
+end
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/network.md b/docs/guide/libraries/network.md
new file mode 100644
index 0000000..afeb5d2
--- /dev/null
+++ b/docs/guide/libraries/network.md
@@ -0,0 +1,94 @@
+# Network
+
+Wrapper library over [networkmanager](https://networkmanager.dev/) to better integrate with Astal.
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala libnm gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac NetworkManager-libnm-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson valac libnm-dev gobject-introspection
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/network
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Network reference](https://aylur.github.io/libastal/network).
+
+### CLI
+
+There is no CLI for this library, use the one provided by networkmanager.
+
+```sh
+nmcli --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Network from "gi://AstalNetwork"
+
+const network = Network.get_default()
+
+print(network.wifi.ssid)
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalNetwork as Network
+
+network = Network.get_default()
+
+print(network.get_wifi().get_ssid())
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Network = require("lgi").require("AstalNetwork")
+
+local network = Network.get_default()
+
+print(network.wifi.ssid)
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/notifd.md b/docs/guide/libraries/notifd.md
new file mode 100644
index 0000000..7e02149
--- /dev/null
+++ b/docs/guide/libraries/notifd.md
@@ -0,0 +1,106 @@
+# Notifd
+
+A [notification daemon](https://specifications.freedesktop.org/notification-spec/latest/) implementation as a library and CLI tool.
+
+## How it works
+
+The first instantiation of the [Notifd](https://aylur.github.io/libastal/notifd/class.Notifd.html) class will become the daemon and every subsequent instantiation will queue up to act as the daemon and will act as a client in the meantime. This means this library can be used throughout multiple processes.
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala gdk-pixbuf2 json-glib gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac gdk-pixbuf2-devel json-glib-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson valac libgdk-pixbuf-2.0-dev libjson-glib-dev gobject-introspection
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/notifd
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Notifd reference](https://aylur.github.io/libastal/notifd).
+
+### CLI
+
+```sh
+astal-notifd --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Notifd from "gi://AstalNotifd"
+
+const notifd = Notifd.get_default()
+
+notifd.connect("notified", (_, id) => {
+ const n = notifd.get_notification(id)
+ print(n.summary, n.body)
+})
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalNotifd as Notifd
+
+notifd = Notifd.get_default()
+
+def on_notified(_, id):
+ n = notifd.get_notification(id)
+ print(n.get_body(), n.get_body())
+
+notifd.connect("notified", on_notified)
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Notifd = require("lgi").require("AstalNotifd")
+
+local notifd = Notifd.get_default()
+
+notifd.on_notified = function(_, id)
+ local n = notifd.get_notification(id)
+ print(n.body, n.summary)
+end
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/powerprofiles.md b/docs/guide/libraries/powerprofiles.md
new file mode 100644
index 0000000..8571c29
--- /dev/null
+++ b/docs/guide/libraries/powerprofiles.md
@@ -0,0 +1,97 @@
+# Power Profiles
+
+Library and CLI tool for monitoring [upowerd](https://upower.freedesktop.org/) powerprofiles.
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala json-glib gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac json-glib-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson valac libjson-glib-dev gobject-introspection
+```
+
+:::
+
+::: info
+Although UPower is not a direct build dependency,
+it should be self-explanatory that the daemon is required to be available at runtime.
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/powerprofiles
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [PowerProfiles reference](https://aylur.github.io/libastal/powerprofiles).
+
+### CLI
+
+```sh
+astal-power-profiles --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import PowerProfiles from "gi://AstalPowerProfiles"
+
+const powerprofiles = PowerProfiles.get_default()
+
+print(powerprofiles.activeProfile)
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalPowerProfiles as PowerProfiles
+
+powerprofiles = PowerProfiles.get_default()
+
+print(powerprofiles.get_active_profile())
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local PowerProfiles = require("lgi").require("AstalPowerProfiles")
+
+local powerprofiles = PowerProfiles.get_default()
+
+print(powerprofiles.active_profile)
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/references.md b/docs/guide/libraries/references.md
new file mode 100644
index 0000000..8f2bd02
--- /dev/null
+++ b/docs/guide/libraries/references.md
@@ -0,0 +1,44 @@
+# References
+
+The references of the libraries are annotated for the C language.
+Reading their documentation will vary depending on the language they are used in.
+
+<!--TODO: list some examples on how to read docs,-->
+<!--for example the difference between C enums and gjs enums-->
+
+## Additional references
+
+### GJS
+
+- [gjs-docs.gnome.org](https://gjs-docs.gnome.org/): Library references annotated for GJS
+- [gjs.guide](https://gjs.guide/): GJS and GObject guide
+
+### Python
+
+- [pgi-docs](https://lazka.github.io/pgi-docs/): Library references annotated for Python
+- [pygobject.gnome.org](https://pygobject.gnome.org/): PyGObject reference and guide
+
+### Lua
+
+- [lua-lgi docs](https://github.com/lgi-devs/lgi/tree/master/docs): GObject bindings guide for Lua
+
+### Vala
+
+- [vala.dev](https://vala.dev/): Guide for the Vala language
+- [valadoc.org](https://valadoc.org/): Library references annotated for Vala
+
+## Astal Libraries
+
+- [Astal](https://aylur.github.io/libastal): libastal the core library, which has the widgets and utilites
+- [Apps](https://aylur.github.io/libastal/apps): Library and cli tool for querying applications
+- [Auth](https://aylur.github.io/libastal/auth): Authentication library using PAM
+- [Battery](https://aylur.github.io/libastal/battery): DBus proxy library for upower daemon
+- [Bluetooth](https://aylur.github.io/libastal/bluetooth): Library to control bluez over dbus
+- [Hyprland](https://aylur.github.io/libastal/hyprland): Library and cli tool for Hyprland IPC socket
+- [Mpris](https://aylur.github.io/libastal/mpris): Library and cli tool for controlling media players
+- [Network](https://aylur.github.io/libastal/network): NetworkManager wrapper library
+- [Notifd](https://aylur.github.io/libastal/notifd): A notification daemon library and cli tool
+- [PowerProfiles](https://aylur.github.io/libastal/powerprofiles): Library and cli to control upowerd powerprofiles
+- [River](https://aylur.github.io/libastal/river): Library and cli tool for getting status information of the river wayland compositor
+- [Tray](https://aylur.github.io/libastal/tray): A systemtray library and cli tool
+- [WirePlumber](https://aylur.github.io/libastal/wireplumber): A library for audio control using wireplumber
diff --git a/docs/guide/libraries/river.md b/docs/guide/libraries/river.md
new file mode 100644
index 0000000..4818d0b
--- /dev/null
+++ b/docs/guide/libraries/river.md
@@ -0,0 +1,97 @@
+# River
+
+Library and CLI tool for monitoring the [River Wayland Compositor](https://isaacfreund.com/software/river/).
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson json-glib gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc json-glib-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson libjson-glib-dev gobject-introspection
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/river
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [River reference](https://aylur.github.io/libastal/river).
+
+### CLI
+
+```sh
+astal-river --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import River from "gi://AstalRiver"
+
+const river = River.get_default()
+
+for (const output of river.get_outputs()) {
+ print(output.name)
+}
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalRiver as River
+
+river = River.get_default()
+
+for output in river.get_outputs():
+ print(output.get_name())
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local River = require("lgi").require("AstalRiver")
+
+local river = River.River.get_default()
+
+for _, o in ipairs(river.outputs) do
+ print(o.name)
+end
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/tray.md b/docs/guide/libraries/tray.md
new file mode 100644
index 0000000..c8d093b
--- /dev/null
+++ b/docs/guide/libraries/tray.md
@@ -0,0 +1,97 @@
+# Tray
+
+Library for managing the systemtray by implementing the [StatusNotifierItem](https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/) protocol.
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson gtk3 gobject-introspection libdbusmenu-gtk3
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc gtk3-devel libdbusmenu-gtk3 gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+sudo apt install meson libgtk-3-dev libdbusmenu-gtk3-dev gobject-introspection
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/tray
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Tray reference](https://aylur.github.io/libastal/tray).
+
+### CLI
+
+```sh
+astal-tray --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Tray from "gi://AstalTray"
+
+const tray = Tray.get_default()
+
+for (const item of tray.get_items()) {
+ print(item.title)
+}
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalTray as Tray
+
+tray = Tray.get_default()
+
+for item in tray.get_items():
+ print(item.title)
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Tray = require("lgi").require("AstalTray")
+
+local tray = Tray.get_default()
+
+for _, i in ipairs(tray.items) do
+ print(i.title)
+end
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::
diff --git a/docs/guide/libraries/wireplumber.md b/docs/guide/libraries/wireplumber.md
new file mode 100644
index 0000000..5f1daab
--- /dev/null
+++ b/docs/guide/libraries/wireplumber.md
@@ -0,0 +1,94 @@
+# Wire Plumber
+
+Wrapper library over [wireplumber](https://pipewire.pages.freedesktop.org/wireplumber/) to better integrate with Astal.
+
+## Installation
+
+1. install dependencies
+
+:::code-group
+
+```sh [<i class="devicon-archlinux-plain"></i> Arch]
+sudo pacman -Syu meson vala wireplumber gobject-introspection
+```
+
+```sh [<i class="devicon-fedora-plain"></i> Fedora]
+sudo dnf install meson gcc valac wireplumber-devel gobject-introspection-devel
+```
+
+```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu]
+# Not yet documented
+```
+
+:::
+
+2. clone repo
+
+```sh
+git clone https://github.com/aylur/astal.git
+cd astal/lib/wireplumber
+```
+
+3. install
+
+```sh
+meson setup build
+meson install -C build
+```
+
+:::tip
+Most distros recommend manual installs in `/usr/local`,
+which is what `meson` defaults to. If you want to install to `/usr`
+instead which most package managers do, set the `prefix` option:
+
+```sh
+meson setup --prefix /usr build
+```
+
+:::
+
+## Usage
+
+You can browse the [Wireplumber reference](https://aylur.github.io/libastal/wireplumber).
+
+### CLI
+
+There is no CLI for this library, use the one provided by wireplumber.
+
+```sh
+wpctl --help
+```
+
+### Library
+
+:::code-group
+
+```js [<i class="devicon-javascript-plain"></i> JavaScript]
+import Wp from "gi://AstalWp"
+
+const audio = Wp.get_default().audio
+
+print(audio.default_speaker.volume)
+```
+
+```py [<i class="devicon-python-plain"></i> Python]
+from gi.repository import AstalWp as Wp
+
+audio = Wp.get_default().get_audio()
+
+print(audio.get_default_speaker().get_volume())
+```
+
+```lua [<i class="devicon-lua-plain"></i> Lua]
+local Wp = require("lgi").require("AstalWp")
+
+local audio = Wp.get_default().audio
+
+print(audio.default_speaker.volume)
+```
+
+```vala [<i class="devicon-vala-plain"></i> Vala]
+// Not yet documented
+```
+
+:::