4.3 Settable Attributes

The sections that follow describe the options and default values for each member of the XSetWindowAttributes structure. The attributes control a wide variety of ways for a window to act. They can be grouped loosely to help you understand when you might want to set each attribute.

One group of attributes controls the appearance of a window. These are background_pixel, background_pixmap, border_pixel, border_ pixmap, colormap, and cursor. Most clients will set the border, background, and cursor but use the default colormap.

A second group is provided to allow clients to improve their redrawing performance under certain conditions. These are backing_pixel, backing_planes, backing_store, bit_gravity, and save_under. These attributes do not affect the appearance or operation of a client. It is advisable to consider bit_gravity when designing a client, but the code for using these attributes can be added after a client’s functionality is complete.

The event_mask and do_not_propagate_mask attributes control the selection and propagation of events. These attributes are described briefly in this chapter but also in much more detail in Chapter 8.

The win_gravity attribute provides a means for relocating a window automatically when its parent is resized. Applications can take advantage of this feature to simplify the code that positions their subwindows when they are resized.

The override_redirect attribute controls whether requests to map or reconfigure the window can be intercepted by the window manager. override_redirect is meant to be set for the most temporary types of windows such as pop-up menus. In practice, this attribute only affects the top-level windows of an application (all children of the root window).

As described in Chapter 2, there are two window classes: InputOutput and InputOnly. The class of a window is specified in the call to XCreateWindow(), or is InputOutput if the window is created with XCreateSimpleWindow().

InputOutput windows have all of the attributes described in the sections below. InputOnly windows have only the following subset of attributes:

  • win_gravity

  • event_mask

  • do_not_propagate_mask

  • override_redirect

  • cursor

Any attempt to set attributes other than these five on an InputOnly window will cause an X protocol error (BadMatch).

4.3.1 The Window Background

The background of a window is the drawing surface on which other graphics are drawn. It may be a solid color, or it may be patterned with a pixmap. This choice is mostly an aesthetic decision for the programmer. However, users expect to be able to specify the background color on the command line or in the resource database. Therefore, if a pixmap is used, the code for creating the pixmap should use two colors specified by the user (see 6.1.5 Creating Bitmaps, Pixmaps, Tiles, and Stipples for information on creating pixmaps).

The two attributes that control the background are background_pixmap and background_pixel, set by XSetWindowBackgroundPixmap() and XSetWindowBackground(), respectively.

These two attributes are not independent since they affect the same pixels. Either attribute can take precedence over the other, the winner being the one that is set last. If both are set in the same call to XCreateWindow() or XChangeWindowAttributes(), the background_pixel value is used.

The background of exposed areas of windows is automatically repainted by the server, regardless of whether the application selects Expose events.

However, changes in background attributes will not take effect until the server generates the next Expose event on that window. If you want the new background to be visible immediately, call XClearWindow() and flush the request buffer with XFlush().

Applications must set one or the other for all windows. Otherwise, the results are undefined. Most applications set backgrounds to a solid color by setting the background_pixel attribute. The easiest way to do this is by setting the last argument of XCreateSimpleWindow() to BlackPixel or WhitePixel().

4.3.1.1 background_pixmap

If the background is set to a pixmap, the background is tiled with the pixmap. Tiling is the laying out of a pixmap to cover an area. The first pixmap is applied at the origin of the window (or its parent’s origin if using the parent’s background pixmap by specifying ParentRelative, as described below). Another copy of the same pixmap is applied next to that one and another below it and so on until the window is filled.

The pixmap may be any size, though some sizes may be tiled faster than others. To find the most efficient tile size for a particular screen, call XQueryBestTile().

A pixmap must be created with XCreatePixmap() or XCreatePixmapFromBitmapData() before being set as the background_pixmap attribute. The pixmap must have the same depth as the window and be created on the same screen. These characteristics are assigned to a pixmap as it is created. (For more information on creating pixmaps for tiles, see 6.1.5 Creating Bitmaps, Pixmaps, Tiles, and Stipples.)

The background_pixmap attribute has the following possible values:

None (default)

