In this post we will show you how to get Growl working in you MonoMac Application.  It is a little complicated at first but if you follow along we will have your MonoMac application growling in no time.  If you do not know what Growl is then have a look at the Growl About page and for developers you might want to take a look here Introduction to Growl.

Here is a list of requirements:

Growl Application

The Growl application has to be installed on your system before you can begin sending notifications from your application.

If you already have Growl installed then you will not need to install it again so you can skip that step.

Click on the link and wait for the download to finish.  Once it finishes mount the disk image if it does not do so automatically.

Double click on the Growl.pkg icon labeled ingeniously as Double-click to Install.

 Screen shot 2011-01-23 at 2.04.40 PM

You also might want to keep this image around if you decide to uninstall Growl.

Growl Developers SDK

The Growl SDK allows the developer to interact with the Growl Notification system from within your application.

Once you download the SDK and mount the disk image you will see the following:

Screen shot 2011-01-23 at 2.08.17 PM

You will need to copy the Frameworks folder to you somewhere you can access it later.  Remember the location because we will be using it later to complete the integration within your application.

  • Note: The SDK is delivered with two frameworks, one with an automatic Growl installer and another without.  Due to a bug in 1.2.1 with the WithInstaller framework we will only cover the main Growl.framework in this article but it is easily modified afterwards when they get it fixed. Screen shot 2011-01-23 at 2.12.28 PM

Sample Application Code

I have created a sample application called TwoMinuteGrowler to go along with this article so we can focus on only the parts to get Growl up and running.

The simple application is a Two-Minute Timer like could be used for the Getting Things Done ™.  It starts out with Two minutes on the clock and counts down to zero.  There will be a Growl notification sent at the start of two minutes, at the one minute mark, when the timer runs out of time and another sent if the timer is stopped before the two minutes is up.

Download the sample application, unzip it and click on the accompanying solution file to open it in MonoDevelop.

Screen shot 2011-01-23 at 2.36.56 PM

Linking the Framework

Linking in frameworks with MonoMac is not quite there yet within MonoDevelop so we have to jump through some hoops. 

OSX app bundles can include third party frameworks in the Contents/Frameworks folder of the .app that is created.  Unfortunately this is not quite automated within MonoDevelop so we have to do it manually.

What I decided to do was add a custom command to the application build to copy the framework to the write place.  This could also be done with a shell script if you want but decided to do it this way so we could get a chance to play with some of the environment variables made available in MonoDevelop.

Custom Command

Let’s go ahead and get the framework copied to the correct place which is needed before we can begin to load the framework within the application.

Right click on the TwoMinuteGrowler project and select Options from the popup panel.

Screen shot 2011-01-23 at 2.46.56 PM

This will open up the Options panel for the project.

Screen shot 2011-01-23 at 2.48.58 PM

In (Select a project operation) selection box select After Build

Screen shot 2011-01-23 at 2.50.36 PM

In the field labeled Command: copy and past the following:

mkdir -p ${TargetDir}/${SolutionName}.app/Contents/Frameworks; cp –r <LINK_TO_YOUR_GROWL_FRAMEWORK>/Frameworks/Growl.framework ${TargetDir}/${SolutionName}.app/Contents/Frameworks

Make sure you replace the <LINK_TO_YOUR_GROWL_FRAMEWORK> with the directory location where you copied the framework source above.

 Screen shot 2011-01-23 at 3.08.05 PM

Also; make sure the Run on external console check box is checked.  The framework was not being copied correctly if it was not run on an external console. 

What this does is creates the Contents/Frameworks directory in you .app file with mkdir -p ${TargetDir}/${SolutionName}.app/Contents/Frameworks;

Then it copies the Growl framework libraries into the .app contents.

Now when you build the solution you will see a terminal window open and you can see what is actually happening.

Screen shot 2011-01-23 at 3.11.09 PM

You can see if it worked by right clicking the TwoMinuteGrowler application like explained above but this time select the Open Containing Folder option.

Screen shot 2011-01-23 at 3.12.31 PM

Now navigate to the bin/Debug directory and right clicking on the TwoMinuteGrowler app file and select Show Package Contents.   You should have something like is shown below.

