Archive for the 'Programming' Category

Framework Friday: OSAKit

Sunday, November 9th, 2008

Apologies for posting this late. I was finishing up the test app and filing relevant bugs.


So, how do you run an AppleScript script?

One way is NSAppleScript. The problem with that is that, in our experience on the Adium project, it leaks memory. Profusely. (Maybe this has changed; Adium has for a long time used a separate process for running AppleScript scripts.)

Another way is to use the Open Scripting Architecture API directly. But that API is pre-Carbon, making it painful to deal with. One specific problem is that the object types aren’t reference-counted.

The third way is OSAScript, one of the classes in OSA Kit.

Wait. OSA Kit?

Yes. OSA Kit is an Objective-C wrapper around the Open Scripting Architecture. Like QTKit (a similar wrapper around QuickTime), it’s a framework in Mac OS X that Apple added in version 10.4. It’s public, but undocumented: As of right now, searching developer.apple.com for that name yields exactly five hits, none of which are documentation of OSA Kit.

One of those hits is the list of system frameworks, which says that OSA Kit:

Contains Objective-C interfaces for managing and executing OSA-compliant scripts from your Cocoa applications.

OSA-compliant… like AppleScript!

The interface for OSAScript is the same as that of NSAppleScript, except larger: OSAScript is a superset of NSAppleScript. This is mostly because the OSA supports languages other than AppleScript, such as JavaScript (with this component). Running a script, for example, is no different:

NSDictionary *errorDict = nil;
script = [[[(OSAScript | NSAppleScript) alloc] initWithContentsOfURL:scriptURL error:&errorDict] autorelease];
result = [script executeAndReturnError:&errorDict];

But, unlike NSAppleScript, the OSA Kit does not stop there.

OSA Kit is to Script Editor as Image Kit and PDF Kit are to Preview. Apple removed those applications’ capabilities to a new framework, and rewrote the applications to use the new frameworks. In the process, they added features and made the existing features prettier.

In the case of OSA Kit, the new framework includes classes you can use to build your own Script Editor (or CLImax)—specifically, OSAScriptView and OSAScriptController. You may find these classes useful if you plan to embed some of Script Editor’s functionality into your application.

Of course, you don’t need to use OSA Kit from an application. Here’s a command-line tool I wrote:

File: OSAKit-osascript-1.0.2.tbz

A clone of osascript, written to use either NSAppleScript or OSAScript. The Xcode project contains two targets, one for each version.

There are very few differences between the two variants:

  1. Obviously, the OSA Kit version uses OSAScript, whereas the other version uses NSAppleScript.
  2. The OSA Kit version has an option to allow you to specify the language, and another one to list available languages. (NSAppleScript, as you might guess, only supports AppleScript.)
  3. The OSA Kit version can (theoretically) pass arguments to the script; NSAppleScript doesn’t support this.

OSA Kit does have some downsides:

  1. As I mentioned, it isn’t documented. All we have to go on are the headers.
  2. There’s not even sample code, other than the example I wrote and included in this very post.
  3. Calling a handler by name with arguments doesn’t work (hence the “theoretically” above). I believe that this is a bug in OSAScript; I have filed it, with a summary of -[OSAScript executeHandlerWithName:arguments:error:] always returns an error, never executes the handler.

There is also one potential downside: I don’t know for certain that OSAScript doesn’t leak memory the same way NSAppleScript does (or did). It’s worth trying, though, and it’s more capable anyway.

I consider the OSA Kit to be the future of running AppleScript scripts from Objective-C.

UPDATE 2008-11-09 13:18 PST: Replaced OSAKit osascript 1.0 with 1.0.1, which has a couple more sample scripts.

UPDATE 2008-11-09 17:40 PST: Changed link to CLImax. I had linked to Drew Thaler’s blog post announcing its re-release; now I’m linking to the actual product page, as saved by the Internet Archive’s Wayback Machine.

UPDATE 2008-11-11 20:43 PST: Replaced OSAKit osascript 1.0.1 with 1.0.2, which has better (more GCC-like) error-reporting.

Tabs vs. spaces redux

Wednesday, November 5th, 2008

Alice, who prefers four-space indents, wants this:

Four-space indents, with non-first lines of an Objective-C messages lined up on the colon of each argument.

Bob, who prefers eight-space indents, wants this:

Eight-space indents, with non-first lines of an Objective-C messages lined up on the colon of each argument.

Is it possible for this source code file to be written such that Alice and Bob each see what they want?

Yes, but not yet.

What they need to do is to use tabs only up to the level of indent of the first line, then spaces the rest of the way:

Screenshot demonstrating this with four-space indents.
Screenshot demonstrating this with eight-space indents.

Note that the contents of the file are the same in both of these images; the only change is the indent width. The characters shown in these two images produce the result shown in the earlier two images.

I say “not yet” because no editor currently does this. Xcode, for example, will use on each line as many tabs as it can fit, followed by < tabstop spaces. Other editors won’t line up the colons; they’ll just left-justify all of the non-first lines.

