x11freetype2xft

How to get XFontStruct from XftFont?


I'm writing a simple X11 clock app, it can be scaled by resizing the window,

I want to draw some numbers (1..12) around the clock face, and the numbers should be scaled too.

So I can't use XLoadQueryFont, which only accept descrete pre-defined font sizes, can't be scaled.

I tried Xft, but failed to do XftDrawString, which raised error BadPicture.

And it seems big mess when mixing drawing codes of Xlib (like XSetForeground) and Xft (color as parameter).

So I guess maybe I can get XFontStruct from XftFont? I only need to use Xft to find a appropriate font matching arbitray font-size.

xft(3) said, XftFont contains a pointer to XFontStruct:

XftFont contains general font metrics and a pointer to either the core XFontStruct data or a structure holding FreeType and X Render Extension data.

while in the header file (xft.h), I didn' find the pointer:

    typedef struct _XftFont {
        int     ascent;
        int     descent;
        int     height;
        int     max_advance_width;
        FcCharSet   *charset;
        FcPattern   *pattern;
    } XftFont;

The clock app:

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xft/Xft.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <math.h>
#include <time.h>
#include <sys/time.h>

#define MWM_HINTS_DECORATIONS (1L << 1)
#define MWM_DECOR_ALL (1L << 0) /* [v] */

typedef struct
{
    unsigned long flags;
    unsigned long functions;
    unsigned long decorations;
    long input_mode;
    unsigned long status;
} MotifWmHints;

static MotifWmHints *
get_motif_wm_hints(Display *display,
                   Window window)
{
    Atom property;
    int result;
    Atom actual_type;
    int actual_format;
    unsigned long nitems;
    unsigned long bytes_after;
    unsigned char *data;

    property = XInternAtom(display, "_MOTIF_WM_HINTS", False);
    result = XGetWindowProperty(display, window, property,
                                0, LONG_MAX, False, AnyPropertyType,
                                &actual_type, &actual_format,
                                &nitems, &bytes_after, &data);

    if (result == Success && data != NULL)
    {
        size_t data_size;
        size_t max_size;
        MotifWmHints *hints;

        data_size = nitems * sizeof(long);
        max_size = sizeof(*hints);

        hints = calloc(1, max_size);

        memcpy(hints, data, data_size > max_size ? max_size : data_size);
        XFree(data);

        return hints;
    }

    return NULL;
}

void removeBorder(Display *display,
                  Window window)
{
    MotifWmHints *hints;
    Atom property;
    int nelements;

    hints = get_motif_wm_hints(display, window);
    if (hints == NULL)
    {
        hints = calloc(1, sizeof(*hints));
        hints->decorations = 0;
    }

    hints->flags |= MWM_HINTS_DECORATIONS;
    hints->decorations = 0;
    property = XInternAtom(display, "_MOTIF_WM_HINTS", False);
    nelements = sizeof(*hints) / sizeof(long);

    XChangeProperty(display, window, property, property, 32, PropModeReplace,
                    (unsigned char *)hints, nelements);

    free(hints);
}

int clock_margin_h = 80;
int clock_margin_v = 80;
int clock_radius = 100;

Colormap colormap;
XColor blue_color;
XColor red_color;
XftDraw *draw = NULL;
XftFont *font = NULL;

