Displaying Newsletter
Issue 51, 15 Feb 2004


  In This Issue:
 
OBOS App_Server Overview by DarkWyrm and Michael Phipps 

Recently, I asked DarkWyrm to give me a quick overview of the app_server. It is a piece of OBOS that I do not really understand; I wanted to start looking at code and I was looking for an introduction to help me get started. He provided most of this article; my work has been mostly editorial with a little bit of clarification and introduction to make it more appropriate to a newsletter.

The app_server is a process within BeOS--an application, if you will. It runs in user space, not kernel mode. Applications communicate with app_server via the Interface Kit. The IK provides all of the controls--sliders, buttons, text panels, etc.--and calls on app_server to draw everything. As such, I am sure that you can imagine that app_server is a complex piece of code. Performance and threading are critical to its success. Like any complex piece of software, it is broken into logical subsystems and different classes to make it comprehensible to its developers. For app_server, these subsystems are Fonts, Desktop, Management, Graphics, and Support.

Fonts

Our font rendering is provided by FreeType2. FT2 is a complete, open source font rendering system that is used in many other projects. Their development has saved us a great deal of time and effort and it is appreciated. The Fonts subsystem is technically the smallest, being limited to just a few classes: FontServer, FontFamily, FontStyle, and ServerFont. The main component for managing fonts is the FontServer. When the app_server starts, the font server iterates over the standard BeOS font directories, finding the fonts kept there, and creates a list of available font families and styles using the appropriate classes. Most work which needs done with fonts for OBOS will end up being done with ServerFont, which is the server-side equivalent of BFont. Almost any objects which need to work with fonts should use ServerFont. The Fonts subsystem is not considered to be complete.

Desktop

Desktop, in this case, does not refer to OpenTracker, folders, or the Deskbar. It refers to the portion of the app_server that deals with the windows, managing the monitor(s) and the drawing area, and "clipping"--only drawing what is actually visible. The OBOS Desktop is, for the most part, organized hierarchically with respect to class stucture. The Desktop class is at the top, performing high-level tasks, such as loading and initializing a display driver, handling window focus, sending input messages to the appropriate handler, and managing the data needed by global API functions like set_scroll_bar_info() and the like. Beneath the Desktop object is the RootLayer object, which performs two tasks: it manages all sublayers and keeps track of the available ServerScreen and Workspace objects. The next level down is occupied by ServerScreen, which at this point, is little-used but lays the foundation for the support of multiple monitors. At this point, its purpose is to house a DisplayDriver object. Also at this third level of management is the WinBorder class, which provides a way to manage window objects in the display hierarchy. While each WinBorder object technically "owns" its own instance of a Decorator, its Decorator object gives the WinBorder its shape according to its window size. The lowest echelon of the hierarchy is occupied by Layer objects. Layers are the server counterpart to BView and also provides the base code for redraw and clipping for it and its subclasses. ColorSet is devoted to handling the color attributes of the desktop, such as window tab color, panel background color, etc. It is written to be source compatible with Dano. If this proves to be a problem with binary compatibility, steps will be taken to ensure that it is BC. SysCursor is a tweaked-up ServerBitmap which is used to hold BCursor data.

Management

Management refers mostly to message handling--interfacing the app_server with the other system processes that need to communicate with it. The management classes are AppServer, ServerApp, ServerWindow, SessionStreamReader, BitmapManager, and CursorManager. AppServer performs very basic startup tasks: it spawns housekeeping threads, loads an appropriate Decorator add-on, and allocates global management objects. Once the system init is done, it watches for a select few kinds of messages--mostly app creation, but also for server shutdown when in test mode. The threads it spawns--Poller and Picasso--process input messages and check for dead applications, respectively. ServerApp objects are counterparts to BApplications and provide handling for BApplication services. Their message ports are also utilized for global function calls and creation of ServerWindows. ServerWindows are the counterparts to BWindows and provide server-side functionality for numerous BWindow calls. Graphics messages are translated into DisplayDriver calls by its DispatchGraphicsMessage method. SessionStreamReader is utilized by ServerWindow to handle graphics messages because graphics messages are streamed--cached on the client side and sent in blocks when the cache fills or when a sync is required. It is necessary to allow it to peacefully coexist with the normal packet-based messaging protocols without catastrophic results popping up from time-to-time. BitmapManager provides services for allocating and freeing ServerBitmaps and the buffer memory associated with them. The CursorManager tracks all ServerCursors used by the system. It also provides services for the new System Cursor API to allow for more consistency and flexibility across the GUI.

