soeren says

Twitting in the Morning Sun, part 2: Twittereeze 1.0

January 23rd, 2007

All that said, the release of Twitterrific intrigued me enough to figure out if this was for me.

Twitterific

Now, Twitterific is to Twitter what Flickr Uploadr is to Flickr, or what Pukka is to del.icio.us: a desktop client to a web application (or, perhaps more appropriately, a web service). This is great for me: I’m generally not too fond of the inferior user interface web applications can provide (no menu bar, very few advanced controls, very little integration with the operating system, a monolithic application – the browser – underneath, etc.) but do understand its perks (no installation, near-instant access). Having a web client as a fallback option while having a powerful desktop client as an alternative to one and the same service is a great combination, and one of the things I love the most about Flickr.

With Twitterific, this connection goes two ways: not only can you “upload”, i.e. send your tweets (thus changing the status message other Twitter users see as your latest); you can also see the latest tweets from others; either everyone, or only “friends” of yours. (Arguably, “friends” is a lot more loosely defined than is the case on Flickr, where you have a distinction between “contacts” and “friends”; Twitter’s friends are more akin to “contacts”.) As others change their status, you can choose to get notified through a window and/or a little alarm sound. This works through polling; the amount of data downloaded is lightweight enough that this is basically irrelevant; unlike with RSS feeds, there is little that speaks against contacting Twitter once every single minute – and indeed, Twitterrific offers this low interval.

It’s a little jarring to me, though, that this “notification window” is one and the same as the one you enter your own “tweets” in: while still very compact, it is needlessly complicated. It would make much more sense to use Growl in this regard. The relevant information is a picture (the user’s avatar), a headline (the user’s name), a few lines of text (the tweet) and, possibly, a link (to the user’s website). Link aside (which doesn’t exactly change anyway), this would fit greatly into the more typical Growl display styles such as “Bubbles”, “Smoke” or “Crystal”.

And indeed, someone has developed a little Ruby script that polls Twitter and then notifies Growl through its CLI growlnotify tool. One of Twitter’s own people, Alex, has enhanced this script.

Twitterrific uses the “HUD” style that Apple has been gradually introducing since Motion 1.0 (though they called it a “dashboard” back then, which was completely unrelated to 10.4 Tiger’s Dashboard); a palette-esque window with partial transparency, a black/dark grey chrome and completely custom GUI widgets. Far less obtrusive than a traditional palette, to the point where I hardly feel palette windows even have their merits any more. This notification window can be manually invoked (by clicking the icon in the menu bar’s status area), or, as said, be made to appear when new tweets have been received. The interface follows Twitter’s own maxime to be as simple as possible; you’d be hard-pressed to find something superfluous.

That also means, though, that some people may find it a little lacking, as did I.

Twittereeze

I’ve had some experience working with SIMBL before, on two separate projects, neither of which have been released thus far (and possibly never will). As Twitterrific doesn’t have a documented API or anything, doing something like this pretty much amounts to reverse-engineering, is bound to have its completely unexpected share of bugs, is even more likely to break completely in newer versions of the host app (Twitterrific in this case) and, most of all, is scorned upon by many developers. (In other words, don’t expect to get much sympathy on IRC channels like #macdev when you work on such a project.) It is also a great example of always completely misestimating the troubles you will run into.

My initial goal of Twittereeze was one single feature: as you post your status with Twitteriffic, have the status updated in your IM apps as well. iChat, Skype and Adium all have a simple AppleScript API to do this. Let’s say your status message is: “Zomg, PONIES!”; then the necessary AppleScript calls can all be done in one line each, like this:

tell application "iChat" to set status message to "Zomg, PONIES!"
tell application "Skype" to send command "SET PROFILE MOOD_TEXT Zomg, PONIES!"
tell application "Adium" to set my status message to "Zomg, PONIES!"

Of those, iChat evidently has the cleanest API, but the increased complexity in Adium’s and Skype’s cases is easily explained by their far more versatile abilities. Whatever the case, wrapping this into an AppleScript is trivial. Putting it into Objective-C code requires some escaping and wrapping; the NSAppleScript API helps here, but be aware of its limitations. Threads? Forget about it! It isn’t thread-safe, which normally means you’re not supposed to use one and the same object in multiple threads simultaneously (it wouldn’t be synchronized properly); in this particular case, for whatever reason, it means that you’re more likely to crash the app than to get any meaningful result, unless you do everything in the main thread.

As everyone knows, AppleScript is awfully slow. We can normally use pre-compiling to mitigate this, but not here: the status message, after all, changes each time; that’s the entire point. (We could, perhaps, compile a version without the status message itself, and pass it on as a parameter.) NSTask, while not thread-safe either, works just fine in its own thread. So I figured “hey, let’s use osascript and launch the script in its own process that way”. That would have been great, and wouldn’t even really have required threading at all. Problem:

NSTask converts both path and the strings in arguments to appropriate C-style strings (using fileSystemRepresentation) before passing them to the task via argv[]).

