Getting Started with LGI (Lua GTK)
This tutorial covers everything you need to know to build your first GUI application using Lua and the GTK3 toolkit on Linux (I will be assuming Arch Linux, but any distro will work just fine). The focus will be on clean code architecture and understanding the core concepts that often confuse beginners.
Prerequisites
Before writing code, ensure the environment is set up correctly. Three main components are needed:
Lua: The programming language interpreter. GTK3: The underlying C library that draws the windows and widgets (buttons, labels, etc.). LGI: The Lua binding that allows your Lua code to talk to the GTK C library.
We will use Luarocks (the Lua package manager) for the Lua-specific libraries to ensure we have the most up-to-date version.
Installation
First, ensure you have the necessary system tools and the GTK3 library. Open your terminal:
sudo pacman -S vim gtk3 luarocks
Now, install the Lua bindings using Luarocks. This pulls down the LGI library which bridges Lua and GTK.
luarocks install lgi luarocks install luafilesystem
The Mental Model: How GTK Works
Before typing code, it is crucial to understand how GTK (and therefore LGI) structures an application. If you skip this, the code will feel arbitrary.
Widgets
Everything is a βWidgetβ. A window is a widget. A button is a widget. A text label is a widget. You build an application by creating widgets and stacking them together.
Containers and Packing
A Window widget can only hold one child item. If you want a window with 5 buttons, you cannot simply βaddβ 5 buttons to the window. The window will reject them. Instead, you add a Container (like a Box) to the window, and then you βpackβ your 5 buttons into that Box.
Visual Representation
Window
Box (Container)
Button 1
Button 2
Label
Properties as Tables
In traditional C programming, setting a button label looks like a function: gtkbuttonset_label(btn, βClick Meβ). In LGI Lua, we use a cleaner object-oriented style. Properties are treated like table fields:
btn.label = "Click Me"
First Application
We will build a simple βGreeterβ application. It will have a text input field, a button, and a result label.
Project Setup
Create a directory for your project to keep things organized. We will use Vim to create and edit our main file.
mkdir lua_greeter cd lua_greeter vim main.lua
Writing the Code
Press i to enter Insert Mode in Vim, then paste the following code. Read the comments (lines starting with β) to understand what each part does.
-- 1. LOAD THE LIBRARY local lgi = require("lgi") local Gtk = lgi.require("Gtk", "3.0") -- 2. INITIALIZE GTK -- This function checks the system environment and prepares the GTK engine. -- It must be called before creating any widgets. Gtk.init() -- 3. CREATE THE MAIN WINDOW local window = Gtk.Window { title = "My First LGI App", default_width = 400, default_height = 300, -- CONNECTING SIGNALS: -- When the user clicks the 'X' button, the 'destroy' signal is emitted. -- We connect this directly to Gtk.main_quit to stop the application loop. -- Without this, the window would close, but the process would keep running in the background. on_destroy = Gtk.main_quit } -- 4. CREATE CONTAINERS -- Since a Window can only hold one item, we create a vertical Box. -- This Box will stack our items top-to-bottom. local vbox = Gtk.Box { orientation = "VERTICAL", spacing = 10, -- 10 pixels of space between children -- Margins create padding around the edges of the box inside the window margin_top = 20, margin_bottom = 20, margin_start = 20, margin_end = 20 } -- 5. CREATE WIDGETS local entry = Gtk.Entry { placeholder_text = "Type your name here..." } local btn = Gtk.Button { label = "Greet Me" } local resultLabel = Gtk.Label { label = "Result will appear here..." } -- 6. DEFINE LOGIC (SIGNAL HANDLERS) -- We define what happens when the button is clicked. -- 'on_clicked' is a special event listener built into Gtk.Button. function btn:on_clicked() local name = entry.text -- Access the text property of the entry widget if name == "" then resultLabel.label = "Please enter a name!" else resultLabel.label = "Hello, " .. name .. "!" end end -- 7. ASSEMBLE THE UI -- We use pack_start to add widgets to our Box container. -- Syntax: box:pack_start(widget, expand, fill, padding) -- expand (false): Should this widget take up all extra vertical space? (No) -- fill (false): If expanded, should the widget fill that space? (No) -- padding (0): Extra pixels outside the widget. vbox:pack_start(entry, false, false, 0) vbox:pack_start(btn, false, false, 0) -- For the label, we set expand to 'true'. It will take all remaining empty space. vbox:pack_start(resultLabel, true, true, 0) -- Finally, add the fully populated Box to the Window. window:add(vbox) -- 8. RUN THE APPLICATION -- show_all() renders the window and all its children. window:show_all() -- Gtk.main() starts the Event Loop. -- The program pauses here and waits for user actions (clicks, key presses). -- It will only exit when Gtk.main_quit() is called. Gtk.main()
Press Esc to exit Insert Mode, then type :wq and press Enter to save and quit Vim.
Running the Application
Back in the terminal, run your script with the Lua interpreter:
lua main.lua
You should see a native Linux window appear. Try typing your name and clicking the button.
Key Concepts Deep Dive
The Event Loop
You will notice that the script does not finish execution immediately. It hangs at the line Gtk.main(). This is intentional.
GUI applications are event-driven. They do not run linearly from start to finish; they wait. The Gtk.main() function creates a loop that constantly listens for events (mouse movements, clicks, system redraw requests). The program sleeps inside this loop until an event occurs.
Layout Management (Packing)
The pack_start function is the heart of GTK layout. It determines how widgets behave when you resize the window.
Expand (true/false): Does the widget grow if the window gets larger? Fill (true/false): If the widget grows, does the content fill the new space, or does the widget just float in the center?
Example: If you set a Button to expand=true and fill=false, the button would stay its normal size but float in the middle of a large empty space when you maximize the window.
Finding Documentation
LGI documentation is sparse, but you can use the official GTK3 C documentation and βtranslateβ it to Lua mentally.
C Syntax: gtk_button_new_with_label("Hello")
Lua Syntax: Gtk.Button { label = "Hello" }
C Syntax: gtk_window_set_title(win, "Title")
Lua Syntax: window.title = "Title"
Resource: Search for GTK3 Reference Manual online. It lists all widgets (Buttons, ComboBoxes, Scrollbars) and their properties, which apply directly to LGI.
- snippet.cpp
by: β βββ β βββββββ βββββββ β written: February 25 2026
