Merge branch 'master' into config
[amazing.git] / lib / amazing / cli.rb
blob862182d3c08179bff48cdd8824f3e8f1db6a7957
1 # Copyright (C) 2008 Dag Odenhall <dag.odenhall@gmail.com>
2 # Licensed under the Academic Free License version 3.0
4 require 'amazing'
5 require 'fileutils'
6 require 'logger'
7 require 'thread'
8 require 'timeout'
9 require 'yaml'
11 module Amazing
13   # Command line interface runner
14   #
15   #   CLI.run(ARGV)
16   class CLI
17     def initialize(args)
18       $KCODE = "utf-8"
19       @args = args
20       @log = Logger.new(STDOUT)
21       @options = Options.new(@args)
22       begin
23         @display = X11::DisplayName.new
24       rescue X11::EmptyDisplayName => e
25         @log.warn("#{e.message}, falling back on :0")
26         @display = X11::DisplayName.new(":0")
27       rescue X11::InvalidDisplayName => e
28         @log.fatal("#{e.message}, exiting")
29         exit 1
30       end
31       @threads = []
32     end
34     def run
35       at_exit { Thread.list.each {|t| t.exit } }
36       trap("SIGINT") do
37         @log.fatal("Received SIGINT, exiting")
38         remove_pid
39         exit
40       end
41       @options.parse
42       show_help if @options[:help]
43       set_loglevel
44       stop_process(true) if @options[:stop]
45       load_scripts
46       list_widgets if @options[:listwidgets]
47       test_widget if @options[:test]
48       parse_config
49       wait_for_sockets
50       @awesome = Awesome.new(@display.display)
51       explicit_updates unless @options[:update] == []
52       stop_process
53       save_pid
54       update_non_interval
55       @config[:awesome].each do |awesome|
56         awesome[:widgets].each do |widget|
57           if widget[:interval]
58             @threads << Thread.new(awesome, widget) do |awesome, widget|
59               iteration = 1
60               loop do
61                 Thread.new { update_widget(awesome[:screen], awesome[:statusbar], widget, iteration) }
62                 iteration += 1
63                 sleep widget[:interval]
64               end
65             end
66           end
67         end
68       end
69       @threads.each {|t| t.join }
70     end
72     private
74     def show_help
75       puts @options.help
76       exit
77     end
79     def set_loglevel
80       begin
81         @log.level = Logger.const_get(@options[:loglevel].upcase)
82       rescue NameError
83         @log.error("Unsupported log level #{@options[:loglevel].inspect}")
84         @log.level = Logger::INFO
85       end
86     end
88     def stop_process(quit=false)
89       begin
90         Process.kill("SIGINT", File.read("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid").to_i) 
91         @log.warn("Killed older process") unless quit
92       rescue
93       end
94       exit if quit
95     end
97     def load_scripts
98       scripts = @options[:include]
99       if @options[:autoinclude]
100         scripts << Dir["#{ENV["HOME"]}/.amazing/widgets/*"]
101       end
102       scripts.flatten.each do |script|
103         if File.exist?(script)
104           @log.debug("Loading script #{script.inspect}")
105           begin
106             Widgets.module_eval(File.read(script), script)
107           rescue SyntaxError => e
108             @log.error("Bad syntax in #{script} at line #{e.to_s.scan(/:(\d+)/)}")
109           end
110         else
111           @log.error("No such widget script #{script.inspect}")
112         end
113       end
114     end
116     def parse_config
117       @log.debug("Parsing configuration file")
118       begin
119         @config = Config.new(@options[:config])
120       rescue
121         @log.fatal("Unable to parse configuration file, exiting")
122         exit 1
123       end
124     end
126     def list_widgets
127       if @options[:listwidgets] == true
128         longest_widget_name = Widgets.constants.inject {|a,b| a.length > b.length ? a : b }.length
129         Widgets.constants.sort.each do |widget|
130           widget_class = Widgets.const_get(widget)
131           puts "%-#{longest_widget_name}s : %s" % [widget, widget_class.description]
132         end
133       else
134         widget_class = Widgets.const_get(@options[:listwidgets].camel_case)
135         puts
136         puts "#{@options[:listwidgets].camel_case} - #{widget_class.description}"
137         puts
138         dependencies = widget_class.dependencies
139         unless dependencies.empty?
140           longest_dependency_name = dependencies.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
141           longest_dependency_name = 10 if longest_dependency_name < 10
142           longest_description = dependencies.values.inject {|a,b| a.length > b.length ? a : b }.length
143           longest_description = 11 if longest_description < 11
144           puts " %-#{longest_dependency_name}s | DESCRIPTION" % "DEPENDENCY"
145           puts "-" * (longest_dependency_name + longest_description + 5)
146           dependencies.keys.sort.each do |dependency|
147             puts " %-#{longest_dependency_name}s | #{dependencies[dependency]}" % dependency
148           end
149           puts
150         end
151         options = widget_class.options
152         unless options.empty?
153           longest_option_name = options.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
154           longest_option_name = 6 if longest_option_name < 6
155           longest_description = options.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
156           longest_description = 11 if longest_description < 11
157           longest_default = options.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
158           longest_default = 7 if longest_default < 7
159           puts " %-#{longest_option_name}s | %-#{longest_description}s | DEFAULT" % ["OPTION", "DESCRIPTION"]
160           puts "-" * (longest_option_name + longest_description + longest_default + 8)
161           options.keys.sort_by {|option| option.to_s }.each do |option|
162             puts " %-#{longest_option_name}s | %-#{longest_description}s | %s" % [option, options[option][:description], options[option][:default].inspect]
163           end
164           puts
165         end
166         fields = widget_class.fields
167         unless fields.empty?
168           longest_field_name = fields.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
169           longest_field_name = 5 if longest_field_name < 5
170           longest_description = fields.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
171           longest_description = 11 if longest_description < 11
172           longest_default = fields.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
173           longest_default = 7 if longest_default < 7
174           puts " %-#{longest_field_name + 1}s | %-#{longest_description}s | DEFAULT" % ["FIELD", "DESCRIPTION"]
175           puts "-" * (longest_field_name + longest_description + longest_default + 9)
176           fields.keys.sort_by {|field| field.to_s }.each do |field|
177             puts " @%-#{longest_field_name}s | %-#{longest_description}s | %s" % [field, fields[field][:description], fields[field][:default].inspect]
178           end
179           puts
180         end
181       end
182       exit
183     end
185     def test_widget
186       widget = Widgets.const_get(@options[:test].camel_case)
187       settings = YAML.load("{#{ARGV[0]}}")
188       instance = widget.new(settings)
189       longest_field_name = widget.fields.merge({:default => nil}).keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
190       puts "@%-#{longest_field_name}s = %s" % [:default, instance.instance_variable_get(:@default).inspect]
191       widget.fields.keys.sort_by {|field| field.to_s }.each do |field|
192         puts "@%-#{longest_field_name}s = %s" % [field, instance.instance_variable_get("@#{field}".to_sym).inspect]
193       end
194       exit
195     end
197     def wait_for_sockets
198       @log.debug("Waiting for awesome control socket for display #{@display.display}")
199       begin
200         Timeout.timeout(30) do
201           sleep 1 until File.exist?("#{ENV["HOME"]}/.awesome_ctl.#{@display.display}")
202           @log.debug("Got socket for display #{@display.display}")
203         end
204       rescue Timeout::Error
205         @log.fatal("Socket for display #{@display.display} not created within 30 seconds, exiting")
206         exit 1
207       end
208     end
210     def explicit_updates
211       @config[:awesome].each do |awesome|
212         awesome[:widgets].each do |widget|
213           locator = "%s/%s/%s" % [widget[:identifier], awesome[:statusbar], awesome[:screen]]
214           next unless @options[:update] == :all || @options[:update].include?(locator)
215           @threads << Thread.new(awesome, widget) do |awesome, widget|
216             update_widget(awesome[:screen], awesome[:statusbar], widget)
217           end
218         end
219       end
220       @threads.each {|t| t.join }
221       exit
222     end
224     def save_pid
225       path = "#{ENV["HOME"]}/.amazing/pids"
226       FileUtils.makedirs(path)
227       File.open("#{path}/#{@display.display}.pid", "w+") do |f|
228         f.write($$)
229       end
230     end
232     def remove_pid
233       File.delete("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid") rescue Errno::ENOENT
234     end
236     def update_non_interval
237       @config[:awesome].each do |awesome|
238         awesome[:widgets].each do |widget|
239           next if widget[:interval]
240           @threads << Thread.new(awesome, widget) do |awesome, widget|
241             update_widget(awesome[:screen], awesome[:statusbar], widget)
242           end
243         end
244       end
245       @threads.each {|t| t.join }
246     end
248     def update_widget(screen, statusbar, widget, iteration=0)
249       threads = []
250       @log.debug("Updating widget #{widget[:identifier]} of type #{widget[:module]} on screen #{screen}")
251       begin
252         mod = Widgets.const_get(widget[:module]).new(widget.merge(:iteration => iteration))
253         if widget[:properties].empty?
254           threads << Thread.new(screen, statusbar, widget, mod) do |screen, statusbar, widget, mod|
255             @awesome.widget_tell(screen, statusbar, widget[:identifier], widget[:property], mod.formatize)
256           end
257         end
258         widget[:properties].each do |property, format|
259           threads << Thread.new(screen, statusbar, widget, property, mod, format) do |screen, statusbar, widget, property, mod, format|
260             @awesome.widget_tell(screen, statusbar, widget[:identifier], property, mod.formatize(format))
261           end
262         end
263       rescue WidgetError => e
264         @log.error(widget[:module]) { e.message }
265       end
266       threads.each {|t| t.join }
267     end
268   end