Skip to content

Migration Guide

From v2

Subclassing

astalify has been removed, jsx function and JSX expressions handle everything. It is possible to use Gtk widgets directly without any prior setup.

tsx
const Calendar = astalify(Gtk.Calendar) 
const _ = <Calendar />
const _ = <Gtk.Calendar /> 

If you still prefer to use regular JS functions instead of JSX, you can do

ts
import { CCProps } from "ags/gtk4"
type BoxProps = CCProps<Gtk.Box, Gtk.Box.ConstructorProps>
const Box = (props: BoxProps) => jsx(Gtk.Box, props)

Box({
  orientation: state,
  children: [Box()],
})

GObject decorators

They were updated to the stage 3 proposal. You can read more about them on the Gnim documentation.

ts
@register()
class MyObj extends GObject.Object {
  @property(String) declare myProp: string
  @property(String) myProp = ""

  @property(String) 
  @getter(String) 
  get myProp() {
    return ""
  }

  @property(String) 
  @setter(String) 
  set myProp(v: string) {
    //
  }
}

Syntax changes

  • setup -> $
  • className -> class
tsx
<button class="my-button" $={(self) => print("ref", self)} />

Variable

Variable has been removed in favor of Accessor and createState. You can read more about them on the Gnim documentation.

Dynamic rendering

Dynamic children rendering is done with <With> and <For> components. children prop can no longer take a Binding.

tsx
const value: Binding<object>
const list: Binding<Array<object>>

return (
  <box>
    {value.as((value) => ( 
      <></>
    ))}
    <With value={value}>
      {(value) => <></>}
    </With>
    {list.as(list => list.map(item => ( 
      <></>
    )))}
    <For each={list}>
      {(item) => <></>}
    </For>
  </box>
)

From v1

Ags was rewritten from scratch and unfortunately everything changed drastically, you will have to rewrite your projects from the ground up.

There were so many changes I'm unable to list everything, but these are some highlights.

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

js
App.config({ 
  windows: [
    // window instances
  ],
})
import app from "astal/gtk4/app"

app.start({
  main() {
    // any initialization code
  },
})

Instantiating widgets

It is no longer recommended 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 blocks.

js
const win = Widget.Window() 

App.config({
  windows: [win],
})
app.main({ 
  main() {
    new Widget.Window()
  },
})

Templating

AGS now supports and recommends the usage of JSX.

jsx
const _ = Widget.Box({ 
  vertical: true,
  children: [Widget.Label("hello")],
})
const _ = ( 
  <box vertical> 
    <label label="hello" />
  </box>
)

Reactivity

Variable has been removed in favor of signals.

jsx
const label = Variable("hello") 

Label({
  label: label.bind().as((hello) => `${hello} world`),
})
import { createState } from "ags"
const [label, setLabel] = createState("hello")
return <label label={label((hello) => `${hello} world`)} />

Hooks

Widgets are no longer subclassed, added methods have been removed.

jsx
Widget.Button({ 
  setup: (self) => {
    self.on("signal-name", handler)
    self.hook(obj, handler, "changed")
  },
})
import { onCleanup } from "ags"

function MyWidget() {
  const id = obj.connect("signal-name", callback)

  onCleanup(() => {
    obj.disconnect(id)
  })

  return (
    <button onClicked={handler} />
  )

}

NOTE

.keybind and .poll hooks have been removed. Polling should be done using createPoll. Keybinds should be done using the intended Gtk APIs.

Widgets

JSX handles everything, it is no longer needed to subclass widgets.

jsx
import Gtk from "gi://Gtk"
const calendar = <Gtk.Calendar />

Globals

App, Service, Utils, Widget, Variable are no longer globally available

js
import app from "ags/gtk4/app"
import * as fileUtils from "ags/file"
import * as procUtils from "ags/process"
import * as timeUtils from "ags/time"
import { createBinding, createState } from "ags"

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.

They work very similarly however.

js
// importing
const battery = await Service.import("battery") 
import Battery from "gi://AstalBattery"
const battery = Battery.get_default()

// binding
const b = battery.bind("percentage") 
import { createBinding } from "ags"
const b = createBinding(battery, "percentage")

Creating custom "Services" now simply means creating a GObject.Object subclass.

ts
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 "ags/gobject"

@register()
class MyService extends GObject.Object {
  @property(Number) myValue = 0

  @signal(Number)
  mySignal(n: number): void {}
}

Utils

  • File, Process and Time utility functions are available from their own module

    js
    Utils.exec("command") 
    Utils.readFile("file")
    Utils.timeout(1000, callback)
    Utils.fetch("url")
    import { exec } from "ags/process"
    import { readFile } from "ags/file"
    import { timeout } from "ags/time"
    import { fetch } from "ags/fetch"
  • Icon lookup is has no alternative. Use Gtk.IconTheme.

  • Authenticating have been moved to AstalAuth

  • 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 an array of windows to App.config.

js
App.config({ 
  windows: [Widget.Window({ name: "window-name" })],
})
app.start({ 
  main() {
    return <window name="window-name" application={app}></window>
  },
})

ags --run-js have been removed in favor of requests.

ts
globalThis.myfunction = () => { 
  print("hello")
}
app.start({ 
  requestHandler(request: string, res: (response: any) => void) {
    if (request == "myfunction") {
      res("hello")
    }
    res("unknown command")
  },
})
sh
ags -r "myfunction()"
ags request myfunction

Instance name is now defined in code instead of cli of first launch

js
app.start({
  instanceName: "name",
})
sh
ags -i name
ags -t window-name -i name
ags run
ags toggle window-name -i name

Released under the GPL v3.0 License