×
Namespaces

Variants
Actions

Symbian OS Internals/11. The Window Server

From Nokia Developer Wiki
Jump to: navigation, search
Article Metadata
Article
Created: hamishwillee (17 Jan 2011)
Last edited: hamishwillee (30 May 2013)

by Douglas Feather

The truth of the matter is that window management under X is not yet well understood.
The Xlib Programming Manual

The window server (or WSERV) works in conjunction with almost every part of Symbian OS, from the kernel to the applications, with the only real exception being the communications sub-systems. Its two main responsibilities are screen management and event management. WSERV receives events from the kernel and passes them to its clients (which are normally applications). It receives commands from clients and updates the screen accordingly. My discussion of these two key responsibilities will make up the backbone of this chapter.

WSERV is started during system boot and runs continually throughout the life of the system. It is a standard system server, being a derived class of CServer2 (or CPolicyServer from Symbian OS v9 onwards) called CWindowServer.

In this chapter I shall also cover animation DLLs (anim DLLs), which are a plug-in to WSERV. I will discuss what an anim DLL is, how to create one and how an anim DLL interacts with events. To illustrate this, I will develop a simple handwriting recognition system.

And of course I will cover windows in great depth - different types of windows, window classes on both the client and server sides, how window objects link together (the window tree), the different regions that windows have, different window effects and how clients can draw to windows. But first I will consider WSERV as the kernel's event handler.

Contents

The kernel's event handler

During system bootup, WSERV calls the function UserSvr::CaptureEventHook(), which tells the kernel that WSERV wants to become the kernel's event handler. To actually receive the events, WSERV then has to call the function UserSvr::RequestEvent(TRawEventBuf& aBuf, <tt style="font-family:monospace;">TRequestStatus& aStatus)</tt>, passing in the request status of the active object CRawEventReceiver, which will then run whenever the kernel has an event waiting. In this way, the kernel passes all its events (for example, digitizer and key events) to WSERV, which then has to perform further processing on these events.

WSERV is not just a simple pipe that passes events on to its clients. It does a lot of processing on the events, discarding some events, acting upon others, creating new events and deciding which client to send the event to. The set of event types that is passed between the kernel and WSERV is not exactly the same as the set of event types that is passed between WSERV and its clients. The kernel is not the only source of events; clients can also generate them, as can anim DLLs and WSERV itself.

As well as passing events to clients, WSERV can pass them to anim DLLs.

Different types of events

In this section I will list the different types of events that WSERV deals with, and give a brief description of what each type is for. I will describe both the set of events passed from the kernel to WSERV and the set passed from WSERV to the client. Figure 11.1 gives an overview of the paths that events can take within the system. It shows three different threads: the kernel, WSERV and a client of WSERV - the boundaries between these threads are represented by the three dividing lines. The three small boxes represent processing that goes on inside WSERV itself.

Figure 11.1 WSERV event flow

Although Figure 11.1 does not give the complete picture, it should suffice to give you a feel for how events pass through WSERV.

Events from the kernel to WSERV

The events that are passed from the kernel to WSERV are listed in the class TRawEvent::TType. Several of these are to do with pointer events. In the following table, I only list a representative set of the pointer events:

Raw events

Events Purpose
ENone A dummy value that is not actually used.
EPointerMove The pointer or pen has moved position. This could be a move or drag event.
EPointerSwitchOn The digitizer was pressed and this caused the device to power up.
EKeyDown A key on the keyboard was pressed.
EKeyUp A key on the keyboard was released.
ERedraw The emulator has received a Win32 redraw event.
ESwitchOn The device has just been powered up.
EActive The emulator window has gained focus.
EInactive The emulator window has lost focus.
EUpdateModifiers The modifier key settings have changed. Sent by the emulator when it regains focus.
EButton1Down The pen or mouse button 1 has been pressed.
EButton1Up The pen or mouse button 1 has been released.
ESwitchOff The device is about to be switched off.
ECaseOpen The case on a clam-shell device has been opened.
ECaseClose The case on a clam-shell device has been closed.

I will discuss what happens to key and pointer events later in this chapter. The following table shows how WSERV responds to all the other raw events in the system:

WSERV events

Event Response
ERedraw On the emulator, there is a bitmap managed by the screen driver component that contains the complete copy of the display. WSERV calls the function: CFbsScreenDevice::Update(const TRegion& aRegion) passing in the full screen area, which updates the screen from the bitmap.
ESwitchOn Stops the keyboard repeat timer. Puts up the password window if one has been set. Switches on the screen hardware by calling UserSvr::WsSwitchOnScreen(). Sends on events to clients that have requested this notification.
EInactive Stops keys auto-repeating.
EUpdateModifiers Resets the state of all the modifier keys.
ESwitchOff If a client has registered itself for switch-off events, then WSERV sends an event to that client. Otherwise it powers down the device by calling: UserHal::SwitchOff().
EKeyRepeat Nothing.
ECaseOpen Same as ESwitchOn except that it does not power up the screen hardware.
ECaseClose Sends an event to the client that has registered itself to deal with switching off. (Does not power down if such a client does not exist.)

Events from WSERV to its clients

In the next two tables, I show the list of events that WSERV sends to its clients. I will discuss key and pointer events in more detail later in this chapter. The first table contains events that WSERV sends to the relevant client whenever they occur:

Non-client registered events

Event Meaning
EEventNull Should be ignored.
EEventKey A character event.
EEventKeyUp A key up event.
EEventKeyDown A key down event.
EEventPointer An up, down, drag or move pointer event.
EEventPointerEnter The pointer has moved over a window.
EEventPointerExit The pointer has moved away from a particular window.
EEventPointerBufferReady A buffer containing pointer drag or move events is ready for delivery.
EEventDragDrop A special kind of pointer event. These events can be requested by clients so that they can receive UI widgets by dragging and releasing.
EEventFocusLost The window has just lost focus.
EEventFocusGained The window has just gained focus.
EEventPassword Sent to the owner of the password window when the password window is displayed.
EEventMessageReady A message has arrived from another application.
EEventMarkInvalid Internal use only, never sent to clients.
EEventKeyRepeat Not sent to clients, sent to key click makers.

In the next table I show events that WSERV only sends to clients that register for them - most of these events can be sent to more than one client:

Client-registered events

