Body
All BeOS programs have "resources". Simply put, a resource is data that is bundled with your application's executable. Typical examples are the application's icons and its signature, but you can attach any data you want. Many applications store bitmaps, text, cursors, and even complete user interfaces (dialog boxes, menus, etc.) as resources.
This may sound a lot like attributes; after all, aren't attributes a way to store additional data alongside your files as well? Yes, but the difference is that resources can only be used with application files, not with just any file. Unlike attributes, they are not intended as general purpose data storage. Instead, they only provide data that "sticks" to the executable. Although applications can overwrite their own resources, this is not recommended. Resources are typically set during compile time and never change afterwards. This means, for example, that you should not use resources to store user settings.
To see the resources that are bundled with an application, you can drop the executable on a tool like Resourcer. You can also go into a Terminal and type listres filename.
Making resources
If you have ever written a BeOS app, you probably used the standard FileTypes tool to create a .rsrc file with the application's signature, the launch flags, version information, and the icons. You then added the rsrc file to your Makefile or BeIDE project, and when you compiled the app, the resources were automatically copied into the executable. Maybe you have also used a tool like QuickRes or Resourcer to stuff additional resources into the rsrc file.
These tools are not the only way to create resources; you can also use a "resource compiler". A resource compiler is a (command line) tool that takes a text-based resource script and turns it into an rsrc file. With a resource compiler, you express your resources as ASCII text using a simple scripting language, which makes the resource files much easier to edit and maintain.
BeOS R5 comes with an (experimental) resource compiler called "beres", and a corresponding decompiler called "deres". The resource definition language they use is called the "rdef" language. Recently I wrote an open source replacement for these tools, called "rc". This article demonstrates some of the ways you can use rc to create your own resource files.
Note that rc is not the only resource compiler for BeOS. The source tree of the Pe text editor contains an alternative compiler called Rez. Why did I write rc if another open source resource compiler was already available? Mainly because Rez does not understand the rdef language. In addition, several projects (such as OpenTracker), already use rdef files. Since the goal of Haiku is to provide an open source re-creation of BeOS R5, I felt it was important to re-implement the resource compiler as well.
The rdef language
The syntax of the rdef language is straightforward, so writing resource scripts is not very difficult. A resource script is a plain text file with one or more resource definition statements. It may also contain C or C++ style comments. By convention, resource script files have the extension ".rdef".
Here is an example of a simple resource script:
resource(1) true; /* this is a comment */
resource(2) 123; // and so is this
resource(3) 3.14;
resource(4) "hello world";
resource(5) $"0123456789ABCDEF";
When compiled, this script produces a resource file with five resources. The above example also illustrates the types of data that resources are allowed to have: boolean, integer, floating point, character string (UTF-8), and raw data buffer (hexadecimal).
By default, integer data is stored as a 32-bit int, and floating point data is stored as a 4-byte float. If you want to change the way the data is stored, you have to cast it. The resource compiler understands many of the native BeOS types, such as int8, int16, size_t, and a whole bunch of others:
resource(10) (int8) 123;
resource(11) (double) 3.14;
You can also change the resource's type code. This does not change the way the data is stored, only what it means. The type code gives you, the programmer, a hint as how to interpret the data. To change the type code of a resource:
resource(12) #'dude' 123;
For your own convenience, you can also name resources:
resource(13, "Friday") "Bad Luck";
The resources we have made so far consisted of a single data item, but you can also supply a collection of data values. The simplest of these compound data structures is the array:
resource(20) array { 1234, 5678 };
An array is nothing more than a raw data buffer. The above statement takes the two 32-bit integers 1234 and 5678 and stuffs them into a new 64-bit buffer. You can put any kind of data into an array, even other arrays:
resource(21) array
{
"hello",
3.14,
true,
array { "a", "nested", "array" },
$"AABB"
};
It is up to you to remember the structure of this array, because array resources don't keep track of what kind of values you put into them or where you put these values. For that, we have messages. A message resource is a flattened BMessage. A message has a "what" code and any number of fields:
resource(22) message('blah')
{
"Name" = "Santa Claus",
"Number" = 3.14,
"Small" = (int8) 123, // use cast to change data type
int16 "Medium" = 12345, // specify data type
#'dude' "Buffer" = $"00FF" // specify a new type code
};
Besides arrays and messages, the compiler also supports a number of other data structures from the Be API:
type | corresponds to | fields |
---|---|---|
point | BPoint, B_POINT_TYPE | float x, y |
rect | BRect, B_RECT_TYPE | float left, top, right, bottom |
rgb_color | rgb_color, B_RGB_COLOR_TYPE | uint8 red, green, blue, alpha |
For example, to add a color resource to your script, you can do:
resource(30) rgb_color { 255, 128, 0, 64 };
Or you can use the field names, in which case the order of the fields does not matter:
resource(31) rgb_color
{
blue = 0, green = 128, alpha = 64, red = 255
};
The compiler also provides convenient shortcuts for the resources you would normally set from the FileTypes application:
type | corresponds to | fields |
---|---|---|
app_signature | the app's MIME signature | string signature |
app_flags | application launch flags | uint32 flags |
app_version | version information | uint32 major, middle, minor, variety, internal string short_info, long_info |
large_icon | 32x32 icon | array of 1024 bytes |
mini_icon | 16x16 icon | array of 256 bytes |
file_types | supported file types | message |
To conclude this short summary of the rdef language, this is how you would set the application's signature:
resource app_signature "application/x-vnd.my.app";
As you can see, rc lets you do pretty much everything that the GUI resource tools do. It has several other interesting features, such as the ability to make your own data structures ("user-defined types"), that I haven't touched on here. The documentation that accompanies rc goes into much more depth, so I suggest you take a look at that if you want to know more.
Compiling the rdef script
Once you have written your script, you need to compile it. Since rc is a command line tool, you must run it from a Terminal window. Compiling is as simple as typing:
rc -o outputfile.rsrc inputfile.rdef
If your project uses a Makefile or Jamfile, you can add a rule for rc and it will automatically generate the rsrc file for you when you compile the project. Below I will discuss how to do this for projects that are part of the Haiku CVS tree. But first...
Using the resources in your app
So you have added a bunch of resources to your application. Now what? You create a BResources object, that's what. The BResources class, which is part of the Storage Kit, is initialized with a pointer to a BFile object. For example:
BFile file("/boot/home/SomeFile", B_READ_ONLY);
BResources res;
if (res.SetTo(&file) == B_OK)
{
...
}
Typically, you only want to look at the resources from your own application, so first you need to make a BFile with the path to your app. The following code snippet illustrates how to do this:
app_info info;
be_app->GetAppInfo(&info);
BFile file(&info.ref, B_READ_ONLY);
BResources res;
if (res.SetTo(&file) == B_OK)
{
...
}
Now that we have a BResources object, we can call its LoadResource() function to load one of the resources into memory:
size_t size;
const void* data = res.LoadResource('type', id, &size);
LoadResource() gives you a pointer to a memory buffer and the size of that buffer. This memory belongs to the application and you are not allowed to modify or free it. Otherwise, do with the data as you please. BResources also contains several functions that return information about the resources, which can be handy if you don't know beforehand which resources are available.
Note that you should only use BResources to look at custom resources. The standard application resources (signature, icons, app flags) should be accessed through the BAppFileInfo class, which is also part of the Storage Kit.
Finally, you can save yourself some trouble if the resources contain images. The Translation Kit's TranslationUtils helper class can create BBitmap objects straight from the resources:
BBitmap* bitmap1 = BTranslationUtils::GetBitmap('type', "name");
BBitmap* bitmap2 = BTranslationUtils::GetBitmap('type', id);
Refer to the corresponding chapters of the BeBook for more info.
Switching to rdef
The plan is to switch all rsrc files from the Haiku CVS tree to rdef in due time. The main reason is that CVS doesn't handle rsrc files very well, because they are binary. Most of our resource files will be much easier to edit and maintain if they are text-based.
To give you an impression of how easy it is to switch to rdef files, let me relate my experiences with switching over StyledEdit. The source code folder of StyledEdit already contained several rdef scripts. These scripts, however, were not used in the compilation directly, and the project's maintainer used the old beres compiler by hand to make the rsrc file. Like most other projects, the relevant part of the Jamfile looked like this:
AddResources StyledEdit :
StyledEdit.rsrc
;
All I had to do to make the switch was change the file names. Jam is smart enough to first compile the rdef scripts into an rsrc file, and then copy those resources in the StyledEdit executable. After the change the Jamfile looks like this:
AddResources StyledEdit :
StyledEdit.rdef
StyledEdit.icons.rdef
StyledEdit.version.rdef
;
Your project probably doesn't have an rdef script yet. If you already have an rsrc file, then making the rdef is easy, because rc also includes a decompiler that will take one or more rsrc files (or any file with resources) and produce a ready-to-use rdef script. That should save you some typing.
To decompile, type the following from a Terminal window:
rc -d -o outputfile.rdef inputfile.rsrc
Note: Even though it is already possible to replace the rsrc files from the Haiku tree, it can sometimes be a little inconvenient. Some of the resources (such as the application version) are still a little hard to specify because rc's type mechanism is not advanced enough yet. A future version of the compiler will make this much easier. But don't let that scare you from experimenting with rc ;-)