X11 Programming with the Athena Widgets
Build a working X11 GUI application using Xlib, the Xt Intrinsics, and the Athena widget set — covering the full stack from wire protocol to compiled binary.
Before you start
- ▸A working C development toolchain (gcc and make)
- ▸An X11 server or Wayland compositor with XWayland support (GNOME, KDE, or a standalone Xvfb instance)
- ▸Basic familiarity with C pointers and the concept of callback functions
- ▸Root or sudo access to install development packages
The Athena Widget Set (Xaw) is a lightweight, MIT-licensed toolkit built on top of Xlib and the X Toolkit Intrinsics (Xt). It shipped with the original MIT X11 distribution in the late 1980s and is still present on virtually every Linux desktop today, quietly powering utilities like xterm, xedit, and xclock. Writing code against Xaw forces you to understand the full X11 stack from the wire protocol up — a genuinely useful exercise even if your production UIs live in GTK or Qt.
Historical Context
The X Window System separates the display server from client applications over a network-transparent protocol. Xlib (libX11) is the thin C binding to that protocol. Sitting above Xlib, the X Toolkit Intrinsics (Xt, libXt) introduced an object-oriented widget abstraction in C, using a class/instance model predating C++. The Athena widgets (libXaw) are one concrete widget set built on Xt — MIT's own, intentionally minimal answer to Motif and OpenLook. Modern desktops use Wayland compositors, but XWayland provides a compatibility layer, and Xaw programs run inside it without modification.
Prerequisites and Package Setup
You need the Xlib, Xt, and Xaw development headers plus the matching shared libraries. The Xaw3d variant (three-dimensional appearance) is a drop-in replacement and is optional.
Debian / Ubuntu
sudo apt update
sudo apt install libx11-dev libxt-dev libxaw7-dev build-essential
Fedora / RHEL family (RHEL 9, Rocky 9)
sudo dnf install libX11-devel libXt-devel libXaw-devel gcc make
Arch Linux
sudo pacman -S libx11 libxt libxaw base-devel
Confirm the headers are reachable:
ls /usr/include/X11/Intrinsic.h /usr/include/X11/Xaw/Command.h
Understanding the Xt Programming Model
Every Xt application follows the same skeleton:
- Call
XtOpenApplication(or the olderXtAppInitialize) to connect to the X server and create a top-level shell widget. - Create child widgets, setting resources (properties) either in code or via X resource files.
- Add callbacks to handle events such as button clicks.
- Call
XtRealizeWidgetto map the widget tree to actual X windows. - Enter
XtAppMainLoop, which dispatches X events forever.
Resources are the configuration mechanism. A resource like *label: Click me in ~/.Xresources can override a widget's label without recompiling — a clean separation of behaviour and appearance that predates CSS by a decade.
A Working Minimal Example
The program below creates a window containing a single Athena Command (button) widget. Clicking it prints a message and exits.
mkdir ~/xaw-demo && cd ~/xaw-demo
cat > hello_xaw.c << 'EOF'
#include <stdio.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Command.h>
/* Callback invoked when the button is activated */
static void quit_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
(void)w; (void)call_data;
printf("Button pressed: %s\n", (char *)client_data);
XtDestroyApplicationContext(
XtWidgetToApplicationContext(w));
exit(0);
}
int main(int argc, char **argv)
{
XtAppContext app;
Widget top, button;
Arg args[4];
Cardinal n;
/* Step 1: initialise Xt, connect to X server */
top = XtOpenApplication(
&app,
"HelloXaw", /* application class (resource DB key) */
NULL, 0, /* no custom options */
&argc, argv,
NULL, /* fallback resources */
applicationShellWidgetClass,
NULL, 0
);
/* Step 2: create the Command widget with explicit resources */
n = 0;
XtSetArg(args[n], XtNlabel, "Hello, Athena!"); n++;
XtSetArg(args[n], XtNwidth, 200); n++;
XtSetArg(args[n], XtNheight, 60); n++;
button = XtCreateManagedWidget(
"quitButton", /* instance name */
commandWidgetClass, /* widget class */
top, /* parent */
args, n
);
/* Step 3: register callback */
XtAddCallback(button, XtNcallback, quit_cb, (XtPointer)"goodbye");
/* Step 4 & 5: realise and loop */
XtRealizeWidget(top);
XtAppMainLoop(app);
return 0; /* not reached */
}
EOF
Compiling
Link against libXaw, libXt, and libX11 explicitly. The order matters to the linker — higher-level libraries come first.
gcc -Wall -Wextra -o hello_xaw hello_xaw.c \
-lXaw -lXt -lX11
Running
On a native X session or inside XWayland (which starts automatically on GNOME/KDE Wayland desktops when an X11 client launches):
./hello_xaw
A small window appears with a grey button labelled Hello, Athena!. Clicking it prints Button pressed: goodbye to the terminal and exits.
Overriding Resources Without Recompiling
Resources can be set in ~/.Xresources and loaded with xrdb. The selector syntax is ApplicationClass.instanceName.resourceName.
cat >> ~/.Xresources << 'EOF'
HelloXaw.quitButton.label: Farewell, World!
HelloXaw.quitButton.background: steelblue
HelloXaw.quitButton.foreground: white
EOF
xrdb -merge ~/.Xresources
./hello_xaw
The button now shows the new label and colours — the binary is unchanged. This is the intended workflow for Xt-based applications.
Key Xaw Widgets Reference
| Widget class | Header | Purpose |
|---|---|---|
commandWidgetClass | Xaw/Command.h | Clickable button |
labelWidgetClass | Xaw/Label.h | Static text or image |
textWidgetClass | Xaw/Text.h | Single/multi-line editable text |
boxWidgetClass | Xaw/Box.h | Flow layout container |
formWidgetClass | Xaw/Form.h | Constraint-based layout |
scrollbarWidgetClass | Xaw/Scrollbar.h | Standalone scrollbar |
toggleWidgetClass | Xaw/Toggle.h | On/off button (radio groups) |
listWidgetClass | Xaw/List.h | Selectable string list |
Verification
Check the binary links correctly and the X connection is functional:
# Confirm shared library linkage
ldd hello_xaw | grep -E 'Xaw|Xt|X11'
# Confirm DISPLAY is set (X11 or XWayland)
echo $DISPLAY
# Run with Xt debug output for resource resolution tracing
XTDEBUG=1 ./hello_xaw 2>&1 | head -30
ldd output will list paths like libXaw.so.7, libXt.so.6, and libX11.so.6. $DISPLAY should be :0 or :1; on a pure Wayland session it will be set automatically once any X11 client requests a connection.
Troubleshooting
Cannot open display
If $DISPLAY is empty when running over SSH, either forward X11 (ssh -X host) or install and start a virtual framebuffer: Xvfb :99 & export DISPLAY=:99. On Wayland, DISPLAY is populated by XWayland only after the first X client connects — this is normal and handled transparently by the compositor on Fedora 38+, Ubuntu 22.04+, and Arch.
Linker errors: undefined reference to Xt symbols
Library order on the gcc command line is significant. Always place -lXaw before -lXt before -lX11. If using pkg-config:
gcc -Wall -o hello_xaw hello_xaw.c \
$(pkg-config --cflags --libs xaw7 xt x11)
Blank or miscoloured widgets
A corrupt or conflicting ~/.Xresources entry can override colours unexpectedly. Test by renaming the file temporarily and running xrdb -remove to clear the resource database, then relaunch the application.
Deprecation warning: XtAppInitialize
Older tutorials use XtAppInitialize, which is deprecated since X11R6. The example here uses XtOpenApplication, the current replacement. Both compile and run, but expect compiler warnings with the old form.
Frequently asked questions
- Do Athena widget programs work on Wayland desktops?
- Yes. Modern GNOME and KDE Wayland compositors start XWayland automatically when an X11 client requests a DISPLAY connection, so Xaw applications run unmodified. You may notice a brief delay on first launch while XWayland initialises.
- What is the difference between Xaw and Xaw3d?
- Xaw3d is a drop-in API-compatible replacement that draws widgets with a three-dimensional bevelled appearance instead of the flat Athena look. Swap -lXaw for -lXaw3d and change the pkg-config module name; no source changes are needed.
- Why use XtSetArg macros instead of assigning struct fields directly?
- Xt resources are identified by string names resolved at runtime against the widget class resource table. XtSetArg is a macro that stores the name/value pair into an Arg array element safely; direct struct assignment would bypass the resolution mechanism entirely.
- Is Xlib programming still relevant when GTK and Qt exist?
- For production applications, GTK or Qt are the better choice. Xlib/Xt knowledge is valuable for understanding how those toolkits work internally, for maintaining legacy code, for writing very lightweight utilities, and for debugging X protocol-level issues with tools like xev and xtrace.
- How do I handle window close (WM_DELETE_WINDOW) in an Xaw application?
- Xt does not handle the ICCCM WM_DELETE_WINDOW protocol automatically. You must call XSetWMProtocols to advertise support, then intercept the ClientMessage event in a custom event handler added with XtAppAddActions or by processing raw events before passing them to XtDispatchEvent.
Related guides
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.
C Pointers Explained
Understand C pointers from first principles: addresses, dereferencing, pointer arithmetic, arrays, common bugs like null dereferences and dangling pointers, and how to use ASan and Valgrind.
A C Programming Tutorial for Linux
Learn C on Linux from hello-world through gcc flags, header files, multi-file projects, make, and the standard library — with real commands and examples.