1

I'm using Gnome on Ubuntu 20.04. When I use XGetWindowAttributes(), the window position I get has an offset, as if the window has a thick (invisible) margin, but the offset is different for different windows.

I looked at the component border_width in the window attributes, but it is always zero. I tried XTranslateWindowCoordinates() (https://stackoverflow.com/a/23940869/3852630), but it doesn't affect the positions.

To give some details, maybe they are relevant: The window I look at comes from XGetInputFocus(), and I use the code from this answer https://stackoverflow.com/a/39549363/3852630 to get the parent of the focused window. Gnome draws a shadow around the windows, but that should be the same for all windows.

Any idea where I can get the information on the size of this margin or on the true window position?

Thanks a lot!


In response to datenwolf's comments, here are the trees. I get the focused window with XGetInputFocus and then go down the window tree with XQueryTree until I reach a window that has the root window as parent (see link above), the last one in the tree lists below. I print the window position and window size and the window's name (if it has one); n is the loop index of the descent in the tree.

This is the tree for an emacs window (has a small border, i.e. there's a small position offset):

n=1 x=-1 y=-1 w=1 h=1
n=2 x=10 y=45 w=752 h=714 name = emacs@laptopC3
n=3 x=3064 y=104 w=772 h=769

The line n=3 has the window position which fits with my dual monitor setup, but the line n=2 gives a smaller size (but totally wrong position). I wonder what the window in line n=1 is.

This is the tree for a terminal (has a large border):

n=1 x=-1 y=-1 w=1 h=1
n=2 x=2889 y=390 w=786 h=533 name = Terminal

Nothing to see, except the weird window for n=1.

This is a Tcl/Tk application (which has a tiny border):

n=1 x=1 y=38 w=1026 h=770 name = PNGShow-6-6
n=2 x=3192 y=234 w=1028 h=809

Note that here we have no weird window for n=1.

I don't see a consistent effect.

Ralf
  • 1,203
  • 1
  • 11
  • 20
  • 1
    Could be the padding added to account for compositing effects. But that's pure speculation. – datenwolf Jul 07 '22 at 14:53
  • @datenwolf: Yes, compositing may be an explanation. But isn't compositing a property of the window manager, not of the individual window? – Ralf Jul 07 '22 at 15:07
  • 1
    Actually strictly speaking its neither, it's done by a compositor – which for the most parts also happens to be the WM. My hypothesis would be, that the WM is reparenting your client window into an management window with some serious extra padding, only a portion of which is actually painted over with decoration. The rest may just be there to help with generating expose/damage events for windows below. Again, pure speculation, you'd have to look at the window tree for that (and I'm pulling my hairs here, because a couple of years ago I wrote a tool just for that, which seems I have misplaced). – datenwolf Jul 08 '22 at 07:41
  • @datenwolf: So if we look at the window tree (from `XQueryTree`?), the window which has the root window as parent may be the padded one, and the window which has the padded window as parent the unpadded one? – Ralf Jul 08 '22 at 10:22
  • @datenwolf: ... but some windows are completely unpadded, so I wonder how I can find the proper window in the tree. – Ralf Jul 08 '22 at 10:25
  • what do you understand by "proper window"? Is it a window you created yourself? Depending on the behavior of the WM you might be able to identify it by its properties. Usually you can identify the main window by having EMWH properties like WM_NAME or WM_CLASS. Of course not all X11 clients do set those properties, but most do. – datenwolf Jul 08 '22 at 10:42
  • 1
    @datenwolf: I interpreted your idea with the window tree like that: normally, you would have root -> focuswindow, but reparenting leads to root -> paddedwindow -> focuswindow. Since I descend from focuswindow downwards until I reach a window which has root as parent (see link in my question), I reach paddedwindow. Unfortunately, I have to descend in the tree to find such a window, since focuswindow is apparently not the right one, so there are other intermediate windows in the tree. I'll try to print the tree. – Ralf Jul 08 '22 at 10:54
  • @datenwolf: The trees are in my answer now. Do you have an interpretation? – Ralf Jul 08 '22 at 11:43
  • Hmm, I'll have to meditate about that. Are you using plain Gnome with gnome-shell, or a Gnome based DE with a different WM? And what version? I'd like to tinker with that myself. – datenwolf Jul 08 '22 at 11:53
  • @datenwolf: `wmctrl -m` says `GNOME Shell`, `echo $XDG_CURRENT_DESKTOP` prints `ubuntu:GNOME`, and `gnome-shell --version` gives `GNOME Shell 3.36.9`. Thanks for your help! – Ralf Jul 08 '22 at 14:29

1 Answers1

1

After rummaging around in the heap of stuff that is my ~/misc directory I actually found a WIP of that X11 window tree traversal program I did mention in the comments. I originally wrote this to visualize the different ways in which window managers create decorations and place their clients within. Essentially it's a screenshot-all-the-things (except the root window) tool writing out a SVGs with an <image> object for each window and a red border around it (so kind of like how you'd debug CSS with border; solid red 1px;), showing the underlying X11 drawable's contents.

Here's the source code. You'll need stb_image (for PNG compression), cppcodec (for base64 encoding) and xcb headers to build:

#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>

#include <sstream>
#include <iostream>
#include <type_traits>

#include <xcb/xcb.h>
#include <xcb/xcb_image.h>
#include <cppcodec/base64_url.hpp>
#include <cppcodec/base64_rfc4648.hpp>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

static
void stringstream_write_b64(void *buf, void *data, int size)
{
    auto v = reinterpret_cast<std::vector<uint8_t>*>(buf);
    v->insert(v->end(), (uint8_t*)data, (uint8_t*)data+size);
}

std::string x11drawable_to_b64png(
    xcb_connection_t *const conn,
    xcb_drawable_t drawable,
    xcb_translate_coordinates_cookie_t const *const tcc )
{
    std::ostringstream s;

    xcb_generic_error_t *err_gg = nullptr, *err_tc = nullptr;
    auto const r_gg = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, drawable), &err_gg);
    auto const r_tc = tcc ? xcb_translate_coordinates_reply(conn, *tcc, &err_tc) : nullptr;
    if( r_gg ){
        int ox = 0, oy = 0;
        if( r_tc ){
            ox = r_tc->dst_x; oy = r_tc->dst_y;
        } else {
            ox += r_gg->x; oy += r_gg->y;
        }

        xcb_image_t * const image =
            xcb_image_get(conn, drawable,
                0, 0, r_gg->width, r_gg->height,
                0xFFFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP );
        if( image ){
            s << "<g>";
            s << "<image "
                << "x=\"" << ox << "\" "
                << "y=\"" << oy << "\" "
                << "width=\"" << r_gg->width << "\" "
                << "height=\"" << r_gg->height << "\" "
                << "xlink:href=\"data:image/png;base64,";
            std::ostringstream fn;
            fn << drawable << ".png";
            auto str = fn.str();

            for(size_t y = 0; y < image->height; ++y){
                auto r = (uint32_t*)(
                    (uint8_t*)image->data + y*image->stride );
                for(size_t x = 0; x < image->width; ++x ){
                    r[x] |= 0xFF000000;
                }
            }

            std::vector<uint8_t> buf;
            stbi_write_png_to_func(
                stringstream_write_b64, reinterpret_cast<void*>(&buf),
                image->width, image->height, 4,
                image->data, image->stride );
            s << cppcodec::base64_rfc4648::encode(buf);
            s << "\"/>\n";

            s << "<rect "
                << "x=\"" << ox << "\" "
                << "y=\"" << oy << "\" "
                << "width=\"" << r_gg->width << "\" "
                << "height=\"" << r_gg->height << "\" "
                << "style=\"fill:none;fill-opacity:1;stroke:#f00;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;\"/>";
            s << "</g>\n";
        }
    }

    free(err_gg); free(err_tc); free(r_gg); free(r_tc);

    return s.str();
}