So Alice and Bob can each have the indent width they want; it’s just a pain in the ass for them to do the editing necessary to get it. Therefore, they won’t bother, just as I don’t.

So, consider this my call to the makers of all the editors to auto-indent the right way, as I have just shown it.

Added a few minutes later: Alternatively stated by Christopher Bowns on his blog post.

Manpage Monday: backtrace(3)

Monday, October 27th, 2008

New series: Manpage FridayMonday. Every Fridayother Monday, I will post a link to one manpage that comes with Mac OS X. [See update below.]

Today, it’s backtrace(3), which tells you about three functions:

SYNOPSIS

#include <execinfo.h>

int
backtrace(void** array, int size);

char**
backtrace_symbols(void* const* array, int size);

void
backtrace_symbols_fd(void* const* array, int size, int fd);

DESCRIPTION

These routines provide a mechanism to examine the current thread’s call stack.

backtrace() writes the function return addresses of the current call stack to the array of pointers referenced by array. At most, size pointers are written. The number of pointers actually written to array is returned.

backtrace_symbols() attempts to transform a call stack obtained by backtrace() into an array of human-readable strings using dladdr(). The array of strings returned has size elements. It is allocated using malloc() and should be released using free(). There is no need to free the individual strings in the array.

backtrace_symbols_fd() performs the same operation as backtrace_symbols(), but the resulting strings are immediately written to the file descriptor fd, and are not returned.

Added 2008-10-25: Here’s a test app to show the output.

File: print_backtrace.tbz

Output:

0   print_backtrace                     0x00001fc9 print_backtrace + 31
1   print_backtrace                     0x00001ff6 main + 11
2   print_backtrace                     0x00001f7e start + 54

In order:

  1. Index in the array of addresses
  2. Executable name
  3. Address
  4. Name + offset

UPDATE 2008-10-24 21:19 PDT: I’ve decided to change the schedule on this. Instead of Manpage Friday, I’ll do Manpage Monday, and it will be every two weeks. In between will be Framework Friday.

So this is now the first Manpage Monday post, and I will update its post date on Monday, the 27th. (I can’t update it now because WordPress won’t let me publish a post from the future—only schedule it. Grrr.) The week after that, November 7th will be the first Framework Friday. And in the week after that, November 10th will be the second Manpage Monday.

New Mercurial extension: Bitbucket Extension

Monday, September 29th, 2008

If you’ve ever used Bazaar, you know that one of its features is a shorthand URL scheme for referring to Bazaar repositories on Launchpad. For example, to clone Sparkle’s main repository:

% bzr clone lp:sparkle

I’ve created an extension that enables you to do the same thing in Mercurial with Bitbucket repositories.

% hg clone bb://boredzo/bitbucketextension

The Bitbucket extension adds two URL schemes: bb:, and bb+ssh:. The bb: scheme communicates with Bitbucket over HTTPS; you can guess what bb+ssh does.

(Note: As of Mercurial 1.0.2, you must include the double-slash, because hg pull will interpret the URL as a relative path without it.)

svn merging is full of fail

Thursday, August 7th, 2008

[SCENE: adium/branches/summer_of_code_2008/unit_testing. I’m merging changes from trunk to this branch.]

svn --version
svn, version 1.5.0 (r31699)
   compiled Jul  5 2008, 04:29:25