Graphics

The entire subsystem is built upon the DisplayDriver class, which provides a context-independent graphics interface. This allows for testing by rendering to a DirectWindow, a WindowScreen, or a BBitmap/BView combination in addition to being able to utilize regular hardware and hardware acceleration. One change from R5 is that hardware cursors are not supported, in favor of software cursors, which are much more flexible. Be used hardware cursors for performance reasons: Older video cards, especially those without accelerated blitting, needed that performance boost. Modern video cards do not. The API for utilizing 32-bit cursors is currently private and will remain so until R2. PNGDump contains one function and is used for saving the contents of the framebuffer to a PNG file. This replaces the huge BMP that the R5 app server creates. PatternHandler is used by DisplayDriver implementations to convert patterns into (x,y) coordinates. ServerPicture is used for server-side BPicture support. As of this writing, quite a lot of work remains to be done to implement BPicture support. Decorator objects are used to define and draw the border and tab of each window. The server also contains its own default decorator so that the system can function even if no decorators are otherwise installed.

Support

These classes don't have a cohesive place anywhere inside the other four groups.

  • Globals.h contains the definitions of window types, and Will probably have more definitions and things in the future.
  • Angle provides a reasonably fast means for getting trig function values for angles via a lookup table. This is currently needed only for internal use by the DisplayDriver.
  • ServerProtocol.h provides message code definitions for server communications.
  • SharedObject.h provides a rudimentary shared-object base class. It probably will become the basis for ServerBitmap in the future.
  • CursorData defines the default system cursors. It's used only by the CursorManager.
  • TokenHandler provides a simple means to obtain tokens with the option to exclude values.
  • RGBColor is a generic color encapsulation class which allows colors to be specified in 8-, 15-, 16-, or 32-bit values. Color caching is performed for speedier lookups when converting to 8-bit color.
  • Utils is a generic file for miscellaneous utility functions, mostly for working with BMessages and message codes.
  • RectUtils contains functions for working around existing R5 bugs in working with BRegions and BRects. This file will disappear after R1 is released.
  • ServerBitmap is the server-side counterpart to BBitmap. It also serves as the basis for ServerCursor. ServerBitmaps are not directly created or destroyed when used for BBitmap support--the BitmapManager is used so that the memory pool is properly taken care of.
  • areafunc.c and bget.c provide the BGET public domain pool allocator and functions to interface it with BeOS areas.
  • FMWList contains a list class for managing floating and modal windows.
  • ServerConfig.h is a file used to configure compile-time options for the app_server. Within it are definitions for the filenames and paths for various things like app_server settings, current decorator settings, and the name for the file containing the list of fonts available to the system.

The app_server has (obviously) made a lot of progress, even since the prototypes. The efforts of Gabe, Adi and DarkWyrm and their teammates are vastly appreciated!

 
How to Write a BeOS R5 Printer Driver by Michael Pfeiffer 

A printer driver in BeOS R5 is an add-on that exports a specific C interface. This add-on is used by the print_server to add a new printer, configure a page, configure the print job, and print the print job on the printer. This article describes how the print_server interfaces with the printer driver.

The print_server is responsible for maintaining common settings. BMessage objects are used to pass settings from the print_server to a printer driver and vice versa. If the printer driver has to return a BMessage object and wants to show the successful completion of an operation, it has to set the field what to the value 'okok'. To indicate an error, the preferred method is to return NULL. In some cases the print_server interprets a value in what other than 'okok' as the failure of an operation. A printer driver should not rely on this; instead, it should return NULL.

