Compile them resources
Compile them resources
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 OpenBeOS 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 OpenBeOS
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 OpenBeOS 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
OpenBeOS 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 ;-)
And finally...
You can download rc
1.0 in a pre-compiled package. If you have checked out the OpenBeOS CVS,
you can also compile rc yourself. Its source code lives in the
/current/src/tools/rc
directory.
Enjoy!