Event Meaning
EEventModifiersChanged One or more modifier keys have changed their state.
EEventSwitchOn The machine has just been switched on. (This event is not generated on a phone, but is generated on, for example, a Psion Series 5 PDA.)
EEventWindowGroupsChanged Sent when a group window is destroyed or named.
EEventErrorMessage Sent when an error, such as out-of-memory, occurs in WSERV. (For more details, see the documentation for
TEventCode in the Symbian Developer Library's C++ component reference section.)
EEventSwitchOff Sent to the client dealing with switch off.
EEventKeySwitchOff Sent to clients dealing with switch off if the off key is pressed.
EEventScreenDeviceChanged Sent when the screen size mode changes.
EEventFocusGroupChanged Sent when the focused group window changes.
EEventCaseOpened Sent when the clam-shell device is opened.
EEventCaseClosed Sent to the client dealing with switch off when the clam-shell is closed.
EEventWindowGroupListChanged Sent when there is a change in group window order.

11.3 How WSERV processes events

WSERV processes events in many stages; some of these are general to all events, in particular the way in which WSERV queues events for the client. Other stages are specific to certain types of events. For example, both pointer events and key events have to undergo special processing.

The first stage WSERV goes through is to process the pointer events - this is so that the main processing of pointer events later on receives pointer events of a standard type. WSERV does various things at this point:

  • Filters out Win32 move events for the emulator of a pen-based device
  • Where the co-ordinates the kernel has delivered to WSERV are relative to the last position, converts them to absolute co-ordinates for a virtual pointer cursor
  • For real devices (that is, not the emulator), WSERV rotates co-ordinates to the current screen rotation. (Screen rotation allows address of the screen in a co-ordinate set rotated by 0, 90, 180 or 270 degrees from the physical co-ordinates of the screen)
  • Offsets the co-ordinates to the current screen origin and scaling
  • If pointer events are being limited to a sub rectangle of the display, then WSERV restricts the co-ordinates if they are outside of this area.

It is worth noting that rotation does not need to be taken into account in the emulator, because the fascia bitmap rotates, and so the co-ordinates the kernel receives from Win32 are already rotated.

We support the screen origin and scaling functionality from Symbian OS v8.1. By using screen origin and scaling, a user interface designer can allow different applications to address the pixels of the screen with different co-ordinate sets.

Under some circumstances, WSERV turns off its heart beat timer - this normally happens when a client calls RWsSession::PrepareForSwitchOff() so that the processor can be powered down to save battery power. In response to each event from the kernel WSERV turns this timer back on (if it's off).

Next WSERV passes the event to any anim DLL that has registered itself as being interested in events. The anim DLL registers by calling the function MAnimGeneralFunctions::GetRawEvents(ETrue). To deliver the event to the anim DLL, WSERV calls the function: MEventHandler::OfferRawEvent().The anim DLL can consume the event so that WSERV does no further processing; to do this it should return ETrue from the OfferRawEvent function, otherwise it should return EFalse.

From this point onward WSERV treats the different types of events in different ways. This is shown for all events other than key and pointer events, in the WSERV events table above.

Processing key events

There are three kernel events that are directly related to keys: EKeyDown, EKeyUp and EUpdateModifiers. To process these key events, WSERV uses an instance of a CKeyTranslator-derived object. This object understands character mappings and modifier keys, and its main purpose is to tell WSERV which characters a particular key press should map to. For example, pressing the a key could result in a or A, and it is CKeyTranslator that analyzes the state of the shift keys and determines which it should be.

The event EUpdateModifiers is passed straight through to the CKeyTranslator object. The kernel generates this event in the emulator when the emulator window gains focus from another Windows applications. The data passed with the event tells us the current state of all the modifier keys, and enables the emulator to take into account any changes the user has made to modifier keys while other applications on the emulator host had focus.

Key ups and downs

WSERV processes each up and down event thus:

  • It logs the event, if logging is enabled
  • It tells the keyboard repeat timer object about the event
  • It passes the event to the keyboard translator object
  • It checks for any modifier changes
  • It queues the key up/down event
  • It performs further processing to create the character event (if there is to be one).

The keyboard-repeat-timer object controls auto repeating of key presses. WSERV only receives a single up or down event from the kernel, no matter how long the key is pressed. If a key press maps to a character, WSERV starts a timer, and every time that timer goes off, WSERV generates another instance of the character for the client queue. If the client is responding promptly to the events, then it will get many events for that character, and the timer will have generated all but the first of them.

When a new key down event occurs, WSERV must inform the timer, so that it can cancel the current repeat - this is needed because any key press should change the character the timer generates. Similarly, when a key up event occurs, WSERV informs the timer, so that it can stop the repeat if the key up comes from the currently repeating character.

WSERV calls the keyboard translator object next, using the function:

 TBool TranslateKey(TUint aScanCode, TBool aKeyUp,
const CCaptureKeys &aCaptureKeys, TKeyData &aKeyData)

As you can see, WSERV passes the scan code of the key event, a Boolean to say whether the key is an up or down event, and the current list of capture keys. The key translator object returns a TBool saying whether the key maps to a character event or if it does, the key translator also returns the following details of the character event in the TKeyData object:

  • The code of the character
  • The current state of all the modifiers
  • Whether the key has been captured.

If the key is captured, the key translator also returns:

  • A handle indicating which window captured the object
  • Another handle which WSERV uses for its own capture keys.

WSERV capture keys or hotkeys are system wide. There are hotkeys for increasing or decreasing contrast, toggling or turning the backlight on or off and more - you can see the full list in the enum THotKey.

Clients can request events to let them know when certain modifier keys change their state. When this happens, WSERV checks all client requests to see if any are requesting information about the particular change that has occurred. For each such request, WSERV queues an event to the relevant client.

WSERV has to decide which client to send the up or down key event to. Usually it chooses the client that owns the currently focused window - the only exception is if a particular client has requested the capture of up and down events on that particular key. WSERV also sends the event to the key click plug-in in case there is a sound associated with this event.

WSERV now processes those key up or down events that the key translator object decided gave rise to character events. This processing is quite involved and I will describe it in the next section.

Character events

The main steps WSERV performs in processing character events are:

  • Calls the key click plug-in
  • Deals with capture keys (including WSERV capture keys)
  • Determines who should receive the event
  • Checks to see if the event has a long capture
  • Checks to see if repeat timer should start
  • Queues the event.

First, WSERV sends the event to the key click plug-in, if there is one. The key translator object has already returned a flag to say whether the character event should be captured, so WSERV checks this and sets the destination for the event accordingly. If the key has not been captured, then WSERV sends the event to the currently focused window. If the event was captured but WSERV is currently displaying the password window, then WSERV only sends the event if it was captured by the same group window as the password window.

If the character event is one of WSERV's capture keys, then WSERV will have captured it itself. In this case, WSERV will process the event immediately and not send it on to a client.

If the key is either:

  • A long capture key, or
  • There is currently no repeating key, and the key is allowed to be auto-repeatable

then the repeat timer is started.

Long capturing is a feature that we added in Symbian OS v7.0. It allows a long press of a key to be treated differently from a quick tap. The long-key-press event and the short-key-press event yielded by a single physical key press can differ both in their destination client and the actual character code generated. This feature allows a quick press of a number key on a phone to enter a number into the dial number dialogue, while a longer press of the same key could launch the contacts application and jump to first contact starting with a certain letter.

Processing pointer events

The processing of pointer events is much more complicated than the processing of key events. This is because WSERV calculates which window to send the event to from the exact location of the click. Pointer grabbing and capturing affect it too.

The normal sequence of pointer events starts with a pointer down event, is followed by zero or more pointer drag events, and ends with a pointer up event. It is possible for all of these events to go to the window visible on the screen at the location that they occur. However, there are two features that clients can use to vary this behavior: grabbing and capturing.

If a window receives a down event and it is set to grab pointer events, then WSERV sends all the drag events and the following up event to that window, even if they actually take place over other windows. If a window is capturing, and the down event is on a window behind it, then the window that is capturing will receive the event. If that window is also grabbing, then it will receive the following drag and up events too.

In practice, most windows will grab pointer events and some windows will also capture them too. Capturing allows dialogs to prevent pointer events from being sent to windows behind them.

WSERV takes the following major steps during the processing of pointer events:

  • Calculates the actual window the pointer event is on
  • Determines if another window's grabbing or capturing means that it should get the pointer event
  • Queues enter and exit events if the current window has changed
  • Tells the key click plug-in about the pointer event
  • For move and drag events, checks the window to see if it doesn't want such events
  • If the window has requested it, stores move and drag events in a pointer buffer
  • If window has a virtual keyboard and the event occurs on one of the virtual keys, converts the event to a key event
  • Checks the event to see if it should be a double-click event, and if a drag-drop event is needed too.

WSERV calculates the actual window that a pointer event occurs in by analyzing the window tree in a recursive way. Starting with top-level client windows, it finds the foremost window at that level that contains the point on which the user clicked. Then it goes on to check each of this window's children, and so it continues, until there are no more children or none of the children contain the point. Then WSERV analyses the windows in the same way again, but this time it checks the capturing flag of each window to see if it should be capturing the event.

When WSERV adds a move or a drag event to the client queue, it checks the event that is currently at the end of the client queue, and if this is an identical event apart from the co-ordinates, then WSERV will just replace the old event with the new one. This means that the client won't get very fine-grained information on pen or mouse moves. This is no problem, indeed it is beneficial, for most applications, but for a drawing application it is not ideal. So, for such applications, WSERV can alternatively store all the events that it gets from the kernel in a buffer, and the client will then get a block of them delivered at once.

Client queues

WSERV uses client queues to store events while they are waiting to be delivered to the client. There are three different queues for each client; each of these stores a different type of event:

  • Redraw events
  • Priority key events
  • All other events (main queue).

We designed priority key events initially for the OPL programming language and this is the only application to use them to date. While an OPL program was running, the user could press Ctrl+Esc and this would immediately terminate the program. This was because this key was delivered via the priority key queue and so could by-pass all other events.

More generally, we have three queues so that the client can treat different events with different active-object priorities. In general, a client wants to receive pointer and key events before any redraw events that are already queued, so it sets the priority on its active objects for the redraw queue to be lower than those for the main queue.

When WSERV has an event for the client, it places the event in the queue and completes the request status that client has supplied for that queue, so the client knows that there is at least one event waiting. But the system may be busy and WSERV may generate many events before the client has the chance to ask for an event. This means that WSERV has to deal with the problem of the queues overflowing.

Overflow in the priority key queue

We designed this queue to take a single key press instructing the application to close; this means that in this queue we are not interested in multiple occurrences of that key press. So the queue only ever holds the last key press that has the relevant special status - if a new event comes along before the old one is delivered, then the old one is overwritten.

Overflow in the redraw queue

The redraw queue is an array that lists all of the client's windows currently needing a redraw. The array is ordered from front to back so that the window to be redrawn first is the foremost one. If there is enough memory available, the array could expand indefinitely - except that each window can only appear in it once.

If at any time the array cannot be extended, then the redraw queue sets a flag to say that the array is not complete. When the array becomes empty and the flag is set, WSERV scans all the client's windows to find one that needs a redraw. Only when it has scanned all the windows will it clear the flag.

Overflow in the event queue

WSERV uses many tactics to avoid or reduce the effect of overflow in this queue. However, they are not foolproof - it might happen that, in very extreme situations, an event could be lost. However, this has not, to our knowledge, happened in practice, or if it has, it has shown no side effects!

The event queue is a global heap cell - there is only one event queue for the whole system. WSERV grows and shrinks this cell as the number of clients changes. The size of the queue is about 48+2*(number of clients) entries and each entry is 40 bytes, the size of a TWsEvent.

The heap cell is divided into sections and each client is allocated a section. WSERV also has the freedom to change the size of each client section within the cell, growing or shrinking the other clients' sections in response. A particular client's section can have between 2 and 32 entries.

If WSERV needs to queue an event, and there isn't room in the client's section, then obviously it will first try to expand the client's section up to its maximum size of 32 entries. (If the client's section already has 32 entries, then WSERV tries to purge that client's queue - in other words, it tries to find an event that it can delete.) To do this, WSERV first tries to find other clients that have room in their sections and shrink those sections. If this fails, then WSERV makes an attempt to purge an event from one of the other clients' queues. The focused client is the last to be chosen for this operation - WSERV will only purge the focused client's queue if none of the other queues have events that can be purged. If the purge fails then the event will be discarded.

To purge events from a client queue, WSERV will try a variety of tactics, including:

  • Deleting an associated pair of up and down pointer events. (If the associated up event hasn't been received yet, it will even delete a down event and later delete the next matching up event)
  • Deleting key up or down events from the non-focused client queue
  • Deleting matched pairs of key up and down events from the focused client queue. (Most applications ignore these events)
  • Merging two modifier change events and deleting one of them
  • Deleting matched pairs of focused lost and gained events
  • Deleting repeated switch on events
  • Deleting these events: key events, pointer enter and exit events, drag drop events, pointer buffer ready events and the following pointer events: drag, move, button repeat and switch on.

A simple handwriting animation DLL

In this section, I will develop a simple handwriting animation DLL. I won't attempt real character recognition, but I will show all the surrounding framework, including getting the pointer events, drawing the ink on the screen and sending a character event to the application. My intention is to explain the basics of anim DLLs, and especially to show how they can deal with events.

We originally designed anim DLLs for clocks in Symbian OS v5. At this time they provided two main features:

  • Accurate timing information. Simple use of a relative CTimer, for example, would provide a clock that would update slower than real time
  • The ability for user code to draw to a window while executing inside the same thread as WSERV, thus avoiding delays caused by IPC.

Creating an anim DLL

There are two parts to an anim DLL: the anim DLL itself, which is a plug-in to WSERV, and the client-side code that loads the anim DLL, and controls it.

It is possible to give an anim DLL the standard type DLL in the MMP build file, but then you would need to do more work to set it up correctly. It is better to define it with type ANI:

TARGETTYPE ANI

The client-side code then calls the following function to load the anim DLL:

TInt RAnimDll::Load(const TDesC &aFileName)

WSERV then responds by calling ordinal 1 in the anim DLL. This function should return a sub-class of CAnimDll to the server:

 EXPORT_C CAnimDll* CreateCAnimDllL() 
{
return new(ELeave) CHandWritingAnimDll();
}

This CAnimDll sub-class normally has only one function, which creates the anim DLL plug-in object:

class CHandWritingAnimDll : public CAnimDll 
{
public: //Pure virtual function from CAnimDLL
CAnim* CreateInstanceL(TInt aType);
};

Each anim DLL can supply many different sorts of anim DLL objects, and each one can be instantiated many times. But my example handwriting anim DLL will only provide one such object, so the implementation of this function is quite simple:

CAnim* CHandWritingAnimDll::CreateInstanceL(TInt ) 
{
return new(ELeave) CHandWritingAnim();
}

CreateInstanceL() is called in response to one of the four overloads of the RAnim::Construct() client-side functions:

TInt Construct(const RWindowBase &aDevice, TInt aType, const TDesC8 &aParams); 
TInt Construct(const RWindowBase &aDevice, TInt aType, const TDesC8 &aParams, const TIpcArgs& aIpcArgs);
TInt Construct(const RWsSprite &aDevice, TInt aType, const TDesC8 &aParams);
TInt Construct(const RWsSprite &aDevice, TInt aType, const TDesC8 &aParams, const TIpcArgs& aIpcArgs);

The two types of anim DLL

Originally, anim DLLs only provided the ability to draw to a single window per anim object. This type of anim DLL is now called a window anim, and to create one you would return an object derived from CWindowAnim from CreateInstanceL().

To provide digital ink on-screen, we developed a new type of anim that allowed drawing to a sprite. This is known as a sprite anim, and to create one you would return an object derived from CSpriteAnim from CreateInstanceL(). The relationships between these, and other, classes are shown in Figure 11.2.

Figure 11.2 Animation class hierarchy

There are two types of functions in the anim interface. Firstly, there are functions in the anim that WSERV calls. These are shown in the diagram as member functions of the four classes: MEventHandler, CAnim, CSpriteAnim and CWindowAnim; they are all pure virtual functions and so the anim writer has to provide implementations for the relevant set of these functions, depending on which class she has derived from.

The WSERV functions that an anim can call are provided by means of the member data shown in Figure 11.2. There are four in all, shown with the class they belong to in parentheses:

  • iFunctions (MAnimGeneralFunctions)
  • iSpriteFunctions (MAnimSpriteFunctions)
  • iWindowFunctions (MAnimWindowFunctions)
  • iGc (CAnimGc).

CAnimGc provides drawing functions that the window anim uses to draw to its window.

Thus our example CHandWritingAnim is a sub-class of CSpriteAnim.

Functions a sprite anim must provide

All the derived classes of CSpriteAnim must provide all the virtual functions of that class, CAnim and MEventHandler, so part of our class definition will be:

class CHandWritingAnim : public CSpriteAnim
{
public:
∼CHandWritingAnim();
//pure virtual functions from CSpriteAnim
void ConstructL(TAny* aArgs);
//pure virtual functions from MEventHandler
TBool OfferRawEvent(const TRawEvent& aRawEvent);
//pure virtual functions from CAnim
void Animate(TdateTime* aDateTime);
void Command(TInt aOpcode,TAny* aArgs);
TInt CommandReplyL(TInt aOpcode,TAny* aArgs);
private:
TInt iState;
CFbsBitmapDevice* iBitmapDevice;
CFbsBitmapDevice* iMaskBitmapDevice;
CFbsBitGc* iSpriteGc;
TBool iIsMask;
CPointStore* iPointStore;
};

I will talk about the purpose of these functions in the following sections.

Construction

The first anim DLL function that will be called is CSpriteAnim::ConstructL. WSERV calls this function when responding to the client's call to RAnim::Construct(). The parameter aArgs passed to the ConstructL function is a pointer to a copy of the content of the descriptor that was originally passed in to the client-side Construct function. It will normally need casting to the correct type.

void CHandWritingAnim::ConstructL(TAny* ) 
{
TSpriteMember* spriteMember=iSpriteFunctions->GetSpriteMember(0);
iIsMask=(spriteMember->iBitmap->Handle() !=spriteMember->iMaskBitmap->Handle());
iBitmapDevice=CFbsBitmapDevice::NewL(spriteMember->iBitmap);
if (iIsMask)
iMaskBitmapDevice=CFbsBitmapDevice::NewL(spriteMember->iMaskBitmap);
iSpriteGc=CFbsBitGc::NewL();
iSpriteGc->Reset();
iState=EHwStateInactive;
iPointStore=new(ELeave) CPointStore();
iPointStore->ConstructL();
iSpriteFunctions->SizeChangedL();
...
}

The call to GetSpriteMember() returns details of the sprite that this anim is allowed to draw to. Usually, sprites can animate. To do this, a client needs to give a sprite several members - each member contains one frame of the animation. In this case, the client only needs to create one frame for the ink and thus we specify a value of 0 as the parameter in this function call. This is then checked to see if the client has provided separate bitmaps for the mask and the sprite content. Drawing to any graphics object, such as a bitmap or the screen, requires a device for that object. From a device, we can create a graphics context (or GC) and use this to do the drawing. We now create the following objects - a CFbsBitmapDevice for each bitmap and a single CFbsBitGc which can be used on both bitmaps.

The state member, iState, is initialized to say that no handwriting recognition is currently required. Since the shape drawn on the screen is to be converted to a character, we create a point store so that the detailed shape of the ink can be recorded. The call to SizeChangedL() is part of the standard, general initialization of a sprite. It sets up the backup bitmap for the sprite, which needs to be the size of the largest frame. This function will go on to set the parameters for the ink, such as color and line width - this is not shown in the previous code segment.

Receiving events

Events are received when WSERV calls the function: MEventHandler::OfferRawEvent(), see Section 11.3, How WSERV processes events. Remember that this function was pure virtual in the base class, and so I have an implementation in my CHandwritingAnim class. By default, WSERV will not pass events to anims, so if the anim wants to receive the events, it has to call the function MAnimGeneralFunctions::GetRawEvents(), passing in the parameter ETrue. Once an event has been passed to the Anim DLL, it has to return a TBool to say if it has consumed the event (ETrue) or not (EFalse):

TBool CHandWritingAnim::OfferRawEvent(const TRawEvent &aRawEvent) 
{
if (iState==EHwStateDeactive)
return EFalse;
switch (aRawEvent.Type())
{
case TRawEvent::EButton1Down:
return HandlePointerDown(aRawEvent.Pos());
case TRawEvent::EPointerMove:
return HandlePointerMove(aRawEvent.Pos());
case TRawEvent::EButton1Up:
return HandlePointerUp(aRawEvent.Pos());
default:
return EFalse;
}
}

The first thing this function does is to check to see if handwriting is turned on. If it is not, it will return EFalse to tell WSERV to process the event itself. It also does this if the event is not a pointer event; this is what the default part of the switch statement is for. This function then calls three other functions that will process the pointer events. I have not shown an implementation of these functions, but will note that it would be advisable for them to adopt the strategy that if the user clicks and holds for a certain length of time, then this should be treated as a pointer to be sent to applications, rather than for drawing digital ink.

Animating

The Animate() function is designed for receiving periodic events to update the clock.

 void Animate(TDateTime* aDateTime);

By default, WSERV does not call this virtual function. If an anim does want it to be called, then the anim should call this function:

 void MAnimGeneralFunctions::SetSync(TAnimSync aSyncMode);

The parameter specifies how often to call the Animate() function. The options are:

enum TAnimSync 
{
ESyncNone,
ESyncFlash,
ESyncSecond,
ESyncMinute,
ESyncDay,
};

Clearly ESyncNone means that WSERV never animates. The remaining three values tell WSERV to call the animate function after the specified time intervals.

The second value is slightly different. It tells WSERV to animate twice a second. However, these animations are not evenly spaced, every half a second - they happen on the second and after 7/12 of a second. This is so that when the separator character (the : in 12:45:37) flashes it will be visible for slightly longer than it is invisible. (WSERV uses the same internal timer to flash the cursor (and sprites), which also are visible for slightly longer than they are invisible.)

To do these animations, WSERV uses a lock timer (possibly the only use of a lock timer in Symbian OS). This timer makes it easy for WSERV to determine if the system is running slowly. Its API is:

 void CTimer::Lock(TTimerLockSpec aLock);

The parameter specifies a particular twelfth of a second. The first time the function is called, the kernel will signal the associated active object when it reaches that point in the second. The second time the function is called, the kernel will only signal the active object if the requested twelfth of the second is within a second of the first requested point. If it is not, the function will return with an error, showing that the system is very busy.

Suppose, for example, that a clock uses this timer to update a display of seconds. Each second, the clock calls the lock timer to ask to be notified when the current second is over. At the next second boundary, the kernel signals the active object. If the active object runs and re-queues itself within that second, everything is fine. If the system is busy and by the time the active object runs and re-queues itself, the second had passed, then the active object will complete with an error telling the clock that it needs to reset itself, rather than just doing an increment.

When WSERV gets an error back from the lock timer, it tells the anim that it should reset itself by passing the current date-time to the animate function. (When things are running normally, it passes NULL to this function.)

Although the handwriting anim does not need to do any animation, it does need a timer. Instead of creating its own timer, it uses the CTimer::Lock() function to receive timer notifications - I will say more on this later.

Client communication

The following functions can both receive commands from the client side:

TInt CAnim::CommandReplyL(TInt aOpcode, TAny* aArgs)=0; 
void CAnim::Command(TInt aOpcode, TAny* aArgs)=0;

One difference between them is that the first one can also return a Tint value to the client. It can do this either by returning the value to send back to the client, or by leaving - the leave code will then be sent to the client. Another difference between these two functions is that the first one is sent directly to the window server. This is done because the client needs to know the return value before any more of its code can execute, while the second function will just be stored by WSERV on the client side and sent to the server side later. WSERV calls these functions in response to the client calls to the RAnim functions CommandReply() and Command(), respectively:

void CHandWritingAnim::Command(TInt aOpcode,TAny* aArgs) 
{
THandAnimArgUnion pData;
pData.any=aArgs;
switch (aOpcode)
{
case EHwOpActivate:
Activate();
break;
case EHwOpDeactivate:
Deactivate();
break;
case EHwOpSetDrawData:
SetDrawData(pData.DrawData);
break;
default:
iFunctions->Panic();
}
}

The previous function shows three commands that the client can call on the handwriting anim. These are to turn the handwriting recognition on and off, and to change some display settings, such as line-width and color. The client has to pass a large block of data with this call; this is passed into the function with an untyped pointer. To avoid a cast, we use a union, but of course this provides no more type safety that a cast would - it just makes the code more readable.

union THandAnimArgUnion 
{
const TAny* Any;
const TBool* Bool;
const THandwritingDrawData* DrawData;
};

The CommandReplyL() function also provides two functions that the client can call:

TInt CHandWritingAnim::CommandReplyL(TInt aOpcode, TAny* aArgs) 
{
THandAnimArgUnion pData;
pData.any=aArgs;
switch (aOpcode)
{
case EHwOpSpriteMask:
SpriteChangeL(*pData.Bool);
break;
case EHwOpGetLastChar:
return iLastGeneratedCharacter;
default:
iFunctions->Panic();
}
return KErrNone;
}

The first function allows the client to change the bitmaps that are actually used to draw the ink. There is no return value from this function, but it can fail. In this case it will leave and the leave value will be returned to the client. The second function returns the last generated character to the client.

Handling pointer events and updating the sprite

When a pointer down is received, the anim sets a timer. It does this because the pointer event might be a click on a UI feature rather than the start of the drawing of some digital ink. If the user clicks and holds until the timer expires, or clicks and releases without moving the pen, then this indicates a click on a UI feature.

The following routine deals with pointer move events. It only has to deal with them when the handwriting is active and the pen is down, so most states just return EFalse to say that the event has not been consumed:

TBool CHandWritingAnim::HandlePointerMove(TPoint aPoint) 
{
switch (iState)
{
case EHwStateWaitingMove:
{
const TInt KMinMovement=5 ;
TPoint moved=aPoint-iCurrentDrawPoint;
if (Abs(moved.iX)< KMinMovement && Abs(moved.iY)< KMinMovement)
return ETrue;
iSpriteFunctions->Activate(ETrue);
DrawPoint();
iState=EHwStateDrawing;
}
case EHwStateDrawing:
break;
default:
return EFalse;
}
DrawLine(aPoint);
UpdateSprite();
return ETrue;
}

If we are still waiting for the timer to expire (iState==EHwStateWaitingMove), and the point is still close to the original down, then the move event is ignored - but it is still consumed. If not, the anim makes the sprite visible by calling the Activate() function, then draws a point into the sprite, and updates the state to indicate that drawing is in progress. It then calls the following function to draw a line into the sprite bitmap:

void CHandWritingAnim::DrawLine(TPoint aEndPoint) 
{
iSpriteGc->Activate(iBitmapDevice);
iSpriteGc->SetPenSize(TSize(iDrawData.iLineWidth, iDrawData.iLineWidth));
iSpriteGc->SetPenColor(iDrawData.iLineColor);
iSpriteGc->MoveTo(iCurrentDrawPoint);
iSpriteGc->DrawLineTo(aEndPoint);
if (iMaskBitmapDevice)
{
iSpriteGc->Activate(iMaskBitmapDevice);
iSpriteGc->SetPenSize(TSize(iDrawData.iMaskLineWidth, iDrawData.iMaskLineWidth));
//Mask must be drawn in black
iSpriteGc->SetPenColor(KRgbBlack);
iSpriteGc->MoveTo(iCurrentDrawPoint);
iSpriteGc->DrawLineTo(aEndPoint);
}
iCurrentDrawPoint=aEndPoint;
iPointStore->AddPoint(aEndPoint);
}

The anim uses the same graphics context to draw to the sprite bitmap and to the mask bitmap - it activates that context on the bitmap device of the bitmap in which the drawing is to be done. If there is no mask, then the anim will use the bitmap itself as the mask, and so the ink will have to be drawn in black. If there is a mask, then the line of the digital ink needs to be drawn into the mask and the bitmap. In this case, it should be drawn in black in the mask, but any color can be used for the ink in the bitmap. WSERV stores the end point of the line, so that it can use it as the starting point of the line the next time this function is called. It also stores the point in a buffer so that later on the character recognition algorithm can make an analysis of the ink shape. After the bitmaps are updated, the code calls this function to update the screen (see CHandWritingAnim::HandlePointerMove earlier in the chapter):

void CHandWritingAnim::UpdateSprite() 
{
TRect drawTo;
iSpriteGc->RectDrawnTo(drawTo);
iSpriteFunctions->UpdateMember(0,drawTo,EFalse);
}

When any drawing is being done, BITGDI keeps track of which pixels have been drawn to - or at least a bounding rectangle of those pixels. This rectangle is not always pixel perfect, but serves as a good approximation. This rectangle is retrieved by calling RectDrawnTo() and this same function also resets the rectangle. Then the function calls the update member function. This is a function provided by WSERV to all sprite anims and its purpose is to correct the screen in the area of this rectangle.

The normal way to update a sprite is to remove it and then draw it again to the screen - but of course if this is done, the screen will flicker. In Symbian OS v5u, we added a way to update a sprite without flicker - this is the function used in the previous code and defined thus:

void MAnimSpriteFunctions::UpdateMember(TInt aMember,const TRect& aRect,TBool aFullUpdate);

The third parameter to this function tells WSERV whether it should do a full update of the sprite by removing it and redrawing it, or just do an incremental update by redrawing the sprite to the screen. Obviously, when drawing digital ink, the ink only increases, so if we do an incremental update we will get the correct screen content. (It's worth noting that there is one circumstance in which WSERV will do a full update even if an incremental one is requested. If there is no mask bitmap, then the way the bitmap is drawn to the screen is determined by the iDrawMode member of the TSpriteMember class. If this is not EDrawModePEN, then WSERV will always do a full update.)

Sending events

There are two situations in which the anim will need to send (or create) an event. The first is when the ink is converted to a character. The second is when the timer goes off, without the user having moved the pen from the position where it was clicked down. Since the down event itself was consumed, the anim will need to resend it so that the client code can act upon it. This is done in the following code:

void CHandWritingAnim::CharacterFinished() 
{
iState=EHwStateInactive;
iLastGeneratedCharacter=iPointStore->GetChar();
TKeyEvent keyEvent;
keyEvent.iCode=iLastGeneratedCharacter;
keyEvent.iScanCode=iLastGeneratedCharacter;
keyEvent.iModifiers=0;
keyEvent.iRepeats=0;
iFunctions->PostKeyEvent(keyEvent);
iPointStore->ClearPoints();
iSpriteFunctions->Activate(EFalse);
ClearSprite();
}
 
 
void CHandWritingAnim::SendEatenDownEvent()
{
TRawEvent rawEvent;
rawEvent.Set(TRawEvent::EButton1Down,iCurrentDrawPoint.iX,iCurrentDrawPoint.iY);
iFunctions->PostRawEvent(rawEvent);
iState=EHwStateInactive;
}

There are two functions that allow an anim to send events and they are both illustrated in the previous code and defined in MAnimGeneralFunctions. This is one of the classes that allows anims to call functions on WSERV:

void PostRawEvent(const TRawEvent& aRawEvent) const; 
void PostKeyEvent(const TKeyEvent& aRawEvent) const;

PostRawEvent() is used by an anim to send the down event. It allows the sending of any raw event (that is, one of the set of events that the kernel sends to WSERV) into WSERV for processing. It's worth noting that the first thing that WSERV will do with the event is to pass it back to any anims that are receiving events - so it would be easy for code to create an infinite loop with bad use of this function! You can send most key events using PostRawEvent(), but you would have to send a key up event, a key down event and in some cases up and down events for modifier keys too. This is the reason for the existence of the second function, PostKeyEvent(), which allows the sending of an EEventKey event.

Client-side code - construction

The client has to create both the anim and the sprite. A class, CHandWriting, is used to own and manage both of these. I have written this class to allow it to be included into any other project that wants to own the handwriting anim:

class CHandWriting : public CBase 
{
public:
CHandWriting(RWsSession& aSession);
void ConstructL(TSize aScreenSize, RWindowGroup& aGroup, TBool aUseSeparateMask);
~CHandWriting();
void SetMaskL(TBool aUseSeparateMask);
void ToggleStatus();
 
private:
void CreateSpriteL(TSize aScreenSize, RWindowGroup& aGroup, TBool aUseSeparateMask);
void LoadDllL();
void FillInSpriteMember(TSpriteMember& aMember);
 
private:
RWsSession& iSession;
RAnimDll iAnimDll;
RHandWritingAnim iAnim;
RWsSprite iSprite;
CFbsBitmap *iBitmap;
CFbsBitmap *iMaskBitmap;
TBool iActive;
};

The sprite has to be created first as this has to be passed to the function that constructs the anim. We do this using the following two routines:

void CHandWriting::CreateSpriteL(TSize aScreenSize, RWindowGroup& aGroup,TBool aUseSeparateMask) 
{
TInt color,gray; //Unused variables
TDisplayMode mode=iSession .GetDefModeMaxNumColors(color,gray);
iBitmap=new(ELeave) CFbsBitmap();
User::LeaveIfError(iBitmap->Create(aScreenSize,mode));
TSpriteMember member;
member.iMaskBitmap=iBitmap;
if (aUseSeparateMask)
{
iMaskBitmap=new(ELeave) CFbsBitmap();
User::LeaveIfError(iMaskBitmap->Create(aScreenSize,mode));
member.iMaskBitmap=iMaskBitmap;
}
User::LeaveIfError(iSprite.Construct(aGroup,TPoint(), ESpriteNoChildClip|ESpriteNoShadows));
FillInSpriteMember(member);
iSprite.AppendMember(member);
}
 
void CHandWriting::FillInSpriteMember(TSpriteMember& aMember)
{
aMember.iBitmap=iBitmap;
aMember.iInvertMask=ETrue; //Must be inverted
aMember.iDrawMode=CGraphicsContext::EDrawModePEN;
 
//Ignored when using mask
aMember.iOffset=TPoint(); //Must be 0,0
aMember.iInterval=0;
//Not used as only one TSpriteMember in sprite
}

We construct the sprite by calling iSprite.Construct() in the third line from the end of the first routine. All sprites have to be associated with a window, and they will always be clipped to the area of that window. If you specify a group window, as I do in the previous code, then the sprite will be allowed to display over the whole screen. By default, the sprite will also be clipped to the window's visible area. In this case, however, my code specifies the flag ESpriteNoChildClip, which means that this clipping is not done. Thus the hand writing will always be able to appear over the whole screen, even if the group window involved is behind other windows. The other flag, ESpriteNoShadows, means that even if there is a shadow-casting window above the sprite window, WSERV will not shadow the pixels of the sprite. Once the sprite has been created, I add a member or frame to it. This is done in the final two lines of the function.

The other point of note in this function is the color depth with which the sprite's bitmaps are created. When sprites are drawn to the screen, WSERV uses either BitBlt() or BitBltMasked() from the CFbsBitGcclass. These functions execute much faster when the bitmap that they are drawing and the bitmap or screen that they are drawing it to have the same color depth. For a sprite that is normally viewed over a window, it is best to set the sprite's bitmaps to the same color depth as the window. However, for the handwriting anim, where the sprite is effectively viewed over the whole screen, it is best to choose the default color mode of windows, since most applications will be running with this color depth. You can find out what this is by calling the function GetDefModeMaxNumColors().

Having created the sprite, we can create the anim. There are two stages to this - first we ask WSERV to load the anim DLL and then we create the instance of the sprite animation. We do this using the following two functions, the second being called by the first:

void CHandWriting::LoadDllL() 
{
_LIT(DllName,"HandAnim.DLL");
TInt err=iAnimDll.Load(DllName);
if (err==KErrNone)
err=iAnim.Construct(iSprite);
if (err==KErrNone)
{
iAnim.Activate();
iActive=ETrue;
}
User::LeaveIfError(err);
}
 
TInt RHandWritingAnim::Construct(const RWsSprite& aDevice)
{
TPtrC8 des(NULL,0);
return RAnim::Construct(aDevice,0,des);
}

To load the anim DLL, you must give WSERV the name of the DLL involved. You do this using an RAnimDll object. Then you need an RAnim-derived class - since the interface of RAnim is protected to force you to derive from it. The interface to the anim constructor has three parameters. These are the sprite of the window, a type and the configuration data packed in to a descriptor. The type allows one anim DLL to have many anim types, this being the way to specify which one to create. In my example, the handwriting anim only has one type, and there is no configuration data used.

Other client-side code

RHandWritingAnim contains several other functions for communicating with the anim. Here are a couple of examples:

void RHandWritingAnim::SetDrawData(const THandwritingDrawData& aDrawData) 
{
TPckgBuf<THandwritingDrawData> param;
param()=aDrawData;
Command(EHwOpSetDrawData,param);
}
 
TInt RHandWritingAnim::GetLastGeneratedCharacter()
{
return CommandReply(EHwOpGetLastChar);
}

The first of these functions tells the handwriting animation to draw the digital ink differently (that is, with different color or line width). This requires the sending of data to the anim - this is packaged into a descriptor using the TPckgBuf class. Since no return value is needed, it can just use the RAnim::Command() function. This will in turn be passed to the function CHandWritingAnim::Command() function.

The second function is passed the code of the last generated character. There is no data to send with this request, so it doesn't need to use a TPckgBuf, but since it does require a reply, it uses RAnim::CommandReply() and this request gets sent in turn to the function CHandWritingAnim::CommandReplyL().

Window objects and classes

Windows are the mechanism that Symbian OS uses to control access to the screen. They are rectangular by default and may overlap each other. They have a front to back order, and this defines which of two overlapping windows is in front. Applications may create and destroy windows. Windows are individually addressable, and an application can draw to only one of its windows at a time. Typically an application will have many windows.

Windows are important since they allow different applications to draw to different parts of the screen at the same time. Furthermore, applications do not need to concern themselves with which part of the screen they are allowed to draw to. An application just draws to its window, and only if that window is visible will it appear on the screen.

In the following sections, I will cover the window tree, the ways in which WSERV navigates this structure, window classes and their structure, the properties of windows, drawing windows and more. I will also cover Direct Screen Access (DSA), which could also be described as drawing without windows.

Diagram of the window tree

Figure 11.3 shows the relationships between different windows. It is presented as an upside down tree. It shows what different window types can appear at which point in the tree.

Figure 11.3 shows four types of windows (although one of them, Group Win, is never displayed and so is not a window as windows are defined above). This diagram is an object diagram in which each row can only contain objects of a certain class type. The different types of windows shown are:

  • The root window. WSERV creates this window; it is not directly accessible to any client. It is used as a starting point for the window structure and exists throughout the life of the system. On multiple screen devices there will one of these for each screen
  • Group windows. These windows can only be direct children of the root window. WSERV's client creates them, but they do not have any associated screen area. They provide a way for a client to group together some of its windows, so that it can move them together
  • Top client window. The third row in the figure consists only of top client windows. These windows are displayable, and so a client will need at least one of these
  • Client windows. All the subsequent rows of the diagram consist of client windows. A client can have anything from no client windows to multiple levels of nested client windows.
    Figure 11.3 Relationships between windows

The following table shows the classes that represent these types of windows, on both the client and server side:

Window type Client-side class Server-side class
Root Window <none> CWsRootWindow
Group Window RWindowGroup CWsWindowGroup
Top Client Window Subclass of RWindowBase CWsTopClientWindow
Client Window Subclass of RWindowBase CWsClientWindow

Traversing the window tree

Figure 11.3 contains three different types of arrows. These represent pointers to objects, and give the structure of the window tree thus:

  • Parent. These are the arrows that point upward. All windows have a pointer to their parent window on the line above. (Since the root window doesn't have a parent, its pointer will be NULL)
  • Child. These are the diagonal arrows down to the left. They show that if a window has any child windows, then it will have a pointer to the first, usually the oldest, such window. (By default the pointer will denote the first window created, but of course the order of the children can be changed, so this is not always the case)
  • Sibling. These are the arrows going across to the right. They show that each window knows the next oldest window with the same parent. Siblings form a singly linked list of all windows with the same parent. (As for the child pointer, this oldest to youngest ordering holds at the time the windows are created, but may subsequently be altered.)

These pointers are defined in the server-side class CWsWindowBase, which is the base class for all server-side window classes, as I will show later. You can use the pointers to move through the window objects in various ways. For example, to get to the oldest sibling of your parent, you navigate using iParent->iChild.

Let's look at a more complex example of the use of these pointers. The following function updates the pointers when a window is removed from the window tree:

void CWsWindowBase::Disconnect() 
{
if (iParent!=NULL)
{
CWsWindowBase** prev=&iParent->iChild;
while ((*prev)!=this)
prev=&(*prev)->iSibling;
*prev=iSibling;
}
}

When a window is removed from the window tree, only one pointer needs to be updated. If the window is the oldest child of its parent, then the parent's child pointer needs to be updated. If it is not, then its next oldest sibling's iSibling pointer needs updating. In both cases, the pointers need to be updated to point to the next younger sibling.

Walking the window tree

There are many occasions when the window tree must be traversed in the front to back order, for example:

  • When a window is made invisible and the windows that it was covering up need to be exposed
  • When a window is made visible and the windows behind it are covered up.

There is a single algorithm for doing the traversal, which is used in many places throughout WSERV, and which I will discuss in this section. There are two rules that define the front to back order of windows as they are shown on the display:

  • A child window is always in front of a parent window
  • If two windows are children of the same parent then the older is in front of the younger.

The first rule has the consequence that the root window is always the back-most window. One of the root window's jobs is to clear areas of the display where there are no other windows visible. The color it will use can be set by calling the function RWsSession::SetBackgroundColor().

The walk window tree mechanism is implemented in a single function, CWindowBase::WalkWindowTree(), which uses the three pointers that link all windows to traverse the windows in the correct order. WalkWindowTree() takes a class with a virtual function as a parameter, and on each new window it finds, it calls the virtual function passing the window as a parameter.

One of the parameters of this function is: TWalkWindowTreeBase& aWalkClass. Figure 11.4 shows some of the derived classes and the virtual function on this class - DoIt().

Figure 11.4 Walking the window tree

In all, there are over 20 classes deriving from TWalkWindowTreeBase for a variety of different purposes. Some operations only apply to visible windows, hence the need for TWalkWindowTreeRegionBase. One of the members of this class is a region, which is initialized to the whole area of the screen.When the operation is applied to each window, the visible area of that window is subtracted from the region and when this region becomes empty no more windows are scanned.

Class structure of windows

Figure 11.5 shows all the classes that we use to represent windows, on both the server and the client sides. You can see that the class structure on the two different sides is not quite the same. The purpose of RWindowTreeNode and CWsWindowBase is to represent a window that fits into the window tree at any point. The equivalence of the window-group classes is clear. Yet the other part of the class structure takes a very different shape. On the client side, the class structure is determined by the drawing functionality that each class provides. On the server side, the class structure is more closely related to the window's position in the window tree. The difference in drawing behavior is determined by a plug-in object, which is a class deriving from CWsWindowRedraw.

Figure 11.5 Window classes

When we consider windows from the drawing point of view, there are three types of windows: blank windows, bitmap backup windows and redraw windows. I will describe the exact differences between these sorts of windows in later sections of this chapter.

The root window, being a derived class of CWsWindow, also has a plug-in drawing object. Its type is blank window since it is only ever drawn with a solid color; this is shown by a dotted line in the figure.

Properties of windows

In this section I will discuss a selection of the properties of the different classes of windows. With the exception of the root window, which WSERV creates during bootup, all other windows are created at the request of a WSERV client. Clients are said to own the windows they create. The owner has control of certain properties of the window, while others are assigned by WSERV.

11.9.1 Properties held by all windows

Properties held by all windows include:

  • Parent
  • Oldest or first child
  • Next sibling
  • Client handle
  • Ordinal priority.

I've discussed the first three of these properties in earlier sections. The client handle is a value that the owner of the window gives to WSERV when it is created. It is very important that this value be unique amongst all windows owned by the client, otherwise the client code will not work as expected. In debug builds, WSERV enforces the uniqueness of the values, and panics the client if they are duplicated.

Ordinal priority is closely related to another window property, known as ordinal position. Ordinal priority is a value set by the owner of a window and it is stored on the server side. Ordinal position concerns a window's position among the other children of its parent. This value isn't stored explicitly, but can be calculated by analyzing the structure of the server-side window classes. The rule is that if two children of the same parent have different ordinal priority then the one that has the highest will always be older and therefore in front of the one with the lowest. If we have a group of children, all with the same priority, then their ordinal position will start at zero and increase through consecutive positive integers. For example, suppose that a window has five children, two of ordinal priority ten and three of ordinal priority zero. Then the two with ordinal priority ten will be the oldest, and in front of the others, and their ordinal positions will be zero and one. Similarly, the three of ordinal priority zero will have ordinal positions zero, one and two.

One use of ordinal priority is to make sure a group window comes in front (or behind) other group windows. For example, the group window associated with the application picker in the UIQ interface has a negative ordinal priority to ensure that it appears behind all the normal applications, which by default have zero for their ordinal priority.

Properties of group windows

Properties of group windows include:

  • Name
  • Identifier
  • Screen device.

The identifier is a number from 1 to 10,000 inclusive that WSERV assigns to each group window. The numbers are unique across all group windows in existence at any one time; they are also allocated cyclically so that when a group window is destroyed, it is unlikely that its identifier will be used again immediately. Identifiers give applications a way of discovering all of the group windows in the system at any time. The APIs in RWsSession use identifiers as a means of referring to group windows:

TInt SetWindowGroupOrdinalPosition(TInt aIdentifier, TInt aPosition); 
TInt GetWindowGroupClientThreadId(TInt aIdentifier, TThreadId &aThreadId);
TInt GetWindowGroupHandle(TInt aIdentifier);
TInt GetWindowGroupOrdinalPriority(TInt aIdentifier);
TInt SendEventToWindowGroup(TInt aIdentifier, const TWsEvent &aEvent);
TInt FindWindowGroupIdentifier(TInt aPreviousIdentifier, const TDesC& aMatch, TInt aOffset=0);
TInt FindWindowGroupIdentifier(TInt aPreviousIdentifier, TThreadId aThreadId);
TInt SendMessageToWindowGroup(TInt aIdentifier,TUid aUid, const TDesC8 &aParams);

These functions allow group windows to be listed, interrogated and re-ordered. There are two API functions for setting and getting a group window's name in the RWindowGroup class. In addition, there is one API function in the RWsSession class:

 TInt GetWindowGroupNameFromIdentifier(TInt aIdentifier, TDes &aWindowName);

Group window names are used so that the system can get a list of running applications.

It is normal for a WSERV client to create a screen device, CWsScreenDevice, which has an associated object of class DWsScreenDevice created in the server. The first screen device created by a client becomes the primary screen device for that client. This screen device will be assigned to each group window that that client creates. Group windows created before the primary screen device are never associated with a screen device.

The association of group windows with screen devices is used in systems with more than one screen size mode. (In future, it will also be used in systems with more than one screen.) In this case, each screen device has a screen size mode associated with it. If the current system screen size mode differs from the screen device's screen size mode, then WSERV will make all of the group windows and their children invisible.

Properties of client windows

Client windows have two screen modes and many different areas or regions.

Screen modes

The two screen modes (or color depths) are both associated with the screen mode that the client requests. One of these is the drawing mode, which specifies which set of colors can be drawn to the window. The other mode is the hardware mode, which is the minimum mode that the hardware needs to be switched into so that this window can be displayed correctly. A window can request its own drawing mode, but by default WSERV will give it the mode that is specified in the WSINI.INI file using the keyword WINDOWMODE. WSERV calculates the actual drawing mode that a window will get using the requested mode and the modes available on the hardware. In Symbian OS, color modes are specified using the enum TDisplayMode. These have a naming convention, depending on whether they are gray scale or color, and the number of colors they contain. So, for example, EColor64K is a mode with 65536 non-gray colors. Here are some examples of how the modes get assigned to windows for hardware that supports EColor4Kand EColor64K:

Request
mode
Drawing
mode
Hardware
mode
EGray16 EGray16 EColor4K
EColor256 EColor256 EColor4K
EColor64K EColor64K EColor64K
EColor16M EColor64K EColor64K

The drawing mode is changed to the one requested, unless it requires more colors than the hardware supports.

Regions

Windows have many different regions associated with them. I will now discuss some of these. In Symbian OS, we provide a base class, TRegion, which has several derived classes. These classes store a list of disjoint rectangles and can be used to describe a two-dimensional area. There are many manipulation functions for these classes, including functions to add and intersect regions. WSERV makes extensive use of these classes - this is why we recommend that you machine code these classes when porting Symbian OS to new hardware.

Calculating the regions is a time-consuming business. Because of this, WSERV caches the regions after it has first calculated it. Then the next time it needs it, it uses the cached value. This of course means that when an operation takes place that might have changed one of the cached regions, WSERV must discard them all.

Base area This is the area that the client specifies using the function RWindowBase::SetShape(). However, the more rectangles that there are in the region describing the area, the greater the processing required when these windows are visible. Thus circular or triangular windows will be particularly inefficient. By default, windows are rectangular and the same size as their parent. This means that they are full screen if their parent is a group window. Their size can be changed by using the SetSize() function (defined in RWindow or RBlankWindow) or by the SetSizeErr()function (defined in RWindowBase).

Visible area This is an area that WSERV calculates for each window. It is the area of the window that is not obscured by any other window.

Invalid area This is the area of a window that is waiting for a redraw. Areas may need redrawing after any of the following:

  • They become visible when another window is destroyed or made invisible
  • Their window is made visible
  • Their window is positioned so it is partly outside the screen and then moved back onto the screen
  • Part of their window loses its shadow
  • Their window becomes unfaded
  • The client calls Invalidate() on the window.

Only instantiations of RWindow can have invalid areas, since WSERV knows how to draw other window types (see latter section on drawing of windows). The invalid area must be contained in the visible area. When a window has invalid areas, then WSERV will send it a redraw event. The client can discover the invalid area using the GetInvalidRegion() function.

Drawing area This is the area that WSERV clips drawing to. It is calculated differently, depending on whether the window is being redrawn or not. If it's not being redrawn, then the drawing area is just the visible area less the invalid area. If the window is being redrawn, then the drawing area is the area being redrawn (that is, the area validated by the redraw) less any area that has become invalid since the redraw started.

Shadow area This is the area of the window that is currently in shadow. When WSERV draws to a window, it actually does the drawing twice. First it draws to the part of the drawing region that is not in shadow, and then it draws to the part that is in shadow. The shadow flag is set for the second drawing.

Drawing to windows

In this section, I will discuss the different ways to draw to a window and the mechanisms in WSERV to support them.

Drawing of blank windows

WSERV handles the drawing of all blank windows (RBlankWindow) for the client. The client can specify a color for WSERV using the function SetColor(TRgb aColor). When drawing the window, WSERV must take account of the fact that this window could be faded (by calling the RWindowTreeNode::SetFaded() or RWindowBase::FadeBehind() functions) and that the window could have shadows cast on it. This means that when drawing a blank window, WSERV can use any of four different colors.

Drawing of backup content windows

A client requiring this kind of window must instantiate the class RBackupWindow. These windows could also be called bitmap backup windows, because WSERV keeps the content of the window in a bitmap, so that when the window would otherwise become invalid, WSERV can draw it from the bitmap without needing any co-operation from the owner. WSERV creates a bitmap that is the same size and color depth as the window, and uses this bitmap to mirror the content of the window. Because of the existence of the bitmap, the client must specify the window's color depth when it creates the window. If the client subsequently wants to change the window's size, then the operation may fail if there is not enough memory to change the size of the bitmap.

There are two ways in which the bitmap can be used. When the window is created, the bitmap will store all the parts of the window that are not fully represented on the screen. Situations in which the window's content is not fully represented on the screen include:

  • Parts of the window are behind other windows or outside the screen's area
  • Parts of the window that have shadow cast upon them
  • The window is faded
  • There is not enough memory to calculate the area that is fully represented.

However, if the window owner calls the function MaintainBackup() on its window object, then all of the content will also be stored in the bitmap as it changes. The disadvantage of doing this is that most pixels of the window will be drawn twice, to the screen and to the bitmap.

There are two ways in which the two drawings can differ slightly. Firstly, if you are using the DrawBitmap() function, which scales things depending on the twips size of the relevant objects, then the scaling onscreen and in the backup bitmap can be different. This is because the screen and backup bitmap will have slightly different twips to pixel mappings. (This is because the only way to guarantee the same mapping would be to create a backup bitmap the same size as the screen, and of course this would be a waste of memory if the window was small. In any case, the differences are small, being due to rounding errors.) Secondly, the exact color shade can change when copying the content from the screen to the bitmap and back again. If the window is EColor4K then the bitmap will also be EColor4K. However, the screen may be EColor64K because another window that requires this mode is also visible. Even though EColor64K has a much richer set of colors, they are not a superset of the colors in EColor4K, and sometimes the mappings from one to the other won't be the most ideal.

Drawing to redraw windows

A client requiring this kind of window must instantiate the class RWindow. Unlike the windows described in the previous section, WSERV requires co-operation from the client to keep the content of these windows correct. It does this by sending redraw messages to the client telling it that the window content needs redrawing. This happens in the circumstances listed in Section 11.9.3.2, under the title Invalid area.

A redraw window is either in redraw mode, or not. The mode changes how drawing takes place - the main difference being the drawing area used. This is described in Section 11.9.3.2.

Drawing outside of a redraw is rare in Symbian OS, because of the way that Cone deals with drawing. Cone is a component that makes use of WSERV APIs and provides a framework for the controls used in applications. When a client calls the DrawNow() function on a CCoeControl, the control invalidates the whole of the control's area and then draws it all again in a redraw.

However, there is nothing to stop a particular application bypassing this mechanism to draw outside of a redraw. This is usually done to update the current content of the window because the application's model has changed - for example, if the user has tapped a key and added another character to a word processor document. This is one of the few situations in which drawing occurs outside of a redraw in Symbian OS. This is because the drawing is done by the component FORM rather than through the normal framework of a control.

Drawing inside a redraw is typically done because an update of the window is needed. That is, part of the window's area has become invalid. WSERV requests a redraw by sending a redraw message to the client to tell it that a specified window needs redrawing. The redraw message also contains a rectangle, to reduce the area of the window that the client needs to draw. The rectangle is the bounding rectangle - that is the smallest rectangle that contains all of the invalid area. The application calls the functions BeginRedraw() and EndRedraw() on the window object to indicate to WSERV when it wants to do a redraw.

The BeginRedraw() function has two variants - one that takes a rectangle and one that doesn't; if the one without a rectangle is used, WSERV will take the rectangle to be the whole of the window. The area that will be drawn by WSERV in response to the draw command is the rectangle that was specified in the BeginRedraw() function, intersected with the invalid region. Clearly, if the rectangle specified is greater than the rectangle from the redraw event, then this makes no difference to the redraw. If, it is smaller, then the redraw will only be for part of the invalid region. This will then mean that WSERV must issue another redraw for the remainder of the area that needs drawing. Of course, all this is assuming that no part of the window is invalidated or covered up between WSERV signaling that a redraw is needed and the client acting on the redraw. (If this were to happen, the details become more complicated, but the general principles remain the same.)

Since, in general, a redraw will only draw to part of the window, it is important that the same state of the model (that is, the last drawn content) be reflected in the redraw. To see why, suppose that an application's model changes, but it does not update its window immediately. Then, if it receives a redraw event before it has done the update, it must draw the previous state of the model - because if it drew the current state of the model, part of the window would show the old state while part showed the new. Cone circumvents this requirement, since it always invalidates and redraws the area of the whole control. This means that all the pixels will always be drawn - and so it is safe for the client application to draw the new state of the model.

This section covers redraws from when Symbian OS was first released, until Symbian OS v7.0. In later releases, we have added two new features that complicate things further. These are flicker-free redrawing and redraw command storing, and I will discuss them after I have explained a window property known as backup behind, which allows redrawing to be avoided in certain circumstances.

Backup behind

Backup behind is a property that you can give to any displayable window as you construct it. It is particularly useful for short-lived windows, such as dialogs.

When such a window first becomes visible, WSERV creates a bitmap and copies into it the part of the screen that will be obscured by this window. Then, when the window is deleted, WSERV doesn't need to issue redraws to the windows that were covered, but can use the backup bitmap to restore them.

This feature is expensive on memory, since it requires a bitmap and a region. Thus we limit its use to one window at a time, and say that if a second window requests this functionality, then the first window will lose it. It is also worth noting that this feature can fail due to lack of memory. However, it works in simple situations and can be very useful in avoiding the IPC required by a redraw.

Flicker-free redrawing

We introduced this feature in Symbian OS v7.0s. For it to work, we need at least one full-screen off-screen bitmap (known as the OSB). There will be one OSB if the keyword FLICKERFREEREDRAW is present in the WSINI.INI file, and from Symbian OS v8.0 onwards, there will be two OSBs if the keyword TRANSPARENCY appears in this file.

When the OSB exists, WSERV uses it for all redraws. When WSERV receives the begin redraw command, all the graphics contexts are redirected to draw to the OSB. Then, when the end redraw command is received, WSERV copies the content of the off-screen bitmap back to the screen.

The advantage of this is that no pixel will be changed on the screen more than once, removing the chance of flicker during the redraw.

There is one functionality change as a result of drawing in this way. Before we introduced this feature, the content of the window was not changed in the begin redraw step. In particular, if a pixel was not drawn, it would remain unchanged. But now, since the whole area that is being validated by the redraw will be copied from the OSB to the screen in the end redraw, even pixels that are not drawn will be copied from the OSB. Since the content of the OSB is undefined, this is obviously undesirable. So, to avoid this problem, we set the area that is being drawn to the background color of the window (or white if it doesn't have one) in the OSB. However, this has been known to cause problems for some drawing code, which assumed that the previous screen content was still there.

Redraw command storing

In Symbian OS v8.0, we added support for transparent windows. This feature requires that WSERV be able to draw the content of windows upon demand. When the client draws a window, WSERV stores the redraw commands. It does this for all windows, not just windows that might be involved with transparency.

The advantage of this to the client is that, so long as it doesn't change the content of the window, it doesn't have to redraw that window each time part of it becomes invalid - WSERV will just reprocess the commands that it has stored. However, if the content of the window does change, then the client must tell WSERV, so that it can discard the commands it has stored. The client can do this by calling Invalidate()on the window object for the area that has changed. It's worth noting that before this feature came along, a client could rely on WSERV issuing a redraw when a window was made visible, and use this as a signal to update its content.

Direct screen access

This could be described as drawing without windows, since the drawing commands do not go through WSERV.

Before we introduced this feature, clients could create a screen device object and a graphics context object (defined by the BITGDI component - CFbsScreenDevice and CFbsBitGc), and then use the latter to draw to any area of the screen. The problem was that this would write over any application that happened to be on the screen at the time! So we designed the direct screen access (DSA) feature to give a well-behaved application a means of drawing directly to the screen using the previously mentioned classes, but restricting it only to the area in which its window is visible. The DSA mechanism allows an application to determine the visible area of its window and also to receive notification when that visible area changes.

To do DSA, a client needs to create an instance of CDirectScreenAccess. This class has a very simple interface with only six public functions:

static CDirectScreenAccess* NewL(RWsSession& aWs, CWsScreenDevice& aScreenDevice, RWindowBase& aWin, MDirectScreenAccess& aAbort); 
~CDirectScreenAccess();
void StartL();
inline CFbsBitGc* Gc();
inline CFbsScreenDevice*& ScreenDevice();
inline RRegion* DrawingRegion();

The first function constructs the object, and the second destroys it. The last three functions provide the objects that you use to do the drawing. The graphics context is already active on the screen, and its clipping region is set to the area that you are allowed to draw to. The last two functions that I list provide a screen device set to the current color depth of the screen and the region that you are allowed to draw to. This is provided in case you ever need to reset the clipping region of the graphics context.

The third function, StartL, is the function that you need to call to inform WSERV that you want to start doing DSA. During this function call, the three objects returned by the inline functions will be set up. After you have called this function, you can safely draw to the screen using the provided graphics context.

When WSERV detects that the visible area of the window may be changing, it notifies the client. The client receives this notification through one of the two callback functions that it provided (specified in the class MDirectScreenAccess, and passed by the client as the last parameter to the NewL of the CDirectScreenAccess class). They are:

virtual void AbortNow(RDirectScreenAccess::TTerminationReasons aReason) 
virtual void Restart(RDirectScreenAccess::TTerminationReasons aReason)

The first of these functions is called by the DSA framework to tell the client to abort. When the client has aborted, WSERV must be informed. The framework will do this for you, but only after the client has returned from the AbortNow() call. This means that this function should do the minimum amount of work and return as quickly as possible.

When WSERV tells the client that it needs to abort its DSA, it waits to receive the acknowledgment from the client that it has done so. However, it doesn't wait for ever, since the client may have entered some long running calculation or even an infinite loop. So WSERV also waits on a timer. If the timer expires before the client acknowledges, then WSERV continues. If, later on, WSERV gets notification from the client that it has aborted the DSA, then WSERV will invalidate the region in which the DSA was taking place, just in case there had been a conflict between the DSA and another client.

Here are a few other restrictions on what you can do in the AbortNow() function. You can't call WSERV, because then a temporary deadlock will occur. This is because WSERV is waiting to receive the client's acknowledgment that it has aborted, and so will not be able to service the call. Also, since you are not allowed to make calls to WSERV in the AbortNow()function, you can't restart the DSA in this function. This is where the other callback is needed, the Restart() function. The DSA framework has set up an active object to call this function and it should be called during the next active object run in that thread. The purpose of this function is to give the client the earliest possible opportunity to restart the DSA with the new area.

There is one final restriction on clients performing DSA. This is that, while the DSA is in operation, the client should not make any call to WSERV that will affect the visible area of the window in which the DSA is taking place. Again, if this were to happen, it will cause temporary deadlock - since the client will be waiting for WSERV to make the requested window rearrangement, and WSERV will be waiting for the client to acknowledge that the DSA has aborted.

Platform security in WSERV

WSERV's command buffer

In Symbian OS, platform security is enforced on client server boundaries (for more on this, see Chapter 8, Platform Security). WSERV is no exception to this. However, because of WSERV's command buffer, the implementation differs from that of other servers. Other servers receive a single command sent by the client with a single call to either RSessionBase::Send() or the RSessionBase::SendReceive(), and the kernel can be instructed which commands (specified with the aFunction parameter) need which security capabilities. WSERV has one main command which is called in this function:

TInt RWsSession::DoFlush(const TIpcArgs& aIpcArgs) 
{
return SendReceive(EWservMessCommandBuffer,aIpcArgs);
}

This command tells the server that there is a command buffer waiting for it. WSERV places many individual commands into this command buffer, saying which server object to send the command to, which command or function to execute on that object and any parameters for that command. Each of these commands potentially needs to be policed for the capabilities of the client. Thus we have to do the policing in the server code that executes the commands.

On the server side, each command buffer is unpacked in the function CWsClient::CommandBufL(), which passes the commands onto this functions:

virtual void CWsObject::CommandL(TInt aOpcode,const TAny *aCmdData)=0;

or at least the version of this function in the respective derived class. This means that we must do the policing in the respective CommandL() function.

What things does WSERV police?

WSERV checks for three different capabilities - SwEvent, WriteDeviceData and PowerMgmt. The details of which APIs police which capabilities are given in any Symbian OS SDK, so there is no need for me to reiterate here. Instead I will describe the type of API that is policed with each of these capabilities, and the motivation for this policing.

There are various client-side functions that can be used to send an event to a WSERV client. Examples are RWsSession::SendEventToWindowGroup and RWsSession::SimulateRawEvent. These functions are policed by SwEvent to spot rogue applications that generate events designed to break other applications.

WSERV has many global settings that can be changed by any client. These include keyboard repeat rate, system pointer cursor list, default fading parameters and current screen size mode. Since these settings affect all clients of WSERV, we must intervene if rogue applications try to set them to bad values. So we police them using WriteDeviceData.

WSERV has two APIs that are concerned with switching off the mobile phone. One of these is RWsSession::RequestOffEvents(), which an application calls if it wants to receive off events. Only one application can receive these events, as it is then in charge of making sure that the system is ready to be turned off (for example, ensuring all data is saved) and then calling the kernel to power down the device. The other function, RWsSession::PrepareForSwitchOff() is called to tell WSERV to stop its timer so that the processor can be switched off - if the timer continued to run it would wake the processor up again. These APIs are both protected by PowerMgmt.

Summary

WSERV has a major role to play in all aspects of the UI of any device. It has rich APIs for dealing with all aspects of event handling, window management and drawing window content, as well as code for managing the different types of event and holding them until clients are ready to receive them. WSERV provides a variety of ways of drawing to the screen, including anim DLLs, redraw windows and direct screen access.

Licence icon cc-by-sa 3.0-88x31.png© 2010 Symbian Foundation Limited. This document is licensed under the Creative Commons Attribution-Share Alike 2.0 license. See http://creativecommons.org/licenses/by-sa/2.0/legalcode for the full terms of the license.
Note that this content was originally hosted on the Symbian Foundation developer wiki.

This page was last modified on 30 May 2013, at 07:36.
65 page views in the last 30 days.