Search code examples
clinuxxlib

XLib returning corrupt results for XQueryTree()?


Under Linux / X-Windows I've been trying to fetch a recursive list of windows. My ultimate aim is to produce a list of windows with ID, position, dimensions and title. Some windows (for example xeyes) do not have the window title on the top-level window. It's necessary to find the first child window for there to be a human-readable window name.

So I ended up writing code to recursively fetch every window. However the window list being returned by XQueryTree() seems to have corrupt data - some child window-IDs being zero. So it looks like XQueryTree() is returning an incorrect number of children (which should match the length of the allocated children_list parameter).

Eventually the code mostly fails with a BadWindow error. Running it through valgrind shows a read-past-end in data allocated by XQueryTree().

// COMPILE: gcc -Wall -g recursive_window_count.c -o recursive_window_count  -lX11


#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#define MAX_INDENT 100

unsigned int getWindowCount( Display *display, Window parent_window, int depth )
{
    Window  root_return;
    Window  parent_return;
    Window *children_list = NULL;
    unsigned int list_length = 0;
    char    indent[MAX_INDENT+1];

    // Make some indentation space, depending on the depth
    memset( indent, '\0', MAX_INDENT+1 );
    for ( int i=0; i<depth*4 && i<MAX_INDENT; i++ )
    {
        indent[i] = ' ';
    }

    // query the window list recursively, until each window reports no sub-windows
    printf( "getWindowCount() - %sWindow %lu\n", indent, parent_window );
    if ( 0 != XQueryTree( display, parent_window, &root_return, &parent_return, &children_list, &list_length ) )
    {
        printf( "getWindowCount() - %s    %d window handle returned\n", indent, list_length );
        if ( list_length > 0 && children_list != NULL )
        {
            for ( int i=0; i<list_length; i++)
            {
                // But typically the "top-level" window is not what the user sees, so query any children
                // Only the odd window has child-windows.  XEyes does.
                if ( children_list[i] != 0 )
                {
                    unsigned int child_length = getWindowCount( display, children_list[i], depth+1 );
                    list_length += child_length;  
                }
                else
                {
                    // There's some weirdness with the returned list
                    // We should not have to be doing this at all
                    printf( "%sHuh? child window handle at index #%d (of %d) is zero?\n", indent, i, list_length );
                    break;  
                }
            }

            XFree( children_list ); // cleanup
        }
    }

    return list_length; 
}


int main( int argc, char **argv )
{
    Display *display;
    Window   root_window;

    display = XOpenDisplay( NULL );
    if ( display ) 
    {
        // Get the number of screens, it's not always one!
        int screen_count = XScreenCount( display );

        // Each screen has a root window
        printf( "There are %d screens available on this X Display\n", screen_count );

        for ( int i=0; i < screen_count; i++ )
        {
            root_window = XRootWindow( display, i );
            printf( "Screen %d - %u windows\n", i, getWindowCount( display, root_window, 0 ) );
        }
        XCloseDisplay( display );
    }

    return 0;
}

The head of the valgrind log:

prompt> valgrind ./recursive_window_count 
==5117== Memcheck, a memory error detector
==5117== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5117== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==5117== Command: ./recursive_window_count
==5117== 
There are 1 screen available on this X Display
getWindowCount() - Window 625
getWindowCount() -     105 window handles returned
getWindowCount() -     Window 48234747
getWindowCount() -         1 window handle returned
getWindowCount() -         Window 75497478
getWindowCount() -             1 window handle returned
getWindowCount() -             Window 75497479
getWindowCount() -                 0 window handles returned
==5117== Invalid read of size 8
==5117==    at 0x1093CD: getWindowCount (recursive_window_count.c:38)
==5117==    by 0x109406: getWindowCount (recursive_window_count.c:40)
==5117==    by 0x109530: main (recursive_window_count.c:77)
==5117==  Address 0x4bff508 is 0 bytes after a block of size 8 alloc'd
==5117==    at 0x483A723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==5117==    by 0x483D017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==5117==    by 0x489A53B: XQueryTree (in /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0)
==5117==    by 0x10934D: getWindowCount (recursive_window_count.c:29)
==5117==    by 0x109406: getWindowCount (recursive_window_count.c:40)
==5117==    by 0x109530: main (recursive_window_count.c:77)
==5117== 
    Huh? child window handle at index #1 (of 2) is zero?
getWindowCount() -     Window 48236534
getWindowCount() -         1 window handle returned
getWindowCount() -         Window 96468999
getWindowCount() -             1 window handle returned
getWindowCount() -             Window 96469000
getWindowCount() -                 0 window handles returned
    Huh? child window handle at index #1 (of 2) is zero?
... truncated

Solution

  • No, XQueryTree is not returning garbage data. The problem is with your code. The list_length += child_length after the recursive call to getWindowCount makes no sense, and is triggering undefined behaviour. Just remove that line.

    Some observations:

        char    indent[MAX_INDENT+1];
    
        // Make some indentation space, depending on the depth
        memset( indent, '\0', MAX_INDENT+1 );
        for ( int i=0; i<depth*4 && i<MAX_INDENT; i++ )
        {
            indent[i] = ' ';
        }
    
        // query the window list recursively, until each window reports no sub-windows
        printf( "getWindowCount() - %sWindow %lu\n", indent, parent_window );
    

    Use just printf("... %*s%lu\n", depth * 4, "", parent_window) instead of all that, and you will not have to care about any MAX_INDENT.