Cygwin/X's X Server architecture was heavily inspired by Angebranndt94, the Definition of the Porting Layer for the X v11 Sample Server.
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.
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.
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.
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.
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.
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.
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.
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:
The primary framebuffer is locked, causing the Cygwin/X X Server to hold the Win16Mutex.
Windows switches the Cygwin/X X Server out of the current process slot; another process is switched in.
The newly selected process makes a GDI function call.
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.
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
.
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.
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.
Certain other WM_ messages are also processed. TBD.