2
\$\begingroup\$

I am developing a simple taskbar for X11 using the Athena widgets. The current code is very limited: it assumes an EWMH-compliant window manager and only adds the initially running windows to the taskbar. Here is a screenshot:

screenshot

The program depends on the libX11, libXt and libXaw development packages. It can be compiled with:

gcc `pkg-config --cflags --libs x11 xaw7` -o xpanel xpanel.c

xpanel.c

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Command.h>
#include <X11/Xatom.h>

/* used as return values for functions */
enum status {
    SUCCESS,
    FAILURE
};

static char *atom_names[] = {
    "WM_DELETE_WINDOW",
    "_NET_CLIENT_LIST",
    "_NET_WM_NAME",
};

enum {
    WM_DELETE_WINDOW,
    NET_CLIENT_LIST,
    NET_WM_NAME,
    ATOM_COUNT
};

/* the X server variables are kept in a global struct */
struct app_data {
    Display *display;
    XtAppContext    app_context;
    Widget  app_shell;
    Window  root_window;
    Atom    atoms[ATOM_COUNT];
    Window  main_window;
};

struct app_data app_data;

/* the tasks (application windows) are organized as a linked list of structs */
struct task {
    struct task *next_task;
    Window  window;
    char    *title;
    Widget  button;
};

struct task_list {
    struct task *first_task;
    struct task *last_task;
};

/* this gets the list of currently open application windows */
enum status get_window_list(Window **window_list, long unsigned int *window_count) {
    Atom actual_type;
    int item_size;
    unsigned long leftover_byte_count;

    /* max. size 256L: 256 x 4-byte multiples = 1 MB */
    if (XGetWindowProperty(app_data.display, app_data.root_window, app_data.atoms[NET_CLIENT_LIST], 0L, 256L, False, XA_WINDOW, &actual_type, &item_size, window_count, &leftover_byte_count, (unsigned char **) window_list) != Success || window_count == 0) {
        fputs("Could not get window list property.\n", stderr); return FAILURE;
    }

    return SUCCESS;
}

enum status get_window_title(Window window, char **window_title) {
    enum status return_code = FAILURE;

    XTextProperty text_property;
    char **text_list = NULL;

    if (XGetTextProperty(app_data.display, window, &text_property, app_data.atoms[NET_WM_NAME]) == 0) {
        if (XGetWMName(app_data.display, window, &text_property) == 0) {
            fputs("Could not get window title property.\n", stderr); goto out;
        }
    }

    /* the text property is converted to a text list to convert it to the encoding of the current locale */
    int string_count = 0;
    XmbTextPropertyToTextList(app_data.display, &text_property, &text_list, &string_count);

    size_t buffer_size = strlen(text_list[0]) + 1;
    *window_title = malloc(buffer_size);
    if (window_title == NULL) {
        perror("malloc"); goto out;
    }
    memcpy(*window_title, text_list[0], buffer_size);

    return_code = SUCCESS;

    out:
        XFree(text_property.value);
        XFreeStringList(text_list);
        return return_code;
}

void button_callback(Widget button, XtPointer client_data, XtPointer call_data) {
    XRaiseWindow(app_data.display, (Window) client_data);
    XSetInputFocus(app_data.display, (Window) client_data, RevertToParent, CurrentTime);
}

/* this fills the variables of a task struct */
void initialize_task(Widget taskbar, Window window, struct task *task) {
    task->window = window;

    char *window_title = NULL;
    if (get_window_title(window, &window_title) == SUCCESS) {
        task->title = window_title;
    } else {
        task->title = "<No Title>";
    }

    task->button = XtVaCreateWidget(NULL, commandWidgetClass, taskbar, XtNlabel, window_title, NULL);
    XtAddCallback(task->button, XtNcallback, button_callback, (XtPointer)window);
}

