Skip to content

Simpler Actions

heliguy4599 edited this page Jul 7, 2026 · 1 revision

Gio.SimpleAction and Gio.PropertyAction are GObject's mechanisms for exposing named, invokable operations on a widget or application. They are similar to signals, but feature different options and use cases. They integrate with GTK's action system, meaning they can be triggered from menus and keyboard shortcuts without needing access to the source object.

Background - Simple Actions in Plain GJS

In plain GJS, setting up Simple Actions on a widget requires some manual wiring.

class MyWdiget extends Gtk.Box {
	static {
		GObject.registerClass({ GTypeName: "MyWidget" }, this)
	}

	readonly #save_action = new Gio.SimpleAction({ name: "save" })
	readonly #open_action = new Gio.SimpleAction({ name: "open", parameter_type: new GLib.VariantType("s") })

	constructor(params?: Partial<Gtk.Box.ConstructorParams>) {
		super(params)
		const group = new Gio.SimpleActionGroup()

		this.#save_action.connect("activate", this.#on_save.bind(this))
		group.add_action(this.#save_action)

		this.#open_action.connect("activate", this.#on_open.bind(this))
		group.add_action(this.#open_action)

		this.insert_action_group(group)
	}

	#on_save(): void {
		print("saving!")
	}

	#on_open(action: Gio.SimpleAction, path: GLib.Variant<"s">): void {
		print("opening:", path)
	}
}

Every action requires manual instantiation, connection, and the action group itself needs to be created and inserted manually as well.

The GObjectify Way

@GClass()
class MyWidget extends from(Gtk.Box, {
	save: SimplerAction.void(),
	open: SimplerAction.param.string(),
}) {
	@OnSimplerAction("save")
	#on_save(): void {
		print("saving!")
	}

	@OnSimplerAction("open")
	#on_open(path: string): void {
		print("opening", path)
	}
}

GObjectify creates and inserts the action group, creates each action and adds them to the group, and through the use of OnSimplerAction, connects the action's activation or state-change to the method. Also, each action is available on the instance by name (example: this.save is the save SimplerAction).

Action Kinds

Every action created through SimplerAction falls into one of four kinds:

  • void: SimplerAction.void(config?) no parameters and no stored state.
  • param: SimplerAction.param.<type>(config?) takes on parameter of the specified type when activated, but does not store the value.
  • state: SimplerAction.state.<type>(cofnig?) takes a parameter of the specified type when activated, and does store the value, which can be read and written through with the .state property on the action.
  • property: SimplerAction.property(field, transformer, config?). This is a Gio.PropertyAction, a type of action which bases off of a GObject instance's property of the given field name. The field used must be a readwrite property of type boolean, string, or number. See the Property Actions section below for more info.

Property Actions

SimplerAction.property creates a Gio.PropertyAction, whose state always mirros the value of the backing property on the class. The backing property must be a readwrite property of type boolean, string, or number. This kind of action is primarily useful for Gio.Menu bindings, which allow an application's menu to change options of operation, or other settings, such as a Theme setting.

Example:

@GClass()
class MyWidget extends from(Gtk.Box, {
	dark_mode: Property.readwrite.bool(),
	dark_mode_act: SimplerAction.property("dark_mode", (v) => GLib.Variant.new_boolean(v)),
}) { ... }

The transformer function (second parameter to property) converts an incoming JS value into the GLib.Variant needed to update the underlying property. Property actions cannot be connected with @OnSimplerAction, since the changes are done to the underlying property. The @WatchProp decorator can be used insidead for this behavior.

Accessing Actions on the Instance

Each action is available on the instance by name, with the Action's kind defining what methods and fields are available on it.

Kind Members
void .activate(), .on_activated(callback), .enabled
param .activate(param), .on_activated(callback), .enabled
state .activate(new_state), .on_state_changed(callback), .state, .enabled
prop .activate(new_state), .enabled (read-only)

Connecting Handlers with @OnSimplerAction

The method decorator @OnSimplerAction connects a method to the specified action's activate or state-change event. The decorator accepts the name of the action field on the class. Multiple may be stacked upon a method to connect it to multiple actions.

@OnSimplerAction("save")
#on_save(): void { ... }

// param action, recieves the unpacked parameter directly
@OnSimplerAction("rename")
#on_rename(new_name: string): void { ... }

// state action, recieves the new unpacked state directly
@OnSimplerAction("sort_order")
#on_sort_order_changed(order: string): void { ... }

@OnSimplerAction cannot target prop-kind actions, since they rely on the underlying property changing instead of their own state changing.

Keyboard Shortcuts (Accelerators)

On subclasses of GioApplication and GtkApplicationWindow, Simple Actions may optionally have accelerators, which allows them to be activated with keyboard shortcuts.

@GClass()
export class MyApp extends from(Gtk.Application, {
	save: SimplerAction.void({ accels: ["<Control>s"] }),
	open: SimplerAction.void({ accels: ["<Control>o"] }),
}) { ... }

The save and open actions can be globally activated by pressing the corresponding keyboard shortcut. The string format for the accels array follows the gtk_accelerator_parse format used in gtk_application_set_accels_for_action. Attempting to use accels on any other kind of subclass throws an error as soon as the class is decorated.

Where Actions are Registered

GObjectify automatically determines where to register actions based on the class being extended:

  • Gtk.Widget -> actions go into a Gio.SimpleActionGroup inserted into the instance
  • Gtk.ApplicationWindow -> actions are added directly to the window instance
  • Gtk.Application -> actions are added directly to the application instance, and accelerators are registered

Detailed Action Names & Prefixes

Every action is ultimately addressed by a detailed action name, which is a prefix + name to refer to the action. GTK expects these string-based IDs Gio.MenuItem.set_detailed_action and widget.activate_action. GObjectify chooses the prefix automatically based on what the class extends:

Extends Prefix Example
Gtk.ApplicationWindow win save: SimplerAction... -> "win.save"
Gio.Application & Gtk.Application app quit: SimplerAction... -> "app.quit"
Any other subclass The class's own name refresh: SimplerAction... -> "MyWidget.refresh"

For plain widgets, this isn't an arbitrary choice. GObjectify uses the class name as the name of the Gio.SimpleActionGroup it inserts onto the instance, so the group and the prefix always agree.

You will rarely need to type a detailed action name yourself, as the action's own .activate() method, @OnSimplerAction, Menu.item/Menu.item_group/Menu.items_for, and $actions.<name>.activate() (below) all resolve it internally. Base-GTK/GIO methods will still ask for this detailed name, so it's useful to know.

Static Action Descriptors

Every class that extends from also exposes a static $actions object, giving access to each action's fully-qualified detailed_name, an activate method, and the underly GLibVariant format string, all without needing an instance at all. The static descriptor holds the static activate method, the detailed action.

type SortOrder = "name" | "date-created"

@GClass()
class MyBox extends from(Gtk.Box, {
	save: SimplerAction.void(),
	sort_order: Property.readwrite.string("name").as<SortOrder>(),
	sort_order_act: SimplerAction.property("sort_order", (s: SortOrder) => GLib.Variant.new_string(s)),
}) { ... }

// No need to have a reference to a MyBox object to activate its actions in a type-safe manner
const w = new SomeWidgetClass()
MyBox.$actions.save.activate(w)
MyBox.$actions.sort_order_act.activate(w, "date-created")

Clone this wiki locally