As you might guess, this means bye-bye to the full Unicode character set. Whereas Twitter nicely uses UTF-8, this would ‘downgrade’ our beloved tweek-turned-IM-status-message in lossy ways just to please the POSIX underpinnings; option-; never looked so ugly. So, while it nicely worked around AppleScript’s slowness, it was clearly a less than desirable option. (Not that it’s much of a surprise that abusing NSTask for this isn’t that great of an idea.)

Unfortunately, I have yet to find a better solution to this. When you experience “beachballing” in Twitterrific with Twittereeze while sending your tweet, this is why, and I’m aware of the problem. It should only take a few seconds, but if the target application (iChat, Adium or Skype) happens to be hanging for whatever reason, it can take up to three minutes, I believe (I think the timeout is one minute per script), though I have yet to experience such a massive hold-up. In fact, I have found this to be a very minor issue that barely distracts from the experience at all.

If you’re looking at the code, you may be curious why most of the methods are class methods, rather than instance ones (or why, for that matter, the category of NSApplication is currently named PostNibLoad, which is historic and ought to be changed). In the previous two SIMBL projects of mine I haven’t experienced this, and I’m still at a loss of why this is happening: not only can’t I create new instance methods in categories; I also can’t override (let alone swizzle) existing ones. At the same time, I can create/override/swizzle class methods just fine, so the category itself is clearly not at fault. My only explanation is that the bundle gets loaded too early, so its instance-based modifications don’t apply, but I can’t see any such problem anywhere on the web.

That meant I had to get a little creative with how I hooked into Twitterrific itself, but I think I managed to make the code just about readable and efficient enough to compensate this issue.

So the core functionality – keeping your Twitter tweets and your IM status messages in sync – was done within a day and a half.

More Features

I quickly realized, though, that there were other things that bugged me about Twitterrific. Not “bugged” in the sense of “gee, that’s stupid on their part”; I’m quite fond of the app, and I’m sure it works great as-is for most people. But I found myself frequently having to switch between keyboard and mouse (whereby I use “mouse” interchangeably with “trackpad”, of course) to use the user interface:

  1. When the notification window pops up having received tweets, it isn’t frontmost, so you can’t just hit the esc key to dismiss it, and while you can set it to auto-disappear after an interval, the minimum for that is ten seconds. That’s too long! My Growl messages fade out after four seconds, and even that is a bit long sometimes (it turns out it’s very hard to find the sweet spot on such a thing). Because Twittereeze can’t be made front-most through cmd-tab (due to its LSUIElement setting), you effectively have to use the mouse to manually bring the window to the front (and then press esc), at which point you might as well just click the close button.
  2. Likewise, you can’t open the window with the keyboard either; you have to click the icon in the menu bar’s status area. This is especially jarring when you just want to quickly send a tweet; you simply cannot type right away.
  3. You can either have the list of tweets selected and navigate through them, but then not type in a tweet. Or, you can have the text field selected and then not select a different previous tweet. You need the mouse to change the focus manually.

