Monthly Archive for January, 2010

Fun With PyObjc: Growl Notification Hell

Most of the time PyObjc is wonderful to work in, I love the python libraries and language.

Other times it just makes me angry.

It seems, at least initially, that PyObjc (2.2b3, the default installation with 10.6 snow leopard) does not work with the Growl framework. You can register your application with growl fine, but if you try to push notifications to growl nothing happens.

Here is what I tried initially to post a growl notification in pyobjc:

GrowlApplicationBridge.notifyWithTitle_description_notificationName_iconData_priority_isSticky_clickContext_(u"Notification", "A description.", u"Notification", objc.nil, 0, objc.NO, objc.nil)

This does not work, and for a minute I thought I was the only one experiencing this problem (all open source pyobjc projects using growl had almost identical growl notification code) until I found this commit message on github. I found an associated discussion on the growl discussion group and was disheartened that the developer resorted using a command line version of growl to post notifications. However I decided to try one last thing, and checked the className of python objects passed through the objc bridge.

Here is a table of the objc className of different string creation methods in python:

  • unicode(“a string”) or u”a string”: OC_PythonUnicode
  • str(“a string”) or “a string”: OC_PythonString
  • r”a string”: OC_PythonString

Interestingly enough a python string (unicode, str, raw) is not ‘toll free’ bridged like CFString & NSString are. I guess this is to be expected and shouldn’t cause any problems since both OC_PythonString and OC_PythonUnicode are subclasses of NSString, the documentation even states “A Python unicode may be used anywhere a NSString is expected”. It seems that this is not always the case though. I tried one last thing before resorting to rewriting the code in objc:

GrowlApplicationBridge.notifyWithTitle_description_notificationName_iconData_priority_isSticky_clickContext_(NSString.stringWithString_("Notification"), NSString.stringWithString_("A description."), NSString.stringWithString_("Notification"), objc.nil, 0, objc.NO, objc.nil)

And it worked. Note that the created NSString’s don’t get converted by the bridge into a OC_PythonString object, they pass through as regular NSString’s. Note that you also write the above as the more python native:

GrowlApplicationBridge.notifyWithTitle_description_notificationName_iconData_priority_isSticky_clickContext_(NSString.stringWithString_("Notification"), NSString.stringWithString_("A description."), NSString.stringWithString_("Notification"), False, 0, False, None)

One other interesting that I found is that bridge throws a memory leak warning when running

NSString.alloc().initWithString_("Notification")

:

UninitializedDeallocWarning: leaking an uninitialized object of type NSPlaceholderString

My only guess is that because initWithString is called instead of

init

the bridge sees it as a uninitialized object (so I would guess that this warning is falsely thown). Unfortunately the PyObjc documentation is too sparse for me to able to determine what the real cause of the error is.

Create TGZ Automator Service

With Snow Leopard came some nice refinements to automator actions. However, all existing automator actions had to be recreated as services in order to be accessed through the Finder’s contextual menu. One automator action which I used fairly often was the create tgz workflow. I always found that action to be fairly useful so I recreated it as an action.

Another unfortunate change with Snow Leopard was the elimination of input managers. This eliminated the convenient F-Script injection functionality that was present in F-Script anywhere. Luckily this functionality has been recreated using an automator service. Nice work!

PyObjc & Seemingly Incorrect Return Value Mismatch

I’ve come to love developing in PyObjc. Sure, it has its quirks, isn’t the fastest, and isn’t the easiest to debug when something goes really wrong (aka imperfect integration with the xcode IDE); but it is fast to develop in. Almost every seemingly complex task that has come my way has been 50% completed by some open source python module that I can include in a commercial app without any licensing trouble.

However, the other day I came across a bug (at least, what I thought was a bug) that seemed very blaringly obvious and for a production version of a scripting bridge. When implementing the NSTableView delegate’s method

tableView_toolTipForCell_rect_tableColumn_row_mouseLocation_

I was getting an error when simply returning an NSString (

 TypeError: tableView:toolTipForCell:rect:tableColumn:row:mouseLocation:: Need tuple of 2 arguments as result

) and for the life of me I couldn’t figure out why. Apple’s documentation states that I should return an NSString and I couldn’t find any information about a special case in pyobjc for this specific method.

I guess I did not search hard enough through the uncentralized incomplete documentation spread across the web since one of the PyObjc devs was kind enough to respond with a simple explanation:

This is not a bug, the ‘rect’ argument is a pass-by-reference argument that
can be modified, hence you have to return two values: both the return value
itself and the (possibly updated) value of rect:

return (aToolTip, aRect)