Screen shot 2011-01-23 at 3.15.50 PM

If not then you will need to check the output from the console window to see if there are any errors.  This has to be done before we can continue.

Loading the Growl Framework

Before moving on make sure that the Growl framework is in the Contents/Frameworks folder.  If not return to the previous section and then come back.  We will be waiting.

Ok so we now have the framework available to our application.  Now we just have to tell MonoMac to load the framework and make it available to the application.

Go back to the solution and double click on Main.cs  to open it in source view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Runtime;
using System.IO;
using System.Drawing;
using MonoMac.Foundation;
using MonoMac.AppKit;
using MonoMac.ObjCRuntime;
 
namespace TwoMinuteGrowler {
	class MainClass {
		static void Main ( string[] args ) {
 
			var baseAppPath = Directory.GetParent (Directory.GetParent (System.AppDomain.CurrentDomain.BaseDirectory).ToString ());
			//var growlPath = baseAppPath + &quot;/Frameworks/Growl-WithInstaller.framework/Growl-WithInstaller&quot;;
			var growlPath = baseAppPath + &quot;/Frameworks/Growl.framework/Growl&quot;;
 
			Dlfcn.dlopen (growlPath, 0);
 
			NSApplication.Init ();
			NSApplication.Main (args);
		}
	}
}

We need to add two using statements to the class on Line 3 System.IO to get directory paths and Line 7 a reference to MonoMac.ObjCRuntime so the we can dlopen the framework and make it available to MonoMac.

Line 13 we get a reference to our base Contents application path.

Line 15 we get a reference to our growl framework within our Frameworks.

Now here on Line 17 we load the framework BEFORE the NSApplication.Init().  I tried multiple places with no success until I finally had to ask on the MonoMac IRC channel to find the answer for this.  Once this is in place we can now access the Growl framework.

Notice on Line 14 we also have a reference to the Growl framework with Installer that you can uncomment and use later when the bug is fixed and made available to the public.

Growl Registration

According to the documentation we have to provide a way to register the the notifications to Growl.  This is where the the GrowlRegistrationTicket.plist comes in.

You will need to add a .plist to your project.  It does not have to be named GrowlRegistrationTicket.plist as we will be loading this programmatically and provided these definitions within the program delegate method.

Open the GrowlRegistrationTicket.plist to take a look at it.

Screen shot 2011-01-23 at 3.41.06 PM

You will see that we have filled out all the required information that the Growl documentation mentions as being necessary.  You can see that we will be providing three different Notifications from our program

  • Start
  • Stop
  • Info

Note: If you send a notification type that is not registered it will not be displayed.  You are forewarned and don’t say I did not tell you so.

To finish of the plist discussion make sure you mark the build action as Content.  Right click the .plist and from the popup menu select Build Action and from that menu select Content.  That should do it.

Screen shot 2011-01-23 at 4.25.53 PM

Screen shot 2011-01-23 at 4.26.44 PM

Sending Notfications with GrowlApplicationBridge

We are now ready to start sending notifications.  To see how we do that open the MainWindowController.cs file in source view by double clicking it.

I am not going to reproduce the whole code but will point out specific points in the program to watch out for.

We need to reference the Growl framework by a using statement.  You can see this on Line 7.

2
3
4
5
6
7
using System;
using System.Collections.Generic;
using System.Linq;
using MonoMac.Foundation;
using MonoMac.AppKit;
using MonoMac.Growl;

Now we turn our attention to the AwakeFromNib method.

35
36
37
38
39
40
		public override void AwakeFromNib () {
 
			GrowlApplicationBridge.WeakDelegate = this;
			counter = new CountDownTimer ();
			Bind ("countDown", counter, "timeLeft", null);
		}

Line 37 we establish ourselves as the Delegate that the bridge will use.  This is obligatory or you will not receive any messages.  This also has to be done so that we can register our Notifications from our .plist that we created in the previous section which we will see in a bit.

Line 38 we create our CountDowntimer object which takes care of the time.

Line 39 we bind ourselves to the CountDownTimers property so that we receive notifications when the property changes.  It will call our custom method “countDown”  when the timer is fired and changes the time left in our count down.

