In this article, we are going to create a simple X11 program that display a window. This is the first in the series of articles on X11 and GUI in general.
You need X11 development libraries in order to follow these articles. Go ahead and install the lib based on your distro and/or package manager. As we are going to write the code in C, GCC - a C compiler is needed. Install that one as well.
Go ahead and create a file with the name wm.c (wm - window manager) and write the following code.
#include <stdio.h> int main(void) { printf("hello, world\n"); return 0; }
Run the following command to compile and execute the program.
gcc wm.c -o wm && ./wm
You should see "hello, world" as the output.
In order to create a window in X11, we first need to make a connection to the display. To create a connection, XOpenDisplay() function is used. You can pass the name of the display to this function. But, you don't have to. Just pass the NULL value and rest is taken care by XOpenDisplay() function. When you call XOpenDisplay() two things can happen - On success it'll return a pointer to the display or on failure you won't get anything. If we get the pointer as a connection to display, we should save it into the variable of type Display. But, if it fails, we need to terminate the program as if we don't have a connection to display, how should we render a window and interact with it?!
XOpenDisplay() function is declared in the X11/Xlib.h header file. So, we need to include this header file in order to use the XOpenDisplay() function.
Let's update the wm.c program with what we just discussed.
#include <stdio.h> #include <X11/Xlib.h> int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Unable to open X display\n"); return 1; } return 0; }
In X11, display doesn't mean a single monitor or the screen. If you have multiple screens attached to a single computer and you can interact with the same keyboard and/or mouse with all the screens, you still have a single display. As official definitions says,
"In X11, display is defined as a workstation consisting of a keyboard, a pointing device such as mouse, and one or more screens. Multiple screens can work together, with mouse movement allowed to cross physical screen boundaries. As long as multiple screens are controlled by single user with single keyboard and pointing device, they comprise only a single display."
As we can have multiple screens, we need to select the screen on which we need to display our window. For that, we need to call the DefaultScreen() function and pass the display connection to this function call. This function returns a screen number (int) on which we can display a window later on.
#include <stdio.h> #include <X11/Xlib.h> int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Unable to open X display\n"); return 1; } int screen = XDefaultScreen(display); return 0; }
We are now ready to create a window. We need to call the XCreateSimpleWindow() function to create a window. We need to pass NINE arguments to this function call - a display, a root window, x, y (as coordinate), width, height (dimensions), border width, border color, background color (of window). On successful call, this function returns a window of type Window. Let me call this function by passing the values to it and then we can discuss again.
#include <stdio.h> #include <X11/Xlib.h> int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Unable to open X display\n"); return 1; } int screen = XDefaultScreen(display); Window window = XCreateSimpleWindow( display, RootWindow(display, screen), 100, 100, // x, y 400, 200, // width, height 1, // border width BlackPixel(display, screen), // border color WhitePixel(display, screen) // background color ); return 0; }
Every window in X11 always has a parent window (except the root window) as windows in X11 are always in hierarchy form. To make this hierarchy form, we need to add a parent of this window. We can fetch the root or parent window using the RootWindow() function. This function accepts display and screen as the two arguments. x and y are the position of the window. In X11, the (0, 0) starts from the top-left corner. Then we defined the size of the window as width and height in pixels. We also need to tell the border width and color of the window which are 1 and black respectively. Finally, the background of the window is white that you should see when we run the program and render the window on screen. BlackPixel() and WhitePixel() are the macros that return default black and white color on the specified screen (more on this in future articles).
We can simplify this XCreateSimpleWindow() function call by putting these functions outside as follows.
#include <stdio.h> #include <X11/Xlib.h> int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Unable to open X display\n"); return 1; } int screen = XDefaultScreen(display); Window root = RootWindow(display, screen); unsigned long black = BlackPixel(display, screen); unsigned long white = WhitePixel(display, screen); Window window = XCreateSimpleWindow( display, root, 100, 100, 400, 200, 1, black, white ); return 0; }
I've also removed the comments as explanation is provided.
XCreateSimpleWindow() creates a window. But, creating a window is not enough. We need to display it or as X11 says, we need to map it. To map it, we need to call the XMapWindow() function. This function accepts the display and a window that we want to map.
#include <stdio.h> #include <X11/Xlib.h> int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Unable to open X display\n"); return 1; } int screen = XDefaultScreen(display); Window root = RootWindow(display, screen); unsigned long black = BlackPixel(display, screen); unsigned long white = WhitePixel(display, screen); Window window = XCreateSimpleWindow( display, root, 100, 100, 400, 200, 1, black, white ); XMapWindow(display, window); return 0; }
Finally (not actually), we need to close the display connection right before the return statement to free the memory once we are done with the program using the XCloseDisplay() function. This function accepts a display that we are interested in closing.
#include <stdio.h> #include <X11/Xlib.h> int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Unable to open X display\n"); return 1; } int screen = XDefaultScreen(display); Window root = RootWindow(display, screen); unsigned long black = BlackPixel(display, screen); unsigned long white = WhitePixel(display, screen); Window window = XCreateSimpleWindow( display, root, 100, 100, 400, 200, 1, black, white ); XMapWindow(display, window); XCloseDisplay(display); return 0; }
We are ready to run our program. But, we need to tweak that previous command, in order to include the X11 header in the compilation process.
gcc wm.c -lX11 -o wm && ./wm
The program compiles and executes but nothing happens! I mean we are supposed to see a window with white background. But, we didn't see it. Actually, we need to keep a program or window running in order to see the window and interact with it. How about having an infinite while loop?
#include <stdio.h> #include <X11/Xlib.h> int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Unable to open X display\n"); return 1; } int screen = XDefaultScreen(display); Window root = RootWindow(display, screen); unsigned long black = BlackPixel(display, screen); unsigned long white = WhitePixel(display, screen); Window window = XCreateSimpleWindow( display, root, 100, 100, 400, 200, 1, black, white ); XMapWindow(display, window); while (1) { } XCloseDisplay(display); return 0; }
Perfect! Within this while loop, we can write custom code against the interaction. For example, close the window if the user presses any key from the keyboard. In order to do such interaction, we need to listen for input events such as KeyPress event. We need to call the XSelectInput() function to register the type of events we are interested in listening to. This function accepts three arguments - display, a window, and event (e.g. KeyPressMask in our scenario). Let's write this before we map the window as we need to register everything needed for the window before we display it on the screen.
#include <stdio.h> #include <X11/Xlib.h> int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Unable to open X display\n"); return 1; } int screen = XDefaultScreen(display); Window root = RootWindow(display, screen); unsigned long black = BlackPixel(display, screen); unsigned long white = WhitePixel(display, screen); Window window = XCreateSimpleWindow( display, root, 100, 100, 400, 200, 1, black, white ); XSelectInput(display, window, KeyPressMask); XMapWindow(display, window); while (1) { } XCloseDisplay(display); return 0; }
Now, we can check for the event in a loop. XNextEvent() will loop for the event. It accepts two arguments - display and address of the event as a type of XEvent. It can then have a type that we can compare against e.g. KeyPress.
#include <stdio.h> #include <X11/Xlib.h> int main(void) { Display* display = XOpenDisplay(NULL); if (!display) { fprintf(stderr, "Unable to open X display\n"); return 1; } int screen = XDefaultScreen(display); Window root = RootWindow(display, screen); unsigned long black = BlackPixel(display, screen); unsigned long white = WhitePixel(display, screen); Window window = XCreateSimpleWindow( display, root, 100, 100, 400, 200, 1, black, white ); XSelectInput(display, window, KeyPressMask); XMapWindow(display, window); XEvent event; while (1) { XNextEvent(display, &event); if (event.type == KeyPress) { break; } } XCloseDisplay(display); return 0; }
I wrote the XNextEvent() function within a loop as we continuously look for the event or interaction while our program is running. Currently, we are just listening for key presses but in future, we'll listen for many other types of interaction such as, mouse interaction, window map or unmap interaction, etc.
We are now ready to re-run the program again. This time you should see a window and will close on any key-press.
The article is long enough. I want you to take a break before we continue adding more code in this program. See you in the next article!
