Cygwin/X X Server Architecture

Cygwin/X's X Server architecture was heavily inspired by Angebranndt94, the Definition of the Porting Layer for the X v11 Sample Server.

Server Privates

X Servers use various structures to pass information around to functions. Some of those structures are colormaps, graphics contexts (GCs), pixmaps, and screens. The X protocol defines the contents of each of these structures, however, the X Server implementation and various X Server libraries (MI, FB, Shadow, etc.) may require additional information to be associated with these internal structures. For example, the Cygwin/X X Server must associate a Windows window handle (hwnd) with each X Server screen that is open.

Privates are the mechanism provided by the X protocol for associating additional information with internal X Server structures. Privates originally consisted of a single pointer member contained in each structure, usually named devPrivate or devPriv. This original specification only allowed one of the X Server layers (mi, fb, shadow, etc.) to have privates associated with an internal structure. Privates have since been revised.

The current privates implementation requires that each X Server layer call a function on startup to indicate that that layer will require privates and to obtain an index into the array of privates that that layer's privates will be stored at. Modern privates are generally stored in an array of type DevUnion pointed to by a structure member named devPrivates; DevUnion is defined in xserver/include/miscstruct.h. There are two different memory allocation schemes for devPrivates.

Memory for privates structures can either be preallocated or allocated upon use. Preallocation, the preferred method for GCs, pixmaps, and windows, requires that the size of the privates memory needed be specified during X Server initialization. Preallocation allows the DIX layer to allocate all memory needed for a given internal structure, including all privates memory, as a single contiguous block of memory; this greatly reduces memory fragmentation. Allocation upon use, used by screens, requires the DDX structure creation function to allocate memory for the privates; winScreenInit calling winAllocatePrivates, which allocates screen privates memory directly, is an example of this. Allocation upon use can optionally and non-optimally be used by GCs, pixmaps, and windows.

Macros

Three macros are provided for each class of privates that make setting up and using the privates easier. The macros for screen privates are examined as an example.

winPrivScreenPtr winGetScreenPriv(ScreenPtr pScreen);

winGetScreenPriv takes a non-NULL pointer to a screen, a ScreenPtr, and returns the pointer stored in the DDX privates for that screen. Passing a NULL or invalid ScreenPtr to winGetScreenPriv will cause an access violation, crashing the Cygwin/X X Server.

void winSetScreenPriv(ScreenPtr pScreen, void * pvPrivates);

winSetScreenPriv takes a non-NULL pointer to a screen, a ScreenPtr, and sets the DDX privates pointer to the value of the pvPrivates parameter. Passing a NULL or invalid ScreenPtr to winSetScreenPriv will cause an access violation, crashing the Cygwin/X X Server.

void winScreenPriv(ScreenPtr pScreen);

winScreenPriv takes a non-NULL pointer to a screen, a ScreenPtr, and declares a local variable in the calling function named pScreenPriv. winScreenPriv may only be called at the top of a C function within the variable declaration block; calling the function elsewhere will break the ANSI C rule that all variables must be declared at the top of a scope block. Passing a NULL or invalid ScreenPtr to winScreenPriv will cause an access violation, crashing the Cygwin/X X Server.

Engine System

The Cygwin/X X Server uses several methods of drawing graphics on the display device; each of these different drawing methods is referred to as an engine. It should be noted that the Primary FB engine is historical and is discussed here only for completeness.

Shadow FB Engines

The Shadow FB engines use Keith Packard's FB drawing procedures wrapped with his Shadow layer that allows drawing to an offscreen framebuffer with periodic updates of the primary framebuffer.

Currently, shadow FB engines exist using DirectDraw4 and GDI.

Primary FB Engine

The Primary FB engine worked in the same manner that the original Cygwin/X X Server worked, namely, it uses IDirectDrawSurface_Lock to obtain a pointer to the primary framebuffer memory at server startup. This memory pointer is held until the X Server shuts down. This technique does not work on all versions of Windows.

