Basically I'm doing it by re-implementing Xlib in terms of Quartz, so that the source code of the individual savers doesn't have to change. I also wrote code that reads the existing XML files and constructs Cocoa controls, so all the savers will have the same configuration UI as before, too. That was actually the hardest part so far.
I have some OSX hacking questions: <lj-cut text=" --More--(14%) ">
I've got an XCode project that will eventually include hundreds of .saver targets. Each of these is built from about a dozen source files, only one file of which differs in each target. XCode builds a dozen .o files for each target, instead of sharing the majority that they have in common (which is slow, and takes up a lot of space). What's the right way to share these? Build a .a? A bundle? And how do I do that in XCode? (I don't want to build a library that gets installed globally on the machine; I want each .saver bundle to be self-contained. I'd just like to optimize the build process.)
- Answer: Add a ".a" target. Pretty straightforward.
Is there some sensible way to make XCode's "Build and Run" and "Build and Debug" commands work with .saver targets? If I double-click on the .saver product, it will launch System Preferences, as me if I want to re-install, and let me run it there, but that's a lot of clicking, so I've just been debugging them using a tiny test harness .app version. It's kinda kludgey though, because I have to modify what gets linked into that app when I want to switch from one saver to another.
- Answer: no, not really. You can make "Build and Run" launch System Preferences, but then you still have to manually select the Screen Savers page.
How do you debug use of freed memory errors on OSX? MallocDebug and the various MallocCheckHeap* environment variables aren't being any help. Things like:
- Target application (pid 18224) attempted to read address 0x55555575, which can't be read.
MallocDebug can't do anything about this, so the app's just going to have to be terminated.
- objc: FREED(id): message retain sent to freed object=0x3604a0
objc: FREED(id): message retain sent to freed object=0x38fd90
- Answer: No good answer.
Sadly, most of the older screen savers are written in a very non-reentrant way: heavy use of global variables, and some use of static locals to hold state machines. This means that when the OSX screen saver framework creates more than one ScreenSaverView at once (e.g., the preview view and the full-screen view; or one view on each screen of a multi-head system) the two copies fight with each other and it all blows up.
So, the right way to fix this is to change the code so that they store all their state in a structure that gets passed around, like the more modern savers do. But that's a lot of really tedious, boring work and I'd rather avoid it.
Maybe I could have the ScreenSaverView fork() so that each instance actually runs the graphics code in a private address space? But I don't know whether Quartz is re-entrant in that way, and that sounds like a big hassle anyway. Any other ideas?
- Answer: No good answer; I'll just fix them all to not use globals.
Likewise, the old savers aren't good about cleaning up after themselves: I'm sure most of them leak, because they assumed that when they stopped running, the process would exit and it didn't matter. I'm not really clear on how the OSX screen saver framework works, but it looks like it's just loading the .saver bundles right into its own address space and not sandboxing them in any way. What is the lifetime of the process that loads that bundle? If the answer is "until you log out", well,that could be a problem...
Is there some way to say that "all calls to malloc() between points A and B go into region X" and then at the end, just free region X in bulk? I know about NSAutoreleasePool, but I don't know if there's any mechanism like that that works with C code and plain-old-malloc.
- Answer: malloc_create_zone(), malloc_zone_malloc() and malloc_destroy_zone() look promising here.
As far as I can tell, CGContextClipToRect() has no effect on anything anywhere anytime. WTF? Without this, the savers are able to write outside of their View and mess up the enclosing window (e.g., the enclosing System Preferences dialog.)
- Answer: I was drawing while the wrong window was focused. Don't draw from within [ScreenSaverView startAnimation].