$linuxjunkies
>

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.

AdvancedUbuntuDebianFedoraArch12 min readUpdated May 26, 2026

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:

  1. Call XtOpenApplication (or the older XtAppInitialize) to connect to the X server and create a top-level shell widget.
  2. Create child widgets, setting resources (properties) either in code or via X resource files.
  3. Add callbacks to handle events such as button clicks.
  4. Call XtRealizeWidget to map the widget tree to actual X windows.
  5. 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 classHeaderPurpose
commandWidgetClassXaw/Command.hClickable button
labelWidgetClassXaw/Label.hStatic text or image
textWidgetClassXaw/Text.hSingle/multi-line editable text
boxWidgetClassXaw/Box.hFlow layout container
formWidgetClassXaw/Form.hConstraint-based layout
scrollbarWidgetClassXaw/Scrollbar.hStandalone scrollbar
toggleWidgetClassXaw/Toggle.hOn/off button (radio groups)
listWidgetClassXaw/List.hSelectable 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.

tested on:Ubuntu 24.04Fedora 40Arch 2024.05.01Debian 12

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