Class: Growl::UDP

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

Overview

Implements the UDP growl protocol used in growl 1.2 and older.

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"
LITTLE_ENDIAN =

Endianness of this machine

little_endian
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_PROTOCOL_VERSION =

Growl Protocol Version

1
GROWL_TYPE_REGISTRATION =

Growl Registration Packet Id

0
GROWL_TYPE_NOTIFICATION =

Growl Notification Packet Id

1
PORT =

Growl UDP Port

9887

Instance Method Summary collapse

Constructor Details

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

Creates a new Growl UDP 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.



120
121
122
123
124
125
126
127
128
129
# File 'lib/ruby-growl/udp.rb', line 120

def initialize(host, app_name, all_notifies, default_notifies = nil,
               password = nil)
  @socket = socket host
  @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



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/ruby-growl/udp.rb', line 219

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.bytesize
  packet << title.length
  packet << description.bytesize
  packet << @app_name.bytesize

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

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

  checksum = Digest::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.

Raises:



144
145
146
147
148
149
150
151
152
# File 'lib/ruby-growl/udp.rb', line 144

def notify(notify_type, title, message, priority = 0, sticky = false)
  raise Growl::Error, "Unknown Notification" unless
    @all_notifies.include? notify_type

  raise Growl::Error, "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.



157
158
159
# File 'lib/ruby-growl/udp.rb', line 157

def register
  send registration_packet
end

#registration_packetObject

Builds a Growl registration packet



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/ruby-growl/udp.rb', line 173

def registration_packet
  data = []
  data_format = ""

  packet = [
    GROWL_PROTOCOL_VERSION,
    GROWL_TYPE_REGISTRATION
  ]

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

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

  @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 = Digest::MD5.new << packet
  checksum.update @password unless @password.nil?

  packet << checksum.digest

  return packet
end

#send(packet) ⇒ Object

Sends a Growl packet



164
165
166
167
168
# File 'lib/ruby-growl/udp.rb', line 164

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?



259
260
261
# File 'lib/ruby-growl/udp.rb', line 259

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

#socket(host) ⇒ Object



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/ruby-growl/udp.rb', line 263

def socket host
  addrinfo = Addrinfo.udp host, PORT

  socket = Socket.new addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol

  if addrinfo.ip_address == '255.255.255.255' then
    socket.setsockopt :SOL_SOCKET, :SO_BROADCAST, true
  elsif Socket.respond_to?(:getifaddrs) and
          Socket.getifaddrs.any? do |ifaddr|
            ifaddr.broadaddr and
              ifaddr.broadaddr.ip_address == addrinfo.ip_address
          end then
    socket.setsockopt :SOL_SOCKET, :SO_BROADCAST, true
  end

  socket.connect addrinfo

  socket
end