A print job is a file, generated by a printing application using the Interface Kit class BPrintJob (see Figure 1). This file contains archived BPicture objects--one for each call made to BPrintJob::DrawView(). A raster printer driver, for example, can use the BPicture objects for each page and draw them into a BBitmap object and convert the pixels in the BBitmap object into the format the printer understands. The driver can make changes to the content of the BPicture, including dithering and color correction. There is no standard or common code for these features in R5.

When a printer driver is requested to print a print job it uses a transport add-on to transfer data to the printer. Transport add-ons know how to write to an individual device (parallel, USB, network, etc.) Separating transports from drivers allows R5 to work with, for example, a printer on a USB port or the same printer on a parallel port with only one driver. It also makes writing the drivers simpler: the driver outputs a "stream" of data, without having to know how it is delivered.


Figure 1: BeOS R5 Printing Overview

Driver Location

System printer drivers that are installed with the OS are located in B_BEOS_ADDONS_DIRECTORY in the subfolder Print (i.e., /boot/beos/system/add-ons/Print).
Printer drivers installed by the user are usually placed into B_USER_ADDONS_DIRECTORY in the subfolder Print (i.e., /boot/home/config/add-ons/Print).

Transport add-ons (like Print To File, Parallel Port, and USB Port) are located in a folder named transport inside of the system or user printer driver folder.

The Life of a Printer Driver

Installing the Printer Driver

To install a printer driver, the driver has to be moved into one of the printer driver folders. Printer drivers should not link to shared libraries other than those provided by the OS--third-party libraries should be statically linked instead. This helps to avoid version conflicts.

Adding a New Printer

Users add a new printer to the system using the Printers preflet. They select a printer name, printer type, and transport add-on. The preference application creates a spool folder with the name of the printer in B_USER_PRINTERS_DIRECTORY (i.e., /boot/home/config/settings/printers). The name of the transport add-on is stored in the spool folder's file attribute named transport. The type code of this attribute is B_STRING_TYPE.

The print_server, when notified of the new printer, calls the printer driver to configure the printer. The function prototype within the printer driver for this is:

char* add_printer(char* printer_name);

This gives the printer driver the chance to open a window for configuration of the printer model. The configuration can be stored in the attributes of the spool folder. On success the printer driver should return the pointer to the string printer_name or NULL on failure.

Configuring the Page

When an application calls BPrintJob::ConfigPage(), the print_server requests that the printer driver configure the page by calling:

BMessage* config_page(BNode* spool_folder, BMessage* settings);

The printer server calls this function with spool_folder, a pointer to a BNode object whose path is the spool folder, so the printer driver can access its attributes. It also passes settings, a pointer to a BMessage object that contains the previous page settings, if any. At the very least, the page size and orientation have to be specified.

The printer driver usually opens a window to let the user select the page size, orientation, and (optionally) other settings; these other settings may be printer specific. The mandatory fields are:

Field Type Code Meaning
printable_rect B_RECT_TYPE The printable rectangle in 1/72 inches. That is the area the printer is able to write into.
paper_rect B_RECT_TYPE The paper rectangle in 1/72 inches.
orientation B_INT32_TYPE 0 ... portrait, 1 ... landscape.
xres B_INT32_TYPE Horizontal DPI.
yres B_INT32_TYPE Vertical DPI.

If the configuration was successful, a new BMessage object with the settings is returned. If it fails, NULL has to be returned (e.g. in response to the user clicking the Cancel button in the page setup window).

Configuring the Print Job

The main purpose of the configuration of the print job is to let the user select the range of pages to be printed and the number of copies of each page that should be printed. Again, printer-driver-specific settings can be added.

The print_server calls this function with the same parameters as it does when configuring the page:

BMessage* config_job(BNode* spool_folder, BMessage* settings);
Settings contains the fields from page configuration, which should not be changed. The mandatory fields that have to be added or changed are:

