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