ruby-growl 3.0 → 4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+