1 # Copyright (C) 2008 Dag Odenhall <dag.odenhall@gmail.com>
2 # Licensed under the Academic Free License version 3.0
5 require 'amazing/options'
6 require 'amazing/x11/display_name'
7 require 'amazing/config'
8 require 'amazing/string'
9 require 'amazing/widget'
10 require 'amazing/proc_file'
11 require 'amazing/widgets'
12 require 'amazing/awesome'
19 # Command line interface runner
25 @log = Logger.new(STDOUT)
26 @options = Options.new(@args)
28 @display = X11::DisplayName.new
29 rescue X11::EmptyDisplayName => e
30 @log.warn("#{e.message}, falling back on :0")
31 @display = X11::DisplayName.new(":0")
32 rescue X11::InvalidDisplayName => e
33 @log.fatal("#{e.message}, exiting")
42 Thread.list.each {|t| t.exit }
45 @log.fatal("Received SIGINT, exiting")
49 show_help if @options[:help]
51 stop_process(true) if @options[:stop]
53 list_widgets if @options[:listwidgets]
54 test_widget if @options[:test]
57 @awesome = Awesome.new(@display.display)
58 explicit_updates unless @options[:update] == []
62 @config[:awesome].each do |awesome|
63 awesome[:widgets].each do |widget|
65 @threads << Thread.new(awesome, widget) do |awesome, widget|
67 Thread.new { update_widget(awesome[:screen], awesome[:statusbar], widget[:identifier]) }
68 sleep widget[:interval]
74 @threads.each {|t| t.join }
86 @log.level = Logger.const_get(@options[:loglevel].upcase)
88 @log.error("Unsupported log level #{@options[:loglevel].inspect}")
89 @log.level = Logger::INFO
93 def stop_process(quit=false)
95 Process.kill("SIGINT", File.read("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid").to_i)
96 @log.warn("Killed older process") unless quit
103 scripts = @options[:include]
104 if @options[:autoinclude]
105 scripts << Dir["#{ENV["HOME"]}/.amazing/widgets/*"]
107 scripts.flatten.each do |script|
108 if File.exist?(script)
109 @log.debug("Loading script #{script.inspect}")
111 Widgets.module_eval(File.read(script), script)
112 rescue SyntaxError => e
113 @log.error("Bad syntax in #{script} at line #{e.to_s.scan(/:(\d+)/)}")
116 @log.error("No such widget script #{script.inspect}")
122 @log.debug("Parsing configuration file")
124 @config = Config.new(@options[:config])
126 @log.fatal("Unable to parse configuration file, exiting")
132 if @options[:listwidgets] == true
133 longest_widget_name = Widgets.constants.inject {|a,b| a.length > b.length ? a : b }.length
134 Widgets.constants.sort.each do |widget|
135 widget_class = Widgets.const_get(widget)
136 puts "%-#{longest_widget_name}s : %s" % [widget, widget_class.description]
139 widget_class = Widgets.const_get(@options[:listwidgets].camel_case)
141 puts "#{@options[:listwidgets].camel_case} - #{widget_class.description}"
143 dependencies = widget_class.dependencies
144 unless dependencies.empty?
145 longest_dependency_name = dependencies.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
146 longest_dependency_name = 10 if longest_dependency_name < 10
147 longest_description = dependencies.values.inject {|a,b| a.length > b.length ? a : b }.length
148 longest_description = 11 if longest_description < 11
149 puts " %-#{longest_dependency_name}s | DESCRIPTION" % "DEPENDENCY"
150 puts "-" * (longest_dependency_name + longest_description + 5)
151 dependencies.keys.sort.each do |dependency|
152 puts " %-#{longest_dependency_name}s | #{dependencies[dependency]}" % dependency
156 options = widget_class.options
157 unless options.empty?
158 longest_option_name = options.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
159 longest_option_name = 6 if longest_option_name < 6
160 longest_description = options.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
161 longest_description = 11 if longest_description < 11
162 longest_default = options.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
163 longest_default = 7 if longest_default < 7
164 puts " %-#{longest_option_name}s | %-#{longest_description}s | DEFAULT" % ["OPTION", "DESCRIPTION"]
165 puts "-" * (longest_option_name + longest_description + longest_default + 8)
166 options.keys.sort_by {|option| option.to_s }.each do |option|
167 puts " %-#{longest_option_name}s | %-#{longest_description}s | %s" % [option, options[option][:description], options[option][:default].inspect]
171 fields = widget_class.fields
173 longest_field_name = fields.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
174 longest_field_name = 5 if longest_field_name < 5
175 longest_description = fields.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
176 longest_description = 11 if longest_description < 11
177 longest_default = fields.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
178 longest_default = 7 if longest_default < 7
179 puts " %-#{longest_field_name + 1}s | %-#{longest_description}s | DEFAULT" % ["FIELD", "DESCRIPTION"]
180 puts "-" * (longest_field_name + longest_description + longest_default + 9)
181 fields.keys.sort_by {|field| field.to_s }.each do |field|
182 puts " @%-#{longest_field_name}s | %-#{longest_description}s | %s" % [field, fields[field][:description], fields[field][:default].inspect]
191 widget = Widgets.const_get(@options[:test].camel_case)
192 settings = YAML.load("{#{ARGV[0]}}")
193 instance = widget.new(settings)
194 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
195 puts "@%-#{longest_field_name}s = %s" % [:default, instance.instance_variable_get(:@default).inspect]
196 widget.fields.keys.sort_by {|field| field.to_s }.each do |field|
197 puts "@%-#{longest_field_name}s = %s" % [field, instance.instance_variable_get("@#{field}".to_sym).inspect]
203 @log.debug("Waiting for awesome control socket for display #{@display.display}")
205 Timeout.timeout(30) do
206 sleep 1 until File.exist?("#{ENV["HOME"]}/.awesome_ctl.#{@display.display}")
207 @log.debug("Got socket for display #{@display.display}")
209 rescue Timeout::Error
210 @log.fatal("Socket for display #{@display.display} not created within 30 seconds, exiting")
216 @config[:awesome].each do |awesome|
217 awesome[:widgets].each do |widget|
218 locator = "%s/%s/%s" % [widget[:identifier], awesome[:statusbar], awesome[:screen]]
219 next unless @options[:update] == :all || @options[:update].include?(locator)
220 @threads << Thread.new(awesome, widget) do |awesome, widget|
221 update_widget(awesome[:screen], awesome[:statusbar], widget[:identifier])
225 @threads.each {|t| t.join }
230 path = "#{ENV["HOME"]}/.amazing/pids"
231 FileUtils.makedirs(path)
232 File.open("#{path}/#{@display.display}.pid", "w+") do |f|
238 File.delete("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid") rescue Errno::ENOENT
241 def update_non_interval
242 @config[:awesome].each do |awesome|
243 awesome[:widgets].each do |widget|
244 next if widget[:interval]
245 @threads << Thread.new(awesome, widget) do |awesome, widget|
246 update_widget(awesome[:screen], awesome[:statusbar], widget[:identifier])
250 @threads.each {|t| t.join }
253 def update_widget(screen, statusbar, identifier)
255 @config[:awesome].each do |awesome|
256 next unless screen == awesome[:screen] && statusbar == awesome[:statusbar]
257 awesome[:widgets].each do |widget|
258 next unless widget[:identifier] == identifier
259 @log.debug("Updating widget #{identifier} of type #{widget[:module]} on screen #{screen}")
260 threads << Thread.new(widget) do |widget|
262 mod = Widgets.const_get(widget[:module]).new(widget)
263 if widget[:properties].empty?
264 @awesome.widget_tell(screen, statusbar, identifier, widget[:property], mod.formatize)
266 widget[:properties].each do |property, format|
267 @awesome.widget_tell(screen, statusbar, identifier, property, mod.formatize(format))
269 rescue WidgetError => e
270 @log.error(widget[:module]) { e.message }
275 threads.each {|t| t.join }