/* this appends a task struct to the linked list */
void append_task(struct task_list *task_list, struct task *task) {
    task->next_task = NULL;

    if (task_list->first_task == NULL) { // list is empty
        task_list->first_task = task_list->last_task = task;
    } else {
        task_list->last_task->next_task = task;
        task_list->last_task = task;
    }
}

/* this gets the list of currently running windows, creates a task struct including a button widget for each of them, appends the structs to the linked list and adds the buttons to the taskbar */
enum status initialize_task_list(Widget taskbar, struct task_list *task_list) {
    enum status return_code = FAILURE;

    Window *window_list;
    long unsigned int window_list_size;
    if (get_window_list(&window_list, &window_list_size) == FAILURE) {
        fputs("Could not get window list.\n", stderr); goto out;
    }

    /* the task buttons are collected in a dynamically resized array */
    int button_array_capacity = 32;
    Widget *button_array = malloc(button_array_capacity * sizeof *button_array);
    if (button_array == NULL) {
        perror("malloc"); goto out;
    }

    for (int i = 0; i < window_list_size; i++) {
        struct task *new_task = malloc(sizeof *new_task);
        if (new_task == NULL) {
            perror("malloc"); goto out;
        }

        initialize_task(taskbar, window_list[i], new_task);

        if(i == button_array_capacity) {
            button_array_capacity = button_array_capacity + (button_array_capacity >> 1); // growth factor = 1.5
            Widget *new_button_array = realloc(button_array, button_array_capacity * sizeof *button_array);
            if (new_button_array == NULL) {
                perror("realloc"); goto out;
            }
            button_array = new_button_array;
        }
        button_array[i] = new_task->button;

        append_task(task_list, new_task);
    }

    XtManageChildren((Widget *) button_array, window_list_size);
    return_code = SUCCESS;

    out:
        XFree(window_list);
        free(button_array);
        return return_code;
}

/* this handles window manager events (at the moment only requests for deletion of our own window) */
void wm_event_handler(Widget widget, XtPointer client_data, XEvent *event, Boolean *continue_dispatch) {
    if ((event->type == ClientMessage) && (event->xclient.data.l[0] == app_data.atoms[WM_DELETE_WINDOW])) {
        exit(EXIT_SUCCESS);
    }
}

void initialize_x11(struct app_data *app_data, int *argc, char **argv) {
    app_data->app_shell = XtVaOpenApplication(&(app_data->app_context), "XPanel", NULL, 0, argc, argv, NULL, applicationShellWidgetClass, XtNwidth, 800, XtNheight, 50, NULL);
    app_data->display = XtDisplay(app_data->app_shell);
    app_data->root_window = DefaultRootWindow(app_data->display);
    XInternAtoms(app_data->display, atom_names, ATOM_COUNT, False, app_data->atoms);

    XtRealizeWidget(app_data->app_shell);
    app_data->main_window = XtWindow(app_data->app_shell);

    XSetWMProtocols(app_data->display, app_data->main_window, &(app_data->atoms[WM_DELETE_WINDOW]), 1);
    XtAddEventHandler(app_data->app_shell, NoEventMask, True, &wm_event_handler, NULL);
}

int main (int argc, char **argv) {
    setlocale (LC_ALL, "");

    initialize_x11(&app_data, &argc, argv);

    Widget taskbar = XtVaCreateManagedWidget("taskbar", boxWidgetClass, app_data.app_shell, XtNorientation, XtorientHorizontal, NULL);
    struct task_list task_list = {0};
    initialize_task_list(taskbar, &task_list);

    XtAppMainLoop(app_data.app_context);
    return EXIT_SUCCESS;
}

Questions

I wonder whether it is a good idea to keep the X11 variables in a global struct. Without global variables, it would be necessary to pass a struct or several independent X11 variables through the nested functions initialize_task_list, initialize_task, get_window_list, get_window_title and XtAddCallback. As XtAddCallback accepts only a single pointer to user-defined data, I would also need an additional callback data struct to pass the display and window variables to the button callback procedure.

What else do you think about my code? What could be improved? I appreciate any comments or suggestions!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.