Skip to content

Your First Widget

Start by creating ~/.config/ags/config.js

~/.config/ags/config.js
App.config({
windows: [
// this is where window definitions will go
]
})

then run ags in the terminal

Terminal window
ags

You will see nothing happen, just AGS hanging, this is because you have an empty config. Running ags will execute the config like a regular script, it is just a library over GTK, and its on you to program your windows and widgets.

Top Level: Window

The top level container is a Window that will hold widgets.

const myLabel = Widget.Label({
label: 'some example content',
})
const myBar = Widget.Window({
name: 'bar',
anchor: ['top', 'left', 'right'],
child: myLabel,
})
App.config({ windows: [myBar] })

Reusable Widgets

Both myLabel and myBar constants we declared are a single instance of a Gtk.Widget. What if you have two monitors and want to have a bar for each? Make a function that returns a Gtk.Widget instance.

function Bar(monitor = 0) {
const myLabel = Widget.Label({
label: 'some example content',
})
return Widget.Window({
monitor,
name: `bar${monitor}`, // this name has to be unique
anchor: ['top', 'left', 'right'],
child: myLabel,
})
}
App.config({
windows: [
Bar(0), // can be instantiated for each monitor
Bar(1),
],
})

Dynamic Content

Alright, but static text is boring, let’s make it dynamically change by updating the label every second with a date.

function Bar(monitor = 0) {
const myLabel = Widget.Label({
label: 'some example content',
})
Utils.interval(1000, () => {
myLabel.label = Utils.exec('date')
})
return Widget.Window({
monitor,
name: `bar${monitor}`,
anchor: ['top', 'left', 'right'],
child: myLabel,
})
}

Looking great, but that code has too much boilerplate. Let’s use a fat arrow instead of the function keyword, and instead of calling interval let’s use the poll method.

const Bar = (monitor = 0) => Widget.Window({
monitor,
name: `bar${monitor}`,
anchor: ['top', 'left', 'right'],
child: Widget.Label()
.poll(1000, label => label.label = Utils.exec('date')),
})

Signals

Usually it is best to avoid polling. Rule of thumb: the less intervals the better. This is where GObject shines. We can use signals for pretty much everything.

anytime myVariable.value changes it will send a signal and things can react to it

const myVariable = Variable(0)

for example execute a callback

myVariable.connect('changed', ({ value }) => {
print('myVariable changed to ' + `${value}`)
})

bind its value to a widget’s property

const bar = Widget.Window({
name: 'bar',
anchor: ['top', 'left', 'right'],
child: Widget.Label({
label: myVariable.bind().as(v => `value: ${v}`)
}),
})

incrementing the value causes the label to update and the callback to execute

myVariable.value++

For example with pactl it is possible to query information about the volume level, but we don’t want to have an interval that checks it periodically. We want a signal that signals every time its changed, so that we only do operations when its needed, and therefore we don’t waste resources. pactl subscribe writes to stdout everytime there is a change.

const pactl = Variable({ count: 0, msg: '' }, {
listen: ['pactl subscribe', (msg) => ({
count: pactl.value.count + 1,
msg: msg,
})],
})
pactl.connect('changed', ({ value }) => {
print(value.msg, value.count)
})
const label = Widget.Label({
label: pactl.bind().as(({ count, msg }) => {
return `${msg} ${count}`
}),
})
// widgets are GObjects too
label.connect('notify::label', ({ label }) => {
print('label changed to ', label)
})

Avoiding external scripts

For most of your system, you don’t have to use external scripts and binaries to query information. AGS has builtin Services. They are just like Variables but instead of a single value they have more attributes and methods on them.

const battery = await Service.import('battery')
const batteryProgress = Widget.CircularProgress({
value: battery.bind('percent').as(p => p / 100),
child: Widget.Icon({
icon: battery.bind('icon_name'),
}),
})