October 21, 2006
"W" has taken it in the teeth for his war on Iraq, but, to be fair, let's look at Saddam's Hussein's rap sheet:
- He surrounded himself with lackeys so stupid and/or cowed that he never heard any criticism of even his cruelest or most idiotic decisions,
- He literally believed that God was "on his side" and that rationalized any actions he took, no matter how heinous, against those who worshipped a different god,
- His party controlled the media, so they fed his people a steady diet of misinformation about how great he was and how evil his enemies were,
- He rigged elections to ensure he stayed in power while still seeming democratic; winning with results that turned out to be statistically almost impossible. His cronies in government (including members of his family) ignored, overrode, and silenced any complaints about the voting process,
- He had no concept of "civil rights": he spied on his own people when it suited him and stifled free speech when it bothered him, and kept his country in a perpetual state of high alert that was very close to martial law,
- He unilaterally branded those he even suspected of treachery "enemies of the state" and had them arrested without any due process or charges,
- He had these "enemies" thrown into gulags and tortured until they confessed, or just tortured to scare his other "enemies", real or imagined,
- He amassed a huge arsenal of weapons of mass destruction that, if unleashed, could decimate any country he decided was his enemy, which was an ever-growing list,
- He squandered his country's riches on wars and on pampering his rich friends, at the cost of his people's education, health, and well-being,
- He drove his people to war to expand his empire, killing thousands of his own soldiers in the name of "safety" for his people,
- On his order, over three thousand Americans have died in Iraq.
So I say, thank God for men like George W. Bush, who are willing to say to tyrants like this: No, sir, this will not stand. I will do anything in my power to depose you and bring freedom to your people and safety to the world.
"The United States is committed to worldwide elimination of torture, and we are leading this fight by example. Freedom from torture is an inalienable human right. Yet torture continues to be practiced around the world by rogue regimes, whose cruel methods match their determination to crush the human spirit." -- George Bush, 2003
Thank God. If only there were more men like that.
--
I know some of you are tired of me making political posts. I am, too. Really. But I don't think I have the luxury of ignoring politics any more. We are on the verge of losing our country, and I refuse to stay silent because I might alienate some people by yelling about it. It's time to yell about it. On October 18th, two days after my birthday, Bush signed a law throwing habeas corpus to the winds, and explicitly ALLOWING Americans to torture their unlawful prisoners: "enemy combatants" which *he* is allowed to define.
Maybe his Secret Service will read this and decide *I* am an enemy combatant that needs to be silenced. They could; there is nothing to stop them. Their jackboots could kick in my door some night, haul me off without charging me with any particular crime, torture me until I tell them whatever they want to hear, and then trumpet to the world how successful they are at eliminating terrorists like me.
In eighteen days we have a chance to elect a majority in both the house and senate who will oppose Bush for the next two years, and stop this insanity. I am urging you to do what you can. Even just showing up and watching for voter fraud would help. Take a laptop to your polling place and blog what you see. We've had the last two national elections stolen from us, and if we let it happen again _we_ are to blame.
This is the *real* national emergency. Compare the number of Americans who have died
for no reason in Iraq (2,279), by Bush's order, to the number who died in the 9/11 attacks (2,973). What are we doing? How is this worth it? Why are our priorities so far out of whack?
Why are we giving up our civil liberties, spending trillions on wars, and sacrificing our kids' lives at Bush's behest? Every year more Americans die of cancer (190x), AIDS (4x), heart disease (309x), and car accidents (20x) than died in 9/11. What are we so afraid of? Why are we willing to spend trillions to depose a puffed-up dictator on the other side of the world but not a couple million to educate our kids in music and art?
We have let the fear of violence against us turn us into animals. We're so frightened by those images of jets crashing into skyscrapers that we've forgotten that being the victim of a terrorist attack is, in fact, among the least likely of the bad things that can happen to us. We have to stop.
October 15, 2006
A fellow named "Larry Bodine", who is apparently on the advisory board of "Law Technology News" (e.g., he's a technology advisor) and is also a law firm marketing consultant, has written a piece where describes, using no small amount of factual errors, fallacies, and other bad writing techniques,
why he doesn't like the Mac.
I wouldn't normally see an article on a law technology site like this, but it's getting linked from some of the Mac meta sites I frequent, and this is how idiotic memes get started.
So, since the date on the article is tomorrow (he wrote it FROM THE FUTURE!) and that's my birthday, I considered this an early birthday present and decided a good old-fashioned flaming was in order. Ah, it's been too long. Shall we?
Larry writes:
I was suckered in by the hype about freedom from viruses, simplicity of computing and versatility. Instead, I bought a boat anchor that can't view Web sites properly, is not compatible with Microsoft Word and can run only dumbed-down versions of regular software.
"Suckered" and "hype" aren't supported here: you don't address the issues of "freedom from viruses," "simplicity," and "versatility." You never state whether you found the "hype" to be true? It doesn't matter if these three claims are true or false (they're true) -- it's mislead to say you were "suckered in by the hype" and then let the reader assume the hype was wrong, when, you don't feel that way. It's like me saying, "I was suckered in by the hype about 'food' being 'necessary for survival,' but instead I got fat when I ate too much of it."
And you say "instead" of getting something virus-free you got something dumb, but what you mean is "in addition" (and even
that is a false statement). There's a big difference between "instead" and "in addition." You should know this, Mr. Law Professional: "Instead of giving my client a horse for $2,000, the defendant gave my client the finger" vs. "In addition to giving my client a horse for $2,000, the defendant gave my client the finger." Big difference.
And you
really can't run Word? Because I'm running it right now. La la la, look at me typing in it. In fact, I'm pretty sure
you yourself can and did run Word, because you talk about your problems with Word in a couple paragraphs.
Viewing web sites "properly" would need to be defined; Safari and its free brethren are certainly more compliant with the majority of web standards than Internet Explorer; I haven't had an incompatibility problem in years. You would need to provide at least a partial list of websites you found troublesome in order to prove this claim. You provide none, only one false claim about it.
"Dumbed-down versions" is subjective and requires you to prove it in future paragraphs, which you fail to do.
This time, I'm buying from Hewlett-Packard Co. or Dell Inc. -- anything that runs on Windows. (I'll assume the risk of flaming batteries.) Goodbye Steve Jobs, hello Bill Gates. I'll be lucky to get half of the $4,552.71 I paid for the Mac on May 21, 2006.
Yah, I'm sure it's a huge surprise to you that computer hardware depreciates, since you're on the board of a technology advising firm. I'm also betting the whole "Apple switching all their machines to Intel machines" caught you totally off-guard, since Steve announced in August of 2005 that he would have all the machines switched by August of 2006. There was NO WAY you could have called that, since it's, like, your job to advise people on technology.
Also, Apple had to recall many of their batteries as well, as Sony provided batteries to both Dell and Apple.
Engadget.com. You should look into it.
I realized it was time to unload the silvery box of frustration when I had to buy a "Dummies" book on how to operate it. I'm smart; I shouldn't need this. Aren't Macs supposed to be intuitive and easy to learn? My mistake.
What, exactly, were the problems you had? You don't say. I'm not clear on why buying a book was so maddening to you. It added, what, like $10 to your $4.5K purchase? You're upset because you didn't sit down with an incredibly complicated piece of consumer electronics and understand all of it instantly? I mean, yes, the Mac is easi
er to use, but there's still a lot of functionality there, and it's very different from Windows. Sit down with a friend or a third-grader you know for an afternoon and figure this stuff out, technology advisor.
With a former PC, I had to have my hard drive wiped clean and formatted -- several times -- after catching nasty viruses. So I was enticed by the thought of being online without fear of viruses. I dreamed of going fearlessly to all the sheet music and game sites that are rife with Trojans, spyware and other dangerous bugs.
...and you again imply this thought was false, but never say state that clearly, because
you know it's true.I was encouraged to make the switch by artists, ad agency employees and junior high school kids, even though I don't really create graphics, listen to iTunes or make movies. They all used Macs and were intractable in their support. They seduced me with siren songs, especially good customer support -- which did turn out to be excellent and was staffed with American speakers working in the United States. I liked the sexy FireWire with its zippy transfer speeds, although I used it only to transfer data to my external hard drive.
"Friends, colleagues, schoolchildren, kittens, mice, even a leftover piece of steak in my fridge: they all encouraged me to switch..." You're
finally asserting an actual fact, and it's that Apple's support is great and you're hot for FireWire?
The signs of doom were there on day one, but I ignored them. I pretended that I liked the one button mouse. I quickly started using click + command keys (and other keyboard shortcuts). I really missed the little scrolling wheel in the center of the mouse. I put up with the fact that the HP printer, which I had purchased on the recommendation of an Apple Store, would work about 50 percent of the time with the Mac. I was constantly deleting print jobs and starting them over.
Dude, if you bought a PowerMac G5 in June of 2006 it should have come with the four-button Mighty Mouse (with a two-axis scroll-ball in the middle), which has been shipping PowerMacs since
October, 2005. If not, you know, spend the $12 to buy ANY USB MOUSE ON THE MARKET instead of replacing your whole computer. And I can't help but think you should have simply had the printer serviced or replaced by Apple's excellent support staff instead of starting over with a Dell. I think it would have been easier.
I noticed it was slow; I saw that stupid spinning colored wheel a lot. The Mac would hang up; the TV ads said Macs didn't do that. The widgets were cool and snappy, but after a while I stopped using them. They were fun -- for five minutes. I did like the Finder because it was quick in locating files, but it would turn up a lot of false hits. It was comparable to the Google Desktop searcher on my PC.
Ok, you found the Mac slow because sometimes you saw the spinning cursor. I will give you this point. It's the first actual point you've made that you could possibly consider "evidence," halfway through your article.
What drove me nuts was that I would open Word for Mac and couldn't delete files while I was in Word. There is no File | Delete option. So the documents took up space on my hard drive, until someone told me I had to find the document in Finder and then move it into the trash from there. This seemed stupid to me; I just wanted to highlight a file and tap "delete."
You believe the Mac version of Word should have a delete option. Ok, this isn't the "Mac way" but I'll give you this point, too. Two so far.
Word files transferred from the Mac were missing pictures. PowerPoint files transferred from the Mac would lose their formatting. PCs and Macs are not compatible, regardless of what they say.
Some types of pictures don't translate over from Mac to PC versions of Word. Sounds like a problem with your PC version of Word, really, but I'll semi-give you the point, because there is a big button on the Word save sheet labeled "Compatibility Report..." which was put there just to tell you when you've used incompatible features in the Mac version of Word. I can see how that'd be confusing and stuff. Maybe "Compatibility Report..." isn't part of the dummies' curriculum.
The multiple clicking to accomplish simple tasks was a constant annoyance. Things I could do with a PC in two keystrokes took four or five clicks with the Mac. To do a "fast print" required clicking File, Print, find Copies & Pages, click Paper Type/Quality, click Normal and finally clicking Fast Draft. And there was no way to leave the setting as the default. I had to do it manually every time.
Ok, you've listed THREE complaints in a row now with Mac Word, not the Mac. Now, I appreciate that, as a lawyer, you feel it's your duty to spend all your time in Word, but, seriously, have you thought about flaming Microsoft for this? They wrote it, Apple didn't. These are hardly problems endemic to the Mac.
Doing a simple screen capture was an immense chore. On a PC you just press Alt and tap PrtScr. With the Mac I had to download and launch special programs to accomplish this simple task.
Yah, it's an immense chore to hit command-shift-4. Ow, my pinky!
I didn't even bother with the Mac's iCal or Mail, which required me to buy an @mac.com address.
No, no they don't. Both
can integrate with .Mac but don't require it; all their functionality is available without .Mac, you just have to arrange the server stuff yourself,
just as you do with Windows.
Instead, I went straight to Outlook for Mac.
You went straight to the MICROSOFT alternative, without even trying Apple's really nice Mail client, and decided you didn't like it. Darn, it's like Microsoft isn't really trying to make a good mail client for their competition, isn't it?
You
did not even launch two of the best programs that ship free with the Mac, and then you have the
temerity to write an article about how much the Mac sucks? Seriously?
A lot of the software for Mac -- such as AOL for Mac OS X -- was dumbed down and missing may features of the current PC versions.
AOL was dumbed-down? How would that work, exactly? I mean, this is like saying you got a dumbed-down version of a George Bush speech. It's strange that you would pay for AOL on the Mac but not .Mac. It seems like, at this point, you're just intentionally avoiding anything with the Apple label, but then blaming Apple for the quality of all these products.
For me the killer was the Web browser. Safari simply cannot read Flash. It is, quite simply, a second-rate browser.
It simply can and does read Flash, and you are, quite simply, a big stupid.
I even called Apple headquarters and asked when a better version would be available and was told that Apple is in no hurry to improve it.
Yes, I am positive that Apple said, "What? Safari? Yaaaawn... you're using that? Heh... we're not really working on that much these days."
Oh, no, wait, Safari has had like eight releases since Internet Explorer was last revved.
On the suggestions of friends, I downloaded Netscape and Firefox, which were no better.
"No better?" That's it, huh? Well, I'm convinced! Although I should mention my friends on the Firefox team have told me they are working to upgrade their next version to "kinda better," with the eventual goal of being "somewhat better."
I scraped along with Internet Explorer 5.0 for Mac, and then discovered in 2006 that Microsoft would no longer support the Mac version. You can't do WSYWIG on Typepad (where many folks create their blogs), which you can on a PC.
Yes, you must have been pretty shocked about this, since it was announced in
2003, and IE has three percent of the market on the Mac. Damn! Those companies keep pulling fast ones on you by announcing stuff years ahead of time and expecting you to, you know, make intelligent decisions.
I do sympathize, though, because I learned in 2003 that Apple was no longer going to manufacture the Apple //e! Well, they'd stopped years ago, but I just learned it then, because I'm a technology advisor who lives in a cave, surrounded by books written for idiots. (Oh, sure, I'd tried the Mac, but it was "no better.")
I run several Web sites, all optimized for IE 5.5 or higher. I couldn't operate my own Web sites with the Mac. That was the straw that broke the camel's back.
Yes, if you insist on running a ten-year-old browser on your Mac instead of any of the five or so alternatives, some web sites may not work. In other news, if you spread rotten shit on a hot dog, it doesn't taste as good.
The phrase "couldn't" here is completely a lie; you "could" operate your websites, you just chose to use a ten-year-old browser instead of Safari, Camino, Firefox, Bumpercar, OmniWeb, or any of the zillion other ones available.
Also, the phrase "the straw that broke the camel's back" means you are finished with something (e.g., his back is broken, you can't ride on), but you said earlier that "the killer" was Safari's (in?)ability to read Flash, so your camel is already dead. Also, apparently your camel is still hobbling, because you go on...
Then the hard drive croaked on me after only three months of owning the machine. I couldn't tell what was going wrong and had to hire someone for $125 an hour to come over and tell me what the heck was happening. Apple replaced it for free, but I became leery of what other hardware would fail unexpectedly.
Hard drive failure! That would NEVER happen if you had a Dell!
Apple uses the same hard drives as basically everyone else on the market. Sometimes they fail. They are moving parts. In this case, Apple replaced it for free. I don't see the problem, except you are apparently a huge whiner.
The supportable points of your argument, in total, boil down to this:
- Apple's marketing department and fans lured me in with promises of Macs being virus-free and having great service, both of which turned out to be true.
- FireWire is sexy.
- Sometimes I see the spinny cursor on my Mac.
- Microsoft Word doesn't please me on the Mac because the key shortcuts aren't short enough, and because I can't find the compatibility button on the save panel. Also, PowerPoint.
- Microsoft Outlook doesn't please me on the Mac, and I never tried the free alternatives that were bundled with the system.
- AOL(!) doesn't please me on the Mac.
- Microsoft Internet Explorer 5 from 1997 does not please me on the Mac, and I mistakenly believe Safari doesn't support Flash, nor will I use any of the many free alternatives that would work fine.
- One time, my hard drive broke and was replaced for free.
In summary: you are fundamentally only interested in using Microsoft products, and you find that your experience on a Mac using nothing written by Apple and everything by Microsoft is sub-par. Wow! I am SHOCKED! This is as amazing as when I heard that people who love open-source software sometimes prefer Linux to Mac OS X!
I let the repaired shiny Mac sit on the floor for weeks, and instead used my reliable IBM ThinkPad, and rediscovered how much I enjoy it. Wish me luck on selling the Mac.
Good luck! And good riddance! I'd buy it myself but I'm afraid you might have smeared some stupid on it!
Labels: mac community
October 5, 2006
Carbon, briefly defined, is a compatibility library that ships with Mac OS X that enables older applications, written for Mac OS 9 and before, to run under Mac OS X with minimal changes (and a recompile). Carbon is descended from the original Mac Toolbox written in the early 1980s, and still shows signs of its Pascal and machine-language origins, even though it is now primarily accessed through and written in the C language.
Cocoa, briefly defined, is a new application environment that Apple got (and improved upon) when it bought NeXT in 1997. Cocoa uses a dynamic, object-oriented language (Objective-C) by design, and was the inspiration for modern languages like Java.
For a few years after the initial release of Mac OS X (10.0), there was speculation about whether Carbon or Cocoa would end up dominant. Many Mac programming groups inside and outside of Apple didn't know Cocoa and assumed it would be another flavor-of-the-month that would be quickly abandoned as unworkable, like Pink and Taligent and Copland.
Apple's official stance was initially that developers were encouraged to use either Carbon or Cocoa to write new applications; in the last year or so that message has quietly changed to just encouraging Cocoa, both because Cocoa is being enhanced faster than Carbon and because increasing numbers of Apple engineers are trying Cocoa and finding it an easier and faster to code environment within which to code.
But there are still people out there who use the Carbon APIs to program on the Mac, and there are still those who assume
Carbon is "faster" and Cocoa is just "easier". While this has no basis in any timing tests I've seen, it's a
myth that persists.
It should be noted that over the past eight years Apple has merged the Carbon and Cocoa runtime environments to the point where one can call Carbon routines from inside any Cocoa program, and vice-versa, so the idea that one environment is inherently "faster" is kind of crazy, although one can obviously fail to optimize one's code, and there is a partly-relevant question which goes: if you write the same number of lines of code in each language to do the same job, which would run faster?
The answer, in my experience, is Cocoa wins hands-down, because there's no such thing as a Carbon program that's the same number of lines as a Cocoa program that does the same thing. Carbon is wordy, in part because it is based on C, and in part because it still uses metaphors and programming conventions that were in vogue twenty to thirty years ago.
--
What's distressing to Cocoa programmers is that there are still critically important Apple APIs that are only available through the incredibly byzantine and ill-documented Carbon libraries, and some groups at Apple are
still generating Carbon code, under the guise of "Core".
Now, let me be clear. I have nothing against "real" Core libraries, like CoreFoundation or CoreGraphics or CoreAnimation. These are all modern APIs that happen to be written in C, and they tend to have good to great support above them in Objective-C, either via separate APIs (like NSBezierPath in AppKit, which automatically emits the correct CoreGraphics calls) or via toll-free bridging (like NSStreams and CFStreams).
But some "Core" libraries aren't "Core" at all -- they are still using the conventions and structures of Carbon, and these libraries are nigh-impossible to use. QuickTime, Keychain / Security Services, Core Audio, Launch Services, Speech and Voice recognition -- these are the ones I've recently run up against. The problems with even well-written Carbon libraries are myriad:
- There is usually one, giant, all-encompassing "setAttributes" function and one "getAttributes" function for setting and getting every value associated with a Carbon "object", which requires to you laboriously build up and then laboriously unparse huge, special parameter structures for even the simplest call. For example, see one of the newer (v6.4) QuickTime calls, for setting parameters on a FireWire video camera: VDIIDCSetFeatures(VideoDigitizerComponent ci, QTAtomContainer *container). That one call is used to set a HUGE number of parameters, which is "simple," except, oh, it takes eighty lines of code to prepare for that one call, and the parameters you can set are not actually documented in the documentation. (This is not an exaggeration, I'll show Apple's own example code later.)
- Carbon calls with monolithic parameter blocks and anonymous parameters (like the one above) are not inherently self-documenting, and the documentation for them is often wrong and/or incomplete because the documenters' job is so much harder. For example, with the given documentation there's really no way you could intuit how to build up a legal parameter block to actually call VDIIDCSetFeatures() unless you also read the technote Apple wrote on it in 2005, which is very helpful, but was written a year too late for Delicious Library and several years after QuickTime 6.4 was released.
- Parameters passed into functions are passed by reference, whether they are going to be modified or not, which is just obscure. For example, see SecKeychainItemCopyAttributesAndData(), which takes the input "info" by reference, even though it's read-only!
- Carbon uses the same type, OSTypes (eg, 'sit!'), extensively for everything: return codes, building parameter blocks (specifying what you want to do, how you want to do it), loading QuickTime components, etc., which means reading a function description doesn't actually give you enough information to know what to actually pass it. (Eg: "Pass it some combination of four-character constants. Good luck!")
- Carbon memory management is a mish-mash of new and old metaphors, so you might easily find yourself using, in addition to CoreFoundation objects that can be retained and released, "Handles" or "Atoms", which are obtuse and easy to screw up.
- Carbon often uses FSRefs (although there are now often string-path equivalents) to refer to files, which are, again, not fun or easy to build or use.
Let me note that I'm not trying to slam Apple's Carbon programmers here; if I were a programmer on some Carbon toolkit I wouldn't necessarily try to break every metaphor everyone else was using, either -- I'd add my code and clean things up a bit where I could. (Or, actually, I would, but I would suggest we just rewrite everything in Cocoa, which is what I'm getting to in this post.)
Carbon
has matured a lot in the last several years (eg, more Cocoa-like). But it's not enough. What I want to do, with this post, is encourage Apple to finally move those necessary but neglected frameworks all the way to Cocoa.
--
Ok, so I've been a bit abstract. Let's look at two function calls from two different Carbon libraries that are under active development and that you MUST use if you want their functionality -- there is not equivalent in any other framework. We'll take them apart, see why they are so hard to use, and then look at an ideal Cocoa API that would accomplish the same thing but be a zillion times easier to call and understand.
First off, let's look at how you might set the hue, saturation, sharpness, brightness, gain, iris, shutter, white balance, gamma, temperature, zoom, focus, pan, tilt, optical filter, focus point, and more on IIDC cameras.
Ok, wait, wait a minute. What did I just say? "IIDC cameras"? What the hell is an IIDC camera? And why do I care? Doesn't QuickTime isolate me from the low-level nonsense and just provide me with an opaque Sequence Grabber object which handles all the hardware bullshit and provides me with a single unified interface?
Well, the answer is, "Of cou--no. No, not really. Psych."
If you have a certain class of FireWire cameras which have a common way of setting their parameters,
Apple has provided low-level calls to set those parameters that only work with those cameras, and deprecated the other function calls. Note that, if you have an older (or newer) camera, or a USB camera, like
the internal USB iSight that ships with EVERY iMac and notebook Apple sells, these IIDC functions don't work for you, and you have to use the older functions (where available), even though Apple says they are deprecated. Wheee!
Ok, so assume you have one of these cameras, and you want to set, say, the gain on it. How would you do it? Well, normally, you'd spend months guessing at how to build up the parameter block to please VDIIDCSetFeatures(), but as of 2005, as I said,
Apple has a technote, which shows how the gain can be set, in only eighty lines of code.
Seriously. Eighty. Let's look:
|
ComponentResult ConfigureGain(SGChannel inChannel) { QTAtomContainer atomContainer; QTAtom featureAtom; VDIIDCFeatureSettings settings; VideoDigitizerComponent vd; ComponentDescription desc; ComponentResult result = paramErr;
if (NULL == inChannel) goto bail;
// get the digitizer and make sure it's legit vd = SGGetVideoDigitizerComponent(inChannel); if (NULL == vd) goto bail;
GetComponentInfo((Component)vd, &desc, NULL, NULL, NULL); if (vdSubtypeIIDC != desc.componentSubType) goto bail;
// *** now do the real work ***
// return the gain feature in an atom container result = VDIIDCGetFeaturesForSpecifier(vd, vdIIDCFeatureGain, &atomContainer); if (noErr == result) {
// find the feature atom featureAtom = QTFindChildByIndex(atomContainer, kParentAtomIsContainer, vdIIDCAtomTypeFeature, 1, NULL); if (0 == featureAtom) { result = cannotFindAtomErr; goto bail; }
// find the gain settings from the feature atom and copy the data // into our settings result = QTCopyAtomDataToPtr(atomContainer, QTFindChildByID(atomContainer, featureAtom, vdIIDCAtomTypeFeatureSettings, vdIIDCAtomIDFeatureSettings, NULL), true, sizeof(settings), &settings, NULL); if (noErr == result) { /* When indicating capabilities, the flag being set indicates that the feature can be put into the given state. When indicating/setting state, the flag represents the current/desired state. Note that certain combinations of flags are valid for capabilities (i.e. vdIIDCFeatureFlagOn | vdIIDCFeatureFlagOff) but are mutually exclusive for state. */ // is the setting supported? if (settings.capabilities.flags & (vdIIDCFeatureFlagOn | vdIIDCFeatureFlagManual | vdIIDCFeatureFlagRawControl)) { // set state flags settings.state.flags = (vdIIDCFeatureFlagOn | vdIIDCFeatureFlagManual | vdIIDCFeatureFlagRawControl);
// set value - will either be 500 or the max value supported by // the camera represented in a float between 0 and 1.0 settings.state.value = (1.0 / settings.capabilities.rawMaximum) * ((settings.capabilities.rawMaximum > 500) ? 500 : settings.capabilities.rawMaximum);
// store the result back in the container result = QTSetAtomData(atomContainer, QTFindChildByID(atomContainer, featureAtom, vdIIDCAtomTypeFeatureSettings, vdIIDCAtomIDFeatureSettings, NULL), sizeof(settings), &settings); if (noErr == result) { // set it on the device result = VDIIDCSetFeatures(vd, atomContainer); } } else { // can't do it! result = featureUnsupported; } } }
bail: return result; } |
Wow, easy!
Remember, this is only for a certain class of cameras (including external iSights, but excluding internal iSights) -- if you want your code to work in all cases, you also have to call
SGSetSettings() (or some such, I honestly don't know, as the focus calls I was trying to do don't have equivalents) to set up other kinds of cameras, except this call isn't as flexible, and, of course, nowhere in the documentation for the call does it say
which settings you can actually set. Because, since everything in Carbon just takes and returns OSTypes (see sin #4), it's not self-documenting what kinds of parameters you can pass in to any method -- there are tens of THOUSANDS of OSTypes defined in the system, good luck finding the list of the ones your particular function takes and returns! They certainly are never listed in the documentation of the functions that take them.
Now, if Apple had used an enumerated type instead of an OSType, as such:
|
typedef enum { kSequenceGrabberFocus, kSequenceGrabberZoom, kSequenceGrabberGamma, [...] } SequenceGrabberParameters; |
Well, then we could just command-double-click on the function prototype in XCode, and we'd have our own documentation (after a fashion), in the form of the header file where the parameters are defined.
(On a side note, sadly, the AppKit and Foundation team just announced they are moving AWAY from using enumerated types in Cocoa methods and switching everything to take plain ints, because they HATE self-documenting code and hate Cocoa programmers. No, it's because they are worried that enumerated types could have unspecified sizes. But, seriously, Ali, this is totally broken -- enumerated types make reading and writing code MUCH easier, and they enforce sanity in switch() statements and if() comparisons. Please don't break this! [Update: actually, it's not quite as bad as it could be: the proposed new types aren't totally anonymous, they just are typedef'ed ints, but at least they are defined right below real enums, so command-double-click still works. Debugging and switch() and if () still aren't handled as nicely, though.]
Now, the above enumerated type would be helpful IF QuickTime had to stay with the giant, all-encompassing set-get functions. But must they? Well, no. For some reason some Carbon programmers think it's easier for them to add and remove APIs if they never commit to any particular functionality, so they use those giant anonymous set-get functions and then just have you build up a list of parameters yourself, with the idea being that it's a lot more flexible if they change the parameters out from under you than if they change the functions.
Acrazypersonsezwhat? Ok, I don't know what life was like under Mac OS 9, but nowadays we've got a well-defined system for deprecating APIs, and it works. And the advantages to not building up parameter blocks are huge. To wit, imagine the above gain-setting code, rewritten in a (hypothetical) Cocoa interface to QuickTime:
|
ComponentResult ConfigureGain(QTSequenceGrabblerChannel *sequenceGrabblerChannel) { return [sequenceGrabblerChannel setGain:1.0]; } |
Sure, four lines is less than eighty, but the observant amongst you will say, "Wait a minute, in the original code he checks to see if this is an IIDC camera first!" Well, yes, but, honestly, I should be able to call my mythical -setGain: on
any physical sequence grabber, and if the user's particular hardware doesn't implement gain, QT should just ignore me.
More observant folks may exclaim, "But, he sets the camera's gain feature to be: on, manual, and raw control!" And I must calmly say, if I call -setGain: on a sequence grabber, it's
implicit that I am putting it under manual control! Otherwise, what does setting the gain mean? I should
not have to write this code.
Even more observant folks may add, "Well, but, he also has some crazy code where he either sets the value to the camera's raw maximum or 500..." to which I respond: I should be abstracted away from such nonsense. Parameter values should be normalized from 0.0 to 1.0 for me; where 1.0 is the hardware maximum, whatever it is. I should never have to query the hardware.
Now, it could be I'm oversimplifying a bit. I can imagine there are complexities in this code that require, say, TWO lines of Cocoa inside my imaginary rewrite of ConfigureGain(), not just one. But you can see where I'm going with this.
The fact that Apple had to write EIGHTY lines of example code to show how to set A SINGLE PARAMETER on SOME TYPES of cameras to its maximum value shows that THESE APIS ARE SERIOUSLY BROKEN. If you ever find yourself saying, "Look, to use these APIs I've written, you just have to write four tons of this glue code which never changes..." just stop! Write the damn glue code yourself, and provide a higher-level API!
--
You might hope that QuickTime for Leopard, which has been publicly announced to be at least partly rewritten with Cocoa APIs, will solve these problems.
I certainly hope so. But, historically, some Carbon teams, when faced with writing Cocoa code, take a simple and not-very-helpful route -- they provide only one or two methods in their Cocoa classes, when their Carbon APIs are incredibly rich and feature-ful. The first iteration of QuickTime in Cocoa was like this -- look at
NSMovie, which has TWO whole methods, "-URL" and "-QTMovie".
Wow! I'm overwhelmed! I mean, it's pretty clear how I would add a track, or record from a camera to a movie, or save to disk, or... Oh, wait, no, I can't do any of those things from Cocoa. I've essentially been given a busy-box. "Here, you're a Cocoa programmer, you can't handle actual functionality... just put your little movie in your little view in your little NIB and be happy."
But, wait, my scorn is too quick. Look what we got in 10.4, to replace NSMovie:
QTMovie, which seems pretty darn rich from where I'm standing. At least, it has more than two methods, so I think that's a big improvement.
Or let's take
NSSpeechRecognizer, written by a new friend of mine from WWDC, whom I'm sorry to single out this way.
Now, NSSpeechRecognizer has a nice interface. It's clear how to use a NSSpeechRecognizer, and it's easy as heck to integrate into your applications. I put speech recognition in Delicious Library 1 in about a day using this class, and most of the work was in building up a sane list of words to recognize and keeping it up-to-date without too much overhead.
But if you only speak Cocoa, you'll think that all Apple's speech recognition can do is what NSSpeechRecognizer does: recognize a flat list of words and phrases. You'd assume you can't recognize a phrase from one list followed immediately by a phrase from another list, much less set up a tree of expected phrases. And certainly there's
no way to turn someone's speech into a list of phonemes for further processing, or to record and analyze the actual tones the user uttered. Heck, there are only four attribute set/get methods in NSSpeechRecognizer, so that must be about it for Apple's code, huh?
Well, as you've guessed by now, no. I'm
told, by the original programmers, that all the above functionality is in the Carbon APIs. However, I have no idea, because I simply don't have the patience to learn or program raw Carbon unless I'm forced to, and in this case I wasn't.
There are a ton of neat applications I could make if NSSpeechRecognizer (and its friend, NSSpeechSynthesizer) were fully Cocoa. It's too bad.
--
Ok, let's pick on another Carbon function call, and then invent another Cocoa API that should exist but doesn't.
This time, it's from Keychain. Now, Keychain is massively cool; all your passwords, from all your programs, can be stored securely in a central (encrypted) location, so you don't have to remember them -- all you have to do is remember the password to your keychain. You get to set the policy on which programs can access which passwords and how. Keychain is full-featured and awesome...
...aaaaaaaaaaaaand it's Carbon. So, it brings all that baggage with it, which is really sad.
Let's look at some code I wrote recently to build up a list of FTP sites that are currently stored in the user's keychain, so I can automatically suggest a place to upload a user's Delicious Library in 2.0:
|
#define HANDLE_ERROR(returnCode)if (returnCode != noErr) NSLog(@"keychain error %d encountered on line %d of file %s", returnCode, __LINE__, __FILE__);
- (NSArray *)allFTPSiteDictionariesFromKeychain; { NSMutableArray *mutableSites = [NSMutableArray array]; [mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:kSecProtocolTypeFTPAccount]]; [mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:kSecProtocolTypeFTP]]; [mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:kSecProtocolTypeFTPS]]; [mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:kSecProtocolTypeFTPProxy]]; return mutableSites; }
- (NSArray *)_ftpSitesWithProtocol:(SecProtocolType)protocol; { NSMutableArray *mutableSites = [NSMutableArray array]; SecKeychainAttribute findInternetPasswordsAttributes[] = { {kSecProtocolItemAttr, sizeof(protocol), &protocol}, }; SecKeychainAttributeList findInternetPasswordsAttributeList = { sizeof(findInternetPasswordsAttributes) / sizeof(*findInternetPasswordsAttributes), findInternetPasswordsAttributes }; SecKeychainSearchRef searchRef = NULL; OSStatus returnCode = SecKeychainSearchCreateFromAttributes(NULL, kSecInternetPasswordItemClass, &findInternetPasswordsAttributeList, &searchRef); HANDLE_ERROR(returnCode); if (!searchRef) return nil; do { SecKeychainItemRef internetPasswordKeychainItemRef = NULL; returnCode = SecKeychainSearchCopyNext(searchRef, &internetPasswordKeychainItemRef); if (internetPasswordKeychainItemRef == NULL || returnCode == errKCItemNotFound) break; HANDLE_ERROR(returnCode); #define HACK_FOR_LABEL (7) SecItemAttr itemAttributes[] = {kSecDescriptionItemAttr, kSecTypeItemAttr, kSecCommentItemAttr, kSecProtocolItemAttr, kSecAccountItemAttr, kSecServerItemAttr, kSecPathItemAttr, kSecSecurityDomainItemAttr, kSecCreatorItemAttr, kSecTypeItemAttr, HACK_FOR_LABEL}; // RADAR 3425797 - You can't get the 'kSecLabelItemAttr' using this API (it crashes), so either we have to use the number '7' or use SecKeychainItemCopyContent(). I do the former. See http://lists.apple.com/archives/Apple-cdsa/2006/May/msg00037.html SecExternalFormat externalFormats[] = {kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown}; NSAssert(sizeof(itemAttributes) / sizeof(*itemAttributes) == sizeof(externalFormats) / sizeof(*externalFormats), @"arrays must have identical counts"); SecKeychainAttributeInfo info = {sizeof(itemAttributes) / sizeof(*itemAttributes), (void *)&itemAttributes, (void *)&externalFormats}; SecKeychainAttributeList *internetPasswordAttributeList = NULL; returnCode = SecKeychainItemCopyAttributesAndData(internetPasswordKeychainItemRef, &info, NULL, &internetPasswordAttributeList, NULL, NULL); HANDLE_ERROR(returnCode); if (internetPasswordAttributeList) { NSMutableDictionary *siteDictionary = [NSMutableDictionary dictionary]; unsigned int attributeIndex; for (attributeIndex = 0; attributeIndex < internetPasswordAttributeList->count; attributeIndex++) { OSType tag = internetPasswordAttributeList->attr[attributeIndex].tag; if (tag == HACK_FOR_LABEL) tag = kSecLabelItemAttr; NSString *tagString = [NSString stringForOSType:tag]; id value; if (tag == kSecPortItemAttr) value = [NSNumber numberWithUnsignedInt:*(unsigned int *) &(internetPasswordAttributeList->attr[attributeIndex].data)]; else if (tag == kSecCreatorItemAttr || tag == kSecProtocolItemAttr) value = [NSString stringForOSType: (OSType)internetPasswordAttributeList->attr[attributeIndex].data]; else value = [[[NSString alloc] initWithBytes: internetPasswordAttributeList->attr[attributeIndex].data length:internetPasswordAttributeList->attr[attributeIndex].length encoding:NSUTF8StringEncoding] autorelease]; [siteDictionary setObject:value forKey:tagString]; } if (IsEmpty([siteDictionary objectForKey:[NSString stringForOSType:kSecLabelItemAttr]])) [siteDictionary setObject:[siteDictionary objectForKey:[NSString stringForOSType:kSecServerItemAttr]] forKey:[NSString stringForOSType:kSecLabelItemAttr]]; [siteDictionary setObject:(id)internetPasswordKeychainItemRef forKey:@"keychainItem"]; [mutableSites addObject:siteDictionary]; SecKeychainItemFreeAttributesAndData(internetPasswordAttributeList, NULL); } CFRelease(internetPasswordKeychainItemRef); } while (1); CFRelease(searchRef); return mutableSites; } |
Now, as I said, Keychain doesn't have the worst interface in the world. It's really pretty good for Carbon -- you build the equivalent of an NSEnumerator with SecKeychainSearchCreateFromAttributes() (albeit without the nice generality of the former class), enumerate through it with SecKeychainSearchCopyNext(), and then get the data you want from the item in SecKeychainItemCopyAttributesAndData(). But at this point in this marathon post (aren't you sorry you picked on me for not posting in so long) you're an old Carbon hand, so you can spot the sins Carbon brings with it.
But, just in case, let's go over them:
- We have to build up a bunch of structures by hand so we can build up a "search specifier" in a single parameter in SecKeychainSearchCreateFromAttributes(), which hurts our readability a ton -- we really want for each statement we write to read like a sentence, almost, and if we have to write four lines of "struct this" and "allocate that" before we get to the verb, we've got pretty awkward sentences.
- We loop over the items we find using "SecKeychainSearchCopyNext()" (nice), but we have to manage memory ourselves in Carbon; there's no autorelease (or garbage collection, heaven forfend!) so we have to free every returned value explicitly.
- The returned object is an array of structures of structures of void pointers to things with counters and stuff and things -- ok, I've totally forgotten, honestly. But, my point is, it's not just an NSDictionary full of nice objects (or a CFDictionaryRef), so I have to parse out the values by hand, INCLUDING byte-swapping on Intel machines. Whee! I love it!
- SecKeychainItemCopyAttributesAndData() has a crasher if you ask to retrieve the most common attribute you'd ever ask for from a Keychain item: the label. I should point out that this function is documented to deprecate the older method of getting data from an item, which was more obtuse but didn't crash.
Now, ok, I write crashers too. I don't blame the Apple engineers for that, and it was really nice of them to step up and provide a workaround. Because of their help, this only delayed me by a couple hours instead of a couple days. My point here is, if this whole framework were a lot more easier to use (-cough-Cocoa-cough-) then it'd get used a whole lot more, and this bug would have been found LONG ago. The problem here isn't that a bug was written, it's that an incredibly valuable framework is needlessly crippled by being in Carbon and thus not used enough to be as robust as it should be.
- SecKeychainItemCopyAttributesAndData() isn't actually documented correctly, so the only way to write code for it is to get on the web and look at the source code and read Apple's mailing lists. In particular, the docs say for "attrList": "On input, the list of attributes in this item to get..." which is, frankly, just false. A look at the source code (search for "ItemImpl::getAttributesAndData(") clearly shows "attrList" is ignored on input, which only makes sense, because it's an output structure and you've already passed in the list of attributes to get in "info", so why would you pass it in twice? Note also the docs say that "itemClass" is "A pointer to the item’s class" but don't say if you are supposed to pass it in, or if it gets returned to you? (It turns out to be the latter). And "outData" is described opaquely as "A pointer to a buffer containing the data in this item." Oh, the data, you say? Well, that's certainly specific! I'm always getting and setting data on my items.
It turns out that data in this case means the actual encrypted password. So, in summary, what the documentation for this function doesn't say, but should, is, "You build up a list of parameters you want to fetch from itemRef in info, and this method returns their values in attrList if that's not passed in NULL. The item's class (here there'd be a link to all the classes) is returned by reference in itemClass if it's not passed in NULL, and the password is decrypted from the keychain into outData if that is not passed in NULL."
Ok, we've rewritten the documentation, now let's rewrite my original method to use a hypothetical Cocoa Keychain framework. Note that this rewrite is longer than the last because the base Keychain APIs are more recent than the QuickTime ones I pimped above, so they already use more modern metaphors and are thus already somewhat efficient. But can we do better? (Hint: yes.)
|
- (NSArray *)allFTPSiteDictionariesFromKeychain; { NSMutableArray *mutableSites = [NSMutableArray array]; [mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:NSKeychainSecurityProtocolTypeFTPAccount]]; [mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:NSKeychainSecurityProtocolTypeFTP]]; [mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:NSKeychainSecurityProtocolTypeFTPS]]; [mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:NSKeychainSecurityProtocolTypeFTPProxy]]; return mutableSites; }
- (NSArray *)_ftpSitesWithProtocol:(NSString *)protocol; { NSKeychainFetchRequest *keychainFetchRequest = [[[NSKeychainFetchRequest alloc] init] autorelease]; [keychainFetchRequest setPredicate:[NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:NSKeychainSecurityProtocol] rightExpression:[NSExpression expressionForConstantValue:protocol] modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0]]; NSError *error = nil; NSArray *matchingKeychainItems = [[NSKeychain defaultKeychain] executeFetchRequest:keychainFetchRequest error:&error];
if (error) { NSLog(@"%@: Error doing keychainFetchRequest '%@'", keychainFetchRequest); return nil; }
return matchingKeychainItems; } |
_ftpSitesWithProtocol got so tiny because, assuming I didn't have to access the returned keychain results as raw data -- that is, if there were real, full-fledged NSKeychainItem objects that could be queried for their -protocol and -account and -password and -label, then I wouldn't need to do all the complicated object-building I did in the above example, I'd just return the actual keychain items, and even bind to them in my interface directly! This would be pretty rocking... you can imagine feeding an NSArrayController from this list and then binding columns in a tableView to "label" or what-have-you, and you've got a poor man's Keychain Access app in like five lines of code.
Now, again, this might be an oversimplification of Keychain's APIs. There may be subtleties that I'm missing that would require a slightly more complicated set of Cocoa classes. But in general, I think porting Keychain to Cocoa would decrease the amount of effort to program to it (and increase its audience) by an order of magnitude.
--
Let me wrap up, finally, by apologizing profusely to everyone at Apple and elsewhere whom I've insulted tonight. I've been a programmer for 25 years now, and every year I look back at the code I wrote the year before and think, "What the hell? What idiot-monkey wrote this crap?"
And yet, here I sit, on my medium-size pony, and insult code that some of you wrote
ten years ago, viewing it through the lens of modern coding practices that
you guys taught me. Let me stress this: the good programming practices I've learned, such that I've learned them, are from studying the very best of what Apple's done. There are tools and techniques that I love and am in awe of in Apple's code: bindings, enumerators, object-oriented database access, model-view-controller, all those buzzwords. I didn't invent 'em, you guys did.
I know that Apple people exist in the real world, where going back and rewriting blecherous code is not something any engineer wants to do, nor any manager to fund. I know if you meet with Steve in six months and say, "Hey, sure, we didn't add anything cool in Leopard for you to demo, but check it: Keychain is Cocoa now! Huh? Huh?" he'd probably put his foot so far... well, you know where I'm going with this.
And I know that it's a small miracle that things like QuickTime and Keychain and Speech Synthesis and Core Audio exist in the first place. These have truly amazing functionality, using research and math far beyond my ken: if they weren't, I honestly wouldn't give a crap that all their power is frozen in Carbonite right now. (You don't see me complaining about the, uh, Scrap Manager, do you?)
So, I write this
to help you, putative Carbon-coding-Apple-engineer-reader, not
to harm you. I want you to take this to your manager, and say, "Look, see! We've got to get to Cocoa! This has got to be a priority! We can't keep slapping tar on a boat made out of milk cartons!"
Don't do it for me, do it for your team. Do it because you'll love it, and because more programmers will use your stuff, and they'll show it off in ways you never imagined, and suddenly you'll be in all the Stevenotes, and you'll be wealthy and happy and famous.
Labels: code