void drawClock(Display *display, Window window, int hour, int minute, int second, int usec)
{
    int screen = DefaultScreen(display);

    int clock_center_x = clock_radius + clock_margin_h;
    int clock_center_y = clock_radius + clock_margin_v;

    float hour_len = 0.4;
    float minute_len = 0.6;
    float second_len = 0.8;

    XGCValues values;
    GC gc = XCreateGC(display, window, 0, &values);

    // Draw clock face
    XSetForeground(display, gc, blue_color.pixel);
    XFillArc(display, window, gc, clock_center_x - clock_radius, clock_center_y - clock_radius,
             2 * clock_radius, 2 * clock_radius, 0, 360 * 64);

    double hour_angle = (-(hour + minute / 60.0) / 12 + 1 / 4.0) * M_PI * 2;
    double minute_angle = (-minute / 60.0 + 1 / 4.0) * M_PI * 2;
    double second_angle = (-(second + usec / 1000000.0) / 60.0 + 1 / 4.0) * M_PI * 2;

    // Draw hour hand (white color)
    XSetForeground(display, gc, WhitePixel(display, screen));
    XDrawLine(display, window, gc, clock_center_x, clock_center_y,
              clock_center_x + hour_len * clock_radius * cos(hour_angle),
              clock_center_y - hour_len * clock_radius * sin(hour_angle));

    // Draw minute hand (white color)
    XDrawLine(display, window, gc, clock_center_x, clock_center_y,
              clock_center_x + minute_len * clock_radius * cos(minute_angle),
              clock_center_y - minute_len * clock_radius * sin(minute_angle));

    // Draw second hand (red color)
    XSetForeground(display, gc, red_color.pixel);
    XDrawLine(display, window, gc, clock_center_x, clock_center_y,
              clock_center_x + second_len * clock_radius * cos(second_angle),
              clock_center_y - second_len * clock_radius * sin(second_angle));

    // Draw numbers 1-12 around the clock face
    if (font)
    {
        // XSetFont(display, gc, font_info->fid);
        // XSetForeground(display, gc, WhitePixel(display, screen));
        XftDraw *draw = XftDrawCreate(display, window,
                                      DefaultVisual(display, screen),
                                      DefaultColormap(display, screen));

        XftColor color;
        color.color.red = 65535;
        color.color.green = 65535;
        color.color.blue = 65535;
        color.color.alpha = 65535;

        for (int i = 1; i <= 12; i++)
        {
            double angle = (-(i) / 12.0 + 1 / 4.0) * M_PI * 2;
            int x = clock_center_x + 1 * clock_radius * cos(angle) - 5;
            int y = clock_center_y - 1 * clock_radius * sin(angle) + 10;
            char number[10];
            sprintf(number, "%d", i);
            // XDrawString(display, window, gc, x, y, number, strlen(number));
            XftDrawStringUtf8(draw, &color, font, x, y, number, strlen(number));
        }
        XftDrawDestroy(draw);
    }
    else
    {
        fprintf(stderr, "Error: Unable to load font\n");
    }

    XFreeGC(display, gc);
}

void updateClock(Display *display, Window window)
{
    time_t now = time(NULL);
    struct tm *local_time = localtime(&now);

    struct timeval tvTime;
    gettimeofday(&tvTime, NULL);

    int hour = local_time->tm_hour % 12;
    int minute = local_time->tm_min;
    int second = local_time->tm_sec;

    drawClock(display, window, hour, minute, second, tvTime.tv_usec);
}

int error_logger(
    Display *display,
    XErrorEvent *event)
{
    char msg[200];
    XGetErrorText(display, event->error_code, msg, sizeof(msg));

    fprintf(stderr, "[serial %d] [type %d] error %d (%s): request %d.%d\n", //
            event->serial,
            event->type,
            event->error_code,
            msg,
            event->request_code,
            event->minor_code);
}

static int wait_fd(int fd, long usec)
{
    struct timeval tv;
    fd_set in_fds;
    FD_ZERO(&in_fds);
    FD_SET(fd, &in_fds);
    tv.tv_sec = usec / 1000000;
    tv.tv_usec = usec % 1000000;
    return select(fd + 1, &in_fds, 0, 0, &tv);
}

int XNextEventTimeout(Display *display, XEvent *event, double seconds)
{
    if (XPending(display) || wait_fd(ConnectionNumber(display), seconds))
    {
        XNextEvent(display, event);
        return 0;
    }
    else
    {
        // event->type = 0;
        return 1;
    }
}