Field Type Code Meaning
first_page B_INT32_TYPE The page number of the first page to be printed. Starts with 1.
last_page B_INT32_TYPE The page number of the last page to be printed. If all pages should be printed its value should be MAX_INT32 (= 0x7fffffffL).
copies B_INT32_TYPE The number of copies.

On success, a new BMessage object with the settings should be returned; otherwise NULL has to be returned.

Using a Transport Add-On

The printer driver has to load the transport add-on associated with the printer. The name of the transport add-on is stored in the file attribute transport of the spool folder. The driver should first look into the user's printer folder (start at B_USER_ADDONS_DIRECTORY then proceed to Print/transport) to find the named transport add-on there. If it fails, it should look into the system printer folder (start at B_BEOS_ADDONS_DIRECTORY then proceed to Print/transport).

The transport add-on exports two C functions:

BDataIO* init_transport(BMessage *settings);
void     exit_transport(void);

init_transport() is passed a BMessage object with a field printer_file of type B_STRING_TYPE containing the path to the spool folder. On success it returns a BDataIO object and NULL on failure. The BDataIO object can be used to write data to the printer.

The printer driver must not delete the BDataIO object. Instead it has to call exit_transport() and then unload the transport add-on.

Sample code can be found in class PrintTransport's method Open() in the folder current/src/add-ons/print/shared in the OBOS source code repository. The header file PrintTransport.h can be found in current/headers/private/print.

Printing the Print Job

Now we come to the core of a printer driver. This is the prototype of the function that is called by the print_server when the print job is ready to be processed:

BMessage* take_job(BFile* print_job, BNode* spool_folder, BMessage* settings);

The print job can be read using the file print_job. Its file format is explained below. Again the attributes of the spool folder can be accessed using spool_folder and the settings from the page configuration are also available, e.g. to get the page size.

To write data to the printer, the printer driver has to load the transport add-on and create a BDataIO object as explained in the section entitled Using a Transport Add-on.

To read the print job the class PrintJobReader from the OBOS source code repository can be used. This code snippet demonstrates how PrintJobReader can be used:

PrintJobReader reader(print_job);
if (reader.InitCheck() == B_OK) {
  // the settings stored in the print job
  BMessage* settings = reader.JobSettings();
  
  // page number of the first page
  int32 firstPage = reader.FirstPage();
  
  // page number of the last page
  int32 lastPage = reader.LastPage();
  
  // paper and printable rectangle
  BRect paperRect = reader.PaperRect();
  BRect printableRect = reader.PrintableRect();
  
  // resolution
  int32 xdpi, ydpi;
  reader.GetResolution(&xdpi, &ydpi);
  
  int32 pages = reader.NumberOfPages();
  // for each page
  for (int page = 0; page < pages; page ++) {
    
    PrintJobPage pjp;
    
    if (reader.GetPage(page, pjp) == B_OK) { 
      BPicture picture; 
      BPoint point; 
      BRect rect;
      
      // for each picture on page
      while (pjp.NextPicture(picture, point, rect) == B_OK) {
        // do some thing with the picture at point
      }
    }
  }
}

What the printer driver does with the data from the print job is printer dependent and is not within the scope of this article. Sample source code can be found in the OBOS source code repository. For raster printer drivers see OBOS printer drivers Canon LIPS, PCL5, or PCL6. For "vector" printer drivers see the OBOS PDF printer driver.

Print Job File Format
struct  print_file_header {
  int32  version;
  int32  page_count;
  off_t  first_page;
  int32  _reserved_3_;
  int32  _reserved_4_;
  int32  _reserved_5_;
};
This is declared in PrintJob.h. In the print job file, the header is followed by a flattened BMessage object containing the settings that are passed to take_job().
The print job file contains page_count page sections. The first starts at file offset first_page.

Page Section
struct  page_header {
  int32   picture_count;
  off_t   next_page;
  int32   _reserved[10];
};
This is followed by picture_count picture sections.
The next page section starts at file offset next_page.

