ruby-growl 3.0 → 4.0

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig CHANGED
Binary file
@@ -0,0 +1,16 @@
1
+ require 'autotest/restart'
2
+
3
+ Autotest.add_hook :initialize do |at|
4
+ at.add_exception '.git'
5
+ at.testlib = 'minitest/autorun'
6
+ at.unit_diff = 'cat'
7
+
8
+ def at.path_to_classname s
9
+ sep = File::SEPARATOR
10
+ f = s.sub(/^test#{sep}/, '').sub(/\.rb$/, '').split(sep)
11
+ f = f.map { |path| path.split(/_|(\d+)/).map { |seg| seg.capitalize }.join }
12
+ f = f.map { |path| path =~ /^Test/ ? path : "Test#{path}" }
13
+ f.join('::').gsub('Gntp', 'GNTP').gsub('Udp', 'UDP')
14
+ end
15
+ end
16
+
File without changes
@@ -1,3 +1,18 @@
1
+ === 4 / 2012-04-04
2
+
3
+ * API changes:
4
+ * Growl is now a wrapper for Growl::UDP (1.2 and older) and Growl::GNTP (1.3
5
+ and newer). The main difference is that notifications need to be
6
+ registered with Growl#add_notification instead of via Growl#initialize.
7
+ * Ruby 1.9.2 or newer is required to use ruby-growl
8
+
9
+ * Major enhancements
10
+ * Added GNTP protocol support for registration and notification including
11
+ application and notification icons and callbacks.
12
+ * Moved UDP protocol support to Growl::UDP
13
+ * Growl automatically determines if the growl server supports GNTP or UDP
14
+ and uses the best protocol.
15
+
1
16
  === 3.0 / 2010-09-11
2
17
 
3
18
  * Major enhancement
@@ -1,7 +1,13 @@
1
+ .autotest
1
2
  History.txt
2
3
  Manifest.txt
3
4
  README.txt
4
5
  Rakefile
5
6
  bin/growl
6
7
  lib/ruby-growl.rb
7
- test/test_ruby_growl.rb
8
+ lib/ruby-growl/gntp.rb
9
+ lib/ruby-growl/ruby_logo.rb
10
+ lib/ruby-growl/udp.rb
11
+ lib/uri/x_growl_resource.rb
12
+ test/test_growl_gntp.rb
13
+ test/test_growl_udp.rb
data/README.txt CHANGED
@@ -4,23 +4,27 @@
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- A pure-ruby growl notifier. ruby-growl allows you to perform Growl
8
- notification via UDP from machines without growl installed (for example,
9
- non-OSX machines).
7
+ A pure-ruby growl notifier for UDP and GNTP growl protocols. ruby-growl
8
+ allows you to perform Growl notifications from machines without growl
9
+ installed (for example, non-OSX machines).
10
10
 
11
- What's Growl? Growl is a really cool "global notification system for Mac OS
12
- X". See http://growl.info/
11
+ What is growl? Growl is a really cool "global notification system originally
12
+ for Mac OS X".
13
13
 
14
- See also the Ruby Growl bindings in Growl's subversion repository:
15
- http://growl.info/documentation/growl-source-install.php
14
+ You can receive Growl notifications on various platforms and send them from
15
+ any machine that runs Ruby.
16
16
 
17
- ruby-growl also contains a command-line notification tool named 'growl'. Where
18
- possible, it isoption-compatible with growlnotify. (Use --priority instead of
19
- -p.)
17
+ OS X: http://growl.info
18
+ Windows: http://www.growlforwindows.com/gfw/
19
+ Linux: http://github.com/mattn/growl-for-linux
20
+
21
+ ruby-growl also contains a command-line notification tool named 'growl'. It
22
+ is almost completely option-compatible with growlnotify. (All except for -p
23
+ is supported, use --priority instead.)
20
24
 
21
25
  == FEATURES/PROBLEMS:
22
26
 
23
- * Requires "Listen for incoming notifications" enabled
27
+ * Requires "Listen for incoming notifications" enabled on the growl server
24
28
 
25
29
  == INSTALL:
26
30
 
data/Rakefile CHANGED
@@ -3,10 +3,13 @@ require 'hoe'
3
3
 
4
4
  Hoe.plugin :git
5
5
  Hoe.plugin :minitest
6
+ Hoe.plugin :travis
6
7
 
7
8
  Hoe.spec 'ruby-growl' do
8
9
  developer 'Eric Hodel', 'drbrain@segment7.net'
9
10
 
10
- spec_extras['required_ruby_version'] = '> 1.8.6'
11
+ spec_extras['required_ruby_version'] = '>= 1.9.2'
12
+
13
+ extra_deps << ['uuid', '~> 2.3', '>= 2.3.5']
11
14
  end
12
15
 
data/bin/growl CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/local/bin/ruby -w
1
+ #!/usr/local/bin/ruby
2
2
 
3
3
  require 'ruby-growl'
4
4
 
@@ -1,149 +1,81 @@
1
- #!/usr/local/bin/ruby -w
2
-
3
1
  require 'digest/md5'
4
2
  require 'socket'
5
3
 
4
+ begin
5
+ require 'dnssd'
6
+ rescue LoadError
7
+ end
8
+
6
9
  ##
7
- # ruby-growl allows you to perform Growl notification via UDP from machines
8
- # without growl installed (for example, non-OSX machines).
10
+ # ruby-growl allows you to perform Growl notifications from machines without
11
+ # growl installed (for example, non-OSX machines).
9
12
  #
10
- # What's Growl? Growl is a really cool "global notification system for Mac OS
11
- # X". See http://growl.info/
13
+ # In version 4, the Growl class is a wrapper for Growl::UDP and Growl::GNTP.
14
+ # The GNTP protocol allows setting icons for notifications and callbacks. To
15
+ # upgrade from version 3 replace the notification names passed to initialize
16
+ # with a call to #add_notification.
12
17
  #
13
- # You'll need a Mac to recieve Growl notifications, but you can send Growl
14
- # notifications from any UDP-capable machine that runs Ruby.
18
+ # Basic usage:
15
19
  #
16
- # See also the Ruby Growl bindings in Growl's subversion repository:
17
- # http://growl.info/documentation/growl-source-install.php
20
+ # require 'ruby-growl'
18
21
  #
19
- # ruby-growl also contains a command-line notification tool named 'growl'. It
20
- # is almost completely option-compatible with growlnotify. (All except for -p
21
- # is supported, use --priority instead.)
22
+ # g = Growl.new "localhost", "ruby-growl"
23
+ # g.add_notification "ruby-growl Notification"
24
+ # g.notify "ruby-growl Notification", "It came from ruby-growl!",
25
+ # "Greetings!"
22
26
  #
23
- # = Synopsis
27
+ # For GNTP users, ruby-growl ships with the Ruby icon from the {Ruby Visual
28
+ # Identity Team}[http://rubyidentity.org/]:
24
29
  #
25
- # g = Growl.new "127.0.0.1", "ruby-growl",
26
- # ["ruby-growl Notification"]
27
- # g.notify "ruby-growl Notification", "It Came From Ruby-Growl",
28
- # "Greetings!"
30
+ # require 'ruby-growl'
31
+ # require 'ruby-growl/ruby_logo'
32
+ #
33
+ # g = Growl.new "localhost", "ruby-growl"
34
+ # g.add_notification("notification", "ruby-growl Notification",
35
+ # Growl::RUBY_LOGO_PNG)
36
+ # g.notify "notification", "It came from ruby-growl", "Greetings!"
37
+ #
38
+ # See Growl::UDP and Growl::GNTP for protocol-specific API.
29
39
 
30
40
  class Growl
31
41
 
32
42
  ##
33
- # The Ruby that ships with Tiger has a broken #pack, so 'v' means network
34
- # byte order instead of 'n'.
43
+ # ruby-growl version
35
44
 
36
- BROKEN_PACK = [1].pack("n") != "\000\001" # :nodoc:
37
-
38
- little_endian = [1].pack('V*') == [1].pack('L*')
39
- little_endian = !little_endian if BROKEN_PACK
45
+ VERSION = '4.0'
40
46
 
41
47
  ##
42
- # Endianness of this machine
43
-
44
- LITTLE_ENDIAN = little_endian
48
+ # Growl error base class
45
49
 
46
- ##
47
- # ruby-growl Version
48
-
49
- VERSION = '3.0'
50
-
51
- ##
52
- # Growl Network Registration Packet +pack+ Format
53
- #--
54
- # Format:
55
- #
56
- # struct GrowlNetworkRegistration {
57
- # struct GrowlNetworkPacket {
58
- # unsigned char version;
59
- # unsigned char type;
60
- # } __attribute__((packed));
61
- # unsigned short appNameLen;
62
- # unsigned char numAllNotifications;
63
- # unsigned char numDefaultNotifications;
64
- # /*
65
- # * Variable sized. Format:
66
- # * <application name><all notifications><default notifications><checksum>
67
- # * where <all notifications> is of the form (<length><name>){num} and
68
- # * <default notifications> is an array of indices into the all notifications
69
- # * array, each index being 8 bits.
70
- # */
71
- # unsigned char data[];
72
- # } __attribute__((packed));
73
-
74
- GNR_FORMAT = "CCnCCa*"
75
-
76
- GNR_FORMAT.gsub!(/n/, 'v') if BROKEN_PACK
77
-
78
- ##
79
- # Growl Network Notification Packet +pack+ Format
80
- #--
81
- # Format:
82
- #
83
- # struct GrowlNetworkNotification {
84
- # struct GrowlNetworkPacket {
85
- # unsigned char version;
86
- # unsigned char type;
87
- # } __attribute__((packed));
88
- # struct GrowlNetworkNotificationFlags {
89
- # unsigned reserved: 12;
90
- # signed priority: 3;
91
- # unsigned sticky: 1;
92
- # } __attribute__((packed)) flags; //size = 16 (12 + 3 + 1)
93
- # unsigned short nameLen;
94
- # unsigned short titleLen;
95
- # unsigned short descriptionLen;
96
- # unsigned short appNameLen;
97
- # /*
98
- # * Variable sized. Format:
99
- # * <notification name><title><description><application name><checksum>
100
- # */
101
- # unsigned char data[];
102
- # } __attribute__((packed));
103
-
104
- GNN_FORMAT = "CCnnnnna*"
105
-
106
- GNN_FORMAT.gsub!(/n/, 'v') if BROKEN_PACK
107
-
108
- # For litle endian machines the NetworkNotificationFlags aren't in network
109
- # byte order
110
-
111
- GNN_FORMAT.sub!((BROKEN_PACK ? 'v' : 'n'), 'v') if LITTLE_ENDIAN
112
-
113
- ##
114
- # Growl UDP Port
115
-
116
- GROWL_UDP_PORT = 9887
117
-
118
- ##
119
- # Growl Protocol Version
120
-
121
- GROWL_PROTOCOL_VERSION = 1
122
-
123
- ##
124
- # Growl Registration Packet Id
125
-
126
- GROWL_TYPE_REGISTRATION = 0
50
+ class Error < RuntimeError
51
+ end
127
52
 
128
53
  ##
129
- # Growl Notification Packet Id
54
+ # Password for authenticating and encrypting requests.
130
55
 
131
- GROWL_TYPE_NOTIFICATION = 1
56
+ attr_accessor :password
132
57
 
133
58
  ##
134
59
  # List of hosts accessible via dnssd
135
60
 
136
- def self.list
137
- require 'dnssd'
61
+ def self.list type
62
+ raise 'you must gem install dnssd' unless Object.const_defined? :DNSSD
63
+
64
+ require 'timeout'
138
65
 
139
66
  growls = []
140
67
 
141
- DNSSD.browse! '_growl._tcp' do |reply|
142
- next unless reply.flags.add?
68
+ begin
69
+ Timeout.timeout 10 do
70
+ DNSSD.browse! type do |reply|
71
+ next unless reply.flags.add?
143
72
 
144
- growls << reply
73
+ growls << reply
145
74
 
146
- break unless reply.flags.more_coming?
75
+ break unless reply.flags.more_coming?
76
+ end
77
+ end
78
+ rescue Timeout::Error
147
79
  end
148
80
 
149
81
  hosts = []
@@ -156,8 +88,6 @@ class Growl
156
88
  end
157
89
 
158
90
  hosts.uniq
159
- rescue LoadError
160
- raise 'you must gem install dnssd'
161
91
  end
162
92
 
163
93
  ##
@@ -172,10 +102,10 @@ class Growl
172
102
  end
173
103
 
174
104
  notify_type = options[:notify_type]
175
- notify_types = [notify_type]
176
105
 
177
- g = new(options[:host], options[:name], notify_types, notify_types,
178
- options[:password])
106
+ g = new options[:host], options[:name]
107
+ g.add_notification notify_type
108
+ g.password = options[:password]
179
109
 
180
110
  g.notify(notify_type, options[:title], message, options[:priority],
181
111
  options[:sticky])
@@ -188,15 +118,15 @@ class Growl
188
118
  require 'optparse'
189
119
 
190
120
  options = {
191
- :host => nil,
192
- :message => nil,
193
- :name => "ruby-growl",
194
- :notify_type => "ruby-growl Notification",
195
- :password => nil,
196
- :priority => 0,
197
- :sticky => false,
198
- :title => "",
199
- :list => false,
121
+ host: nil,
122
+ message: nil,
123
+ name: "ruby-growl",
124
+ notify_type: "ruby-growl Notification",
125
+ password: nil,
126
+ priority: 0,
127
+ sticky: false,
128
+ title: "",
129
+ list: false,
200
130
  }
201
131
 
202
132
  opts = OptionParser.new do |o|
@@ -277,7 +207,11 @@ Synopsis:
277
207
 
278
208
  if options[:list] then
279
209
  begin
280
- puts list
210
+ puts 'Growl GNTP hosts:'
211
+ puts list '_gntp._tcp'
212
+ puts
213
+ puts 'Growl UDP hosts:'
214
+ puts list '_growl._tcp'
281
215
  rescue => e
282
216
  raise unless e.message =~ /gem install dnssd/
283
217
 
@@ -290,165 +224,105 @@ Synopsis:
290
224
  end
291
225
 
292
226
  ##
293
- # Creates a new Growl notifier and automatically registers any notifications
294
- # with the remote machine.
295
- #
296
- # +host+ is the host to contact.
297
- #
298
- # +app_name+ is the name of the application sending the notifications.
299
- #
300
- # +all_notifies+ is a list of notification types your application sends.
301
- #
302
- # +default_notifies+ is a list of notification types that are turned on by
303
- # default.
304
- #
305
- # I'm not sure about what +default_notifies+ is supposed to be set to, since
306
- # there is a comment that says "not a subset of all_notifies" in the code.
307
- #
308
- # +password+ is the password needed to send notifications to +host+.
309
-
310
- def initialize(host, app_name, all_notifies, default_notifies = nil,
311
- password = nil)
312
- @socket = UDPSocket.open
313
- # FIXME This goes somewhere else
314
- @socket.connect host, GROWL_UDP_PORT
315
- @app_name = app_name
316
- @all_notifies = all_notifies
317
- @default_notifies = default_notifies.nil? ? all_notifies : default_notifies
318
- @password = password
319
-
320
- register
321
- end
322
-
323
- ##
324
- # Sends a notification.
227
+ # Creates a new growl basic notifier for +host+ and +application_name+.
325
228
  #
326
- # +notify_type+ is the type of notification to send.
229
+ # +growl_type+ is used to specify the type of growl server to connect to.
230
+ # The following values are allowed:
327
231
  #
328
- # +title+ is a title for the notification.
232
+ # nil::
233
+ # Automatically determine the growl type. If a GNTP server is not found
234
+ # then ruby-growl chooses UDP.
235
+ # 'GNTP'::
236
+ # Use GNTP connections. GNTP is supported by Growl 1.3 and newer and by
237
+ # Growl for Windows.
238
+ # 'UDP'::
239
+ # Uses the UDP growl protocol. UDP growl is supported by Growl 1.2 and
240
+ # older.
329
241
  #
330
- # +message+ is the body of the notification.
331
- #
332
- # +priority+ is the priorty of message to send.
333
- #
334
- # +sticky+ makes the notification stick until clicked.
242
+ # You can use <tt>growl --list</tt> to see growl servers on your local
243
+ # network.
335
244
 
336
- def notify(notify_type, title, message, priority = 0, sticky = false)
337
- raise "Unknown Notification" unless @all_notifies.include? notify_type
338
- raise "Invalid Priority" unless priority >= -2 and priority <= 2
245
+ def initialize host, application_name, growl_type = nil
246
+ @host = host
247
+ @application_name = application_name
339
248
 
340
- send notification_packet(notify_type, title, message, priority, sticky)
341
- end
249
+ @notifications = {}
250
+ @password = nil
342
251
 
343
- ##
344
- # Registers the notification types with +host+.
345
-
346
- def register
347
- send registration_packet
252
+ @growl_type = choose_implementation growl_type
348
253
  end
349
254
 
350
255
  ##
351
- # Sends a Growl packet
256
+ # Adds a notification named +name+ to the basic notifier. For GNTP servers
257
+ # you may specify a +display_name+ and +icon+ and set the default +enabled+
258
+ # status.
352
259
 
353
- def send(packet)
354
- set_sndbuf packet.length
355
- @socket.send packet, 0
356
- @socket.flush
260
+ def add_notification name, display_name = nil, icon = nil, enabled = true
261
+ @notifications[name] = display_name, icon, enabled
357
262
  end
358
263
 
359
- ##
360
- # Builds a Growl registration packet
361
-
362
- def registration_packet
363
- length = 0
364
- data = []
365
- data_format = ""
366
-
367
- packet = [
368
- GROWL_PROTOCOL_VERSION,
369
- GROWL_TYPE_REGISTRATION
370
- ]
264
+ def choose_implementation type # :nodoc:
265
+ raise ArgumentError,
266
+ "type must be \"GNTP\", \"UDP\" or nil; was #{type.inspect}" unless
267
+ ['GNTP', 'UDP', nil].include? type
371
268
 
372
- packet << @app_name.bytesize
373
- packet << @all_notifies.length
374
- packet << @default_notifies.length
269
+ return type if type
375
270
 
376
- data << @app_name
377
- data_format = "a#{@app_name.bytesize}"
378
-
379
- @all_notifies.each do |notify|
380
- data << notify.length
381
- data << notify
382
- data_format << "na#{notify.length}"
383
- end
384
-
385
- @default_notifies.each do |notify|
386
- data << @all_notifies.index(notify) if @all_notifies.include? notify
387
- data_format << "C"
388
- end
271
+ TCPSocket.open @host, Growl::GNTP::PORT do end
389
272
 
390
- data_format.gsub!(/n/, 'v') if BROKEN_PACK
391
-
392
- data = data.pack data_format
393
-
394
- packet << data
395
-
396
- packet = packet.pack GNR_FORMAT
397
-
398
- checksum = Digest::MD5.new << packet
399
- checksum.update @password unless @password.nil?
400
-
401
- packet << checksum.digest
402
-
403
- return packet
273
+ 'GNTP'
274
+ rescue SystemCallError
275
+ 'UDP'
404
276
  end
405
277
 
406
278
  ##
407
- # Builds a Growl notification packet
408
-
409
- def notification_packet(name, title, description, priority, sticky)
410
- flags = 0
411
- data = []
412
-
413
- packet = [
414
- GROWL_PROTOCOL_VERSION,
415
- GROWL_TYPE_NOTIFICATION,
416
- ]
279
+ # Sends a notification of type +name+ with the given +title+, +message+,
280
+ # +priority+ and +sticky+ settings.
281
+
282
+ def notify name, title, message, priority = 0, sticky = false
283
+ case @growl_type
284
+ when 'GNTP' then
285
+ notify_gntp name, title, message, priority, sticky
286
+ when 'UDP' then
287
+ notify_udp name, title, message, priority, sticky
288
+ else
289
+ raise Growl::Error, "bug, unknown growl type #{@growl_type.inspect}"
290
+ end
417
291
 
418
- flags = 0
419
- flags |= ((0x7 & priority) << 1) # 3 bits for priority
420
- flags |= 1 if sticky # 1 bit for sticky
292
+ self
293
+ end
421
294
 
422
- packet << flags
423
- packet << name.bytesize
424
- packet << title.length
425
- packet << description.bytesize
426
- packet << @app_name.bytesize
295
+ def notify_gntp name, title, message, priority, sticky # :nodoc:
296
+ growl = Growl::GNTP.new @host, @application_name
297
+ growl.password = @password
427
298
 
428
- data << name
429
- data << title
430
- data << description
431
- data << @app_name
299
+ @notifications.each do |name, details|
300
+ growl.add_notification name, *details
301
+ end
432
302
 
433
- packet << data.join
434
- packet = packet.pack GNN_FORMAT
303
+ growl.register
435
304
 
436
- checksum = Digest::MD5.new << packet
437
- checksum.update @password unless @password.nil?
305
+ growl.notify name, title, message, priority, sticky
306
+ end
438
307
 
439
- packet << checksum.digest
308
+ def notify_udp name, title, message, priority, sticky # :nodoc:
309
+ all_notifications = @notifications.keys
310
+ default_notifications = @notifications.select do |name, (_, _, enabled)|
311
+ enabled
312
+ end.map do |name,|
313
+ name
314
+ end
440
315
 
441
- return packet
442
- end
316
+ notification = all_notifications.first
443
317
 
444
- ##
445
- # Set the size of the send buffer
446
- #--
447
- # Is this truly necessary?
318
+ growl = Growl::UDP.new(@host, @application_name, all_notifications,
319
+ default_notifications, @password)
448
320
 
449
- def set_sndbuf(length)
450
- @socket.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDBUF, length
321
+ growl.notify name, title, message, priority, sticky
451
322
  end
452
323
 
453
324
  end
454
325
 
326
+ require 'ruby-growl/gntp'
327
+ require 'ruby-growl/udp'
328
+