Locking the primary framebuffer on Windows 95/98/Me causes the Win16Mutex to be obtained by the program that locks the primary framebuffer; the Win16Mutex is not released until the primary framebuffer is unlocked. The Win16Mutex is a semaphore introduced in Windows 95 that prevents 16 bit Windows code from being reentered by different threads or processes. For compatibility reasons, all GDI operations in Windows 95/98/Me are written in 16 bit code, thus requiring that the Win16Mutex be obtained before performing those operations. All of this leads to the following situation on Windows 95/98/Me:

  1. The primary framebuffer is locked, causing the Cygwin/X X Server to hold the Win16Mutex.

  2. Windows switches the Cygwin/X X Server out of the current process slot; another process is switched in.

  3. The newly selected process makes a GDI function call.

  4. The GDI function call must wait for the Win16Mutex to be released, but the Win16Mutex cannot be released until the Cygwin/X X Server releases the Win16Mutex. However, the Cygwin/X X Server will not release the Win16Mutex until it exits. The end result is that the Win16Mutex has been deadlocked and the Windows machine is frozen with no way to recover.

Windows NT/2000/XP do not contain any 16 bit code, so the Win16Mutex is not an issue; thus, the Primary FB engine works fine on those operating systems. However, drawing directly to the primary framebuffer suffers performance problems. For example, on some systems writing to the primary framebuffer requires doing memory reads and writes across the PCI bus which is only 32 bits wide and features a clock speed of 33 MHz, as opposed to accessing system memory, which is attached to a 64 bit wide bus that runs at between 100 and 266 (effective) MHz. Furthermore, accessing the primary framebuffer memory requires several synchronization steps that take many clock cycles to complete. The end result is that the Primary FB engine is several times slower than the Shadow FB engines.

The Primary FB engine also has several unique issues that are difficult to program around. Development of the Primary FB engine has ceased, due to the difficulty of maintaining it, coupled with the fact that Primary FB does not run on Windows 95/98/Me and with the poor performance of Primary FB. The Primary FB source code has been left in place so that future programmers can enable it and see the poor performance of the engine for themselves.

User Input

At the end of InitInput in hw/xwin/InitInput.c we open /dev/windows, a special device which becomes ready when there is anything to read on the windows message queue, and add that to the select mask for WaitForSomething using AddEnabledDevice.

The X server's main loop calls the OS layer os/WaitFor.c's WaitForSomething function, which waits for something to happen using select. When select returns, all the wakeup handlers are run. Any queued Win32 user input messages (as well as other Win32 messages) are handled when hw/xwin/winwakeup.c's winWakeupHandler function is called. Each Win32 user input message typically queues an input event, or several input events, using the MI layer's mi/mieq.c's mieqEnqueue function.

Enqueued MI input events are processed when the DIX layer dix/dispatch.c's Dispatch function calls hw/xwin/InitInput.c's ProcessInputEvents function, which calls mi/mieq.c's mieqProcessInputEvents.

Keyboard

Win32 keyboard messages are processed in winwndproc.c's winWindowProc. The messages processed are:

  • WM_SYSKEYDOWN

  • WM_KEYDOWN

  • WM_SYSKEYUP

  • WM_KEYUP

The WM_SYSKEY* messages are generated when the user presses a key while holding down the Alt key or when the user presses a key after pressing and releasing the F10 key. Processing for WM_SYSKEYDOWN and WM_KEYDOWN (and respectively WM_SYSKEYUP and WM_KEYUP) messages are identical because the X Server does not distinguish between a normal key press and a key press when the Alt key is down.

Win32 uses virtual key codes to identify which key is being pressed or released. Virtual key codes follow the idea that the same virtual key code will be sent for keys with the same label printed on them. For example, the left and right Ctrl keys both generate the VK_CONTROL virtual key code. Virtual key codes are accompanied by other state information, such as the extended flag, that distinguishes between the multiple keys with the same label. For example, the left Ctrl key does not have the extended flag asserted, while the right Ctrl key does have the extended flag asserted. However, virtual key codes are not the way that key presses have traditionally been identified on personal computers and in the X Protocol.

Personal computers and the X Protocol use scan codes to identify which key is being pressed. Each key on the keyboard generates a specified number when that key is pressed or released; this number is called the scan code. Scan codes are always distinct for distinct keys. For example, the left and right Ctrl keys generate distinct scan codes, even though their functionality is the same. Scan codes do not have additional state information, as the multiple keys with the same label will each generate a unique scan code. There is some debate as to which of virtual key codes or scan codes is the better system.