Specifies that the window has no defined background pixmap. The window background initially will be invisible and will share the bits of its parent but only if the background_pixel attribute is not set. When anything is drawn by any client into the area enclosed by the window, the contents will remain until the area is explicitly cleared with XClearWindow(). The background is not automatically refreshed after exposure. The main purpose of the setting None is a minor performance improvement. If the application is simply going to cover the entire window with graphics (i.e., there is no reasonable “background” that the application can set), then why bother forcing the server to spend time painting the background? None might also be useful for a subwindow when that subwindow will never be moved in relation to its parent.

a pixmap ID

The background will be tiled with the specified pixmap, but not until the next Expose event or XClearWindow() call. The background tile origin is the window origin. If the pixmap is not explicitly referenced again, it can be freed, since a copy is maintained in the server. Because the server copies the pixmap, changes to it after you set the background_pixmap attribute are not guaranteed to be reflected in the window background. For consistent results, therefore, you need to reset the attribute after each change to the pixmap.

ParentRelative

Specifies that the parent’s background is to be used and that the origin for tiling is the parent’s origin (or the parent’s parent if the parent’s background_pixmap attribute is also ParentRelative and so on). The difference between setting ParentRelative and explicitly setting the same pixmap as the parent is the origin of the tiling. The difference between ParentRelative and None is that for ParentRelative, the background is automatically repainted on exposure.

The window must have the same depth as the parent, or a BadMatch error will occur. If the parent has background None, then the window will also have background None. The parent’s background is re-examined each time the window background is required (when it needs to be redrawn due to exposure). The window’s contents will be lost when the window is moved relative to its parent, and the contents will have to be redrawn.

Changing the background_pixmap attribute of the root window to None or ParentRelative restores the default background, which is server-dependent.

By the way, the symbol CopyFromParent is not used for setting the background, but it will not cause an error, since its value is the same as None.

4.3.1.2 background_pixel

If the background pixel value is specified, the entire background will take on the color (or shade of gray) indicated for that pixel value in the current colormap.[10]

The background_pixel attribute has the following possible values:

undefined (default)

Indicates that the background is as specified in the background_pixmap attribute. This value is possible only by creating a window with XCreateWindow() and not setting the background_pixel attribute.

a pixel value

The background is filled with the specified pixel value. This can be set with the last argument of XCreateSimpleWindow(), XCreateWindow(), or XChangeWindowAttributes().

4.3.2 The Window Border

Like the window background, the window border may have a solid color or may be tiled with a pixmap. This choice is again up to the programmer, though the user should be allowed to determine the color or colors.

Unlike changes to the window background, changes to a window’s border attributes are reflected immediately. No call to XClearWindow() or call to flush the request buffer is necessary. This feature makes it possible to use the window border for indicating a client’s state. But you cannot use the border of the top-level window, since some window managers manipulate this border to indicate the keyboard focus window (see 8.3.2.1 The Keyboard Focus Window for a description of the keyboard focus).

The design of a pattern for the border will be different from the background pixmap, because the border width is usually narrow (at most four pixels).

The two attributes that affect the border are border_pixmap and border_pixel. XSetWindowBorderPixmap() and XSetWindowBorder() can be used to set these attributes. Like the window background, whenever one of these routines is called, it overrides the previous setting of the border. If they are both set simultaneously with XCreateWindow() or XChangeWindowAttributes(), the border_pixel attribute takes precedence.

Most applications simply set the border_pixel to BlackPixel or WhitePixel() in the next-to-last argument of XCreateSimpleWindow().

4.3.2.1 border_pixmap

If the border_pixmap is set to a pixmap, the border is tiled with the pixmap. Tiling is performed as described previously for the background pixmap; the border tile origin is the same as the background tile origin.

The border_pixmap attribute has the following possible values:

CopyFromParent (default)

Specifies that the border pixmap is to be copied from the parent. (Note that CopyFromParent will cause protocol errors if the window’s depth is different from its parent’s.) Subsequent changes to the parent’s border attributes do not affect the child, but changes to the pixmap used by the parent may be reflected in the child border (server-dependent).

None

Specifies that the window has no border pixmap. If the window has no border pixel value either, then it uses the same border pixel value as the parent.

a pixmap ID

Specifies a pixmap to be tiled in the border. The border tile origin is always the window origin; it is not taken from the background tile origin. If the pixmap is not explicitly referenced again, it can be freed since a copy is maintained in the server.

For the root window, CopyFromParent indicates that the default border will be inherited by subsequently created children of the root window, instead of any other border that was set for the root window. Setting the border_pixmap of the root window to CopyFromParent restores the default border pixmap for later inheritance.

