Beryl's core structures - version this-is-not-complete-yet
1. Scope of this document
2. CompDisplay vs CompScreen
1. Scope of this document
With this document I will attempt to clear up some confusion about the most
important data structures in Beryl. I will not describe every part of the
structures, but rather clear up some trickier parts.
A good way to get to know these structures is to use the debug plugin. It
allows you to easily view the CompScreen and CompWindow structure(s) with
some of the sub-structures.
2. CompDisplay vs CompScreen
If you've seen the DISPLAY variable, it'll look something like this for most
people: DISPLAY=:0.0 . The first 0 here is the display, and the second 0
is the screen. Each X server has ONE display, but can have multiple screens.
Do not confuse a screen with a monitor, though. In a xinerama-hinted
environment (the most common multihead setup), the X server presents just one
screen to the programs, but gives us hints about where the head/monitor
stops and starts.
Usually, there isn't any real work needed in a plugin to support multiscreen.
All you need to do is make sure you deal with CompScreen and never assume
that you can get the right CompScreen from display->screens. There are
functions in core that let you find a screen based on the root window. Use
these, even if display->screens is very tempting.
Generally, we keep bindings in Display-scope. This can add some complications,
but make sure you find the right screen if you want your binding to only
affect one screen. Take a look at rotate.c to see how this is done as an
CompDisplay is mostly dealt with by core. It is the DataStructure that glues
all the possible screens together. Chances are, you will mostly just work with
d->activeWindow which defines the currently active (focused) window, and the
d->pointerX/Y which tells you where the pointer is. Also, it stores the atoms
fetched by core.
CompScreen is _the_ structure. You will get to know this whether you want to
or not if you intend to do development on Beryl.
First of all, it is a linked list item, so it contains a link to the next
CompScreen. It also has a link to the CompDisplay structure it belongs to.
Because of that fact, you never need to pass both CompDisplay and
CompScreen to a function, and you are better off passing CompScreen than
CompDisplay, because the latter can have several CompScreens.
Each screen has a unique root window, which is often used in identifying the
screen when you get events. This is process is usually handled by core when
you call findScreenAtDisplay().
'height' and 'width' is the height and width of the entire screen. It is
very tempting to use these a lot, but they are dangerous. The reason they
are dangerous is that they span across all heads associated with that screen.
Therefore, you want to familiarise yourself with the CompOutput structure.
The CompScreen has an array of nOutputDev CompOutput structures; in other
words, one per monitor. It also has a convenient currentOutputDev which core
tries to set, but is not always accurate.
Luckily, the CompOutput struct is very simple. It has a width, height, region
and workRegion variable that we care about. The region helps us establish
where an outputDev starts in the screen. region.x1 defines where the X-coordinate
of that specific window starts, and region.x2 describes where it ends. The same
with region.y1/y2 respectively for the Y coordinate.
For a single monitor with 1024x768, the outputdev will look like this:
Keep in mind that x + width puts you 1 pixel beyond the device. (0-1023 is 1024
numbers. A classic programming error is to forget that 0 is a valid number).
Currently, we don't really use the rects of the regions, so for nowe we can
workRegion is identical to region, but is supposed to take struts into
consideration. This code has undergone some changes, and is likely to change
in the future. The problem is that we never used the region nature of the
workRegion so it was still just a rect. That discussion is beyond the scope
of this text, however. Just keep it in mind if you want a simple but imperfect
way of avoiding panels.
So when do you use s->width and when do you use
s->outpudDev[s->currentOutputDev].region.x1/x2 ? There is no universal answer
to this, but the simple answer is to use s->width when you are concerned with
the entire screen, and the outputDev when you want to constrain yourself to the
monitor. Examples are maximization, which deals with outputDev, and edge-
detection, which wants to find the edge of the screen, not the monitor.
More obvious variables are hsize and vsize, which define the horisontal
and vertical size, x and y, which define which of these you are currently on,
screenNum, which defines which screen you are on (usually 0), and the WorkArea
rectangle, which defines the work area for the screen (avoid using this, as
it will be quite strange on xinerama-hinted multihead.)
Each CompScreen also has a list of windows (s->windows) and the reversed
version. Windows are stored in a bottom-up fashion, so the first window in
s->windows will be the bottommost window.
The projectionStyle sets how the projection matrix should be set up. This
is mostly just relevant for xinerama multihead at the moment. See the multihead
documentation for details. In the future, this might be used to set orthographic
projection instead of perspective.
Each window has a CompWindow structure. However, you should not store this link
unless you also make sure you listen for events which would destroy it and
update your version accordingly. If you want your plugin to remember a window,
it is often wise to use the w->id instead, and locate the CompWindow when you
need it, but this depends on how you use the CompWindow. Storing the CompWindow
pointer is faster, but slightly more complex.