8.2. Module Overview

written by (???)

(Extracted from wine/documentation/internals)

8.2.1. KERNEL Module

Needs some content...

8.2.2. GDI Module

8.2.2.1. X Windows System interface

The X libraries used to implement X clients (such as Wine) do not work properly if multiple threads access the same display concurrently. It is possible to compile the X libraries to perform their own synchronization (initiated by calling XInitThreads()). However, Wine does not use this approach. Instead Wine performs its own synchronization by putting a wrapper around every X call that is used. This wrapper protects library access with a critical section, and also arranges things so that X libraries compiled without -D_REENTRANT (eg. with global errno variable) will work with Wine.

To make this scheme work, all calls to X must use the proper wrapper functions (or do their own synchronization that is compatible with the wrappers). The wrapper for a function X...() is calles TSX...() (for "Thread Safe X ..."). So for example, instead of calling XOpenDisplay() in the code, TSXOpenDisplay() must be used. Likewise, X header files that contain function prototypes are wrapped, so that eg. "ts_xutil.h" must be included rather than <X11/Xutil.h>. It is important that this scheme is used everywhere to avoid the introduction of nondeterministic and hard-to-find errors in Wine.

The code for the thread safe X wrappers is contained in the tsx11/ directory and in include/ts*.h. To use a new (ie. not previously used) X function in Wine, a new wrapper must be created. The wrappers are generated (semi-)automatically from the X11R6 includes using the tools/make_X11wrappers perl script. In simple cases it should be enough to add the name of the new function to the list in tsx11/X11_calls; if this does not work the wrapper must be added manually to the make_X11wrappers script. See comments in tsx11/X11_calls and tools/make_X11wrappers for further details.

8.2.3. USER Module

USER implements windowing and messaging subsystems. It also contains code for common controls and for other miscellaneous stuff (rectangles, clipboard, WNet, etc). Wine USER code is located in windows/, controls/, and misc/ directories.

8.2.3.1. Windowing subsystem

windows/win.c

windows/winpos.c

Windows are arranged into parent/child hierarchy with one common ancestor for all windows (desktop window). Each window structure contains a pointer to the immediate ancestor (parent window if WS_CHILD style bit is set), a pointer to the sibling (returned by GetWindow(..., GW_NEXT)), a pointer to the owner window (set only for popup window if it was created with valid hwndParent parameter), and a pointer to the first child window (GetWindow(.., GW_CHILD)). All popup and non-child windows are therefore placed in the first level of this hierarchy and their ancestor link (wnd->parent) points to the desktop window.

   Desktop window			- root window
    |     \      `-.
    |      \        `-.
   popup -> wnd1  ->  wnd2		- top level windows    
    |       \   `-.      `-.
    |        \     `-.      `-.
   child1  child2 -> child3  child4     - child windows
          

Horizontal arrows denote sibling relationship, vertical lines - ancestor/child. To summarize, all windows with the same immediate ancestor are sibling windows, all windows which do not have desktop as their immediate ancestor are child windows. Popup windows behave as topmost top-level windows unless they are owned. In this case the only requirement is that they must precede their owners in the top-level sibling list (they are not topmost). Child windows are confined to the client area of their parent windows (client area is where window gets to do its own drawing, non-client area consists of caption, menu, borders, intrinsic scrollbars, and minimize/maximize/close/help buttons).

Another fairly important concept is z-order. It is derived from the ancestor/child hierarchy and is used to determine "above/below" relationship. For instance, in the example above, z-order is

child1->popup->child2->child3->wnd1->child4->wnd2->desktop.
          

Current active window ("foreground window" in Win32) is moved to the front of z-order unless its top-level ancestor owns popup windows.

All these issues are dealt with (or supposed to be) in windows/winpos.c with SetWindowPos() being the primary interface to the window manager.

Wine specifics: in default and managed mode each top-level window gets its own X counterpart with desktop window being basically a fake stub. In desktop mode, however, only desktop window has an X window associated with it. Also, SetWindowPos() should eventually be implemented via Begin/End/DeferWindowPos() calls and not the other way around.