In our startStopAction action method associated to our button we find our first reference to sending notifications.

 

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
		partial void startStopAction ( NSButton sender ) {
 
			if ( sender.Title == "Start"; ) {
				counter.Start ();
				sender.Title = "Stop";
				GrowlApplicationBridge.Notify ("The two-minute rule is magic.", 
				                               "You now have two minutes to Get Your Things Done.", "Start", null, 0, false, null);
			} else {
				counter.Stop ();
				sender.Title = "Start";
				if ( counter.TimerMark.Minutes > 0 && counter.TimerMark.Seconds > 0 ) {
					GrowlApplicationBridge.Notify ("Action Completed", String.Format ("You still have {0} left.  Step back and breath.  " 
+ "Take a second and contemplate what you have achieved.  " 
+ "You'll be suprised how many two-minute actions you can " 
+ "perform even on your most critical projects", counter.TimeLeft), "Stop", null, 0, true, null);
				}
			}
		}

Line 47 we have GrowlApplicationBridge.Notify which passes a notification type of “Start”.  The Notify static method takes parameters as follows:

void Notify (string title, string description, string notifName, [NullAllowed] NSData iconData, int priority, bool isSticky, [NullAllowed] NSObject clickContext);
  • title – A title for this notification, “The two-minute rule is magic.” – Required
  • description – A description of the notification, which should provide more detail. – Required
  • notifName – A name for this notification. This must match a name which was in the GROWL_NOTIFICATIONS_ALL array in the dictionary returned by your delegate’s registrationDictionaryForGrowl method. – Required
  • iconData – An NSData object which is a representation of an NSImage to be shown with this notification. If it is nil, the default icon for your application will be used. If you want the notification to have no icon, supply an empty NSData ([NSData data]). – Optional
  • priority – A signed integer value for the priority of this notification. The default value is 0; priority ranges from -2 (“Very Low”) to 2 (“Emergency”). The effect of different priority settings varies by Growl display plugin. – Required pass 0 for no priority.
  • isSticky – If true, the notification will remain on screen until clicked, if supported by the display plugin. The default behavior is for the notification to fade out after a delay. Optional; pass false for the notification to behave normally, without “stickiness”.
  • clickContext – If the delegate implements the growlNotificationWasClicked: method (see above), this context will be passed back to the delegate if the notification is clicked. Optional; pass nil to not receive click notifications for this notification.

With the information you now have at hand you should be able to figure out the rest of the program and the notifications that are sent.

Registering the Growl Dictionary

Now all that is left is taking care of how we register the notifications that we set up in the GrowlRegistrationTicket.plist item.  We do this by listening for the messages registrationDictionaryForGrowl sent to the bridges delegate which just happens to be us.  Here is the code for that.

79
80
81
82
83
84
		[Export("registrationDictionaryForGrowl")]
		NSDictionary RegistrationDictionaryForGrowl () {
			var regPath = NSBundle.MainBundle.PathForResource ("GrowlRegistrationTicket", "plist");
			var reg = NSDictionary.FromFile (regPath);
			return reg;
		}

We register for the messages by using the Export attribute for the method which you can see on line 79.

Next we get the path to our GrowlRegistrationTicket.plist and load it on lines 81 and 82. Then we return it back to the Growl bridge.

Now you should be able to growl to your heart’s content.

Summary

We learned the following:

  • Where to get the Growl package.
  • How to install the Growl package
  • How to link in the Growl framework with our application by using a custom command.
  • How to load the Growl framework to make it available to our application by using the dlopen.
  • How to set up a .plist to register our notifications
  • How to register ourselves as a delegate to the GrowlApplicationBridge and register the notifications that we are to use within our application.
  • And finally how to send a notification

Hopefully this has been fun and instructive.  There are a lot more to the Growl API like styles that I have not explored here.  If you do something with the styles or anything more advanced leave me a comment with a link as would like to see them.

As you can see it is kind of a pain to get this running with MonoDevelop as it is right now.  From some of the discussion on the IRC list it is on their To-Do list to make this process of private frameworks easier to handle.  Until then we are left with some cludges and work arounds.

Until next time have a great time making your applications growl.