4.3.2.2 border_pixel

If a border pixel value is specified, the entire border will take on the color (or shade of gray) indicated for that pixel value in the current colormap.

The border_pixel attribute has the following possible values:

undefined (default)

Indicates that the border is as specified in the border_pixmap attribute. This value is possible only by creating a window with XCreateWindow() and not setting the border_pixel attribute.

a pixel value

Overrides the default and any border_pixmap given, and fills the border with the specified pixel value. This is set by the next-to-last argument of XCreateSimpleWindow().

4.3.3 Bit Gravity

When an unobscured window is moved, its contents are moved with it, since none of the pixel values need to be changed. But when a window is enlarged or shrunk, the server has no idea where in the resulting window the old contents should be placed, so it normally throws them out. The bit_gravity attribute tells the server where to put the existing bits in the larger or smaller window. By instructing the server where to place the old contents, bit gravity allows some clients (not all can take advantage of it) to avoid redrawing parts of their windows.

Bit gravity is never necessary in programs. It does not affect the appearance or functionality of the client. It is used to improve performance in certain cases. Some X servers may not implement bit gravity and may throw out the window contents on resizing regardless of the setting of this attribute. This response is the default for all servers. That is, the default bit gravity is ForgetGravity, which means that the contents of a window are always lost when the window is resized, even if they are maintained in backing store or because of a save_under (to be described in 4.3.5 Backing Store and 4.3.6 Saving Under).

The window is tiled with its background in the areas that are not preserved by the bit gravity, unless no background is defined, in which case the existing screen is not altered.

There is no routine to set the bit_gravity individually; it can be set only with XChangeWindowAttributes() or XCreateWindow().

The bit_gravity attribute has 11 possible values:

ForgetGravity (default)

Specifies that window contents should always be discarded after a size change. Note that some X servers may not implement bit gravity and may use ForgetGravity in all cases.

StaticGravity

Specifies that window contents should not move relative to the origin of the root window. This means that the area of intersection between the original extent of the window and the final extent of the window will not be disturbed.

Each constant below specifies where the old window contents should be placed in the resized window.

NorthWestGravity

Upper-left corner of the resized window.

NorthGravity

Top center of the resized window.

NorthEastGravity

Upper-right corner of the resized window.

WestGravity

Left center of the resized window.

CenterGravity

Center of the resized window.

EastGravity

Right center of the resized window.

SouthWestGravity

Lower-left corner of the resized window.

SouthGravity

Bottom center of the resized window.

SouthEastGravity

Lower-right corner of the resized window.

Here are two examples of applications that could take advantage of bit gravity. Figure 4-1 shows a fictional application that draws a two-axis graph in a window, with the origin at the lower-left corner. If that window were resized, the application would want the old contents to be placed against the new lower-left corner, no matter which sides of the window were moved in or out. That application would set the bit_gravity attribute of this window to SouthWestGravity. Figure 4-1 shows the response of this window to resizing with this bit gravity setting.

bit_gravity for a graphing application

Figure 4-1. bit_gravity for a graphing application

Each compass constant, such as SouthWestGravity, indicates the placement of the retained region in the window after resizing. In this case, the lower-left corner of the existing pixels is placed against the lower-left corner of the resulting window. When an Expose event arrives, the application need only redraw the two new strips of the window at the top and right side. No Expose event will be generated on the area that was saved because of bit_gravity.

For another example, think of a window containing centered text. If that window were resized either larger or smaller, we would still like the text to be centered. In this case, the bit_gravity should be set to CenterGravity. Then only if the window is resized smaller than the length of the text would we have to redraw the area and only then to break the line or use a shorter message. We could see whether changing the message would be necessary by looking at the ConfigureNotify event that occurs as a result of the resize (see basicwin in Chapter 3). The window would still have to be redrawn if it were obscured and then exposed, of course—bit gravity only saves some of the redrawing that would otherwise have to be done.

If the constant were NorthGravity, the top center of the pixels in the window before the resize would be placed against the top center of the resulting window. This would be appropriate if we had a line of text centered at the top of the window that we wished to preserve when possible.

4.3.4 Window Gravity