To solve all of the above, I had to learn how to set up global hot keys, which amounts to at least some Carbon code, because Cocoa simply doesn’t have an API for this (yet? ever?). The ever-resourceful, ever-awesome CocoaDev wiki has several pages on this matter: HotKeys, SystemWideKeyboardShortcut, GlobalHotKeys and, as I discovered a few days later, RegisterEventHotKey. These point to several pre-factored solutions; two simpler ones as well as two far more complete ones including their own GUI to allow users to set their own hotkey. I plan on eventually moving one to the latter; for the time being, though, I hardcoded the relevant hotkeys (call me lazy).

I also had to learn NSEvent for the non-global hot keys.

Here they are:

  1. command-F11 toggles the notification. That is, if it’s visible, it gets hidden. If it’s invisible, it gets shown, Twitterrific gets “activated” (becomes the frontmost app), the notification window becomes “main” and “key”, and the text field “first responder”. In Cocoa-speak, those are all terms, effectively, for one and the same thing: being in focus/frontmost. What this means for you is that, if the window isn’t visible, you can hit the shortcut and type your new tweet right away.
  2. command-shift-F11 is the same with a twist; if the window is visible and front-most and focused on the text field and so on, it does nothing (it does not close the window). If the window isn’t visible and/or the text field not focused, however, it makes sure to do this. So if you have the window open (say, because of a notification), and want to respond quickly, you can use this shortcut to bring the text field in focus, regardless of the situation. (Both these shortcuts ignore any other application’s requests. They simply work.)
  3. ctrl-up and ctrl-down are used to scroll the list of previous tweets of you and others. This is different from using just the arrow keys, because those wouldn’t work while the text field is in focus. Because of this, I’m always focusing the text field; i.e., if you use the mouse to select a different previous tweet to view it, the focus will change back to the text field afterwards. An unusual behavior, but in my experience very useful.
  4. As you would expect, return or enter sends your tweet, and esc closes the window.

Only the first two are global; the rest only applies while the app and window are frontmost.

This is pretty much all that Twittereeze does, as of 1.0. I’ve had it more or less done for a while, but decided to polish the code a little and post it on Google Code, so we have a more permanent download link, a central source repository, an issue tracker, a mailing list, and even a wiki. I’ve also pointed to it from MacUpdate (here) as well as VersionTracker (here).

Within mere hours, Alex contacted me on AIM to thank me for this work, as well as subscribe to my blog ;-) , which I dare say shows just how amazing social networking on the Internet has become: it’s not as if I had launched some massive advertising campaign for the project or anything.

Alex proceeded to request a feature, which I’ll hopefully be working on soon-ish.

One can probably tell I’m fairly ecstatic about all this, which may not make much sense for a bundle whose binary is a mere 48 KBs. However, I’ve always found that a large, yet carefully selected number of small pieces of software amounts to a far greater user experience than a small number of huge, monolithic apps. In the truest fashion of “eat your own dogfood”, I wrote Twittereeze to fulfill a need that I have had for a long time, regardless of whether any of Twittereeze’s other potential users will: to keep my publicly visible status in sync. Twitter provides an excellent platform for this, and all I did was use it to its fullest.

Posted in AppleScript, Cocoa / Objective-C, Internet, Mac, Programming, Projects, Software, Twittereeze, Web

Share

Others' Thoughts

# Fafnir

Via John Gruber: http://www.ianhenderson.org/megazoomer.html

It is… ähm… big.

# David Fredin

Hi,

Twittereeze doesn’t seem to work anymore:

SIMBL Error Twitterrific (null) (v3.0.1) har not been tested with the plugin Twittereeze 1.0.2 (v1.0.2). As a precaution, it has not been loaded. Please contact the plugin developer (not the SIMBL author) for further information.”

Any clues on how to make it work?

# chucker

Twittereeze is only supported for Twitterrific 1.x, seeing as Twitterrific 2.0 implemented most features that caused me to write Twittereeze to begin with.

I’m curious which feature in particular you’re missing in Twitterrific 3?

Your Own Thoughts

I'd love to hear your input. Just try to stick to a few rules:

Before you comment for the first time (or, after you have deleted cookies), you will have to answer a little challenge to prove that you are not a spammer.

Comments are written in Markdown.

Leave the country the same, but correct the continent, and end the sentence with a period instead.