Stateful Actions
Actions that maintain a state are called stateful actions. While these states are typically boolean or string values, other data types can also be used when necessary.
Stateful actions fall into two main categories:
- Stateful actions without parameters: Often used for toggle menus. The state is usually a boolean (TRUE or FALSE), and activating the action simply flips the state to its opposite value.
- Stateful actions with parameters: Used when an action can take on one of several distinct states. For example, a background color action might accept “red”, “green”, or “blue”. To change the state, the caller must pass the target value as a parameter.
Action Activation and State Changes
GIO provides two primary functions for working with actions:
g_action_activate(): activates the action. This function emits the “activate” signal.g_action_change_state(): requests a state change on the action. Note that this function itself doesn’t change the state. This function just emits the “change-state” signal.
Menus and Shortcuts Always Go Through Activate
Menu items and keyboard shortcuts work by calling
g_action_activate() internally. This means the
starting point is always the "activate" signal —
even for stateful actions. Menu items do not
call g_action_change_state() directly.
Aciivate and Change-state Default Behavior
Forwarding Activate to Change-state
So how do stateful actions work naturally with menus?
GSimpleAction has a helpful default behavior built
in.
If no handler is connected to the “activate”
signal, GSimpleAction will automatically forward
the activation to “change-state” (since GLib 2.40), under the
following conditions:
| Action type | Default behavior |
|---|---|
| Boolean state, no parameter type | Toggles the state via “change-state” |
| State type matches the parameter type (*) | Forwards directly to “change-state” |
Note:
- This forwarding is specific to
GSimpleAction. It is not a general feature of theGActioninterface. - (*) If the caller’s parameter type doesn’t match the state type of the action, no signal will be emitted.
change-state’s Default Behavior
When “change-state” signal is emitted and no handler is
connected, GSimpleAction will automatically call
g_simple_action_set_state() to apply the requested
value. This means that for simple use cases where no validation
is needed, you don’t have to write any handler at all — the
state just updates itself.
A Recommended Pattern: Put Your Logic in change-state
Taking advantage of this default behavior leads to a clean and robust design pattern:
Leave "activate" unhandled, and connect
your logic to "change-state" instead.
static void
on_change_state (GSimpleAction *action,
GVariant *value,
gpointer user_data)
{
/* Optionally validate the requested value here */
g_simple_action_set_state (action, value); /* Apply the new state */
/* Perform tasks related to the state change */
}
g_signal_connect (action, "change-state", G_CALLBACK (on_change_state), NULL);With this pattern, activations from menus, keyboard
shortcuts, and direct calls to
g_action_change_state() all flow through the same
handler consistently.
- If you connect your own
"activate"handler: The automatic forwarding to"change-state"is disabled. You become responsible for handling the state update yourself. - If you connect your own
"change-state"handler: The default behavior of automatically callingg_simple_action_set_state()is also disabled. If you want the state to actually change, you must callg_simple_action_set_state()explicitly from within your handler. If you don’t, the state will remain unchanged — which is intentional, as it allows you to validate or reject requests before applying them.
Stateful Action without Parameters
A common example of a stateful action without a parameter is a toggle menu. Its state, represented by a boolean value (TRUE or FALSE), changes each time the menu is clicked.
The following example demonstrates how to implement a toggle menu that updates a GtkLabel. The label is initialized to “OFF”, and its text alternates between “ON” and “OFF” with each click.
This is a part of the menu2.c program, defining
the toggle action and the menu item.
GSimpleAction *act_toggle_switch
= g_simple_action_new_stateful ("toggle-switch", NULL, g_variant_new_boolean (FALSE));
g_signal_connect (act_toggle_switch, "change-state", G_CALLBACK (toggle_switch_changed), label);
g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_toggle_switch));
... ... ...
GMenuItem *menu_item_toggle_switch = g_menu_item_new ("Toggle Switch", "win.toggle-switch");act_toggle_switch is a GSimpleAction instance
created with g_simple_action_new_stateful. This
function takes three arguments:
- The first argument, “toggle-switch”, is the name of the action.
- The second argument is the parameter type. NULL means this action does not take any parameters.
- The third argument is the initial state of the action. It is
provided as a GVariant value (which will be explained in the
next subsection). Here,
g_variant_new_boolean (FALSE)sets the initial state to FALSE.
The action’s “change-state” signal is connected to the
handler toggle_switch_changed. Notice that we pass
label (the GtkLabel) as the user_data so we can
update its text later. As explained in the previous section, we
intentionally leave the “activate” signal unconnected to take
advantage of forwarding it to the “change-state” signal .
The action is added to the GtkWindow (win) using
g_action_map_add_action. Therefore, the scope of
this action is win.
menu_item_toggle_switch is a GMenuItem instance
created with two arguments.
- The first argument, “Toggle Switch”, is the label displayed on the menu.
- The second argument, “win.toggle-switch”, is the action name. The prefix “win.” indicates that the action belongs to the window.
static void
toggle_switch_changed(GSimpleAction *action, GVariant *value, gpointer user_data) {
GtkWidget *label = GTK_WIDGET (user_data);
if (g_variant_get_boolean (value))
gtk_label_set_text (GTK_LABEL (label), "ON");
else
gtk_label_set_text (GTK_LABEL (label), "OFF");
g_simple_action_set_state (action, value);
}This is the signal handler connected to the “change-state” signal of the “toggle-switch” action.
The handler toggle_switch_changed has three
parameters:
- The first parameter is the action that emits the “change-state” signal.
- The second parameter, value, is the requested new state of the action.
- The third parameter is the
user_datathat was passed tog_signal_connect. In this case, it is the pointer to the GtkLabel.
The handler checks the boolean value of the requested state. If it is TRUE, it sets the label’s text to “ON”. Otherwise, it sets the text to “OFF”.
Finally, it sets the actual state of the action using
g_simple_action_set_state.
Note: Before this function is called, the action still holds
its original state. You must explicitly call
g_simple_action_set_state to update the action to
the new state.
As discussed in the previous section, by exclusively handling the “change-state” signal and omitting the handler for the “activate” signal, this single handler gracefully manages both menu clicks and any direct state-change requests.
GVariant and GVariantType
GVariant
Before moving on to the “Stateful Actions with Parameters” subsection, we will discuss GVariant, as these parameters are passed as GVariant instances. This subsection and the next subsection describes GVariant and GVariantType.
GVariant is a fundamental data type in GLib. Unlike many other classes in GTK, it is not a child of GObject. It acts as a strongly-typed container that can hold various types of data, such as booleans, strings, and numeric values (e.g., int16, int32, int64, double).
You might wonder why we need a specialized container like GVariant instead of using standard C types. There are three primary reasons:
- Serialization and Portability: GVariant is designed to be easily “serialized” (converted into a stream of bytes). This makes it perfect for storing data in files, sending data over a network (like D-Bus for inter-process communication), or passing parameters between different programming languages (language bindings).
- Self-Describing Types: In C, data types are defined at compile time, not at runtime. Consequently, a receiver cannot natively determine the data type of a generic pointer at runtime. In contrast, GVariant packages the data along with its type information, ensuring that the receiver always knows exactly what kind of data they are handling.
- Flexibility in UI Actions: In GTK, actions (like “Quit”, “Copy”, or “Change Color”) are often shared across different UI elements (menus, shortcuts, buttons). Since these UI elements might pass different types of data (e.g., a color name as a string, or a zoom level as a double), GVariant serves as a universal, type-safe wrapper that allows any action to receive any data format safely.
The following code creates a GVariant holding a boolean value of TRUE:
GVariant *value = g_variant_new_boolean (TRUE);Similarly, you can create a GVariant containing a string:
GVariant *value2 = g_variant_new_string ("Hello");To extract the original data from a GVariant, you use the
g_variant_get_* family of functions. For instance,
you can retrieve the boolean value from our first example like
this:
gboolean bool_val = g_variant_get_boolean (value); /* bool_val is now TRUE */In the same way, you can extract the string from value2:
const char *str = g_variant_get_string (value2, NULL);The second parameter of g_variant_get_string is
a pointer to a gsize variable (gsize
is defined as an unsigned integer). If you pass a
valid pointer, the function stores the length of the string in
that variable. If you pass NULL, the length is simply
ignored.
Note: The returned string (str) is owned by the
GVariant instance. It must not be modified or freed by the
caller.
GVariantType
GVariantType represents the type of a GVariant. It is initialized using a type string. Type string examples are:
- “b”: Boolean type.
- “s”: String type.
See GLib documentation for details about type string.
The following program demonstrates how to create, use, and free a GVariantType instance. This example outputs the string “s”.
#include <glib.h>
int
main (int argc, char **argv) {
GVariantType *vtype = g_variant_type_new ("s");
const char *type_string = g_variant_type_peek_string (vtype);
g_print ("%s\n",type_string);
g_variant_type_free (vtype);
}g_variant_type_newcreates a GVariantType structure based on the provided type string (e.g., “s” for a string). The caller assumes ownership of the returned structure and must release it usingg_variant_type_freewhen it is no longer needed.g_variant_type_peek_stringretrieves the type string (in this case, “s”) from the GVariantType instance. The returned string is owned by the GVariantType instance itself, so the caller must not modify or free it.g_printoutputs the type string to the terminal. Note thatvtypemust not be freed before this function is called, as the string pointer depends on the lifetime of thevtypeinstance.g_variant_type_freefrees the GVariantType structure.
Alternatively, for standard types, GLib provides predefined macros. This is generally preferred as it avoids dynamic allocation.
#include <glib.h>
int
main (int argc, char **argv) {
const GVariantType *vtype = G_VARIANT_TYPE_STRING;
g_print ("%s\n", g_variant_type_peek_string (vtype));
}G_VARIANT_TYPE_STRING is a macro that provides a
pointer to a pre-allocated GVariantType structure representing a
string. Because this is a constant, it is managed by the system;
you do not need to (and should not) call
g_variant_type_free on it. Using these macros is
more efficient and safer as it eliminates the risk of memory
leaks or double-free errors.
Stateful Actions with Parameters
Next example of stateful actions is an action used with color-selection menus. Imagine three separate menus, each offering the options “red,” “green,” and “blue”. These menus are used to set the background color of a GtkLabel widget.
In this scenario, a single action can be connected to all three menus. The action maintains the selected color as its state, using a string value such as ‘red’, ‘green’, or ‘blue’ When a user makes a selection, the chosen color is passed as a parameter to the signal handler, which then updates the label accordingly.
... ... ...
GSimpleAction *act_color
= g_simple_action_new_stateful ("color", G_VARIANT_TYPE_STRING, g_variant_new_string ("red"));
GMenuItem *menu_item_red = g_menu_item_new ("Red", "app.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "app.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "app.color::blue");
GtkCssProvider *provider = gtk_css_provider_new ();
g_signal_connect (act_color, "change-state", G_CALLBACK (color_state_changed), provider);
... ... ...- The variable
act_colorpoints to a GSimpleAction instance. It is created withg_simple_action_new_stateful.- The first argument, “color”, is the name of the action.
- The second argument is the parameter type.
G_VARIANT_TYPE_STRINGrepresents a string type for GVariant. - The third argument is the initial state of the action.
g_variant_new_string ("red")returns a GVariant holding the string “red”. GVariant uses a floating reference system;g_simple_action_new_statefulconsumes this floating reference, so you do not need to manually release the GVariant instance.
- The variable
menu_item_redpoints to a GMenuItem instance. The functiong_menu_item_newtakes two arguments: the label (“Red”) and a detailed action name (“app.color::red”). The suffix after the::is the target, which is passed to the action as a parameter when the menu item is activated. The same logic applies tomenu_item_greenandmenu_item_blue. - The signal handler
color_state_changedis connected to the “change-state” signal. This signal is emitted when the action’s state is requested to be changed. Typically, clicking one of the menu items activates the action, and this activation is subsequently forwarded to the “change-state” signal. We passproviderto the handler so that the CSS can be updated accordingly. We will discuss the details of GtkCssProvider and how it handles CSS later. - Signal connection:
g_signal_connectlinks the “change-state” signal ofact_colorto thecolor_state_changedcallback. When a menu item is selected, the handler is called with the new target value (the color).
static void
color_state_changed(GSimpleAction *action, GVariant *value, gpointer user_data) {
GtkCssProvider *provider = GTK_CSS_PROVIDER (user_data);
char *color = g_strdup_printf ("label.lb {background-color: %s; font-size: 72pt; color: white;}", g_variant_get_string (value, NULL));
/* Change the CSS data in the provider. */
/* Previous data is thrown away. */
gtk_css_provider_load_from_string (provider, color);
g_free (color);
g_simple_action_set_state (action, value);
}- The handler takes three parameters.
- action: The GSimpleAction that emitted the “change-state” signal.
- value: The new state (a GVariant string) passed to the action, representing the selected color.
- user_data: The data passed from
g_signal_connect. In this case, it is the GtkCssProvider instance.
- We use
g_strdup_printfto construct a CSS rule.g_variant_get_stringextracts the raw string (e.g., “red”) from the GVariant parameter. This color is then injected into the CSS rule “label.lb { background-color: %s; … }”. gtk_css_provider_load_from_stringupdates the provider with the new CSS rule. This automatically overrides any previous styles loaded into this provider. See the following “GTK CSS styling” subsection below.- Finally,
g_simple_action_set_stateupdates the action’s internal state to the new value.
GTK CSS Styling
GTK uses a CSS-based styling system that works very similarly to web CSS (CSS is an abbreviation of Cascading Style Sheet). To style a widget, you typically follow these three steps:
- GtkCssProvider: This acts as a container for your CSS rules. You load your CSS strings or files into this provider.
- Display-wide Application: Register your GtkCssProvider with
the GdkDisplay using
gtk_style_context_add_provider_for_display. This applies the CSS rules globally to all widgets on that display. GdkDisplay represents your application’s connection to the underlying windowing system. Because of this, it manages not only the output screens (monitors) but also the input devices (keyboards and mice) you use to interact with the app. The “default” display is simply the primary connection your computer assigned when the app launched. Standard applications only use this default connection. We callgdk_display_get_default()to access this main connection. In a web browser, CSS styles a single webpage. In GTK, adding a CSS provider to the GdkDisplay acts as a “global stylesheet”. It ensures that your CSS rules are automatically applied to every widget associated with that GdkDisplay. - Selectors: Like in HTML, you use selectors to target widgets
(e.x.
label.lb):label: Targets the node name of all GtkLabel widgets..lb: Targets any widget that has the “lb” class added (usinggtk_widget_add_css_class).label.lb: Combines both, targeting only GtkLabel widgets that also have thelbclass.
By updating the GtkCssProvider dynamically via
gtk_css_provider_load_from_string, you can change
the look of your UI in real-time. Since the provider is
registered at the display level, all widgets matching the
selectors will automatically reflect the updated styles.
Example Program for Stateful Actions
This is an example program to demonstrate for the stateful actions.
#include <gtk/gtk.h>
static void
toggle_switch_changed(GSimpleAction *action, GVariant *value, gpointer user_data) {
GtkWidget *label = GTK_WIDGET (user_data);
if (g_variant_get_boolean (value))
gtk_label_set_text (GTK_LABEL (label), "ON");
else
gtk_label_set_text (GTK_LABEL (label), "OFF");
g_simple_action_set_state (action, value);
}
static void
color_state_changed(GSimpleAction *action, GVariant *value, gpointer user_data) {
GtkCssProvider *provider = GTK_CSS_PROVIDER (user_data);
char *color = g_strdup_printf ("label.lb {background-color: %s; font-size: 72pt; color: white;}", g_variant_get_string (value, NULL));
/* Change the CSS data in the provider. */
/* Previous data is thrown away. */
gtk_css_provider_load_from_string (provider, color);
g_free (color);
g_simple_action_set_state (action, value);
}
static void
app_shutdown (GApplication *app, GtkCssProvider *provider) {
gtk_style_context_remove_provider_for_display (gdk_display_get_default(), GTK_STYLE_PROVIDER (provider));
g_object_unref (provider);
}
static void
app_activate (GApplication *app) {
GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "menu2");
gtk_window_set_default_size (GTK_WINDOW (win), 800, 600);
GtkWidget *label = gtk_label_new ("OFF");
gtk_widget_add_css_class (label, "lb"); /* the class is used by CSS Selector */
gtk_window_set_child (GTK_WINDOW (win), label);
GSimpleAction *act_toggle_switch
= g_simple_action_new_stateful ("toggle-switch", NULL, g_variant_new_boolean (FALSE));
g_signal_connect (act_toggle_switch, "change-state", G_CALLBACK (toggle_switch_changed), label);
g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_toggle_switch));
g_object_unref (act_toggle_switch);
gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
gtk_window_present (GTK_WINDOW (win));
}
static void
app_startup (GApplication *app) {
/* The provider below provides application wide CSS data. */
GtkCssProvider *provider = gtk_css_provider_new ();
GSimpleAction *act_color
= g_simple_action_new_stateful ("color", G_VARIANT_TYPE_STRING, g_variant_new_string ("red"));
GSimpleAction *act_quit
= g_simple_action_new ("quit", NULL);
g_signal_connect (act_color, "change-state", G_CALLBACK (color_state_changed), provider);
g_signal_connect_swapped (act_quit, "activate", G_CALLBACK (g_application_quit), app);
g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_color));
g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
g_object_unref (act_color);
g_object_unref (act_quit);
GMenu *menubar = g_menu_new ();
GMenu *menu = g_menu_new ();
GMenu *section1 = g_menu_new ();
GMenu *section2 = g_menu_new ();
GMenu *section3 = g_menu_new ();
GMenuItem *menu_item_toggle_switch = g_menu_item_new ("Toggle Switch", "win.toggle-switch");
GMenuItem *menu_item_red = g_menu_item_new ("Red", "app.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "app.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "app.color::blue");
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (section1, menu_item_toggle_switch);
g_menu_append_item (section2, menu_item_red);
g_menu_append_item (section2, menu_item_green);
g_menu_append_item (section2, menu_item_blue);
g_menu_append_item (section3, menu_item_quit);
g_object_unref (menu_item_red);
g_object_unref (menu_item_green);
g_object_unref (menu_item_blue);
g_object_unref (menu_item_toggle_switch);
g_object_unref (menu_item_quit);
g_menu_append_section (menu, NULL, G_MENU_MODEL (section1));
g_menu_append_section (menu, "Color", G_MENU_MODEL (section2));
g_menu_append_section (menu, NULL, G_MENU_MODEL (section3));
g_menu_append_submenu (menubar, "Menu", G_MENU_MODEL (menu));
g_object_unref (section1);
g_object_unref (section2);
g_object_unref (section3);
g_object_unref (menu);
gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
g_object_unref (menubar);
/* Initialize the css data */
gtk_css_provider_load_from_string (provider, "label.lb {background-color: red; font-size: 72pt; color: white;}");
/* Add CSS to the default GdkDisplay. */
gtk_style_context_add_provider_for_display (gdk_display_get_default (),
GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), provider);
}
#define APPLICATION_ID "com.github.ToshioCP.menu2"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}- 3-22: Action signal handlers. The
toggle_switch_changedhandler toggles the label text between “ON” and “OFF” depending on the boolean state. Thecolor_state_changedhandler dynamically generates a CSS string to change the background color of the label and loads it into the GtkCssProvider. In both handlers,g_simple_action_set_state()is called at the end to explicitly update the action’s state. - 25-29: The handler
app_shutdownis called right before the application quits. It safely cleans up by removing the CSS provider from the default display and unreferencing it. - 31-49: The activate signal handler.
- 33-35: A new application window is created and assigned to
win. Its title and default size are set to “menu2” and 800x600, respectively. - 37-39: A new label is created with the initial text “OFF”
and assigned to
label. It is given a CSS class “lb” and added towinas its child widget. - 41-44: A stateful toggle action is created and assigned to
act_toggle_switchwith an initial boolean value of FALSE. It is connected to the signal handlertoggle_switch_changedand added to the window. Because it is added to the window’s action map, its scope is “win” (i.e., “win.toggle-switch”). The action object is then unreferenced. - 46: The function
gtk_application_window_set_show_menubaris called to ensure the menubar is displayed in the window. - 48: Shows the window using
gtk_window_present(). - 51-106: The startup signal handler.
- 53-64: A new GtkCssProvider is created to handle
application-wide CSS. Two actions,
act_color(stateful) andact_quit(stateless), are created. Since the startup handler is called only once, there is only one instance of each action. They are connected to their respective handlers, added to the application, and then unreferenced. Because they are added to the application’s action map, their scope is “app”. - 66-95: The menus are built. GMenuItem objects are created and appended to distinct sections (section1, section2, section3) to logically group them (e.g., Toggle Switch, Colors, Quit). Unnecessary references to the menu items and sections are freed.
- 97-98: The fully constructed menubar is applied to the application.
- 100-104: The CSS provider is initialized with default CSS data (setting the label’s background to red and font size to 72pt) and added to the default display so it affects all widgets.
- 105: Finally, the “shutdown” signal on the application is
connected to the
app_shutdownhandler.
Compiling and Running
Change your current directory to src/menu.
$ bash comp menu2
$./a.out
You will see a window with the text “OFF” in the center and a red background. You can change the text to “ON” and back to “OFF” by clicking the “Toggle Switch” menu item. You can change the background color by clicking the “Red”, “Green”, or “Blue” menu items.
If you run the application a second time while the first instance is still running, another window will appear on the screen. Note that both windows belong to the primary application instance. The second instance simply emits the “activate” signal to the primary instance and then immediately quits.
$ ./a.out & ./a.out
$
The “ON” and “OFF” states in the two windows are independent of each other because each window has its own GtkLabel instance.
However, both windows share the same GdkDisplay (the overall screen environment). Since the background color is determined by the CSS applied to the GdkDisplay, both windows will always share the same background color.
Note for WSL users:
You may notice that the menu remains on the screen after being clicked, and only disappears on your next click. This is a known issue specific to WSL. On a native Linux environment, the menu closes immediately after a selection is made.