The X Protocol expects that keyboard input will be based on a scan code system. There are two methods of sending a scan codes from a virtual key code message. The first method is to create a static table that links the normal and extended state of each virtual key code to a scan code. This method seems valid, but the method does not work reliably for users with non-U.S. keyboard layouts. The second method simply pulls the scan code out of the lParam of the keyboard messages; this method works reliably for non-U.S. keyboard layouts. However, there are further concerns for non-U.S. keyboard layouts.

Non-U.S. keyboard layouts typically use the right Alt key as an alternate shift key to access an additional row of symbols from the `, 1, 2, ..., 0 keys, as well as accented forms of standard alphabetic characters, such as á, ä, å, ú and additional alphabetic characters, such as ß. Non-U.S. keyboards typically label the right Alt key as AltGr or AltLang; the Gr is short for "grave", which is the name of one of the accent symbols. The X Protocol and Win32 methods of handling the AltGr key are not directly compatible with one another.

The X Protocol handles AltGr presses and releases in much the same way as any other key press and release. Win32, however, generates a fake Ctrl press and release for each AltGr press and release. The X Protocol does not expect this fake Ctrl press and release, so care must be taken to discard the fake Ctrl press and release. Fake Ctrl presses and releases are detected and discarded by passing each keyboard message to winkeybd.c's winIsFakeCtrl_L function. winIsFakeCtrl_L detects the fake key presses and releases by comparing the timestamps of the AltGr message with the timestamp of any preceding or trailing Ctrl message. Two real key events will never have the same timestamp, but the fake key events have the same timestamp as the AltGr messages, so the fake messages can be easily identified.

Special keyboard considerations must be handled when the Cygwin/X X Server loses or gains the keyboard focus. For example, the user can switch out of Cygwin/X, toggle the Num Lock key, then switch back into Cygwin/X; in this case Cygwin/X would not have received the Num Lock toggle message, so it will continue to function as if Num Lock was in its previous state. Thus, the state of any mode keys such as Num Lock, Caps Lock, Scroll Lock, and Kana Lock must be stored upon loss of keyboard focus; on regaining focus, the stored state of each mode key must then be compared to that key's current state, toggling the key if its state has changed.

Mouse

Win32 mouse messages are processed in winwndproc.c's winWindowProc. The messages processed are:

  • WM_MOUSEMOVE

  • WM_NCMOUSEMOVE

  • WM_LBUTTON*

  • WM_MBUTTON*

  • WM_RBUTTON*

  • WM_MOUSEWHEEL

Handling mouse motion is relatively straight forward, with the special consideration that the Windows mouse cursor must be hidden when the mouse is moving over the client area of a Cygwin/X window; the Windows mouse cursor must be redisplayed when the mouse is moving over the non-client area of a Cygwin/X window. Win32 sends the absolute coordinates of the mouse, so we call miPointerAbsoluteCursor to change the position of the mouse.

Three-button mouse emulation is supported for users that do not have a three button mouse. When three-button mouse emulation is disabled, mouse button presses and releases are handled trivially in winmouse.c's winMouseButtonsHandle by simply passing the event to mieqEnqueue. Three-button mouse emulation is quite complicated.

Three-button mouse emulation is handled by starting a timer when the left or right mouse buttons are pressed; the button event is sent as a left or right mouse button event if the other button is not pressed before the timer expires. The button event is sent as an emulated middle button event if the other mouse button is pressed before the timer runs out.

The mouse wheel is handled in winmouse.c's winMouseWheel by generating sequences of button 4 and button 5 presses and releases corresponding to how much the mouse wheel has moved. Win32 uses variable resolution for the mouse wheel and passes the mouse wheel motion as a delta from the wheel's previous position. The number of button clicks to send is determined by dividing the wheel delta by the distance that is considered by Win32 to be one unit of motion for the mouse wheel; any remainder of the wheel delta must be preserved and added to the next mouse wheel message.

Other Windows messages

Certain other WM_ messages are also processed. TBD.