Picture Section
struct _picture_header_ {
  BPoint  point;
  BRect  rect;
};
This is followed by a flattened BPicture object.

Removing a Printer

With the Printers preference application a printer can be removed. This deletes the printer folder from B_USER_PRINTERS_DIRECTORY if no pending print jobs exist.

Uninstalling a Printer Driver

A printer driver should be removed only if all printers have been removed with the Printers preflet. To remove the printer driver, the printer driver add-on has to be deleted from the system or user printer driver folder. There is little advantage to doing this--printer drivers are small and do not add to boot time or decrease system performance.

Updating a Printer Driver

Usually if a new version of a printer driver should be installed it is not necessary to remove the added printers from the system. In most cases it is sufficient to replace the printer driver add-on.

Issues to Consider When Writing a (Printer Driver) Add-On

A printer driver add-on is loaded on demand and usually unloaded as soon as it is not used any more. This means global states cannot be stored in global variables of the printer driver add-on. As mentioned already, the printer driver can store global states in an attribute of the spool folder.

It is also possible that multiple instances of the printer driver add-on are loaded and used at the same time (e.g. when the printer prints a print job and the user configures a page at the same time).

Multiple threads started by the printer driver can also be an issue. The printer driver has to ensure that all threads that have been started inside of the driver have exited before the driver returns from a function. E.g., when the printer driver opens a window for the configuration of a page in config_page(), a separate thread for the window is started. config_page() has to wait until the window is closed and the object that represents the window is completely destroyed, otherwise it could happen that the printer driver unloads the printer driver add-on while the window thread is still running. This will lead to a memory access violation because the window thread still accesses code in the add-on that is not loaded any more.

Open BeOS Source Code Locations

Files Location
PrintJobReader.cpp current/src/add-ons/print/shared
PrintJobReader.h current/headers/private/print
Printer Drivers current/src/add-ons/print
Transport Protocols current/src/add-ons/print/transports
 
Against Directories by Michael Phipps 

Let me start out by saying that I am not "against" directories. It is more like "rethinking" directories. We have been talking on the Glass Elevator list about queries and how to make them more useful for some time now. There is a pretty broad consensus that we should try to bring more use of queries into R2 and beyond, that we should help the end user to use them far more than R5 does. While not a "killer app", queries can be, at the very least, an "attacker app": one of those little features that you really miss when you move to other operating systems. As a result of some serious consideration of what I know about how people use computers, it seems to me that, in many ways, directories may not make sense anymore. Beginning users struggle with them. Many people just don't use them--they fill their desktop with all of their files, or deposit everything into a large, flat "My Documents" folder. Advanced users become frustrated with them because they are forced into only one organizational method. They can't easily search for files except along the lines that they first organized their files.

If you take a graphic designer, for example, she might organize her files by client. If Client 1 wants a piece of stock clipart, she has three choices. One is to put her only copy of that clip art in that client's folder. This makes it difficult to find again. Her second choice is to keep it in a "stock" folder. This defeats her purposes in a few ways. One is that the folder for the client is no longer a "pick it up and go"--she needs to include some of the stock folder as well. Another is that she needs to remember what clip art a customer needs. A third is that this defeats the "last directory used" concept that most file requesters use--going automatically to the last folder that the user selected. The graphic designer is now forced to "ping pong" between two or more directories to complete her work. For these reasons, her third option, the link, was invented. There are two varieties of links: hard and soft. Soft links are much like references or shortcuts in Windows. They contain a path to the "real" filename. Hard links are a directory entry that points to the same data as another directory entry. Each has issues and difficulties. Links are "just different enough" that users need to be aware of them and consider them. For example, if our graphic artist wants to copy files to a CD, she may well need to tell the burning software to include the original file rather than the link (for soft links). Hard links cannot cross volumes: You can't make a hard link from Disk 1 to Disk 2. None of the options above, though, really make it feasible to ask questions like "which customers use clip art ABC?"