The win_gravity attribute controls the repositioning of subwindows when a parent window is resized. The attribute is set on the children. Normally, each child has a fixed position measured from the origin of the parent window. Window gravity can be used to tell the server to unmap the child or to move the child an amount depending on the change in size of the parent. The constants used to set win_gravity are similar to those for bit gravity, but their effect is quite different.

NorthGravity specifies that the child window should be moved horizontally by an amount one-half as great as the amount the window was resized in the horizontal direction. The child is not moved vertically. That means that if the window was originally centered along the top edge of the window, it will also be centered along the top edge of the window after resizing. If it was not originally centered, its relative distance from the center may be accentuated or reduced depending on whether the parent is resized larger or smaller.

Window gravity is only useful for children placed against or very near the outside edges of the parent or directly in its center. Furthermore, the child must be centered along one of the outside edges or in a corner. Figure 4-2 shows the nine child positions where window gravity can be useful and the setting to be used for each position.

Child positions where window gravity is useful

Figure 4-2. Child positions where window gravity is useful

If any other setting is used for any of these positions, the window gravity may move the child outside the resized parent, since there are no checks to prevent this. The application can try to prevent it by getting the new position of the child from a ConfigureNotify event (see 3.2.16 Handling Resizing of the Window) and moving the child inside if necessary. But this will cause a flash when the child window is automatically placed incorrectly and then moved to the correct position by the application. And if an application has to go to the trouble to check the position and move the child, it might as well just forget about window gravity and place the child itself.

NorthWestGravity (the default) indicates that the child (for which this attribute is set) is not moved relative to its parent.

UnmapGravity specifies that the subwindow should be unmapped when the parent is resized. This might be used when a client wishes to recalculate the positions of its children. Normally, the children would appear in their old positions before the client could move them into their recalculated positions. This can be confusing to the user. By setting the win_gravity attribute to UnmapGravity, the server will unmap the windows. They can be repositioned at the client’s leisure, and then the client can remap them (with XMapSubwindows()) in their new locations.

There is no routine to set the win_gravity attribute individually; it can be set only with XChangeWindowAttributes() or XCreateWindow().

The win_gravity attribute has the following possible values:

UnmapGravity

Specifies that the child is unmapped (removed from the screen) when the parent is resized, and an UnmapNotify event is generated.

StaticGravity

Specifies that the window contents should not move relative to the origin of the root window.

One of the compass constants below

The list below shows the distance the child window will be moved; W is the amount the parent was resized in width, and H is the amount the parent was resized in height:

NorthWestGravity (default)

(0, 0)

NorthGravity

(W/2, 0)

NorthEastGravity

(W, 0)

WestGravity

(0, H/2)

CenterGravity

(W/2, H/2)

EastGravity

(W, H/2)

SouthWestGravity

(0, H)

SouthGravity

(W/2, H)

SouthEastGravity

(W, H)

4.3.5 Backing Store

A backing store automatically maintains the contents of a window while it is obscured or even while it is unmapped. Backing is like having a copy of the window saved in a pixmap, automatically copied to the screen whenever necessary to keep the visible contents up to date. Backing store is only available on some servers, usually on high performance workstations.

These servers can be instructed when to back up a window and which planes to save, through the backing store attributes. Even when it is available, the backing store should be avoided since it may carry a heavy performance penalty on the server. You can find out whether backing is supported on a particular screen with the DoesBackingStore() macro.

A client might use this feature to back up a window the client is incapable of redrawing for some reason or to be able to draw into a window that is obscured or unmapped.

Three separate attributes control backing: backing_store, backing_planes, and backing_pixel. There are no routines for setting these attributes individually (use XChangeWindowAttributes() or XCreateWindow()). The backing_store attribute determines when and if a window’s contents are preserved by the server. The backing_planes attribute specifies which planes must be preserved, and backing_pixel specifies the pixel value used to fill planes not specified in backing_planes. The X server is free to save only the bit planes specified in backing_planes and to regenerate the remaining planes with the specified pixel value.

When the backing store feature is active and the window is larger than its parent, the server maintains complete contents, not just the region within the parent’s boundaries. If the server is maintaining the contents of a window, Expose events will not be generated when that window is exposed.

Use of the backing store does not make a window immune to the other window attributes. If the bit_gravity is ForgetGravity, the contents will still be lost whenever the window is resized.

The backing_store attribute has the following possible values:

NotUseful (default)

