linuxmint / linuxmint/cinnamon

Inconsistent window positions

Open
#12,820 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

BUG
Dominant language
JavaScript
Stars
5.6k
Forks
915
Avg merge
5d 22h
Merged PRs (30d)
3

Description

Distribution

Mint 21

Package version

5.4.12

Graphics hardware in use

GeForce RTX 2080

Frequency

Always

Bug description

First off, I am not writing a Cinnamon specific application. I'm only interacting with X11 APIs because my applications (games and other graphically intensive applications) provide their own UI for maximum portability.

That said, it's not easily possible to retrieve the position of my window, and reapply that position when the application starts. It's not entirely impossible, (I have finally figured it out - see below) but I suspect what I have is fairly hacky.

(I should point out, I not only need this for application startup. One use case for a video wall requires the application remember and reapply window positions when reconfigured on the fly to occupy between one and 10 displays.)

Steps to reproduce

Create a window with XCreateWindow. The returned window ID should be all I need to interact with that window.

Retrieve the position of that window with XGetWindowAttributes. The position is not given in desktop coordinates. It's the position of that window relative to its parent (that Cinnamon creates behind the scenes).

Apply the retrieved position with XMoveWindow. The window jumps to the top left corner of the desktop.

Expected behavior

Retrieving the window position and reapplying it should produce consistent results.

Additional information

In theory, the only thing I should need to interact with X11 windows on Cinnamon is the window ID returned from XCreateWindow.

In practice, this is not the case.

XCreateWindow returns a window ID. If I retrieve the position of that window with XGetWindowAttributes, the position returned is relative to the parent window that Cinnamon creates behind the scenes. However, the position returned from XGetWindowAttributes for that parent window is also not correct.

If I simply retrieve the position of the parent window and then apply that back to the parent window, the window jumps down ~28 pixels. The height of the title bar, I believe.

If I retrieve the position of the parent window and apply that position back to the child window (returned from XCreateWindow) the window jumps up and to the left by (as far as I can tell) the size of the window border and drop shadow.

If I try to save the position of the window when I receive ConfigureNotify events, the position is horribly inconsistent. When moving the window by dragging the title bar, the event data position is the parent window's position. When resizing the window by dragging its borders, the event data position is the child window's position.

I have worked around this problem, but I'm guessing what I have won't work for other window managers that do things properly.

My solution is as follows:

Ignore all position data given to me by ConfigureNotify events and always fetch the window position with the following code:

bool GetExtents(Display* display, Window windowID, glm::ivec4& extents) {
  static Atom       frameExtents = XInternAtom(display, "_NET_FRAME_EXTENTS", false);
  Atom              actualType;
  int               actualFormat;
  unsigned long     itemCount;
  unsigned long     bytesAfter;
  unsigned char*    data = nullptr;

  if (!XGetWindowProperty(display, windowID, frameExtents, 0, 4, false, XA_CARDINAL, &actualType, &actualFormat, &itemCount, &bytesAfter, &data)) {
    if (data) {
      long* extentData = reinterpret_cast<long *>(data);

      extents.x = static_cast<int>(extentData[0]);
      extents.z = static_cast<int>(extentData[1]);
      extents.y = static_cast<int>(extentData[2]);
      extents.w = static_cast<int>(extentData[3]);

      XFree(data);
      return true;
    }
  }
  return false;
}

glm::ivec2 getPosition(Display* display, Window windowID) const {
  XWindowAttributes attr;
  glm::ivec2        position;
  glm::ivec4        extents;
  Window            root;
  Window            parent;
  Window*           children;
  Window            window;
  uint32_t          noofchildren;

  XQueryTree(display, windowID, &root, &parent, &children, &noofchildren);

  if (children) {
    XFree(children);
  }
  if (root == parent) {
    window = windowID;
  }
  window = parent ? parent : windowID;

  XGetWindowAttributes(display, window, &attr);

  position.x = attr.x;
  position.y = attr.y;

  // Hack ahead...
  if (window != windowID) {
    if (GetExtents(display, windowID, extents)) {
      XGetWindowAttributes(display, windowID, &attr);

      position.x += attr.x;
      position.y += attr.y - extents.y;
    }
  }
  return position;
}

By using that position along with the size retrieved with XGetWindowAttributes, I can reposition a window (the child window, to be exact) exactly where it was according to the user's input/application configuration. (With extra logic to handle maximised windows, of course.)

Essentially, this is now a null op:

Window     windowID = XCreateWindow(...);
glm::ivec2 pos = getPosition(display, windowID);

XMoveWindow(display, windowID, pos.x, pos.y);

However, I shouldn't need to do any of that.

The position and size provided for ConfigureNotify events should be all I need to restore the position of the window returned by XCreateWindow.

Equally, the position and size returned by XGetWindowAttributes for the window returned from XCreateWindow should be all I need to later reposition my window(s).

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No Cinnamon source file or test is identified. Start by reproducing the behavior with XCreateWindow, XGetWindowAttributes, ConfigureNotify, and XMoveWindow, then compare the reported coordinates with _NET_FRAME_EXTENTS; done means window positions and sizes can be saved and restored consistently without the workaround.

Written by the indexing model from the issue text.

Assessment

Tech stack
linux
Domain
desktop, operating-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.