Wine Developer's Guide/Windowing system

From WineHQ Wiki
< Wine Developer's Guide
Revision as of 23:48, 15 November 2017 by Zf (Talk | contribs) (a bit of restructuring, rewrite and expand on much of the windowing code (more to write later...))

Jump to: navigation, search


1 USER32 Module

The USER32 module was originally where all public APIs went, before Microsoft started splitting them into separate DLLs. As a result, user32.dll contains exports with a wide variety of functions. Mostly, though, these fall into the categories of:

  • window management
  • code for some built-in windows and controls: dialogs, buttons, static text, input, etc.
  • message queues (which are closely related to window management)
  • keyboard accelerators
  • clipboard management
  • embedded resources
  • string functions

USER32 code is located in the directory dlls/user32/. The 16-bit version of user32.dll is called user.exe, and it lives in the directory dlls/user.exe16/.

1.1 Windowing subsystem

  • dlls/user32/win.c — contains most windowing functions (some of these are in winpos.c or focus.c)
  • dlls/user32/winpos.c — functions relating to window position (some of these are in win.c or focus.c)
  • dlls/user32/focus.c — functions relating to window focus (some of these are in winpos.c or win.c)
  • dlls/user32/defwnd.c — implementation of DefWindowProc() (some cases are in nonclient.c)
  • dlls/user32/desktop.c — implementation of the desktop window
  • dlls/user32/driver.c — interface to the USER driver
  • dlls/user32/mdi.c — Multi-Document Interface (MDI) windowing code
  • dlls/user32/nonclient.c — painting and message handling for the non-client area
  • dlls/user32/painting.c — window painting
  • dlls/user32/property.c — window properties [GetProp() etc.]
  • dlls/user32/winstation.c — window stations and desktops
  • dlls/user.exe16/window.c — 16-bit windowing code; mostly thunks to 32-bit with little modification (some functions are in user.c)
  • server/window.c — server-side windowing code
  • server/winstation.c — server-side window station code

1.1.1 Window basics

Windows is based on windows. As is usual for Windows APIs, there is a quite large amount of data that is associated with its particular window. This includes obvious information like position, size, and title text, but also some other information that you might not necessarily expect:

  • Window style — a collection of bitfields detailing various aspects of the window, like whether it should clip its siblings, or whether it has a minimize box.
  • Thread — all windows are tied to the thread in which they are created, due to the way that messages are implemented.

1.1.2 The window tree

Windows are arranged in a tree-based hierarchy. All windows have an immediate ancestor (i.e. a parent), and they may have descendants (i.e. children) and siblings as well. In general there are three kinds of windows with respect to this hierarchy:

  • Top-level windows are windows created without an explicit parent. Internally (and, depending on the function, externally), these are children of the desktop window, which is a special window that represents the entire desktop.
  • Child windows are windows created with an explicit parent and using the WS_CHILD style. These windows appear inside of the area of their immediate ancestor, and don't show up in the taskbar.
    • The Multi-Document Interface (MDI) is an API created to make navigating between multiple documents in one application easier, mostly used in older applications. MDI child windows are created with the style WS_EX_MDICHILD instead of WS_CHILD. They operate mostly like regular child windows, but have extra functionality making it easier to minimize, arrange, and switch between child windows.
  • Owned windows are windows created with an explicit parent but without the WS_CHILD style. These windows act like normal windows, except that they always appear in front of their owner. An example use of owned windows to create a dialog that must be dealt with before returning to the main window.

Note the distinction between child windows and owned windows. These are mutually exclusive, and both are treated as the immediate ancestor as far as the window hierarchy is concerned, but the two act quite differently in some ways (though not in others). Accordingly they are stored separately under Wine.

The window tree does not have a limited depth—child and owned windows can have their own child and owned windows.

1.1.3 Z-order

Children of a window are stored in Z-order from top to bottom, so normally determining Z-order is just a matter of enumerating this list. However, some complications arise due to special window styles:

  • Owned windows always appear above their owners.
  • Topmost windows have the WS_EX_TOPMOST style. A topmost window always appears above all non-topmost windows, but follows normal z-order rules with regard to other topmost windows.
  • Pop-up windows have the WS_POPUP style. Pop-up windows always act like topmost windows unless they are owned, in which case they behave normally (of course they still must appear above their owner).

The following example demonstrates window order. Note that popup1 behaves like a topmost window and so is listed before its siblings.

   Desktop window                    - root window
          |
  +-------+------------------+
  |       |                  |
popup1   wnd2               wnd3     - top-level windows
          |                  |
          +----------+       |
          |          |       |
       owned2a    owned2b  owned3    - child windows

