Your First Widget
Start by creating ~/.config/ags/config.js
App.config({ windows: [ // this is where window definitions will go ]})
then run ags
in the terminal
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 toolabel.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'), }),})