8.2.3.1.1. Visible region, clipping region and update region

windows/dce.c

windows/winpos.c

windows/painting.c

    ________________________
   |_________               |  A and B are child windows of C
   |    A    |______        | 
   |         |      |       |
   |---------'      |       |
   |   |      B     |       |
   |   |            |       |
   |   `------------'       |
   |                   C    |
   `------------------------'
            

Visible region determines which part of the window is not obscured by other windows. If a window has the WS_CLIPCHILDREN style then all areas below its children are considered invisible. Similarily, if the WS_CLIPSIBLINGS bit is in effect then all areas obscured by its siblings are invisible. Child windows are always clipped by the boundaries of their parent windows.

B has a WS_CLIPSIBLINGS style:

   .          ______ 
   :         |      |
   |   ,-----'      |
   |   |      B     | - visible region of B
   |   |            |
   :   `------------'
            

When the program requests a display context (DC) for a window it can specify an optional clipping region that further restricts the area where the graphics output can appear. This area is calculated as an intersection of the visible region and a clipping region.

Program asked for a DC with a clipping region:

          ______
      ,--|--.   |     .    ,--. 
   ,--+--'  |   |     :   _:  |
   |  |   B |   |  => |  |    | - DC region where the painting will
   |  |     |   |     |  |    |   be visible
   `--|-----|---'     :  `----'
      `-----'
            

When the window manager detects that some part of the window became visible it adds this area to the update region of this window and then generates WM_ERASEBKGND and WM_PAINT messages. In addition, WM_NCPAINT message is sent when the uncovered area intersects a nonclient part of the window. Application must reply to the WM_PAINT message by calling the BeginPaint()/EndPaint() pair of functions. BeginPaint() returns a DC that uses accumulated update region as a clipping region. This operation cleans up invalidated area and the window will not receive another WM_PAINT until the window manager creates a new update region.

A was moved to the left:

    ________________________       ...          / C update region
   |______                  |     :      .___ /
   | A    |_________        |  => |   ...|___|..
   |      |         |       |     |   :  |   |
   |------'         |       |     |   :  '---' 
   |   |      B     |       |     |   :      \
   |   |            |       |     :            \
   |   `------------'       |                    B update region
   |                   C    |
   `------------------------'
            

Windows maintains a display context cache consisting of entries that include the DC itself, the window to which it belongs, and an optional clipping region (visible region is stored in the DC itself). When an API call changes the state of the window tree, window manager has to go through the DC cache to recalculate visible regions for entries whose windows were involved in the operation. DC entries (DCE) can be either private to the window, or private to the window class, or shared between all windows (Windows 3.1 limits the number of shared DCEs to 5).

8.2.3.2. Messaging subsystem

windows/queue.c

windows/message.c

Each Windows task/thread has its own message queue - this is where it gets messages from. Messages can be:

  1. generated on the fly (WM_PAINT, WM_NCPAINT, WM_TIMER)

  2. created by the system (hardware messages)

  3. posted by other tasks/threads (PostMessage)

  4. sent by other tasks/threads (SendMessage)

Message priority:

First the system looks for sent messages, then for posted messages, then for hardware messages, then it checks if the queue has the "dirty window" bit set, and, finally, it checks for expired timers. See windows/message.c.

From all these different types of messages, only posted messages go directly into the private message queue. System messages (even in Win95) are first collected in the system message queue and then they either sit there until Get/PeekMessage gets to process them or, as in Win95, if system queue is getting clobbered, a special thread ("raw input thread") assigns them to the private queues. Sent messages are queued separately and the sender sleeps until it gets a reply. Special messages are generated on the fly depending on the window/queue state. If the window update region is not empty, the system sets the QS_PAINT bit in the owning queue and eventually this window receives a WM_PAINT message (WM_NCPAINT too if the update region intersects with the non-client area). A timer event is raised when one of the queue timers expire. Depending on the timer parameters DispatchMessage either calls the callback function or the window procedure. If there are no messages pending the task/thread sleeps until messages appear.

There are several tricky moments (open for discussion) -