The full z-order in this example would be:

popup1->owned2a->owned2b->wnd2->owned3->wnd3->desktop.

Whenever a window is set to the foreground (usually by being activated), it is moved to the top of the z-order—at least, as far as possible; it will still be behind topmost windows, its owned windows, and non-owned popups.

1.1.4 Visible region, clipping region and update region

dlls/user32/winpos.c

dlls/user32/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. Similarly, 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).

1.2 Messaging subsystem

  • dlls/user32/message.c — contains code related to sending, receiving, and waiting for messages
  • dlls/user32/winproc.c — code directly related to calling window procedures
  • dlls/user32/dde_client.c — DDE client-side code
  • dlls/user32/dde_server.c — DDE server-side code
  • dlls/user32/dde_misc.c — miscellaneous DDE functions

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 dlls/user32/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):

  • System message order has to be honored and messages should be processed within correct task/thread context. Therefore when Get/PeekMessage encounters unassigned system message and this message appears not to be for the current task/thread it should either skip it (or get rid of it by moving it into the private message queue of the target task/thread - Win95, AFAIK) and look further or roll back and then yield until this message gets processed when system switches to the correct context (Win16). In the first case we lose correct message ordering, in the second case we have the infamous synchronous system message queue. Here is a post to one of the OS/2 newsgroup I found to be relevant:
Here's the problem in a nutshell, and there is no good solution. Every possible solution creates a different problem.

With a windowing system, events can go to many different windows. Most are sent by applications or by the OS when things relating to that window happen (like repainting, timers, etc.)

Mouse input events go to the window you click on (unless some window captures the mouse).

So far, no problem. Whenever an event happens, you put a message on the target window's message queue. Every process has a message queue. If the process queue fills up, the messages back up onto the system queue.

This is the first cause of apps hanging the GUI. If an app doesn't handle messages and they back up into the system queue, other apps can't get any more messages. The reason is that the next message in line can't go anywhere, and the system won't skip over it.

This can be fixed by making apps have bigger private message queues. The SIQ fix does this. PMQSIZE does this for systems without the SIQ fix. Applications can also request large queues on their own.

Another source of the problem, however, happens when you include keyboard events. When you press a key, there's no easy way to know what window the keystroke message should be delivered to.

Most windowing systems use a concept known as "focus". The window with focus gets all incoming keyboard messages. Focus can be changed from window to window by apps or by users clicking on windows.

This is the second source of the problem. Suppose window A has focus. You click on window B and start typing before the window gets focus. Where should the keystrokes go? On the one hand, they should go to A until the focus actually changes to B. On the other hand, you probably want the keystrokes to go to B, since you clicked there first.

OS/2's solution is that when a focus-changing event happens (like clicking on a window), OS/2 holds all messages in the system queue until the focus change actually happens. This way, subsequent keystrokes go to the window you clicked on, even if it takes a while for that window to get focus.

The downside is that if the window takes a real long time to get focus (maybe it's not handling events, or maybe the window losing focus isn't handling events), everything backs up in the system queue and the system appears hung.

There are a few solutions to this problem.

One is to make focus policy asynchronous. That is, focus changing has absolutely nothing to do with the keyboard. If you click on a window and start typing before the focus actually changes, the keystrokes go to the first window until focus changes, then they go to the second. This is what X-windows does.

Another is what NT does. When focus changes, keyboard events are held in the system message queue, but other events are allowed through. This is "asynchronous" because the messages in the system queue are delivered to the application queues in a different order from that with which they were posted. If a bad app won't handle the "lose focus" message, it's of no consequence - the app receiving focus will get its "gain focus" message, and the keystrokes will go to it.

The NT solution also takes care of the application queue filling up problem. Since the system delivers messages asynchronously, messages waiting in the system queue will just sit there and the rest of the messages will be delivered to their apps.

The OS/2 SIQ solution is this: When a focus-changing event happens, in addition to blocking further messages from the application queues, a timer is started. When the timer goes off, if the focus change has not yet happened, the bad app has its focus taken away and all messages targeted at that window are skipped. When the bad app finally handles the focus change message, OS/2 will detect this and stop skipping its messages.

As for the pros and cons:

The X-windows solution is probably the easiest. The problem is that users generally don't like having to wait for the focus to change before they start typing. On many occasions, you can type and the characters end up in the wrong window because something (usually heavy system load) is preventing the focus change from happening in a timely manner.

The NT solution seems pretty nice, but making the system message queue asynchronous can cause similar problems to the X-windows problem. Since messages can be delivered out of order, programs must not assume that two messages posted in a particular order will be delivered in that same order. This can break legacy apps, but since Win32 always had an asynchronous queue, it is fair to simply tell app designers "don't do that". It's harder to tell app designers something like that on OS/2 - they'll complain "you changed the rules and our apps are breaking."

