Migrating from v1
Ags was rewritten from scratch and unfortunately everything changed drastically, you will have to rewrite your projects from the ground up.
You will have to go through the new wiki either way, but I'll highlight some of the changes you will have to make.
Sticking to v1
If you wish to stick to v1 you can clone the last commit
NixOS
On NixOS simply switch the flake input to ags.url = "github:aylur/ags/v1"
.
Arch
On Arch you can use the v1 pkgbuild
Entry Point
Instead of a fixed ~/.config/ags/config.js
entry you can name the main file anything and specify it as an arg to ags run </path/to/entry>
.
If you wish to stick to having the source code in ~/.config/ags
then name the entry file app.js
, app.ts
, app.jsx
or app.tsx
which ags run
will use by default.
The entry point in code changed from App.config
to App.start
App.config({
windows: [
// window instances
]
})
import { App } from "astal/gtk3"
App.start({
main() {
// any initialization code
}
})
Instantiating widgets
It is no longer recommend to create top level instances because scripts can run in client mode and it is recommended to only execute code in either main
or client
callbacks.
const win = Widget.Window()
App.config({
windows: [win]
})
App.main({
main() {
new Widget.Window()
}
})
Templating
AGS now supports and recommends the usage of JSX.
const _ = Widget.Box({
vertical: true,
children: [
Widget.Label("hello")
]
})
const _ = <box vertical>
<label label="hello" />
</box>
const _ = Widget.Box({
child: var.bind().as(v => MyWidget(v))
})
const _ = <box>
{var(v => <MyWidget v={v} />)}
</box>
const _ = Widget.Box({
children: var.bind().as(v => [
MyWidget(v)
])
})
const _ = <box>
{var(v => <>
<MyWidget v={v} />
</>)}
</box>
Reactivity
Reactivity is still done through Binding
objects and widget constructors. API of bindable objects are now defined.
const label = Variable("hello")
Label({
label: label.bind().as(hello => `${hello} world`)
})
<label
label={label(hello => `${hello} world`)}
/>
Hooks
Widget.Button({
setup: self => {
self.on("signal-name", handler)
self.hook(obj, handler, "changed")
}
})
<button
onSignalName={handler}
setup={self => {
self.hook(subscribable, handler)
self.hook(connectable, "signal-name", handler)
}}
/>
NOTE
.keybind
and .poll
hooks have been removed. Polling should be done in Variable
. Keybinds can be done in an onKeyPressEvent signal handler
Widgets
Some widgets are no longer builtin, you'll have to make a subclass.
const cb = Widget.ColorButton()
const ColorButton = astalify(Gtk.ColorButton)
const cb = <ColorButton />
Variable
Instead of passing a config object as the second argument Variable
now has .poll(), .watch() and .observe().
// creating
const v = Variable("0", {
poll: [1000, "command"],
})
const v = Variable("initial")
.poll(1000, "command")
// binding
const b1: Binding<number, any, any> = v1.bind().as(Number)
const b2: Binding<number> = v2(Number)
// get and set
v.getValue()
v.value
v.setValue("value")
v.value = "value"
v.get()
v.set("value")
// watching for changes
v.connect('changed', ({ value }) => {
console.log(value)
})
v.subscribe(value => {
console.log(value)
})
Variable composition is also a lot more flexible.
Globals
App
, Service
, Utils
, Widget
, Variable
are no longer globally available
import { Widget, App } from "astal/gtk3"
import * as fileUtils from "astal/file"
import * as procUtils from "astal/process"
import * as timeUtils from "astal/time"
import Variable from "astal/variable"
Services
These are no longer called Service
. There is no longer a distinction between a Service
and GObject.Object
and there are no longer builtin Services.
These are now simply external libraries that will have to be installed next to AGS. They are now implemented in Vala or C which makes it possible to also use them outside of AGS, which is the reason for the existence of Astal and AGSv2.
They work very similarly however.
// importing
const battery = await Service.import("battery")
import Battery from "gi://AstalBattery"
const battery = Battery.get_default()
// binding
const b = battery.bind("percentage").as()
import { bind } from "astal"
const b = bind(battery, "percentage").as()
Creating custom "Services" now simply means creating a GObject.Object
subclass.
class MyService extends Service {
static {
Service.register(this, {
'my-signal': ['float'],
}, {
'my-value': ['float', 'rw'],
});
}
get my_value(): number
set my_value(v: number)
}
import { GObject, register, signal, property } from "astal/gobject"
@register()
class MyService extends GObject.Object {
@property(Number) declare myValue: number
@signal(Number) declare mySignal: (n: number): void
}
Utils
File, Process and Time utility functions are available from their own module
Utils.exec("command")
Utils.readFile("file")
Utils.timeout(1000, callback)
import { exec, readFile, timeout } from "astal"
exec("command")
readFile("file")
timeout(1000, callback)
Icon lookup is available from Astal.
Utils.lookUpIcon("icon-name")
import { Astal } from "astal/gtk3"
Astal.Icon.lookup_icon("icon-name")
Authenticating have been moved to AstalAuth
Fetch has not been ported, you can use wget
or curl
with an exec
or use libsoup. You could also copy paste the source code of Utils.fetch into your own project.
Sending notifications will be available in AstalNotifd. Until then see #26.
CLI
To make windows toggleable through cli you will have to now pass the App
instance to Window
instances instead of passing a an array of windows to App.config
.
App.config({
windows: [
Widget.Window({ name: "window-name" })
]
})
App.start({
main() {
<window name="window-name" application={App}></window>
}
})
ags --run-js
have been retired in favor of requests.
globalThis.myfunction = () => {
print("hello")
}
App.start({
requestHandler(request: string, res: (response: any) => void) {
if (request == "myfunction") {
res("hello")
}
res("unknown command")
},
})
ags -r "myfunction()"
ags request myfunction
Instance name is now defined in code instead of cli of first launch
App.start({
instanceName: "name"
})
ags -i name
ags -t window-name -i name
ags run
ags toggle window-name -i name