Class: Growl

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby-growl.rb

Overview

ruby-growl allows you to perform Growl notification via UDP from machines without growl installed (for example, non-OSX machines).

What’s Growl? Growl is a really cool “global notification system for Mac OS X”. See growl.info/

You’ll need a Mac to recieve Growl notifications, but you can send Growl notifications from any UDP-capable machine that runs Ruby.

See also the Ruby Growl bindings in Growl’s subversion repository: growl.info/documentation/growl-source-install.php

ruby-growl also contains a command-line notification tool named ‘growl’. It is almost completely option-compatible with growlnotify. (All except for -p is supported, use –priority instead.)

Synopsis

g = Growl.new "127.0.0.1", "ruby-growl",
              ["ruby-growl Notification"]
g.notify "ruby-growl Notification", "It Came From Ruby-Growl",
         "Greetings!"

Constant Summary collapse

BROKEN_PACK =

The Ruby that ships with Tiger has a broken #pack, so ‘v’ means network byte order instead of ‘n’.

[1].pack("n") != "\000\001"
VERSION =

ruby-growl Version

"1.0.1"
GNR_FORMAT =

Growl Network Registration Packet pack Format – Format:

struct GrowlNetworkRegistration {
  struct GrowlNetworkPacket {
    unsigned char version;
    unsigned char type;
  } __attribute__((packed));
  unsigned short appNameLen;
  unsigned char numAllNotifications;
  unsigned char numDefaultNotifications;
  /*
   *  Variable sized. Format:
   *  <application name><all notifications><default notifications><checksum>
   *  where <all notifications> is of the form (<length><name>){num} and
   *  <default notifications> is an array of indices into the all notifications
   *  array, each index being 8 bits.
   */
  unsigned char data[];
} __attribute__((packed));
"CCnCCa*"
GNN_FORMAT =

Growl Network Notification Packet pack Format – Format:

struct GrowlNetworkNotification {
  struct GrowlNetworkPacket {
    unsigned char version;
    unsigned char type;
  } __attribute__((packed));
  struct GrowlNetworkNotificationFlags {
    unsigned reserved: 12;
    signed   priority: 3;
    unsigned sticky:   1;
  } __attribute__((packed)) flags; //size = 16 (12 + 3 + 1)
  unsigned short nameLen; 
  unsigned short titleLen;
  unsigned short descriptionLen;
  unsigned short appNameLen;
  /*
   *  Variable sized. Format:
   *  <notification name><title><description><application name><checksum>
   */
  unsigned char data[];
} __attribute__((packed));
"CCnnnnna*"
GROWL_UDP_PORT =

Growl UDP Port

9887
GROWL_PROTOCOL_VERSION =

Growl Protocol Version

1
GROWL_TYPE_REGISTRATION =

Growl Registration Packet Id

0
GROWL_TYPE_NOTIFICATION =

Growl Notification Packet Id

1

Instance Method Summary collapse

Constructor Details

#initialize(host, app_name, all_notifies, default_notifies = nil, password = nil) ⇒ Growl

Creates a new Growl notifier and automatically registers any notifications with the remote machine.

host is the host to contact.

app_name is the name of the application sending the notifications.

all_notifies is a list of notification types your application sends.

default_notifies is a list of notification types that are turned on by default.

I’m not sure about what default_notifies is supposed to be set to, since there is a comment that says “not a subset of all_notifies” in the code.

password is the password needed to send notifications to host.



140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/ruby-growl.rb', line 140

def initialize(host, app_name, all_notifies, default_notifies = nil,
               password = nil)
  @socket = UDPSocket.open
  # FIXME This goes somewhere else
  @socket.connect host, GROWL_UDP_PORT
  @app_name = app_name
  @all_notifies = all_notifies
  @default_notifies = default_notifies.nil? ? all_notifies : default_notifies
  @password = password

  register
end

Instance Method Details

#notification_packet(name, title, description, priority, sticky) ⇒ Object

Builds a Growl notification packet



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/ruby-growl.rb', line 241

def notification_packet(name, title, description, priority, sticky)
  flags = 0
  data = []

  packet = [
    GROWL_PROTOCOL_VERSION,
    GROWL_TYPE_NOTIFICATION,
  ]

  flags = 0
  flags |= ((0x7 & priority) << 1) # 3 bits for priority
  flags |= 1 if sticky # 1 bit for sticky

  packet << flags
  packet << name.length
  packet << title.length
  packet << description.length
  packet << @app_name.length

  data << name
  data << title
  data << description
  data << @app_name

  packet << data.join
  packet = packet.pack GNN_FORMAT

  checksum = MD5.new packet
  checksum.update @password unless @password.nil?

  packet << checksum.digest

  return packet
end

#notify(notify_type, title, message, priority = 0, sticky = false) ⇒ Object

Sends a notification.

notify_type is the type of notification to send.

title is a title for the notification.

message is the body of the notification.

priority is the priorty of message to send.

sticky makes the notification stick until clicked.



166
167
168
169
170
171
# File 'lib/ruby-growl.rb', line 166

def notify(notify_type, title, message, priority = 0, sticky = false)
  raise "Unknown Notification" unless @all_notifies.include? notify_type
  raise "Invalid Priority" unless priority >= -2 and priority <= 2

  send notification_packet(notify_type, title, message, priority, sticky)
end

#registerObject

Registers the notification types with host.



178
179
180
# File 'lib/ruby-growl.rb', line 178

def register
  send registration_packet
end

#registration_packetObject

Builds a Growl registration packet



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/ruby-growl.rb', line 194

def registration_packet
  length = 0
  data = []
  data_format = ""

  packet = [
    GROWL_PROTOCOL_VERSION,
    GROWL_TYPE_REGISTRATION
  ]

  packet << @app_name.length
  packet << @all_notifies.length
  packet << @default_notifies.length

  data << @app_name
  data_format = "a#{@app_name.length}"

  @all_notifies.each do |notify|
    data << notify.length
    data << notify
    data_format << "na#{notify.length}"
  end

  @default_notifies.each do |notify|
    data << @all_notifies.index(notify) if @all_notifies.include? notify
    data_format << "C"
  end

  data_format.gsub!(/n/, 'v') if BROKEN_PACK

  data = data.pack data_format

  packet << data

  packet = packet.pack GNR_FORMAT

  checksum = MD5.new packet
  checksum.update @password unless @password.nil?

  packet << checksum.digest

  return packet
end

#send(packet) ⇒ Object

Sends a Growl packet



185
186
187
188
189
# File 'lib/ruby-growl.rb', line 185

def send(packet)
  set_sndbuf packet.length
  @socket.send packet, 0
  @socket.flush
end

#set_sndbuf(length) ⇒ Object

Set the size of the send buffer – Is this truly necessary?



281
282
283
# File 'lib/ruby-growl.rb', line 281

def set_sndbuf(length)
  @socket.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDBUF, length
end