Sending Growl Notifications From CSharp
// December 8th, 2009 // Useful Code
Growl is much more popular amongst Mac users but its not totally uncommon to have it installed on a Windows machine too. Below is a class to send network notifications to a remote machine running Growl. I actually use this to send remote notifications to me at work when serious errors occur.
//Class
public class NetworkNotification
{
private System.Net.Sockets.UdpClient mClient = new UdpClient();
public string ApplicationName { get; set; }
public string GrowlServer { get; set; }
public int GrowlPort { get; set; }
public string GrowlPassword { get; set; }
public NetworkNotification(string ApplicationName, string Server, int Port, string Password)
{
this.ApplicationName = ApplicationName;
this.GrowlServer = Server;
this.GrowlPort = Port;
this.GrowlPassword = Password;
}
public void RegisterApplication(GrowlNotification[] Notifications)
{
List<byte> tmpPacket = new List<byte>();
//SETUP THE PACKET VERSION ACCORDING TO GROWL STD
tmpPacket.Add((byte)1); //1 GROWL_PROTOCOL_VERSION
tmpPacket.Add((byte)0); //0 GROWL_TYPE_REGISTRATION
//APPLICATION NAME LENGTH AS UNSIGNED INTEGER
//BITCONVERTER REVERSES THE ARRAY FOR SOME REASON SO WE SWITCH IT BACK WITH REVERSE
tmpPacket.AddRange(BitConverter.GetBytes((UInt16)ApplicationName.Length).Reverse());
//NUMBER OF NOTIFICATIONS THIS APPLICATION SUPPORTS
tmpPacket.Add((byte)Notifications.Length);
//NUMBER OF NOTIFICATIONS WHICH ARE ENABLED BY DEFAULT
tmpPacket.Add((byte)(from a in Notifications where a.EnabledByDefault == true select a).Count());
//APPLICATION NAME WHICH NEEDS TO BE INSERTED
tmpPacket.AddRange(System.Text.UTF8Encoding.UTF8.GetBytes(ApplicationName));
//ADD EACH NOTIFIACITON TO THE LIST
foreach (GrowlNotification tmpNotification in Notifications)
{
tmpPacket.AddRange(BitConverter.GetBytes((UInt16)tmpNotification.Name.Length).Reverse());
tmpPacket.AddRange(UTF8Encoding.UTF8.GetBytes(tmpNotification.Name));
}
//ADD EACH OF THE ENABLED NOTIFIACTIONS TO THE LIST
for (int i = 0; i < Notifications.Length; i++ )
{
if(Notifications[i].EnabledByDefault)
tmpPacket.Add((byte)i);
}
//COMPUTE THE HASH FOR THIS PACKET WHICH = HASH OF PACKET+PASSWORD
tmpPacket.AddRange(ComputeHash(tmpPacket.ToArray(), GrowlPassword));
//SEND THE PACKET TO THE CLIENT SYSTEM
mClient.Send(tmpPacket.ToArray(), tmpPacket.Count, new IPEndPoint(Dns.GetHostAddresses(GrowlServer).FirstOrDefault(), GrowlPort));
}
public void SendNotification(string NotificationName, string Title, string Description, NotificationPriority Priority, bool Sticky)
{
List<byte> tmpPacket = new List<byte>();
//SETUP THE PACKET VERSION ACCORDING TO GROWL STD
tmpPacket.Add((byte)1); //1 GROWL_PROTOCOL_VERSION
tmpPacket.Add((byte)1); //1 GROWL_TYPE_NOTIFICATION
//FLAGS ??
Int16 tmpFlags = (Int16)((((int)Priority) & 7) * 2);
if (Priority < 0)
tmpFlags |= 8;
if (Sticky)
tmpFlags |= 256;
tmpPacket.AddRange(BitConverter.GetBytes(tmpFlags).Reverse());
//LENGTH OF THE NOTIFICATION NAME BEING SENT
tmpPacket.AddRange(BitConverter.GetBytes((UInt16)NotificationName.Length).Reverse());
//LENGTH OF THE TITLE BEING SENT
tmpPacket.AddRange(BitConverter.GetBytes((UInt16)Title.Length).Reverse());
//LENGTH OF THE DESCRIPTION
tmpPacket.AddRange(BitConverter.GetBytes((UInt16)Description.Length).Reverse());
//LENGTH OF THE APPLICATION NAME
tmpPacket.AddRange(BitConverter.GetBytes((UInt16)ApplicationName.Length).Reverse());
//NOTIFICATION NAME BEING SENT
tmpPacket.AddRange(UTF8Encoding.UTF8.GetBytes(NotificationName));
//TITLE BEING SENT
tmpPacket.AddRange(UTF8Encoding.UTF8.GetBytes(Title));
//DESCRIPTION BEING SENT
tmpPacket.AddRange(UTF8Encoding.UTF8.GetBytes(Description));
//APPLICATION NAME BEING SENT
tmpPacket.AddRange(UTF8Encoding.UTF8.GetBytes(ApplicationName));
//COMPUTE THE PACKET HASH WHICH = PACKET+PASSWORD
tmpPacket.AddRange(ComputeHash(tmpPacket.ToArray(), GrowlPassword));
//SEND THE PACKET
mClient.Send(tmpPacket.ToArray(), tmpPacket.Count, new IPEndPoint(Dns.GetHostAddresses(GrowlServer).FirstOrDefault(), GrowlPort));
}
private IEnumerable<byte> ComputeHash(byte[] Packet, string Password)
{
List<byte> tmpHash = new List<byte>(Packet);
tmpHash.AddRange(System.Text.UTF8Encoding.UTF8.GetBytes(Password));
return MD5.Create().ComputeHash(tmpHash.ToArray());
}
}
public struct GrowlNotification
{
public string Name;
public bool EnabledByDefault;
}
public enum NotificationPriority
{
VeryLow = -2,
Moderate = -1,
Normal = 0,
High = 1,
Emergency = 2
}
//Usage
class Program
{
static void Main(string[] args)
{
string[] NotificationTypes = { "Notify Type 1", "Notify Type 2" };
NetworkNotification tmpNotify = new GrowlNotifier.NetworkNotification("My Test Library", "10.5.10.62", 9887, "foo");
tmpNotify.RegisterApplication((from a in NotificationTypes select new GrowlNotification() { Name = a, EnabledByDefault = true }).ToArray());
tmpNotify.SendNotification(NotificationTypes[0], "This is a title", "This is a description", NotificationPriority.Normal, false);
}
}
Michael E. Chancey Jr. Software Engineer Extraordinaire