Advises the server that maintaining contents is unnecessary. A server may still choose to maintain contents.

WhenMapped

Advises the server that it would be beneficial to maintain contents of obscured regions when the window is mapped.

Always

Advises the server that it would be beneficial to maintain contents even when the window is unmapped.

The backing_planes attribute specifies a mask (default all 1’s) that indicates which planes of the window hold dynamic data that must be preserved in the backing store.

The backing_pixel attribute specifies a pixel value (default 0) to be used in planes not specified in the backing_plane attribute.

4.3.6 Saving Under

The save_under attribute controls whether the contents of the screen beneath a window should be preserved just before the window is mapped and replaced just after it is unmapped. This attribute is most useful for pop-up windows, which need to be on the screen only briefly. No Expose events will be sent to the windows that are exposed when the pop-up window is unmapped, saving the time necessary to redraw their contents.

Pop-up windows are usually children of the root window and, therefore, are not constrained to appear within the application’s top-level window. Therefore, without save_under both your application and other applications on the screen would need to redraw areas when the pop-up window is unmapped.

Setting save_under is never necessary, but it can improve the performance of the server running clients that frequently map and unmap temporary windows. The user would otherwise have to wait for the area under the menu to be redrawn when the menu was unmapped.

There is no routine for setting the save_under attribute individually; it can only be set with XChangeWindowAttributes() or XCreateWindow().

The save_under attribute is different from the backing store; save_under may save portions of several windows beneath a window for the duration of the appearance of the window on the screen, while the backing store saves the contents of a single window while it is mapped or even when unmapped, depending on the attributes.

Not all servers are capable of saving under windows. You can find out whether this feature is supported on a particular screen with the DoesSaveUnders() macro.

The save_under attribute has the following possible values:

False (default)

Specifies that covered clients should be sent Expose events when the window is unmapped, unless they are preserved in the backing store.

True

Specifies that the server should save areas under the window and replace them when the window is unmapped.

Setting the save_under attribute to True does not prevent all Expose events on the area underneath. For example, assume there is a window whose bit_gravity is ForgetGravity, and this window lies under a window that has the save_under attribute set to True. The contents of the obscured window will be lost if the underlying window is resized while partially obscured, and Expose events will be generated even on the saved area.

4.3.7 Event Handling

The event_mask and do_not_propagate_mask attributes control the propagation of events through the window hierarchy. The event_mask attribute is normally set with XSelectInput(), but it can also be set directly with XChangeWindowAttributes() or XCreateWindow().

The event_mask attribute specifies which event types are queued for the window when they occur. The do_not_propagate_mask attribute defines which events should not be propagated to ancestor windows when the event type is not selected in this window. Both masks are made by combining the constants listed below using the bitwise OR operator (|).

Button1MotionMaskKeyPressMask
Button2MotionMaskKeyReleaseMask
Button3MotionMaskLeaveWindowMask
Button4MotionMaskNoEventMask
Button5MotionMaskOwnerGrabButtonMask
ButtonMotionMaskPointerMotionHintMask
ButtonPressMaskPointerMotionMask
ButtonReleaseMaskPropertyChangeMask
ColormapChangeMaskResizeRedirectMask
EnterWindowMaskStructureNotifyMask
ExposureMaskSubstructureNotifyMask
FocusChangeMaskSubstructureRedirectMask
KeymapStateMaskVisibilityChangeMask

Much more information on setting the event masks, including examples, is presented in Chapter 8. This is a very important topic.

4.3.8 Substructure Redirect Override

A feature called substructure redirect allows a window manager to intercept any requests to map, move, resize, or change the border width of windows. This allows the window manager to modify these requests, if necessary, to ensure that they meet its window layout policy.

Setting the override_redirect attribute True for a window allows a window to be mapped, moved, resized, or its border width changed without the intervention of the window manager. This override is usually done for menus that are frequently mapped and almost immediately unmapped again.

Under properly designed window managers, there is a property you can set to tell the window manager to allow a window to pop up with minimal intervention (XA_WM_TRANSIENT_FOR). This is used for dialog boxes, as described in 12.3.1.4.6 Transient Window Field.

There is no routine for setting the override_redirect attribute individually; it must be set with XChangeWindowAttributes() or XCreateWindow().

The override_redirect attribute has the following possible values:

False (default)

