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);
        }
    }
Be Sociable, Share!
  • RSS
  • Twitter
  • Facebook
  • LinkedIn
  • DeviantArt