Copyright (C) 2000-2008 CollabNet.
Subversion is open source software, see http://subversion.tigris.org/
This product includes software developed by CollabNet (http://www.Collab.Net/).

The following repository access (RA) modules are available:

* ra_neon : Module for accessing a repository via WebDAV protocol using Neon.
  - handles 'http' scheme
  - handles 'https' scheme
* ra_svn : Module for accessing a repository using the svn network protocol.
  - with Cyrus SASL authentication
  - handles 'svn' scheme
* ra_local : Module for accessing a repository on local disk.
  - handles 'file' scheme
* ra_serf : Module for accessing a repository via WebDAV protocol using serf.
  - handles 'http' scheme
  - handles 'https' scheme

___
svn up
At revision 24694.
___
svn st
?      Docs
?      DoxyCleaned
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/outgoing_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/incoming_icon.png

(~ = “versioned item obstructed by some item of a different kind”)

rm -Rf Resources/Message\ Styles/renkooNaked.AdiumMessageStyle
___
svn up Resources/Message\ Styles/renkooNaked.AdiumMessageStyle
[snip]
___
cd ../../..
___
svn merge -r23936:24694 trunk branches/summer_of_code_2008/unit_testing 
--- Merging r23937 through r24694 into 'branches/summer_of_code_2008/unit_testing':
[snip]
Conflict discovered in 'branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (h) help for more options: e
[I correct the plist to match what trunk has.]
Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (h) help for more options: r
___
plutil -lint branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist 
branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist: OK
___
plutil -lint branches/summer_of_code_2008/unit_testing/Adium.xcodeproj 
branches/summer_of_code_2008/unit_testing/Adium.xcodeproj: file does not exist or is not readable or is not a regular file
___
plutil -lint branches/summer_of_code_2008/unit_testing/Adium.xcodeproj/project.pbxproj 
branches/summer_of_code_2008/unit_testing/Adium.xcodeproj/project.pbxproj: OK
___
plutil -lint branches/summer_of_code_2008/unit_testing/Frameworks/AIUtilities\ Framework/AIUtilities.framework.xcodeproj/project.pbxproj
branches/summer_of_code_2008/unit_testing/Frameworks/AIUtilities Framework/AIUtilities.framework.xcodeproj/project.pbxproj: OK
___
cd branches/summer_of_code_2008/unit_testing   %~/Projects/@otherpeoplesprojects/adium(0)
___
xcodebuild -configuration Development       
[snip]
** BUILD SUCCEEDED **
___
svn ci
Sending        unit_testing
[snip]
Transmitting file data .......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................svn: Commit failed (details follow):
svn: Checksum mismatch for '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/.svn/text-base/Emoticons.plist.svn-base'; expected: '109d361ce47a49a66e53e98412251398', actual: 'a21c8336d1f839b9aaa8a2795a7e25f5'
svn: Your commit message was left in a temporary file:
svn:    '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/svn-commit.2.tmp'
___
cd Resources/Emoticons
___
rm -Rf MSN.AdiumEmoticonset 
___
svn up MSN.AdiumEmoticonset
A    MSN.AdiumEmoticonset
[snip]
Updated to revision 24694.
___
cd ../.. 
___
cd ../../..
___
svn merge -r23936:24694 trunk branches/summer_of_code_2008/unit_testing
svn: Working copy path 'Frameworks/Adium Framework/Source/AISharedAdium.h' does not exist in repository
___
svn merge -r23936:24694 trunk/Resources/Emoticons branches/summer_of_code_2008/unit_testing/Resources/Emoticons 
___
svn merge -r23936:24694 trunk/Resources/Emoticons/MSN.AdiumEmoticonset branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset 
___
svn merge -r23936:24694 trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist  branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
___
cd branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset 
___
svn pl Emoticons.plist 
___
svn st
___
popd
___
diff -u trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist 
--- trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist       2008-06-26 20:50:51.000000000 -0700
+++ branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist  2008-08-07 11:20:27.000000000 -0700
@@ -367,7 +367,7 @@
                        Name
                        Cool
                
-               I don't know.gif
+               I Don't Know.gif
                
                        Equivalents
                        
[Um, OK. What happened to all the differences it had from trunk that I had merged over? Suddenly, almost all the differences are gone, and the files are nearly the same!]
___
diff -u trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
___
cd branches/summer_of_code_2008                %~/Projects/@otherpeoplesprojects/adium(0)
___
svn ci unit_testing 
Sending        unit_testing
Transmitting file data ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................svn: Commit succeeded, but other errors follow:
svn: Error bumping revisions post-commit (details follow):
svn: Directory '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Frameworks/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib' is missing
svn: Directory '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Frameworks/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib' is missing
svn: Your commit message was left in a temporary file:
svn:    '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/svn-commit.3.tmp'
___
svn st            %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(1)
?      svn-commit.2.tmp
?      svn-commit.tmp
?      svn-commit.3.tmp
 ML    unit_testing
?      unit_testing/Docs
?      unit_testing/DoxyCleaned
  L    unit_testing/Release
  L    unit_testing/Release/openUp
  L    unit_testing/Release/Artwork
M      unit_testing/Release/Makefile
  L    unit_testing/Frameworks
  L    unit_testing/Frameworks/libgmodule.framework
  L    unit_testing/Frameworks/libgmodule.framework/Versions
[snip]

(L in third column = Locked)
___
svn cleanup       %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(0)
svn: In directory 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Error processing command 'committed' in 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Working copy 'unit_testing/Frameworks/JSON Framework/Tests' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
___
svn cleanup       %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(1)
svn: In directory 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Error processing command 'committed' in 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Working copy 'unit_testing/Frameworks/JSON Framework/Tests' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
[So I get to manually clear the locks. Yay!]
___
tar cjf unit_testing.tbz unit_testing 
___
cd unit_testing   %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(0)
___
svn st | grep -Fw L | head
  L    Frameworks
  L    Frameworks/JSON Framework
  L    Frameworks/JSON Framework/Tests
A L+   Frameworks/JSON Framework/Tests/json.org
A L+   Frameworks/JSON Framework/Tests/types
A L+   Frameworks/JSON Framework/Tests/format
A L+   Frameworks/JSON Framework/Tests/rfc4627
  L    Frameworks/JSON Framework/Tests/Examples
  L    Frameworks/JSON Framework/Tests/Examples/JSONChecker
A L+   Frameworks/JSON Framework/Tests/jsonchecker
svn: Write error: Broken pipe
___
ls Frameworks/.svn 
dir-prop-base format          prop-base/      text-base/
entries         lock            props/          tmp/
___
ls Frameworks/.svn/lock 
Frameworks/.svn/lock
___
rm Frameworks/.svn/lock 
___
svn st -N Frameworks 
  L    Frameworks/JSON Framework
  L    Frameworks/libintl.framework
  L    Frameworks/libpurple.framework
  L    Frameworks/libmeanwhile.framework
  L    Frameworks/LMX.framework
R L+   Frameworks/Sparkle.framework
  L    Frameworks/AutoHyperlinks Framework
  L    Frameworks/libgobject.framework
  L    Frameworks/OTR.framework
  L    Frameworks/libglib.framework
  L    Frameworks/ShortcutRecorder
  L    Frameworks/libgthread.framework
  L    Frameworks/AIUtilities Framework
  L    Frameworks/OCMock.framework
  L    Frameworks/PSMTabBarControl.framework
  L    Frameworks/RBSplitView
  L    Frameworks/Growl-WithInstaller.framework
  L    Frameworks/Adium Framework
___
svn st -N Frameworks/JSON\ Framework 
  L    Frameworks/JSON Framework
A  +   Frameworks/JSON Framework/dmg.sh
  L    Frameworks/JSON Framework/Tests
  L    Frameworks/JSON Framework/Site
  L    Frameworks/JSON Framework/JSON.xcodeproj
  L    Frameworks/JSON Framework/Docs
A  +   Frameworks/JSON Framework/bench.m
M      Frameworks/JSON Framework/JSON-Info.plist
M      Frameworks/JSON Framework/CREDITS
___
rm Frameworks/JSON\ Framework/.svn/lock 
___
svn st -N Frameworks/JSON\ Framework
A  +   Frameworks/JSON Framework/dmg.sh
  L    Frameworks/JSON Framework/Tests
  L    Frameworks/JSON Framework/Site
  L    Frameworks/JSON Framework/JSON.xcodeproj
  L    Frameworks/JSON Framework/Docs
A  +   Frameworks/JSON Framework/bench.m
M      Frameworks/JSON Framework/JSON-Info.plist
M      Frameworks/JSON Framework/CREDITS
___
find . -name lock -print0 | xargs -0 rm
___
svn ci 
svn: Working copy '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
___
rm ../.svn/lock 
___
svn ci
Sending        unit_testing
[snip]
svn: Commit failed (details follow):
svn: Directory '/branches/summer_of_code_2008/unit_testing' is out of date
___
svn up ..
svn: Failed to add file '../unit_testing/Frameworks/JSON Framework/dmg.sh': a file of the same name is already scheduled for addition with history
___
rm Frameworks/JSON\ Framework/dmg.sh 
___
svn up
D    Frameworks/libgmodule.framework/Versions/2.0.0/Resources/English.lproj
G    Frameworks/JSON Framework/Tests/Types.m
svn: Failed to add directory 'Frameworks/JSON Framework/Tests/json.org': a versioned directory of the same name already exists
___
svn st Frameworks/JSON\ Framework/Tests/json.org
A L+   Frameworks/JSON Framework/Tests/json.org
A  +   Frameworks/JSON Framework/Tests/json.org/1.json
A  +   Frameworks/JSON Framework/Tests/json.org/2.json
A  +   Frameworks/JSON Framework/Tests/json.org/1.plist
⋮
___
find . -name lock -print0 | xargs -0 rm
___
svn st Frameworks/JSON\ Framework/Tests/json.org
A  +   Frameworks/JSON Framework/Tests/json.org
⋮
___
cd ..
___
mv unit_testing unit_testing-fail
___
svn up unit_testing
A    unit_testing

With this fresh working copy, I’m able to build, so I believe I’m finally done.

New tool: sednames

Friday, June 20th, 2008

What if you could use sed to rename files?

Well, now you can.

sednames is a utility that lets you specify a program for sed on the command-line, which it then uses to rename the files that you also specify on the command-line.

The twist is that, unlike other batch-renamers, sednames also supports your VCS. For the most common cases (svn, hg, bzr, and git), there’s a –vcs option:

% sednames -e 's/Replace/Mix/' --vcs=hg *

This command-line will use Mercurial (hg mv) to rename every file by replacing “Replace” with “Mix”. Of course, if a name doesn’t contain “Replace”, then that name will be unchanged and sednames is smart enough to not try to rename that file.

Not only that, but just in case you’re using some oddball VCS (or you want to copy instead of rename, or something), sednames supports using any program to rename your files, as long as it accepts both the before and after names in its arguments. The –help output is more specific, but to put it simply, it works similarly to find‘s -exec option.

You can download the current revision directly, or use the Mercurial repository to follow or contribute to its development.

What to do if Python says “character mapping must return integer, None or unicode”

Monday, June 16th, 2008

So you’re using the unicode class’s translate method, and it says:

TypeError: character mapping must return integer, None or unicode

You may be wondering what causes this. After all, you’re duly using the string.maketrans function. Surely this should be valid?

Well, no: You can only use string.maketrans with str.translate. For unicode.translate, you must use a different kind of translation table:

… a mapping of Unicode ordinals to Unicode ordinals [int], Unicode strings [unicode] or None.

3.6.1 String Methods

In Localization Helper, I had to replace my string.maketrans call with this code:

dict(zip(map(ord, u'\a\b\t\n\v\f\r'), u'abtnvfr'))

Note that I need to call ord on each key character, because keys must be ints.

New tool: Localization Helper

Monday, June 16th, 2008

One thing that I noticed a few days ago while working on Growl 1.1.4 is that some strings aren’t translated in a couple of the localizations. I reported this on our localization mailing list, but it got me thinking: I could really use a program that would scan a tree of source code and tell me of problems like this.

So I wrote one.

Localization Helper is a command-line tool to walk a tree of source code looking for .strings files, and compare different localizations of them. It reports duplicate (not-translated) strings, and will soon (maybe by the time you read this) report strings that are missing altogether.

Currently, it only compares all other languages to one primary language, which defaults to English. I didn’t feel like making it compare every language to every other language. ☺

The program scans every directory you specify on the command-line. If you don’t give it any arguments, it scans the current working directory. Also, it has some options, which you can see with the –help option.

Here are some excerpts of its output for the Growl 1.1.3 source code:

*** Found problems in Core/Resources/*.lproj/Localizable.strings
Duplicate strings in Localizable.strings between English.lproj and cs.lproj:
"User went idle" = "User went idle";
"You are now considered idle by Growl" = "You are now considered idle by Growl";
"No activity for more than %d seconds." = "No activity for more than %d seconds.";
"Growl was unable to create the socket for Network notifications." = "Growl was unable to create the socket for Network notifications.";
"You are no longer considered idle by Growl" = "You are no longer considered idle by Growl";
⋮
!!! Warning: Localized file Extras/GrowlSafari/de.lproj/InfoPlist.strings is missing
!!! Warning: Localized file Extras/GrowlSafari/ja.lproj/InfoPlist.strings is missing

*** Could not read plist file at path Extras/GrowlSafari/de.lproj/Localizable.strings
*** Could not read plist file at path Extras/GrowlSafari/ja.lproj/Localizable.strings
*** Could not read plist file at path Extras/GrowlSafari/pt_BR.lproj/Localizable.strings
*** Could not read plist file at path Extras/GrowlSafari/sv.lproj/Localizable.strings

You’ll need to have either Leopard or Python 2.5 + PyObjC (Leopard comes with both) to use the program. (BTW: PyObjC rocks.)

Until I release it sometime later this week (hopefully), I provide two ways to get the program:

  1. You can download it directly.
  2. You can clone my repository:
    hg clone http://boredzo.org/localization-helper/hg/

Of course, if you clone your repository, then you can easily get updates by running hg pull.

Also, there’s a commits feed, in case you want to stay on top of localization_helper’s development using your feed reader.

Growl 1.1.3

Friday, June 6th, 2008

I just released version 1.1.3 of Growl. Some highlights from the version history:

  • Worked around conflict with Logitech Control Center 2.4, and implemented countermeasures in case another input-manager hack in the future has the same bug
  • Show notifications on every Space (Leopard)
  • Rewrote GrowlMail to fix conflict between it and Leopard, and make it much more robust for the future
  • Updated GrowlSafari to work with Safari 3.0 and later (thanks to Ben Willmore)
  • Fixed growlnotify to actually send the notification on Leopard
  • Fixed a hang on changing the default display
  • Fixed displaying a close widget on mouse-over

Those aren’t the only improvements, but they’re the most major. Growl and its extras are finally completely Leopard-ready.

You can download 1.1.3 either from the About tab in the Growl preference pane, or from the website.

In other news, this is my first release as the Lead Developer of Growl. I’m replacing Brian Ganninger, who was (IIRC) Lead Developer for the entire 1.1 series. Let us all thank him for his excellent work, and wish him well for the future.

Things your ReadMe must include

Tuesday, May 20th, 2008
  • The name of your application
  • What it does
  • How to configure the application, if necessary (note: does not include installation)
  • Simple overview of how to use the software, once it’s configured (detailed manual should be in the Help menu)
  • How to uninstall the software, if necessary (i.e., if it isn’t an application)
  • FAQ
  • What it costs, if it’s not free
  • How to register it
  • A link to your website (in case a magazine distributes your app on its CD)
  • Contact information for support questions
  • Contact information for sales (registration/pricing/currency) questions

Things that you may want to include, but aren’t necessary

  • Screenshots
  • Troubleshooting information

Things that you shouldn’t include

  • Installation instructions: If it’s a plain app, you don’t need an installer; otherwise, make an Installer .pkg. In either case, you shouldn’t need instructions.

Formats I approve

  • RTF or RTFd
  • HTML or webarchive
  • Plain text

You may also want to provide a trampoline application to open a localized version of your ReadMe (for example, see the ReadMe on the Mac OS X DVD). Bonus points if you create a kit to make these, for the benefit of other developers.

Formats I disapprove

  • Word or OOXML format: Many people don’t have Word, and everything else handles these documents imperfectly. Use RTF instead.
  • OpenOffice format: Many (probably most) people don’t have OpenOffice. Use RTF instead.
  • PDF: Use PDF either for vector graphics (inside your app) or for documents you expect someone to print. If the user is going to have to print your ReadMe, you need an interface overhaul.

As usual, I invite suggestions, rebuttals, and amendments.

UPDATE 2008-05-24: Recommended including contact information, as suggested by ssp.

UTI Property List Helper 1.0

Friday, March 21st, 2008

For a forthcoming blog post, I was recently writing a test app that displays images. This had me once again slogging through the list of System-Declared Uniform Type Identifiers and assembling type declarations for the image types by hand.

I’m tired of doing that. So I wrote a new application called UTI Property List Helper.

The main window consists of a table view, in which you enter UTIs, and two text views displaying chunks of Info.plist XML data.

This application automatically updates the CFBundleDocumentTypes and UTImportedTypeDeclarations arrays in its window with information on the types you enter, obtained from Launch Services’ database of all types registered by any app on your system.

No more searching for that OSType, or leaving it out because it’s too much work—this app does all the grunt work for you. Enjoy.

.hgignore for Mac OS X applications

Thursday, March 20th, 2008

If you use version control (and you should), then you’re familiar with the pollution that an Xcode build folder can put into your status output:

? build/.DS_Store
? build/Debug/UTI Plist Helper.app/Contents/Info.plist
? build/Debug/UTI Plist Helper.app/Contents/MacOS/UTI Plist Helper
? build/Debug/UTI Plist Helper.app/Contents/PkgInfo
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/InfoPlist.strings
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/MainMenu.nib/info.nib
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
⋮

Good version-control systems offer a way to ignore any file that matches a certain pattern. In the case of an Xcode project, you want to ignore the build folder and a few other things: .DS_Store files, backup nibs (those Foo~.nib packages that IB creates when you save), etc.

In Mercurial, the way to do that is to create a .hgignore file, and populate it with the patterns you want hg to ignore.

In order to save you repetitive work, here’s a .hgignore file, already fully-populated, that you can use when versioning your Xcode-based project with Mercurial:

File: hgignore.bz2

syntax: glob
build
*.swp

*.mode1
*.mode1v3
*.mode2
*.mode2v3
*.pbxuser
*.perspective
*.perspectivev3
xcuserdata

*~.nib

.DS_Store

What to do with this file

  1. Download it, and save the .bz2 file somewhere such as your Documents folder.
  2. cd into the top level of a repository.
  3. Extract the file using this command line: bunzip2 < ~/Documents/hgignore.bz2 > .hgignore
  4. Add the file: hg add .hgignore
  5. Commit it.

Thereafter, not only do you have a .hgignore file keeping your status output clean, but it’s versioned, so it’s easy for you to track and revert changes to the ignore file over time.

UPDATE 2011-05-05: Updated for Xcode 4.

A public thank-you

Monday, March 17th, 2008

This goes out to whichever engineers at Apple fixed the Image Unit template to not suck.

Before Xcode 2.5, that template was useless. Now, it contains everything I need already set up, to the maximum extent possible.

Thank you, Apple engineers.

Multiple methods found

Friday, March 14th, 2008

I wrote some code like this:

[[QTMovie movieNamed:sound error:NULL] play];

and Xcode gave me three warnings:

  • GrowlApplicationController.m:537: warning: multiple methods named ‘-play’ found

    • /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/AppKit.framework/Headers/NSSound.h:47: warning: using ‘-(BOOL)play
    • /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/QTKit.framework/Headers/QTMovie.h:340: warning: also found ‘-(void)play

All warnings are bad, but these are even worse: The compiler is telling me that it’s picked the wrong method. I’m creating a QTMovie, so I want it to use -[QTMovie play] (the one that returns void), but it picked -[NSSound play] (the one that returns BOOL).

Now, in this case, it won’t matter, thanks to Obj-C’s dynamism—all it will do is send a play message to the object, and the object will use whatever play method it knows about. In other words, despite what the compiler is telling me, the program will actually call the correct method (the one in QTMovie).

But this could be a lot worse. If I’m expecting, say, a structure type, and the compiler picks a method that returns a scalar type such as int or float (or vice versa), the compiler will write the wrong assembly code for the message. My method will get gibberish back from the method it called, and Bad Things will follow.

The reason for this  problem is that +movieNamed:error: returns an id—not a QTMovie * specifically. For all the compiler knows, that method returns an NSSound *, or even an NSUserDefaultsController *.

Since the compiler doesn’t know what type the method returns, it must pick one from all the methods by that name that it knows about. You can’t rely on it picking any particular one, and I could only guess as to how it makes its choice. For all I know, it’s blind chance.

The ugly and naïve solution would be to tell the compiler what type we’re expecting by casting it:

[(QTMovie *)[QTMovie movieNamed:sound error:NULL] play];

Of course, besides being ugly, this specific code still has the problem of not accepting an error object. What I should do, and will do, is accept the error object and stash the returned movie in a variable, so that I can test it and then either report the error or play the movie.

NSError *error = nil;
QTMovie *movie = [QTMovie movieNamed:soundName error:&error];
if (movie)
    [movie play];
else
    [NSApp presentError:error];

This code is longer, but it will always use the correct method and it doesn’t ignore the error. (You may also have noticed that I change the previously-misleading name of the name variable from sound to soundName.)

hg st says modified, but hg diff doesn’t say anything

Tuesday, March 11th, 2008

You have a puzzle. When you run hg st, it says one of your files is modified:

hg st                                                              %~/Python/run_tests(0)
M test.py

But when you run hg diff, it doesn’t say anything about the file:

hg diff test.py                                                    %~/Python/run_tests(0)
___
                                                                   %~/Python/run_tests(0)

The reason, at least in my case, was that the file’s permissions had changed. hg st acknowledges this change exactly the same way it acknowledges a change to the file’s contents, but hg diff only shows changes to the contents of the file, not the metadata. Thus, a metadata change only gets reported by hg st and not by hg diff.

While I prefer hg over svn, this is one advantage that svn has over hg. hg only uses one column to indicate the type of change, and shows the same letter (M) for metadata changes as for content changes. svn, on the other hand, uses seven columns, and a metadata change puts the M in a different column.

There’s no way to make hg st use the svn st format, but you can make hg diff show metadata changes. The way to do this is to edit your hgrc file and enable git mode:

[diff]
git=True

There are two hgrc files; you can choose to change either or both. You can edit ~/.hgrc (this is what I recommend), or you can edit the per-repository .hg/hgrc file. (There is no .hg/hgrc file by default, so if you haven’t created one already, you would need to create it.)

The difference, as you’ve probably guessed, is that ~/.hgrc sets hg’s default for all repositories, whereas the per-repository hgrc changes the setting only for one repository, overriding ~/.hgrc and hg’s own defaults.

Once you make this change, the output from hg diff will include metadata information:

hg diff test.py                                                    %~/Python/run_tests(0)
diff --git a/test.py b/test.py
old mode 100755
new mode 100644
___
chmod a+x test.py                                                  %~/Python/run_tests(0)

What to do if Core Image is ignoring your slider attributes

Wednesday, February 27th, 2008

So you’re writing an Image Unit, and it has a couple of numeric parameters. You expect that Core Image Fun House will show a slider for each of them, and it does—but no matter what you do, the slider’s minimum and maximum are both 0. Furthermore, Core Image Fun House doesn’t show your parameters’ real display names; it simply makes them up from the parameters’ KVC keys.

The problem is that you are specifying those attributes in the wrong place in your Description.plist. And yes, I know you’re specifying them where the project template had them—so was I. The template has it wrong.

One of the filter attributes that Core Image recognizes is CIInputs. The value for this key is an array of dictionaries; each dictionary represents one parameter to the filter. The template has all the parameter attributes in these dictionaries. That makes sense, but it’s not where Core Image looks for them.

In reality, Core Image only looks for three keys in those dictionaries:

  • CIAttributeName
  • CIAttributeClass
  • CIAttributeDefault

Anything else, it simply ignores.

The correct place to put all those other keys (including CIAttributeSliderMin, CIAttributeSliderMax, and CIAttributeDisplayName) is in another dictionary—one for each parameter. These dictionaries go inside the CIFilterAttributes dictionary. In other words, the CIFilterAttributes dictionary should contain:

  • CIInputs => Aforementioned array of (now very small) dictionaries
  • inputFoo => Dictionary fully describing the inputFoo parameter, including slider attributes and display name
  • inputBar => Dictionary fully describing the inputBar parameter, including slider attributes and display name
  • inputBaz => Dictionary fully describing the inputBaz parameter, including slider attributes and display name

Finally, an example:

<key>CIFilterAttributes</key>
<dict>
    ⋮
    <key>CIInputs</key>
    <array>
        <dict>
            <key>CIAttributeClass</key>
            <string>CIImage</string>
            <key>CIAttributeName</key>
            <string>inputImage</string>
        </dict>
        <dict>
            <key>CIAttributeClass</key>
            <string>NSNumber</string>
            <key>CIAttributeDefault</key>
            <real>1.0</real>
            <key>CIAttributeName</key>
            <string>inputWhitePoint</string>
        </dict>
        <dict>
            <key>CIAttributeClass</key>
            <string>NSNumber</string>
            <key>CIAttributeDefault</key>
            <real>0.0</real>
            <key>CIAttributeName</key>
            <string>inputBlackPoint</string>
        </dict>
    </array>
    <key>inputWhitePoint</key>
    <dict>
        <key>CIAttributeClass</key>
        <string>NSNumber</string>
        <key>CIAttributeDefault</key>
        <real>1.0</real>
        <key>CIAttributeDisplayName</key>
        <string>White point</string>
        <key>CIAttributeIdentity</key>
        <real>1.0</real>
        <key>CIAttributeMin</key>
        <real>0.0</real>
        <key>CIAttributeMax</key>
        <real>1.0</real>
        <key>CIAttributeName</key>
        <string>inputWhitePoint</string>
        <key>CIAttributeSliderMin</key>
        <real>0.0</real>
        <key>CIAttributeSliderMax</key>
        <real>1.0</real>
        <key>CIAttributeType</key>
        <string>CIAttributeTypeScalar</string>
    </dict>
    <key>inputBlackPoint</key>
    <dict>
        <key>CIAttributeClass</key>
        <string>NSNumber</string>
        <key>CIAttributeDefault</key>
        <real>0.0</real>
        <key>CIAttributeDisplayName</key>
        <string>Black point</string>
        <key>CIAttributeIdentity</key>
        <real>0.0</real>
        <key>CIAttributeMin</key>
        <real>0.0</real>
        <key>CIAttributeMax</key>
        <real>1.0</real>
        <key>CIAttributeName</key>
        <string>inputBlackPoint</string>
        <key>CIAttributeSliderMin</key>
        <real>0.0</real>
        <key>CIAttributeSliderMax</key>
        <real>1.0</real>
        <key>CIAttributeType</key>
        <string>CIAttributeTypeScalar</string>
    </dict>
</dict>

You can see how the descriptions under CIInputs are as terse as possible; everything besides the absolute necessities is specified in the outer dictionaries.

Apple Bug Friday! 74

Friday, February 22nd, 2008

This bug is Typo in syslogd(8) manpage. It was filed on 2008-01-19 at 15:41 PST.

(more…)

Multi-stroke key bindings, Extended vi Edition

Thursday, February 21st, 2008

As some of you know, I use the multi-stroke key bindings by Jacob Rus to easily type strings such as ⌃⇧⌘⌫. I mostly use these characters in three places:

  1. Correspondence with users
  2. Posts here
  3. Documentation

And it sure beats looking those characters up in UnicodeChecker or the Character Palette.

But I was never satisfied with the original set of key bindings that that file provides. The first problem that I had was that the arrow keys are emacs keys, not vi keys:

Solid Left (back) ⌃M + ⌃B
Solid Right (forward) ⌃M + ⌃F
Solid Up (previous) ⌃M + ⌃P
Solid Down (next) ⌃M + ⌃N
Dotted Left (back) ⌃M + B
Dotted Right (forward) ⌃M + F
Dotted Up (previous) ⌃M + P
Dotted Down (next) ⌃M + N

I’m a vi guy, so I wanted the hjkl arrangement for my arrows. So that was the first change I made.

I also added a bunch of characters, and reassigned some of them:

Additions:
Ch Description Key sequence
Return ^M + Return
Command ^M + C
Command (Apple) ^M + ^A
Option ^M + ^O
Control ^M + ^C
Shift ^M + ^S
Caps Lock ^M + ^S
Smiling face ^M + ^F
Frowning face ^M + F
× Multiplication sign ^M + ^X
Modifications:
Ch Description Key sequence Reason for modification
The arrows (solid and dotted), as noted above Because I prefer vi to emacs
Return ^M + ^R To free up ^E for Escape
Enter ^M + R To free up E for Eject
Home ^M + Shift-^H To free up ^H for ←
End ^M + Shift-^E To free up H for ⇠
Escape ^M + ^E To free up ^X for × (multiplication sign)
Eject ^M + E To keep Eject on the same key as Escape

In case you’re wondering about the division sign (÷): The OS already provides this as ⌥/, and always has been (all the way back to 1984). I didn’t need to add it.

Also, in case you’re wondering about “Apple” above: The Apple logo is ⇧⌥K, so I didn’t need to add that, either. (Plus, I never use it.)

File: DefaultKeyBinding.dict.bz2

This file contains all of the above bindings, as well as the ones from Jacob Rus’ original that I didn’t change.

To install it, put it in the KeyBindings folder in the Library folder in your Home folder. You may need to create the folder. The bindings are loaded on application launch by Cocoa, so you will need to relaunch any already-running applications in order to use the bindings in them.

Quickie: Finding an svn conflict marker with vim

Tuesday, February 12th, 2008

/[<=>]\{7\}

Weirdest compiler warning I’ve ever seen

Saturday, February 9th, 2008

Here’s the code:

[[GrowlApplicationController sharedController] setQuitAfterOpen:YES];

And here’s the warning:

Core/Source/GrowlApplicationBridgePathway.m:43: warning: ‘GrowlPreferencesController’ may not respond to ‘-setQuitAfterOpen:’

Wait, what? I said GrowlApplicationController, not GrowlPreferencesController!

The problem is that the compiler is recognizing “sharedController” as a class method, and assuming that I’m using the class that it knows has it—which, in this case, is GrowlPreferencesController. GrowlPreferencesController is declared in its header, which is imported by the prefix header; GrowlApplicationController, meanwhile, is declared only with @class (in another header). So I think it’s assuming that GrowlApplicationController is a subclass or something.

However, +[GrowlPreferencesController sharedController] is typed as returning a GrowlPreferencesController *, so when I try to call a GrowlApplicationController method on it, it gives me that warning.

The fix is to #import "GrowlApplicationController.h" so that the compiler knows that GrowlApplicationController has a sharedController method of its own.