The OS/2 solution's problem is that nothing happens until you try to change window focus, and then wait for the timeout. Until then, the bad app is not detected and nothing is done.

--David Charlap
  • Intertask/interthread SendMessage. The system has to inform the target queue about the forthcoming message, then it has to carry out the context switch and wait until the result is available. Win16 stores necessary parameters in the queue structure and then calls DirectedYield() function. However, in Win32 there could be several messages pending sent by preemptively executing threads, and in this case SendMessage has to build some sort of message queue for sent messages. Another issue is what to do with messages sent to the sender when it is blocked inside its own SendMessage.

1.3 Accelerators

There are three differently sized accelerator structures exposed to the user:

  1. Accelerators in NE resources. This is also the internal layout of the global handle HACCEL (16 and 32) in Windows 95 and Wine. Exposed to the user as Win16 global handles HACCEL16 and HACCEL32 by the Win16/Win32 API. These are 5 bytes long, with no padding:
    BYTE   fVirt;
    WORD   key;
    WORD   cmd;
    
  2. Accelerators in PE resources. They are exposed to the user only by direct accessing PE resources. These have a size of 8 bytes:
    BYTE   fVirt;
    BYTE   pad0;
    WORD   key;
    WORD   cmd;
    WORD   pad1;
    
  3. Accelerators in the Win32 API. These are exposed to the user by the CopyAcceleratorTable and CreateAcceleratorTable functions in the Win32 API. These have a size of 6 bytes:
    BYTE   fVirt;
    BYTE   pad0;
    WORD   key;
    WORD   cmd;
    

    Why two types of accelerators in the Win32 API? We can only guess, but my best bet is that the Win32 resource compiler can/does not handle struct packing. Win32 ACCEL is defined using #pragma(2) for the compiler but without any packing for RC, so it will assume #pragma(4).

    2 X Windows System interface

    2.1 Keyboard mapping

    Wine now needs to know about your keyboard layout. This requirement comes from a need from many apps to have the correct scancodes available, since they read these directly, instead of just taking the characters returned by the X server. This means that Wine now needs to have a mapping from X keys to the scancodes these programs expect.

    On startup, Wine will try to recognize the active X layout by seeing if it matches any of the defined tables. If it does, everything is alright. If not, you need to define it.

    To do this, open the file dlls/winex11.drv/keyboard.c and take a look at the existing tables.

    What you really would need to do, is find out which scancode each key needs to generate. Find it in the main_key_scan table, which looks like this:

    static const int main_key_scan[MAIN_LEN] =
    {
    /* this is my (102-key) keyboard layout, sorry if it doesn't quite match yours */
    0x29,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,
    0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,
    0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x2B,
    0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,
    0x56 /* the 102nd key (actually to the right of l-shift) */
    };
    

    Next, assign each scancode the characters imprinted on the keycaps. This was done (sort of) for the US 101-key keyboard, which you can find near the top in keyboard.c. It also shows that if there is no 102nd key, you can skip that.

    However, for most international 102-key keyboards, we have done it easy for you. The scancode layout for these already pretty much matches the physical layout in the main_key_scan, so all you need to do is to go through all the keys that generate characters on your main keyboard (except spacebar), and stuff those into an appropriate table. The only exception is that the 102nd key, which is usually to the left of the first key of the last line (usually Z), must be placed on a separate line after the last line.

    After you have written such a table, you need to add it to the main_key_tab[] layout index table. This will look like this:

    static struct {
    WORD lang, ansi_codepage, oem_codepage;
    const char (*key)[MAIN_LEN][4];
    } main_key_tab[]={
    ...
    ...
    {MAKELANGID(LANG_NORWEGIAN,SUBLANG_DEFAULT),  1252, 865, &main_key_NO},
    ...
    

    After you have added your table, recompile Wine and test that it works. If it fails to detect your table, try running

    WINEDEBUG=+key,+keyboard wine > key.log 2>&1
    

    and look in the resulting key.log file to find the error messages it gives for your layout.

    Note that the LANG_* and SUBLANG_* definitions are in include/winnls.h, which you might need to know to find out which numbers your language is assigned, and find it in the WINEDEBUG output. The numbers will be (SUBLANG * 0x400 + LANG), so, for example the combination LANG_NORWEGIAN (0x14) and SUBLANG_DEFAULT (0x1) will be (in hex) 14 + 1*400 = 414, so since I'm Norwegian, I could look for 0414 in the WINEDEBUG output to find out why my keyboard won't detect.