Specifies that map, move, and resize requests may be processed by the window manager.

True

Specifies that map, move, and resize requests are to be done verbatim, bypassing any window manager involvement.

4.3.9 Colormap

The colormap attribute specifies which colormap should be used to interpret the pixel values in a window.

For the large majority of clients without special color needs, this attribute can be left in its default state. By default, the colormap attribute from the parent is taken, which, if all ancestors of the window have used the default, will be the default colormap. This means that the default colormap for the screen will be used to translate into colors the pixel values drawn into this window.

If the client requires its own colormap for some reason, the client can create a colormap and set the colormap attribute to the ID of the new colormap. A colormap ID is of type Colormap.

The window manager will read this attribute and install the specified colormap into the hardware colormap when the user indicates that the application should be active. If the system only has one hardware colormap, all other applications will appear in false colors. This is one good reason that applications are encouraged not to create their own colormaps but to use the default colormap instead.

To understand this process, you need to know more about colormaps in X, and for that, see Chapter 7.

XSetWindowColormap() sets the colormap attribute, which can be set to the following values:

CopyFromParent (default)

Specifies that the colormap attribute is to be copied from the parent (subsequent changes to the parent’s attribute do not affect the child), but the window must have the same visual type as the parent and the parent must not have a colormap of None (otherwise a BadMatch error occurs).

a colormap ID

The specified colormap will be used for displaying this window, at least while the window manager considers the application active.

4.3.10 Cursor

The cursor is the object that tracks the pointer on the screen, sometimes called the sprite. In X, a cursor is a server resource which defines a cursor pattern, its colors, and the point within the pattern that will be reported in events (called the hotspot). The ID of a cursor is of type Cursor.

Most clients will define a suitable cursor for their top-level window and other cursors for each subwindow if needed. For example, xterm specifies the thin text cursor for the main window and a vertical bidirectional arrow for the scrollbar.

A cursor can be associated with any InputOutput or InputOnly window using the cursor attribute. Then the specified cursor will track the pointer while the pointer is within the window’s borders.

A primary purpose for having a different cursor in a window is to indicate visually to the user that something different will happen to keyboard or button input while in the window. Another reason might be to change a cursor’s color to increase its visibility over the background of certain windows (although there is another way to obtain contrast, with the cursor mask). There are probably other uses for a separate cursor.

A call to XDefineCursor() sets this attribute to a Cursor, and a call to XUndefineCursor() sets it back to None, which means that the cursor of the parent is used. The resource Cursor must be created before calling XDefineCursor(). This can be done with XCreateFontCursor(). XCreateGlyphCursor(), or XCreatePixmapCursor(), as described in 6.5.1 The Standard Cursor Font. The cursor resource can be freed with XFreeCursor() when no further explicit references to it are to be made.

The cursor attribute has the following possible values:

None (default)

Specifies that the parent’s cursor will be used when the pointer is in the window.

a cursor ID

Specifies a cursor that will be used whenever the pointer is in the window.

The cursor of the root window is initially a large X, but this may be changed like the cursor in any other window if desired. However, this should only be done by the window manager or by the user using the xsetroot application. See Volume Three, X Window System User’s Guide, for a description of xsetroot.

4.3.11 Default Attributes

Table 4-3 summarizes the default attributes for an InputOutput window. Only five of the attributes are relevant for InputOnly windows: cursor, do_not_ propagate_mask, event_mask, override_redirect, and win_gravity. These attributes have the same defaults as for InputOutput windows.

The background, border, and event_mask attributes need to be set for virtually all windows.

Table 4-3. Default Window Attributes

Member

Default Value

background_pixmap

None

background_pixel

Undefined

border_pixmap

CopyFromParent

border_pixel

Undefined

bit_gravity

ForgetGravity

win_gravity

NorthWestGravity

backing_store

NotUseful

backing_planes

All 1’s (ones)

backing_pixel

0 (zero)

override_redirect

False

save_under

False

event_mask

0

do_not_propagate_mask

0

colormap

CopyFromParent

cursor

None



[10] We should inform you here that a pixel value is not something you choose yourself; you choose a color name, and the pixel value is returned to you from BlackPixel or WhitePixel() or one of the routines that allocate colors. We go into this subject in detail in Chapter 7.

Get XLIB Programming Manual, Rel. 5, Third Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.