FreeDOS
SEAL Workshop
Last Updated: 2nd January 2003
This page mostly contains information about SEAL 1.x, and may not be updated much. Please check out SEAL 2.0 - the latest and greatest version of the SEAL graphical environment for DOS.
Also, check out my Star Wars Animation Player for SEAL!
SEAL is a free desktop environment for DOS. Distributions include the original SEAL, Bad Seal and StarSeal. For more information on SEAL, see the SEAL Forums.
I've made the Sound Format API (SFA) work with Bad Seal, and have written a MOD driver for it. This will be included in the new SEAL 1.0E distribution and can be downloaded here. I've also been working on a GEM/32 MCI (Multimedia Control Interface), which is similar to SFA, only sort of platform-independent. It still requires DJGPP and DLX, but it will work with anything that exports the C runtime, DLX functions and (recommended) Allegro. This includes GEMP3, the future GEM[VDI]/32 and SEAL. It is currently not available for download. (BTW, I've sadly lost the source code to GEMVDI/32, as my hard disk died recently. The same goes for the below screenshot utility, but it wouldn't be too hard to recreate this).
I've also created a small screen capture program for Bad Seal. This adds an icon to the system tray which, when double-clicked on, brings up a Save dialog that lets you save a screenshot. You can download it here (2.87KB).
Star Wars text-based animation viewer!
I created a Star Wars Animation Player for SEAL. This has been updated - click here for more information and to download it.
Jon Heather's updated version of Bad Seal
Jon Heather has created an updated version of Bad Seal which, among other things, makes SEAL compatible with the latest version of Allegro. He has also added extension support to various structures, making them more compatible. The latest version of this re-implements the startup window, completes the registry/INI "translation" routines and is very fast.
Click here to download the source code of his version (1.42MB). Alternatively, click here to download the binaries (1.1MB).
Below is the content of various e-mails Jon sent to me. They contain some very interesting and useful information. You'll need to take a few minutes to read it all through, but it's very interesting and detailed:
"It does do a rewrite of the mouse stuff and uses the latest allegttf library for its fonts. It also uses the standard allegro library - none of Michal Stencl's extensions which must be a Good Thing(tm) in my opinion."
"I am now working on re-instating the (rather few) bits of stuff that made BadSectors version incompatible, but in a way that will allow him and anyone else to add things without causing the incompatibility problems his version has given. Essentially I am adding a little list of named & versioned extensions to the base object struct with a couple of access functions using 16 of the spare 48 bytes. I should have that finished shortly too."
I suggested a versioning system for structures, and provided some example code, and his reply was:
"Ye...s. The problem is that everything has to have these
if (win.version == 1) { win1 = (t_window_v1) win; // code specific to v1 that "upgrades" to v2 } else if (win.version == 2) { win2 = win; }
things. Gets really messy and unmaintainable after a few generations. The size thing is the real problem however. You have to malloc enough memory for the struct in the xxx_init function. As more stuff gets added to the struct, older versions and newer versions cease to co-exist well, if at all. You can't go putting your version 666 things in if the struct from version 333 ain't big enough.
What I have in mind though, goes like this, [He says - having implemented it ;-)]
Three access functions -:
p_extension obj_find_extension (p_object o, l_text id, l_long
version) {... }
p_extension obj_add_extension (p_object o, l_text id, l_long
version, void *data, l_long *prev_version, void **prev_data)
{...}
p_extension obj_remove_extension (p_object o, void *data){...}
I wont bore you with the details
{...} just say that they maintain a list of extensions in version
order. add_extension() adds the data pointer to the list if the
version is higher than any other version present,
find_extension() returns the data pointer of the highest version
of the specified extension and (yep) remove extension removes the
data pointer from the list - any earlier
version then being revealed to find_extension().
So, added into obj_init() in the kernel we have
... o->extension_list; /* the head of the extensions list */ o->find_extension = &obj_find_extension; o->add_extension = &obj_add_extension; o->remove_extension = &obj_remove_extension; ...
Now, in your library or wherever, you can extend any struct, window, view, list - they all inherit from t_object, just by add_extension'ing in your own personal additions. Example-: (taken from the mods BadSector had in his app.c but reworked my way)
Somewhere within the xxx_init function (in a .dlx library probably)
... if(OBJECT(o)->add_extension) { /* if the seal kernel supports this */
(Cool! You can even check if the kernel believes in this idea.)
extension=_malloc(sizeof(t_appwin_extension)); if (extension) { l_long prever; void *predat; data=(p_appwin_extension)OBJECT(o)->add_extension(OBJECT(o), BS_APPWIN_NAME, BS_APPWIN_VERSION, extension, &prever, &predat); if(!data) exit(1); /* no mem - bad news */ if(data==extension) { /* if our extension had been added */
(This is the stuff that BadSector stuck direct into his version of the appwin struct)
extension->icon16 = image_sysmenu; extension->icon32 = NULL; extension->orig_bounds=r;
(NB prevver and prevdata can allow you to inherit selectively from any earlier version should you want to. If prevdata is non-null it is the address of a prevver version of the extension struct, no doubt added in somewhere downstream of us.)
} else { /* the extension was already defined and it has a higher version than the one we proposed ... */ _free(extension); /* so ours will not be needed ... */ extension=data; /* and we can assume, being a later version, it will have all our stuff filled in, so thats ok */ } } } ...
Moving on to using it in one of the apps,
{ p_appwin_extension ext; if( OBJECT(mainwin)->find_extension && (ext=(p_appwin_extension)OBJECT(mainwin)->find_extension( OBJECT(mainwin), BS_APPWIN_NAME, BS_APPWIN_VERSION))) ext->icon16= (BITMAP*)GET_DATA(dat, 0); }
(This case needs no else - but you can go do something sane if the library actually in use hasn't got the extension in a version high enough for you)
In the app.h header two
constants let you get at this type of extension data.
#define BS_APPWIN_NAME "BSAppWinExt"
#define BS_APPWIN_VERSION 1
(I made my version numbering a bit more sneaky but it gives the
idea)
Now, any program that doesn't know about the correct extension is totally unaffected, and programs that do can get at the (highest version of that) extension. If the particular library in use has add_extension'ed the right extension, your proggy can be cute and do its good stuff, if not it can either go sulk or take some fallback position that doesnt need the extended info. You never need to alter the base definition of the structs at all. When there is a kernel release needing major & specifically INCOMPATIBLE changes, then it might be worth considering collecting some of the more stable extensions into the base structs. (Hey, is this survival of the fittest in software?)
The idea of my version numbers is that, as you say, you start off defining your extension struct, version 1. Your next revision then adds more functionality and needs more data/functions. So they are added on at the end to make version 2. So long as you keep the earlier bits of the struct the same, any software that has a version less than your 2,200 or whatever can continue working fine. All you need do is make sure any mods you might make involving things in the earlier parts of the struct remain 100% (bug)compatible so everyone is happy. The structs may extend a touch more than strictly neccessary, but who cares its only ***virtual*** memory.
Like I said, I have this implemented now and, for the BadSector extensions at least, it seems to work. I have yet to test my code with different version type operations, but reckon it well fulfils Kostas' requirements (in fact its rather overkill) and should give us pretty good service in the long term. My only concern now about it is the versioning. I can forsee (from Amiga experiences) people releasing identically versioned but internally different structs. Now that IS nasty and quite likely too. It's rather common for two people to decide, at near enough the same time, that something is junk and needs changing. Both of them will for sure call theirs the version 2 struct with predictably horrid consequences. I haven't the slightest clue how to prevent it though.
I can try to get a diff of the various mods I have put in to implement this if you like. (Hint - #ifdef WANT_BAD_STUFF has now gone from my current version)." Owen: The diffs Jon refers to are available above.
That's not all - there's more. In a later e-mail, I'd tested and looked around the code, and gave him a few comments. He replied with this:
> I've spent a couple of
hours looking around the code and hacking away, and
> have a few comments: (Owen: I'd made some
comments about registry_color now being a macro instead of
function)
are concerned, I set registry_color up as a macro so it removes the function entirely. I can't export it, it's not there. If you want to re-enable the registry, not that I've tried, you will need to put the export(s) back into the
DLXUSE_BEGIN LIBEXPORT_BEGIN .. LIBEXPORT(registry_color) ..
section (in the program.c code).
To be honest, I deliberately left it out (I could have put something that did the same thing in) specifically to catch such things. Anything asking for that, I reasoned, would be using the modified BadSector structs and I'd rather the loader refused to load them than crash. I'll put it back into my sources with an #ifdef WANT_REGISTRY and pull out my macros using the same #ifdef if you reckon it works.
If you ARE using my macros though and the program fails for lack of registry_color you need to make sure it is picking up the macros in registry.h correctly.
The startup window; I'll see if I can re-enable this now. If I remember rightly the problem I had was that it blew me out of the water and crashed windows when I first tried using it without a screen (for debugging). Some of Kostas' code assumes a touch more than it should. As you say, it's a friendly feature, though the seal.dbg file gives much more concrete info about what has happened.
Colours - um, possibly the ini file. I also did some stuff to use the internal default colours correctly so Seal works without any ini file/registry. I'll have a look and see what differences I can track down. I must admit I hadn't noticed the color difference, but then I wasn't looking.
Aha, crashes! The only reproducible blue-screen-of-death/crash I have managed to track down is to do with the asynchronous keyboard and mouse interrupts. If, for some reason, seal dies without executing allegro's exit handler functions, (which should be impossible - ha ha ha!), the interrupt eventually blows up. In windows, it's often a general protection fault or some other memory/invalid code related trap, usually in VMOUSE. It's because the interrupt is STILL ACTIVE after seal has departed. Eventually windows pages out the memory that contains the now-defunct but still in-use code. When an interrupt occurs after that point you can get an invalid page fault if windows hasn't reallocated that page or alternatively it may start executing some arbitrary bit of junk as its interrupt code. A cute way of demonstrating some part of this problem is to get gdb to break some time after the keyboard and mouse have been started. Then just wait. If you leave things too long, the computer (reliably!) crashes the moment you move the mouse. Its a full crash because the interrupt code was unable to run while gdb had it stopped in an exception or something.
Basically, I reckon this an allegro problem, or rather an unpleasant side-effect for any program that crashes while using allegro. And probably unavoidable because of the way allegro (has to) handle keyboard and mouse. Under DOS the crash no doubt occurs immediately because, I guess, djgpp invalidates the page tables on exit instead of the lazy way windows does things.
It's an unmitigated pain, but anytime seal dies unexpectedly you're well-advised to reboot to be sure it hasn't left this nasty time-bomb. After rebooting, which your message kinda suggested you did, well that's YOUR problem - its certainly not Seal. There ARE no left-overs, mystery modifications to the registry or other nasties that plague windows programs here. Also once you exit seal normally that problem can't occur because the interrupt will be closed down correctly. I guess its possible that just restarting and immediately exiting Seal (or any other allegro program) will prevent such crashes, since the interrupt will be taken over and closed properly then - I think.
On the other hand, if your crash occurs when seal is just sitting in the background doing nothing, then that is REALLY worrying. It suggests that somehow, something under interrupt isn't being locked and windows is paging it out. Now - I've actually removed most of the code that was happening under interrupts in the original seal (most of which seemed to be to get the rubber-band outline when moving and resizing). That code was the major reason we were tied into Michaels "special" version of the allegro library and I REALLY wanted to use a standard allegro. It's also why the move and resize move the whole window now. I believe that the timer, keyboard and mouse are the only interrupt things left and they really are bog-standard allegro. (Seal without them would be a sorry thing.) I rather hoped the removal of all that code might solve such random inexplicable crashes.
Next, returning to the versioning thing. Yep. Your code works - why am I not surprised.
Lets put this same-version-different-functionality thing into more concrete terms. Suppose your StarWars thingy had been a bit more complex - it needed a library and some structs to support the app that showed that cute video.
Now, deep in the outback of Australia, Crocodile Fundee says, "Hey sport! I can turn this into a real bonza video gizmo!" and produces a version2 library based on your struct for his singing Castlemain XXX video player.
Of course EVERYONE loves it, but at the same time some Idaho potato farmer also produces a library that does superb 3D graphics of talking potatoes. Wow! It also uses a struct based on yours called version2. So now you can either have talking potatoes or Castlemain XXX videos. But not both at the same time. Croc's library can't run Idaho's app and Idaho's (similar) lib won't give you Castlemain XXX for love nor money. You have to edit the .ini file (or zap the registry) to load in one or other of these support libraries. And reboot - cos libraries don't unload.
I can see this going down like a lead duck with our users!
I believe my proposal can handle the struct part. All you do is change the id in one of the libraries and in the applications that use it. It isn't going to happen every day of the week, so changing it in the source, or using a hex editor to zap it if they only release binaries, is OK. For the sake of the latter and for code-cleanliness it might be best if there was just one location that needs altering though. Probably a good idea to force the issue so there IS only ever one string to zap in some way. Making it an export symbol rather than a string COULD be a right sneaky way of doing that, with some benefits.
Example:
#define VERSION 2 l_int OwensVideoStruct somethingorother; ... LIBEXPORT_BEGIN LIBEXPORT(OwensVideoStruct) ... o->find_extension(o,&OwensVideoStruct,VERSION); ...
Its address is unique, it has to be, and the kernels list-search now no longer needs do string compares (good) just a simple comparison of addresses. Its exported so the apps pick up the same address from the dlx loader. Now, if there is any conflicts, the string we alter or hex-edit is the symbol name in the dlx export tables of the library and in the symbol table of the apps that use it.
I have been thinking something else though ... Seals dlx loader is awfully dumb isn't it? Should we really be expecting our users to know the intimate relations between one library and another? Surely our run-time linker should just go off to see if it can find any unresolved symbols by itself. That's what the Unix ld.so does. It has a list of places to look, from ld.so.config or something, and burbles through those (misnamed) "shared" object libraries trying to find something that fits the bill when a page fault of the right type occurs.
Now, I don't think we really want all the virtual-memory-dynamic-loading overhead from unix thanks, but surely some kind of library searching is in order. Maybe the loader scans the export lists of all dlx's in the base (and/or some other defined) directory so he knows which libraries are available to load. Probably he also looks for a dlx to satisfy the unresolved symbols in the directory the app is loading from too. The same as Windows does. The OwensVideoStruct symbol starts coming in handy then. Once we have removed the identical naming between Croc's library and Idaho's a more-intelligent dlx loader can pick the right library to load because Croc's apps will be asking for the (now-modified) symbol which can only be found in his (now-modified) library.
A couple of other things are involved. Firstly I am not sure what the dlx loader currently does if you try to load something that defines exports it already knows about. What would be nice would be for it to simply smile and start using the latest definition, reverting to the prior one when the library gets unloaded. Which is the second issue. With the ability to search and load libraries, dynamic unloading starts to look kinda sensible. You have to track who is using each library and unload them when their useage count drops to 0 of course. It would certainly reduce the potential for such incompatibilities if a library departed along with the app that called it.
I must admit explicitly specifying the libraries to load is nice in one sense though. Like I pointed out in the readme, once initialisation is done no further disk access are needed by the kernel.
<digression>
Incidentally, have you seen the horrid kludge in the dlx loader
which writes to a file called "_0_dat__._x_" to extract
resources from dlx files? Yuk, I hate to think what happens if
you were to put a read-only file of that name in the directory -
absolute mayhem probably. I really must scratch my head and see
if I can't use the allegro library to load resources direct from
the dlx file.
</digression>
You should be able to boot Seal from a write-protected floppy, in my opinion - (super for virus protection!). Being able to run with no boot disk, once booted, ought to be a feature too, really. Of course that's incompatible with dynamically loading and unloading libraries, isn't it?
Anyhow, while we're thinking this library thing out, I WILL go fix the resources thing, it's just too juicy a bug to be allowed to live." [Owen: This has been fixed in the latest version, available above.]