ruku 0.1
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/MIT-LICENSE +20 -0
- data/README.rdoc +96 -0
- data/Rakefile +33 -0
- data/bin/ruku +50 -0
- data/lib/ruku/clients/simple.rb +232 -0
- data/lib/ruku/clients/tk.rb +36 -0
- data/lib/ruku/clients/web.rb +117 -0
- data/lib/ruku/clients/web_static/css/ruku.css +196 -0
- data/lib/ruku/clients/web_static/images/box-medium.png +0 -0
- data/lib/ruku/clients/web_static/images/box-small.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/back-over.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/back.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/down-over.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/down.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/fwd-over.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/fwd.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/home-over.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/home.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/left-over.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/left.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/pause-over.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/pause.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/right-over.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/right.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/select-over.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/select.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/space1.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/space2.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/space3.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/up-over.png +0 -0
- data/lib/ruku/clients/web_static/images/remote/up.png +0 -0
- data/lib/ruku/clients/web_static/images/spacer.gif +0 -0
- data/lib/ruku/clients/web_static/index.html +203 -0
- data/lib/ruku/clients/web_static/js/jquery-1.4.2.js +154 -0
- data/lib/ruku/clients/web_static/js/ruku.js +447 -0
- data/lib/ruku/remote.rb +138 -0
- data/lib/ruku/remotes.rb +78 -0
- data/lib/ruku/storage.rb +77 -0
- data/lib/ruku.rb +5 -0
- data/ruku.gemspec +31 -0
- data/test/helper.rb +11 -0
- data/test/js/qunit.css +119 -0
- data/test/js/qunit.js +1069 -0
- data/test/js/runner.html +29 -0
- data/test/js/test_remote.js +37 -0
- data/test/js/test_remote_manager.js +186 -0
- data/test/js/test_remote_menu.js +208 -0
- data/test/js/test_util.js +15 -0
- data/test/test_remote.rb +89 -0
- data/test/test_remotes.rb +144 -0
- data/test/test_simple_client.rb +166 -0
- data/test/test_simple_storage.rb +70 -0
- data/test/test_web_client.rb +46 -0
- data/test/test_yaml_storage.rb +54 -0
- metadata +156 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Aaron Royer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
= Ruku -- Roku™ set-top box remote control, command line and web interfaces
|
2
|
+
|
3
|
+
== Installation
|
4
|
+
|
5
|
+
gem install ruku
|
6
|
+
|
7
|
+
== Usage
|
8
|
+
|
9
|
+
The following launches the web interface. See it at http://localhost:3030
|
10
|
+
|
11
|
+
ruku --web
|
12
|
+
|
13
|
+
You can use this to scan for or add boxes and start controlling them. I recommend
|
14
|
+
using the keyboard (super snappy controlling!). Arrow keys (and vi directional
|
15
|
+
keys) work, Space plays and pauses, Enter selects, and Esc is home.
|
16
|
+
|
17
|
+
You can also just use the command line. Ruku needs to know about your Roku
|
18
|
+
box(es). If you haven't added any boxes with the web interface, try:
|
19
|
+
|
20
|
+
ruku scan
|
21
|
+
|
22
|
+
This will try to scan your network to find boxes. Read on below if it didn't.
|
23
|
+
Assuming you have a least one box set up, you can start sending commands.
|
24
|
+
|
25
|
+
ruku pause # Play/pause
|
26
|
+
ruku left
|
27
|
+
ruku up
|
28
|
+
ruku select
|
29
|
+
ruku fwd # Fast forward
|
30
|
+
ruku back # Rewind
|
31
|
+
|
32
|
+
Known commands are: up down left right select home fwd back pause
|
33
|
+
|
34
|
+
Making an alias for 'ruku pause' is nice for quick pause/play while computing.
|
35
|
+
|
36
|
+
If scanning doesn't work for adding boxes then you'll have to figure out what
|
37
|
+
the IP of the box is - it's available in Settings -> Player Info on the box.
|
38
|
+
Then add it manually:
|
39
|
+
|
40
|
+
ruku add IP
|
41
|
+
|
42
|
+
Any method of adding boxes creates a '.ruku-boxes' file in your $HOME directory
|
43
|
+
that contains an IP or hostname per line (followed optionally by a colon and a
|
44
|
+
nickname for that box). You can edit or create this yourself. You can see all
|
45
|
+
known boxes with:
|
46
|
+
|
47
|
+
ruku list
|
48
|
+
|
49
|
+
For more help:
|
50
|
+
|
51
|
+
ruku --help
|
52
|
+
|
53
|
+
== Development
|
54
|
+
|
55
|
+
=== Source Repository
|
56
|
+
|
57
|
+
http://github.com/aaronroyer/ruku
|
58
|
+
|
59
|
+
Git clone URL is
|
60
|
+
|
61
|
+
* git://github.com/aaronroyer/ruku.git
|
62
|
+
|
63
|
+
=== Issues and Bug Reports
|
64
|
+
|
65
|
+
You can open issues at Github
|
66
|
+
|
67
|
+
* http://github.com/aaronroyer/ruku/issues
|
68
|
+
|
69
|
+
Or you can send me an email: aaronroyer@gmail.com
|
70
|
+
|
71
|
+
== Legal/Disclaimer
|
72
|
+
|
73
|
+
Roku and the Roku logo are trademarks of Roku Inc. in the United States and other countries.
|
74
|
+
|
75
|
+
Ruku is not made, supported, or endorsed by Roku Inc.
|
76
|
+
|
77
|
+
== License
|
78
|
+
|
79
|
+
Ruku is MIT licensed.
|
80
|
+
|
81
|
+
:include: MIT-LICENSE
|
82
|
+
|
83
|
+
= Other stuff
|
84
|
+
|
85
|
+
Author:: Aaron Royer <aaronroyer@gmail.com>
|
86
|
+
Requires:: Ruby 1.8.6 or later
|
87
|
+
License:: Copyright 2010 by Aaron Royer.
|
88
|
+
Released under an MIT-style license. See the LICENSE file
|
89
|
+
included in the distribution.
|
90
|
+
|
91
|
+
== Warranty
|
92
|
+
|
93
|
+
This software is provided "as is" and without any express or
|
94
|
+
implied warranties, including, without limitation, the implied
|
95
|
+
warranties of merchantability and fitness for a particular
|
96
|
+
purpose.
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << 'lib'
|
7
|
+
t.pattern = 'test/**/test_*.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
Rake::RDocTask.new do |rdoc|
|
11
|
+
rdoc.rdoc_dir = 'rdoc'
|
12
|
+
rdoc.title = 'ruku'
|
13
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
14
|
+
rdoc.rdoc_files.include('README*')
|
15
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'rcov/rcovtask'
|
20
|
+
Rcov::RcovTask.new do |t|
|
21
|
+
t.libs << 'test'
|
22
|
+
t.test_files = FileList['test/**/test_*.rb']
|
23
|
+
t.verbose = true
|
24
|
+
end
|
25
|
+
rescue LoadError
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Build gem package"
|
29
|
+
task :package => 'ruku.gemspec' do
|
30
|
+
sh "gem build ruku.gemspec"
|
31
|
+
end
|
32
|
+
|
33
|
+
task :default => [:test]
|
data/bin/ruku
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
begin
|
3
|
+
require 'ruku/clients/simple'
|
4
|
+
require 'ruku/clients/web'
|
5
|
+
rescue LoadError
|
6
|
+
# For testing and dev and stuff
|
7
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
8
|
+
require 'ruku/clients/simple'
|
9
|
+
require 'ruku/clients/web'
|
10
|
+
end
|
11
|
+
|
12
|
+
if ARGV.empty? || ARGV.include?('-h') || ARGV.include?('--help')
|
13
|
+
help = <<HELP
|
14
|
+
Usage: ruku COMMAND/OPERATION
|
15
|
+
|
16
|
+
Controller for Roku set top boxes. You will need to first scan the network for
|
17
|
+
boxes using 'ruku scan' or add boxes manually using 'ruku add HOST'. Once
|
18
|
+
'ruku list' shows an active box, you can start sending commands like
|
19
|
+
'ruku pause' (play/pause) or 'ruku left' (Roku remote left button).
|
20
|
+
|
21
|
+
COMMAND is a command to be sent to the active Roku box, if there is one.
|
22
|
+
Known Roku commands:
|
23
|
+
#{Ruku::Remote::KNOWN_COMMANDS.join(', ')}
|
24
|
+
(you can send unknown commands; the box should ignore)
|
25
|
+
The -c is available in case you need to explicitly send a command to the
|
26
|
+
Roku box, to disambiguate from a ruku operation (see below)
|
27
|
+
|
28
|
+
OPERATION is for managing the Roku boxes for ruku to use.
|
29
|
+
ruku operations:
|
30
|
+
scan Scan for Roku boxes on the network
|
31
|
+
list List Roku boxes
|
32
|
+
name NUM NAME Set the name of a box from the list
|
33
|
+
add HOST_OR_IP NAME Add a box with the HOST_OR_IP and (optional) NAME
|
34
|
+
|
35
|
+
Alternatively, use 'ruku --web' to start the server for the web client. You
|
36
|
+
should then be able to visit http://localhost:3030 to see the interface. You
|
37
|
+
can specify a different port with '-p PORT'.
|
38
|
+
|
39
|
+
Options:
|
40
|
+
-c, --force-roku-command Force send command to the active Roku box
|
41
|
+
--web Fire up the web client (port 3030 default)
|
42
|
+
-p, --port PORT Specify port (only use with --web)
|
43
|
+
-h, --help Show this message
|
44
|
+
HELP
|
45
|
+
puts help
|
46
|
+
elsif ARGV.include?('--web')
|
47
|
+
Ruku::Clients::Web.new.start
|
48
|
+
else
|
49
|
+
Ruku::Clients::Simple.new.run_from_command_line
|
50
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. ruku])
|
2
|
+
require 'optparse'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module Ruku
|
6
|
+
module Clients
|
7
|
+
# Provides a little wrapper around a Ruku::Remotes for ease of making
|
8
|
+
# a command line client or messing around in an IRB session
|
9
|
+
class Simple
|
10
|
+
OPERATION_NAMES = %w[scan list add remove name activate help]
|
11
|
+
|
12
|
+
attr_accessor :remotes
|
13
|
+
|
14
|
+
def initialize(rs=Ruku::Remotes.new)
|
15
|
+
@remotes = rs
|
16
|
+
end
|
17
|
+
|
18
|
+
# Run from the command line. This parses options as well as the command
|
19
|
+
# or Ruku operation to run.
|
20
|
+
def run_from_command_line
|
21
|
+
handle_exceptions do
|
22
|
+
remotes.load
|
23
|
+
handle_options
|
24
|
+
execute_command_from_command_line
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Checks the command line arguments for a command (options should have already
|
29
|
+
# been parsed and removed) and then sends a Roku command or runs an operation
|
30
|
+
# on the RemoteManager.
|
31
|
+
def execute_command_from_command_line
|
32
|
+
cmd = ARGV[0]
|
33
|
+
if not cmd
|
34
|
+
puts CMD_LINE_HELP
|
35
|
+
elsif OPERATION_NAMES.include?(cmd) && !options.force_command
|
36
|
+
begin
|
37
|
+
self.send(*ARGV)
|
38
|
+
rescue ArgumentError => ex
|
39
|
+
$stderr.puts "Wrong number of arguments (#{ARGV.size-1}) for operation: #{cmd}"
|
40
|
+
end
|
41
|
+
else
|
42
|
+
send_roku_command cmd
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Client options generally parsed from the command line
|
47
|
+
def options
|
48
|
+
@options ||= OpenStruct.new
|
49
|
+
end
|
50
|
+
|
51
|
+
# Parse and handle command line options
|
52
|
+
def handle_options
|
53
|
+
opts = OptionParser.new do |opts|
|
54
|
+
opts.on('-c', '--force-roku-command') { options.force_command = true }
|
55
|
+
end.parse!
|
56
|
+
end
|
57
|
+
|
58
|
+
# Send a command to the active box
|
59
|
+
def send_roku_command(cmd)
|
60
|
+
if remotes.empty?
|
61
|
+
raise UsageError, "No known Roku boxes\n" +
|
62
|
+
"Try 'ruku scan' to find them, or 'ruku add HOST NAME' to add one manually"
|
63
|
+
else
|
64
|
+
remotes.active.send_roku_command cmd
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Ruku command line "operations" follow
|
69
|
+
|
70
|
+
# Scan for boxes
|
71
|
+
def scan
|
72
|
+
remotes.boxes = Ruku::Remote.scan
|
73
|
+
remotes.each_with_index do |box, i|
|
74
|
+
box.name = "My Roku Box#{i == 1 ? i+1 : ''}"
|
75
|
+
end
|
76
|
+
if remotes.empty?
|
77
|
+
puts 'Did not find any Roku boxes'
|
78
|
+
else
|
79
|
+
puts 'Roku boxes found:'
|
80
|
+
remotes.each_with_index do |box, i|
|
81
|
+
print "#{i+1}. #{box.name || '(no name)'} at #{box.host}"
|
82
|
+
print "#{' <-- active' if i == remotes.active_index && remotes.size > 1}\n"
|
83
|
+
end
|
84
|
+
store
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# List the boxes we know about
|
89
|
+
def list
|
90
|
+
if remotes.empty?
|
91
|
+
puts "No Roku boxes known\n" +
|
92
|
+
"Use the scan or add operations to find or add boxes"
|
93
|
+
else
|
94
|
+
puts 'Roku boxes:'
|
95
|
+
remotes.each_with_index do |box, i|
|
96
|
+
print "#{i+1}. #{box.name || '(no name)'} at #{box.host}"
|
97
|
+
print "#{' <-- active' if i == remotes.active_index}\n"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Add a box
|
103
|
+
def add(host=nil, name='My Roku Box')
|
104
|
+
raise UsageError, 'Must specify host of box to add' if not host
|
105
|
+
|
106
|
+
if existing = remotes.find_by_host(host)
|
107
|
+
existing.name = name
|
108
|
+
else
|
109
|
+
remotes.add(Ruku::Remote.new(host, name))
|
110
|
+
end
|
111
|
+
store
|
112
|
+
puts "Added remote with host: #{host} and name: #{name}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Remove a box with the given number (from the list operation) or hostname
|
116
|
+
def remove(number=nil)
|
117
|
+
raise UsageError, 'Must specify number from boxes list or hostname/IP address' if not number
|
118
|
+
|
119
|
+
prev_count = remotes.size
|
120
|
+
msg = 'Box '
|
121
|
+
if number.is_a?(Integer) || number =~ /^\d+$/
|
122
|
+
index = number.to_i - 1
|
123
|
+
remotes.boxes.delete_at(index)
|
124
|
+
msg << (index + 1).to_s
|
125
|
+
else
|
126
|
+
remotes.remove(number)
|
127
|
+
msg << "with IP/host #{number}"
|
128
|
+
end
|
129
|
+
msg << ' removed'
|
130
|
+
|
131
|
+
if prev_count == remotes.size + 1
|
132
|
+
remotes.store
|
133
|
+
puts msg
|
134
|
+
else
|
135
|
+
puts "Could not remove box: #{number}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def name(number=nil, name=nil)
|
140
|
+
raise UsageError, 'Must specify number from remotes list or IP/hostname' if not number
|
141
|
+
raise UsageError, 'Must specify name for box' if not name
|
142
|
+
|
143
|
+
msg = 'Box '
|
144
|
+
if number.is_a?(Integer) || number =~ /^\d+$/
|
145
|
+
self[number.to_i].name = name
|
146
|
+
msg << (number).to_s
|
147
|
+
else
|
148
|
+
remotes.find_by_host(number).name = name
|
149
|
+
msg << "with IP/host #{number}"
|
150
|
+
end
|
151
|
+
msg << " renamed to #{name}"
|
152
|
+
store
|
153
|
+
puts msg
|
154
|
+
end
|
155
|
+
|
156
|
+
def activate(number=nil)
|
157
|
+
raise UsageError, 'Must specify number from remotes list or IP/hostname' if not number
|
158
|
+
|
159
|
+
msg = 'Box '
|
160
|
+
box = if number.is_a?(Integer) || number =~ /^\d+$/
|
161
|
+
msg << (number).to_s
|
162
|
+
self[number.to_i]
|
163
|
+
else
|
164
|
+
msg << "with IP/host #{number}"
|
165
|
+
remotes.find_by_host(number)
|
166
|
+
end
|
167
|
+
|
168
|
+
if box
|
169
|
+
remotes.set_active(box)
|
170
|
+
store
|
171
|
+
puts msg + ' activated for use'
|
172
|
+
else
|
173
|
+
puts 'Unknown box specified'
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def help
|
178
|
+
puts CMD_LINE_HELP
|
179
|
+
end
|
180
|
+
|
181
|
+
# Methods not directly available from the command line
|
182
|
+
|
183
|
+
# Get remotes using 1-based index for the command line
|
184
|
+
def [](num)
|
185
|
+
remotes[num-1]
|
186
|
+
end
|
187
|
+
|
188
|
+
# Assign and store remotes using 1-based index for the command line
|
189
|
+
def []=(num, box)
|
190
|
+
remotes[num-1] = box
|
191
|
+
store
|
192
|
+
end
|
193
|
+
|
194
|
+
def store
|
195
|
+
remotes.store
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
def handle_exceptions
|
201
|
+
begin
|
202
|
+
yield
|
203
|
+
rescue SystemExit
|
204
|
+
exit
|
205
|
+
rescue UsageError => ex
|
206
|
+
$stderr.puts ex.message
|
207
|
+
exit 1
|
208
|
+
rescue OptionParser::InvalidOption => ex
|
209
|
+
$stderr.puts ex.message
|
210
|
+
exit 1
|
211
|
+
rescue Exception => ex
|
212
|
+
display_error_message ex
|
213
|
+
exit 1
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def display_error_message(ex)
|
218
|
+
msg = <<MSG
|
219
|
+
The Ruku application has aborted! If this is unexpected, you may want to open
|
220
|
+
an issue at github.com/aaronroyer/ruku to get a possible bug fixed. If you do,
|
221
|
+
please include the debug information below.
|
222
|
+
MSG
|
223
|
+
$stderr.puts msg
|
224
|
+
$stderr.puts ex.message
|
225
|
+
$stderr.puts ex.backtrace
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
class UsageError < Exception
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
module Ruku
|
3
|
+
class TkClient
|
4
|
+
def initialize
|
5
|
+
sm = YAMLStorage.new
|
6
|
+
sm.load
|
7
|
+
@remote = Remote.new(sm.boxes.first.host, 8080)
|
8
|
+
|
9
|
+
launch
|
10
|
+
end
|
11
|
+
|
12
|
+
def launch
|
13
|
+
require 'tk'
|
14
|
+
root = TkRoot.new() { title "Roku Remote"}
|
15
|
+
root.bind('KeyPress-Left'){
|
16
|
+
@remote.left
|
17
|
+
}
|
18
|
+
root.bind('KeyPress-Right'){
|
19
|
+
@remote.right
|
20
|
+
}
|
21
|
+
root.bind('KeyPress-Up'){
|
22
|
+
@remote.up
|
23
|
+
}
|
24
|
+
root.bind('KeyPress-Down'){
|
25
|
+
@remote.down
|
26
|
+
}
|
27
|
+
root.bind('KeyPress-Return'){
|
28
|
+
@remote.select
|
29
|
+
}
|
30
|
+
root.bind('KeyPress-space'){
|
31
|
+
@remote.pause
|
32
|
+
}
|
33
|
+
Tk.mainloop
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. ruku])
|
2
|
+
require 'rubygems'
|
3
|
+
require 'json'
|
4
|
+
require 'webrick'
|
5
|
+
|
6
|
+
include WEBrick
|
7
|
+
|
8
|
+
module Ruku
|
9
|
+
module Clients
|
10
|
+
class Web
|
11
|
+
attr_reader :options
|
12
|
+
|
13
|
+
def initialize(opts={})
|
14
|
+
@options = OpenStruct.new(opts)
|
15
|
+
handle_options
|
16
|
+
|
17
|
+
server_options = {
|
18
|
+
:Port => options.port || 3030,
|
19
|
+
:DocumentRoot => File.join(File.dirname(__FILE__), 'web_static')
|
20
|
+
}
|
21
|
+
@server = HTTPServer.new(server_options)
|
22
|
+
|
23
|
+
['INT', 'TERM'].each do |signal|
|
24
|
+
trap(signal) { @server.shutdown }
|
25
|
+
end
|
26
|
+
|
27
|
+
@server.mount("/ajax", AjaxServlet)
|
28
|
+
end
|
29
|
+
|
30
|
+
def start
|
31
|
+
@server.start
|
32
|
+
end
|
33
|
+
|
34
|
+
# Parse and handle command line options
|
35
|
+
def handle_options
|
36
|
+
OptionParser.new do |opts|
|
37
|
+
opts.on('-p', '--port PORT') {|p| options.port = p.to_i }
|
38
|
+
opts.on('--web') { } # ignore
|
39
|
+
end.parse!
|
40
|
+
end
|
41
|
+
|
42
|
+
class AjaxServlet < HTTPServlet::AbstractServlet
|
43
|
+
def do_GET(req, resp)
|
44
|
+
@remote_manager ||= Remotes.new
|
45
|
+
@remote_manager.load
|
46
|
+
|
47
|
+
cmd = req.query['command']
|
48
|
+
action = req.query['action']
|
49
|
+
if cmd
|
50
|
+
resp.body = run_command(cmd, req.query['host'])
|
51
|
+
raise HTTPStatus::OK
|
52
|
+
elsif action
|
53
|
+
resp.body = perform_action(action, req.query['data'])
|
54
|
+
raise HTTPStatus::OK
|
55
|
+
else
|
56
|
+
raise HTTPStatus::PreconditionFailed.new("Missing parameter: 'command' or 'action'")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
# Send a regular remote command to the active remote or the remote with the specified host
|
63
|
+
def run_command(cmd, host=nil)
|
64
|
+
remote = host.nil? ? @remote_manager.active : @remote_manager.find_by_host(host)
|
65
|
+
if remote
|
66
|
+
remote.send_roku_command cmd
|
67
|
+
"success"
|
68
|
+
else
|
69
|
+
"error"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Perform some remote management action
|
74
|
+
def perform_action(action, data)
|
75
|
+
if action == 'list'
|
76
|
+
# Get a list of known remotes
|
77
|
+
@remote_manager.remotes_to_json
|
78
|
+
elsif action == 'update'
|
79
|
+
@remote_manager.remotes_from_json(data)
|
80
|
+
@remote_manager.store
|
81
|
+
"success"
|
82
|
+
elsif action =~ /^scan/
|
83
|
+
# Scan the network for Roku boxes - two different action values are
|
84
|
+
# expected: scanForFirst or scanForAll
|
85
|
+
|
86
|
+
# With scanForFirst try to find only one box because that will be the common case
|
87
|
+
@remote_manager = Remotes.new(Remote.scan(action == 'scanForFirst'))
|
88
|
+
@remote_manager.active.name = 'My Roku Box' if @remote_manager.active
|
89
|
+
@remote_manager.store
|
90
|
+
@remote_manager.remotes_to_json
|
91
|
+
else
|
92
|
+
raise HTTPStatus::PreconditionFailed.new("Unknown action: '#{action}'")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Remote
|
100
|
+
def to_json
|
101
|
+
"{\"host\":\"#{@host}\",\"name\":\"#{@name}\",\"port\":#{@port}}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Remotes
|
106
|
+
def remotes_to_json
|
107
|
+
"{\"remotes\":[#{ @boxes.map{|b| b.to_json}.join(',') }], \"active\":#{@active_index}}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def remotes_from_json(json)
|
111
|
+
parsed = JSON.parse(json)
|
112
|
+
@boxes = []
|
113
|
+
parsed['remotes'].each {|r| @boxes << Remote.new(r['host'], r['name'], r['port'] || 8080) }
|
114
|
+
@active_index = parsed['active'] || 0
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|