First, how Logitech Control Center breaks Growl. I'll first present a
user-readable backstory; developers can skip ahead. Please let me
know if any part of this is unclear.
=== User-readable backstory ===
Mac OS X provides a facility called NSConnection that programs can
use to communicate with each other.
Applications that support Growl use the Growl framework, which uses
NSConnection to communicate with the Growl Helper App. GrowlHelperApp
(GHA) is the program that actually shows notifications on the screen.
Every application has one or more threads, and every thread gets
(among other things) one NSConnection called the default connection.
(It can create more NSConnections if it wants them.) Growl Helper
App, through the current version, uses its default connection to
listen for notifications from applications.
Every NSConnection has a “root object”, which is the application's
emissary to other applications. It's like when a country sends an
ambassador to another country: the root object comes from the
application, is the ambassador for the application, and represents it
to other applications. Other apps talk to that object when they want
the application it came from to do something.
Every NSConnection also has a name, which is like a telephone number:
you access another application's connection by asking for it by name,
like dialing the number.
In Growl's case, our root object is the Growl Application Bridge
pathway. When an application wants to post a Growl notification, the
framework looks up the connection named
“GrowlApplicationBridgePathway”, then gets its root object (which
is supposed to be the pathway), then sends that object some messages
to tell GHA the notification.
--
Logitech Control Center consists of several parts. One of them is an
input manager. These are supposed to be used to extend Mac OS X to
extend new methods of entering text (input). Instead, they're mostly
abused to add new features to every application or specific
applications (many Safari “plug-ins” or “extensions” are
actually input managers).
The reason that this works is that an input manager gets loaded into
each process (running program), just like a plug-in. Once there, it
can affect the process any way it wants—usually to add some kind of
feature. However, this is a high-wire act: an input manager can
easily break things if it steps on the application's toes in some way.
--
Finally, it's important to remember that GrowlHelperApp is an
application just like any other. It's a “faceless background
application” (i.e., it has neither a Dock tile nor a menu-bar), but
it's still just an application, subject to all the same rules. In
particular, input managers get loaded into GHA, just as they get
loaded into any other application.
=== End of user-readable backstory ===
The problem is that, when the application launches, Logitech Control
Center's input manager gets the default connection and sets its root
object to its Scroll Enhancer object.
This is a problem for Growl because GHA also uses the default
connection, and sets its root object at about the same time. But LCC
does this slightly later, so the root object it sets is the one that
holds.
So this is what happens in the Growl Helper App process:
1. GHA gets the default connection, sets its root object to the Growl
Application Bridge pathway object, and registers it under the name
“GrowlApplicationBridgePathway”.
2. The LCC input manager gets the default connection (same one), sets
its root object to the LCC Scroll Enhancer object, and registers the
connection under the name “com.Logitech.Control Center.Scroll
Enhancer/” followed by the process ID number.
The problem comes from the fact that *one connection* (GHA's default
connection) is now registered under two names:
“GrowlApplicationBridgePathway” and “com.Logitech.Control
Center.Scroll Enhancer/$PID”.
Then, when an application tries to send Growl a notification, it
looks up the connection for “GrowlApplicationBridgePathway”, and
gets GHA's default connection. Then it gets its root object to send
it the necessary messages, but the root object is Logitech's, not
Growl's, so this fails.
Result: You don't get any notifications.
Important distinction: What we're doing is not wrong. It's perfectly
OK for an application to get its own default connection, set its root
object to an object of its own creation, and expect this to work.
There is no good reason why this should ever break.
Logitech is the party in the wrong here. Input managers should never
attempt to share any resource with the application. That's borderline
acceptable when the input manager is actually intending to affect the
application in some way (e.g., it's borderline acceptable to get the
app's Edit menu to add a menu item to it), but this was an accident
(they didn't intend to affect Growl), and it's their fault for using
the app's default connection rather than creating one of their own.
They assumed that no application would use the default connection (or
didn't think of the possibility). This assumption is incorrect.
--
So, what can we do about it?
While it's not our bug to fix, and I'm still in favor of telling
people that LCC breaks things, we *can* work around the problem. I
see at least two ways:
1. We could move the registration of our default connection to happen
later (in applicationDidFinishLaunching:, rather than initSingleton:).
I see a couple problems with this. First, we'll need to move a *lot*
of code that has to happen after it; basically, about half of
initSingleton: will happen at the start of
applicationDidFinishLaunching: instead. Second, it's potentially not
a permanent fix: if LCC changes to also use
applicationDidFinishLaunching:, or if some other input manager starts
doing the same thing in that method, we're right back in the same boat.
2. We could create our own connection instead of using
defaultConnection.
This is basically the “give up defaultConnection to the broken input
manager” solution. There's nothing wrong with this that I know of,
and it definitely does work. It also is robust against any new
breakage that LCC may come up with in the future, unless they decide
to start actively breaking Growl.
I have this solution ready to commit. It's also a lot smaller than
#1, which makes it much less likely to regress anything. We could
even put this into 1.1.3 if we want.
Of course, there's one more thing we can do:
3. Tell users not to use Logitech Control Center.
This is not mutually exclusive with either of the other two. LCC 2.4
is broken, whether we work around it or not. It's still their bug,
and it's still possible (maybe even likely) that LCC breaks other apps.
Of course, if we can work around it in Growl, we don't need to be so
aggressive about warning users against LCC. It'd be possible again to
use Growl and LCC at the same time, so we wouldn't need to say
anything like “LCC broke Growl” on login.
Devs: Thoughts?