- Fedora:
sudo dnf install vala gtk4-devel - Debian/Ubuntu:
sudo apt install valac libgtk-4-dev - Arch Linux:
sudo pacman -S vala gtk4 - macOS (Homebrew):
brew install vala gtk4 - Windows: Use WSL (Windows Subsystem for Linux) or MSYS2 and follow the Linux instructions.
Verify Installation: valac --version
Use the Meson build system for any project with more than one file.
1. Create a meson.build file:
project('my-gtk-app', 'vala', 'c')
dependencies = [
dependency('gtk4'),
dependency('glib-2.0')
]
executable(
'my-app',
'src/main.vala',
'src/window.vala',
dependencies: dependencies,
install: true
)2. Build your project:
meson setup build
ninja -C buildVala is statically typed, with var for convenient type inference.
string name = "John Doe";
int age = 42;
var greeting = "Hello, Vala!"; // Inferred as 'string'Convention is lower_case_with_underscores.
void print_greeting(string name) {
stdout.printf("Hello, %s\n", name);
}
int add(int a, int b) { return a + b; }Standard if-else, for, while, foreach, and switch.
var colors = new string[] {"Red", "Green", "Blue"};
foreach (var color in colors) {
stdout.printf("Color: %s\n", color);
}Inherit from GLib.Object for automatic memory management, signals, and properties.
public class User : GLib.Object {
public string username { get; set; }
public User(string username) { Object(username: username); }
}Vala manages memory by counting references to objects. When the count drops to zero, the object is freed.
-
The Danger (Reference Cycles): If object A holds a reference to B, and B holds a reference back to A, they create a memory leak.
-
The Solution (
weak): Declare one reference asweakto break the cycle. A weak reference does not keep an object alive.public class Page : GLib.Object { public weak Document parent_document; // Breaks the cycle }
If a regular (non-async) method can fail, it must be declared with throws.
public errordomain ConfigError { PARSE_FAILED; }
public void load_config() throws ConfigError {
throw new ConfigError.PARSE_FAILED("Invalid syntax");
}
try {
load_config();
} catch (ConfigError e) {
warning("Error: %s", e.message);
}This is one of the most powerful but trickiest parts of Vala. Errors in async methods are handled differently.
To catch an error from an async method, you MUST call it with yield from within another async method. The try-catch block must surround the yield statement.
Here is how to correctly handle a potential failure in an asynchronous operation.
// 1. An async method that can fail
public errordomain NetworkError { TIMEOUT; }
public async string fetch_data() throws NetworkError {
// Simulate a network operation that might fail
var success = GLib.Random.next_boolean();
if (success) {
yield new GLib.Timeout.add_seconds(1, () => { return false; });
return "Here is your data";
} else {
throw new NetworkError.TIMEOUT("The server did not respond.");
}
}
// 2. A calling method that correctly handles the error
public async void perform_fetch() {
stdout.printf("Attempting to fetch data...\n");
try {
// The 'yield' pauses execution and waits for fetch_data to finish
string data = yield fetch_data();
// This code only runs if fetch_data() SUCCEEDS
stdout.printf("Success! Data: %s\n", data);
} catch (NetworkError e) {
// The catch block executes if fetch_data() throws an error
stderr.printf("Caught an error: %s\n", e.message);
}
}If you call an async method without yield, you cannot catch its exceptions. The try-catch block will be useless.
public void bad_error_handling() {
try {
// WRONG: This just starts the operation and continues immediately.
// The error will be thrown later, long after the try-catch block has finished.
fetch_data();
} catch (NetworkError e) {
// THIS CODE WILL NEVER BE REACHED
stderr.printf("This will never print.\n");
}
// The program will likely crash or print a critical error to the console.
}Errors bubble up through a yield chain, just like a regular call stack.
// This method doesn't handle the error, it just passes it up.
// Note the 'async throws' signature.
public async string get_and_process_data() throws NetworkError {
// We yield to fetch_data(). If it throws, the error is
// automatically passed up to whoever called get_and_process_data().
string raw_data = yield fetch_data();
return raw_data.up(); // Process the data
}
// The top-level caller can then handle it:
public async void top_level_caller() {
try {
string processed = yield get_and_process_data();
stdout.printf("Processed data: %s\n", processed);
} catch (NetworkError e) {
stderr.printf("Error occurred deep in the call chain: %s\n", e.message);
}
}Sometimes you must start an async operation from a synchronous context (like a signal handler), where you cannot use yield. In this case, you must use the traditional _async/_finish pattern with a callback.
yield is just syntactic sugar for this pattern.
// In a GTK widget (e.g., a window class)
private void on_my_button_clicked() {
// The handler itself is not async, so we cannot use 'yield'.
// We must use a callback to get the result.
perform_fetch.callback.begin(this, (source, res) => {
try {
// The corresponding _finish method re-throws the exception.
// The try-catch block MUST be here, in the callback.
perform_fetch.callback.end(res);
} catch (Error e) {
// Now we can handle the error properly
var dialog = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
"An error occurred: %s".printf(e.message));
dialog.present();
}
});
}All GTK4 apps must start with a Gtk.Application class.
// src/main.vala
public class MyApplication : Gtk.Application {
public MyApplication() { Object(application_id: "com.example.MyApp"); }
protected override void activate() {
var window = new MyWindow(this);
window.present();
}
}
public static int main(string[] args) { return new MyApplication().run(args); }The standard way to link a UI design file (.ui) to a Vala class.
my_window.ui file:
<interface>
<template class="MyWindow" parent="GtkApplicationWindow">
<child>
<object class="Gtk.Button" id="hello_button">
<signal name="clicked" handler="on_hello_button_clicked"/>
</object>
</child>
</template>
</interface>my_window.vala file:
[Gtk.Template(ui = "/com/example/myapp/my_window.ui")]
public class MyWindow : Gtk.ApplicationWindow {
[Gtk.Child] private Gtk.Button hello_button;
public MyWindow(Gtk.Application app) { Object(application: app); }
[Gtk.Callback]
private void on_hello_button_clicked() { /* ... */ }
}The efficient, modern way to display lists of data. It separates the data model from the view.
The modern way to handle mouse and keyboard input.
var gesture = new Gtk.GestureClick();
gesture.pressed.connect((n_press, x, y) => {
stdout.printf("Widget clicked at (%.1f, %.1f)\n", x, y);
});
my_widget.add_controller(gesture);Use add_tick_callback to sync animations with the screen's refresh rate.
widget.add_tick_callback((_, clock) => {
widget.queue_draw(); // Request a redraw
return true; // Return true to continue animating
});