static void traverse_window_tree(
    xcb_connection_t *conn,
    xcb_window_t const win,
    int const level )
{

    xcb_generic_error_t *err_qt = nullptr;
    auto const r_qt = xcb_query_tree_reply(conn, xcb_query_tree(conn, win), &err_qt);
    if( r_qt ){
        auto const root = r_qt->root;
        auto const children = xcb_query_tree_children(r_qt);
        auto const n = xcb_query_tree_children_length(r_qt);
        for( std::remove_const<decltype(n)>::type i = 0; i < n; ++i ){
            auto const child = children[i];

            for(unsigned l=0; l<level;++l){ std::cerr << " "; }
            std::cerr << child << std::endl;

            int c_ox = 0, c_oy = 0;
            auto const xtcc = xcb_translate_coordinates(conn, child, root, 0, 0);
            std::cout << x11drawable_to_b64png(conn, child, &xtcc);

            traverse_window_tree(conn, child, level+1);
        }
    }

    free(r_qt);
    free(err_qt);
}

static xcb_generic_event_t *xcb_poll_for_event_timeout(
    xcb_connection_t *const conn,
    unsigned timeout_us )
{
    unsigned i = 0;
    while( timeout_us >> (2+i) ){ ++i; };
    i /= 2;

    xcb_generic_event_t *ev;
    while( !(ev = xcb_poll_for_event(conn)) && i ){
        unsigned const t = timeout_us >> (1 + (i--));
        usleep( t );
    }
    return ev;
}