int main()
{
    XSetErrorHandler(error_logger);

    Display *display = XOpenDisplay(NULL);
    if (display == NULL)
    {
        fprintf(stderr, "Error: Could not open display\n");
        return 1;
    }

    Window root = DefaultRootWindow(display);
    int screen = DefaultScreen(display);

    Window origFocusWindow;
    int origRevert;
    XGetInputFocus(display, &origFocusWindow, &origRevert);

    // Create a window with transparent background
    XVisualInfo vinfo;
    XMatchVisualInfo(display, screen, 32, TrueColor, &vinfo);
    XSetWindowAttributes attr;
    colormap = XCreateColormap(display, root, vinfo.visual, AllocNone);
    attr.colormap = colormap;
    attr.border_pixel = 0;
    attr.background_pixel = 0;
    // attr.override_redirect = False;

    int left = 100, top = 100;
    int width = (clock_radius + clock_margin_h) * 2;
    int height = (clock_radius + clock_margin_v) * 2;
    int border_width = 0;
    Window window = XCreateWindow(display, root, left, top, width, height, border_width,
                                  vinfo.depth, InputOutput, vinfo.visual,
                                  CWColormap | CWBorderPixel | CWBackPixel, &attr);
    // removeBorder(display, window);

    Colormap colormap = DefaultColormap(display, screen);
    XAllocNamedColor(display, colormap, "blue", &blue_color, &blue_color);
    XAllocNamedColor(display, colormap, "red", &red_color, &red_color);

    font = XftFontOpenName(display, screen, "Sans:size=10");

    // Set window background to transparent
    // XSetWindowBackground(display, window, 0);
    XClearWindow(display, window);

    XMapWindow(display, window);
    XSelectInput(display, window, ExposureMask | StructureNotifyMask);

    // XMapWindow(display, focusWindow);
    // XRaiseWindow(display, focusWindow);
    // XSelectInput(display, focusWindow, ExposureMask | KeyPressMask);
    // XSync(display, True);
    // int r = XSetInputFocus(display, focusWindow, RevertToParent, CurrentTime);

    while (1)
    {
        XEvent event;
        int timeout = XNextEventTimeout(display, &event, 100 * 1000);
        if (timeout)
        {
            updateClock(display, window);
            continue;
        }

        switch (event.type)
        {
        case ConfigureNotify:
            XConfigureEvent xce = event.xconfigure;
            if (xce.width < xce.height)
                clock_radius = xce.width / 2 - clock_margin_h;
            else
                clock_radius = xce.height / 2 - clock_margin_v;

            int font_height = clock_radius / 12;

            if (font)
            {
                XftFontClose(display, font);
                font = NULL;
            }

            char font_name[80];
            sprintf(font_name, "Sans:size=%d", font_height);
            font = XftFontOpenName(display, screen, font_name);

            updateClock(display, window);
            break;

        case Expose:
            updateClock(display, window);

            Window focusWindow;
            int revert;
            XGetInputFocus(display, &focusWindow, &revert);
            if (focusWindow == window)
            {
                XSetInputFocus(display, origFocusWindow, RevertToParent, CurrentTime);
            }
            break;

        case MapNotify:
        case ClientMessage:
            break;

        default:
            printf("other event: %d\n", event.type);
        }
    }

    XCloseDisplay(display);

    return 0;
}

Solution

  • How to get XFontStruct from XftFont?

    You don't. You cannot, and you don't need to. Xft is a client-size font rendering system. The core X fonts are server sise, they have no idea about what you have client-side.

    I tried Xft, but failed to do XftDrawString, which raised error BadPicture.

    The error is because of a visual mismatch. You are using the default visual at some places and a found and matched one at other places. Use the same visual for the same window. As a simple quick and dirty fix, make vinfo global and use vinfo.visual throughout.

    xft(3) said, XftFont contains a pointer to XFontStruct:

    You have an outdated version of the man page. Not only the header file doesn't reference XFontStruct, the entire libXft source has no mention of it. A very old release of libXft, included with XOrg X11R6.7 release, mentions XFontStruct in the manual page, but nowhere in the source code. The mention is gone in X11R6.9 release. I could not find older versions of libXft anywhere on the internet (nor could its author).

    So I guess maybe I can get XFontStruct from XftFont?

    No. Don't try that. Use client-side font rendering. It works.