Directories have problems. We started by discussing all of the "cool things" we could do with queries. Add a tab to the file requester allowing you to do queries on the fly for the file that you are looking for and you have an easier system. We considered some ideas about what metadata should exist by default. The system provides filename, creation date, mod date, mime type, file size, etc. Additional possibilities include resolution (for images), MP3 tags, document type (for text files), etc.--anything that the system can reliably autogenerate. Users could add their own attributes to files to make it easier for them to find things. The core problem that this generates is that it is more work for the end user to do--assigning values. Then it hit me--if we convince end users to replace directories with attributes, it decreases their work and increases the system's usefulness.

This proposal was met with some resistance, because directories have been around a very long time and have solved organizational needs for a long time. I would like to discuss some of the objections here and react to them.

The first is stability--that directories are more stable and queries are more dynamic. That directories only change on command. I would respond that queries change in the same way as directories--when you want them to. If you choose a very dynamic parameter for your query (file size, for example, or last_mod_date), you will have folders that seem random. But that also makes sense--if you told a secretary that you want all of the newest memos on your desk at all times, you wouldn't expect the older ones to remain.

Another is identity--that directories provide one and only one clear path to each file. This is completely true. I don't see a lot of value in it, to be honest. In fact, with hard links, it is not true. BeOS doesn't have them, but Unixes that I have used do. It doesn't disturb me all that much when I see that there are a bunch of hard links to a file--that there is more than one way to get there.

The notion of hierarchy is brought up--that the hierarchy of files that a directory provides brings value. That queries are "opt-out" instead of "opt-in". Much of this depends on how you structure your queries. Sure, if you say "type=text" and nothing more, you get a lot of files. This is not radically different, though, from going to "My Documents" and saying "ls -R", recursively listing everything in your documents. If you instead say "type=text and project=obos-editorials", this is a lot like saying "ls -R 'My Documents/obos-editorials'". In a similar vein, the ability of the hierarchical tree to be divided into subtrees is brought up. You can do things like crawl the whole tree in pieces. This is more powerfully done with queries, though. Pick an attribute, divide up the possible results any way that you like, and run separate queries. So long as there is only one value associated with one attribute, this works well.

Queries make "drag and drop organization" a little bit more of a challenge. For example, if you organized your directories of music by genre then artist, you can assign an artist's music by dragging and dropping them into the genre of choice. That is a little tougher with queries--you would have to assign attributes. For a simple query (genre=blues) this is OK, but for (genre=blues or genre=jazz), this is harder. Various possibilities of a GUI to support this were discussed. The issue comes from the extra power of queries. You can make queries that you can't make with folders. One way to solve this solution is the same way that file permissions are handled in Windows--multiply select a bunch of files, assign a set of permissions and click OK.

Temporarily collecting files with queries is different than with directories. With a directory, I can drag and drop files into a temp directory, then copy the whole thing to a CD. With queries, you would need to assign the files a common attribute which does not get burned to the CD and gets removed as the files are burned. This is a bit of a shuffle around. It does have an advantage, though: The files still match their original queries. So, using backups for example, you could deal with backups by creating a "backed up" attribute of type datetime. Creating a new backup would be an easy query: "last_mod_date>backed_up". While the backup application is running, other applications could still access those files.

Finally, dealing with other filesystems (non-BFS) is more complex without directories. The whole directory API must exist to support interfaces with ext, ntfs, iso9660, udf, fat, FTP and others. Some method of manipulating directories must continue to exist as long as we must coexist. I understand and accept that. That doesn't mean that we need to allow others to dictate our future.

Queries have a vast number of advantages over unidirectional organizational schemes. They allow the users to ask questions in the way that makes sense to them. We have all seen classic Star Trek, in which they use the computer like a librarian--"Show me the files on Midas 5". That is the sort of power that a well built query system can bring to OBOS. The ability to work the way that you think at the moment, not the way that you (or a dev) thought of some time in the past.