int main(int argc, char *argv[])
{
    int rc = 0;

    int screen;
    auto const conn = xcb_connect(NULL, &screen);
    if( !conn ){ rc = -1; }

    auto const xsetup = xcb_get_setup(conn);
    if( !xsetup ){ rc = -2; }

    auto xroots_iter = xcb_setup_roots_iterator(xsetup);
    for(int i = 0; i < screen; ++i){ xcb_screen_next(&xroots_iter); }
    auto const xscreen = reinterpret_cast<xcb_screen_t*>(xroots_iter.data);
    if( !xscreen ){ rc = -4; }

    std::cout << "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" << std::endl;
    traverse_window_tree(conn, xscreen->root, 0);
    std::cout << "</svg>" << std::endl;

    if( conn ){ xcb_disconnect(conn); }
    return rc;
}

Dumping a Gnome Shell session yields the following: On the left column are gtk-demo and gtk3-demo showing the Dialogs example. The center top is gtk4-demo. In the upper right is gnome-control-center, blow that urxvt. In the middle-center and right is a very old version of CadSoft EAGLE running, which uses IIRC Qt2 or Qt3 (don't know which exactly). Below that in the center is an instance of KDE Dolphin and in the bottom right runs an instance of DDD which uses Motif. (find the original SVG at https://dl.datenwolf.net/xsvgtree/gnome-shell.svgsave it locally since my server's Content-Security-Policy disallows browsers from directly showing inline data).

xsvgtree dump of a Gnome shell session with representative clients open

What you can immediately see is, how differently the various toolkits go about structuring their windows and mapping them to the screen. The old school toolkits will create a lot of small, tiny subwindows within their client area, usually one for each user interactable control. Modern GTK and Qt on the other hand opt to just create a single large surface to draw to, with their own methods, and then transfer that over to the X11 server. The latest iterations of GTK in fact opted to completely bypass the decoration facilities of the window manager and just draw everything in the client. And in the case of gnome-control-center the window wherein everything happens is far larger than the extents of the visible region and "decoration" would suggest, and also far larger than the shadow margin. My suspicion is, that this is to avoid changing the size of the actual window in response to altering the extents of the "visible" area, for the simple reason that altering a windows size in a composited environment comes as the considerable cost of having to re-/initialize new GPU resources which can lead to stuttering and visual artifacts. In order to keep things running smoothly – quite literally – Gnome apparently applies a conservative resource reallocation strategy, which however on the X11 side may lead to unintuitive values when querying window properties.

Now to juxtapose this, I ran an instance each of urxvt and gnome-control-center under management by FVWM and dumped it. Things looks like this there (original SVG found here: https://dl.datenwolf.net/xsvgtree/fvwm-gnome-shell.svg ):

gnome-control-center under management by FVWM

As you can see, in the case of being reparented, gnome-control-center will actually "behave" itself. Also it's notworthy that all the little decoration elements FVWM creates each consist its own little window.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • Sorry, I didn't get an alert, so I saw your answer only today. I think it provides good explanations for the different margins, so I'll accept that as the answer. Thanks a lot! – Ralf Sep 13 '22 at 07:27