Make the progress bars and graphs more configurable and themable
[delightful.git] / delightful / widgets / battery.lua
1 -------------------------------------------------------------------------------
2 --
3 -- Battery widget for Awesome 3.5
4 -- Copyright (C) 2011-2016 Tuomas Jormola <tj@solitudo.net>
5 --
6 -- Licensed under the terms of GNU General Public License Version 2.0.
7 --
8 -- Description:
9 --
10 -- Shows a battery status indicator. Battery level is indicated as
11 -- a vertical progress bar and an icon indicator is shown next to it.
12 -- Clicking the icon launches an external application (if configured).
13 --
14 -- Widget extends vicious.widgets.bat from Vicious widget framework.
15 --
16 -- Widget tries to use icons from the package gnome-icon-theme
17 -- if available.
18 --
19 --
20 -- Configuration:
21 --
22 -- The load() function can be supplied with configuration.
23 -- Format of the configuration is as follows.
24 -- {
25 -- -- Name of the battery. Matches a file under the directory
26 -- -- /sys/class/power_supply/ and typically is "BATn" where n
27 -- -- is a number, most likely 0. 'BAT0' by default.
28 --        battery            = 'BAT2',
29 -- -- Command to execute when left-clicking the widget icon.
30 -- -- Empty by default.
31 --        command            = 'gnome-power-preferences',
32 -- -- Don't try to display any icons. Default is false (i.e. display icons).
33 --        no_icon            = true,
34 -- -- Height of the progress bar in pixels. Default is 19.
35 --        progressbar_height = 19,
36 -- -- Width of the progress bar in pixels. Default is 8.
37 --        progressbar_width  = 12,
38 -- -- How often update the widget data. Default is 20 seconds.
39 --        update_interval    = 30
40 -- }
41 --
42 --
43 -- Theme:
44 --
45 -- The widget uses following settings, colors and icons if available in
46 -- the Awesome theme.
47 --
48 -- theme.progressbar_height        - height of the battery charge progress bar in pixels
49 -- theme.progressbar_width         - width of the battery charge progress bar in pixels
50 -- theme.bg_widget                 - widget background color
51 -- theme.fg_widget                 - widget foreground color
52 -- theme.fg_center_widget          - widget gradient color, middle
53 -- theme.fg_end_widget             - widget gradient color, end
54 -- theme.delightful_battery_ac     - icon shown when the machine is connected to AC adapter
55 -- theme.delightful_battery_full   - icon shown when battery has 50%-99% charge
56 -- theme.delightful_battery_medium - icon shown when battery has 15%-49% charge
57 -- theme.delightful_battery_low    - icon shown when battery has less than 15% charge
58 -- theme.delightful_not_found      - icon shown when battery status is unknown
59 -- theme.delightful_error          - icon shown when critical error has occurred
60 --
61 -------------------------------------------------------------------------------
62
63 local awful      = require('awful')
64 local wibox      = require('wibox')
65 local beautiful  = require('beautiful')
66
67 local delightful = { utils = require('delightful.utils') }
68 local vicious    = require('vicious')
69
70 local pairs  = pairs
71 local string = { format = string.format }
72
73 module('delightful.widgets.battery')
74
75 local battery_config
76 local fatal_error
77 local icon_tooltip
78 local icon_files        = {}
79 local icon
80 local prev_icon
81
82 local config_description = {
83         {
84                 name     = 'battery',
85                 required = true,
86                 default  = 'BAT0',
87                 validate = function(value)
88                         local status, errors = delightful.utils.config_string(value)
89                         if not status then
90                                 return status, errors
91                         end
92                         local battery_path = string.format('/sys/class/power_supply/%s/status', value)
93                         if not awful.util.file_readable(battery_path) then
94                                 return false, string.format('Battery not found: %s', value)
95                         end
96                         return true
97                 end
98         },
99         {
100                 name     = 'command',
101                 default  = 'gnome-power-preferences',
102                 validate = function(value) return delightful.utils.config_string(value) end
103         },
104         {
105                 name     = 'no_icon',
106                 validate = function(value) return delightful.utils.config_boolean(value) end
107         },
108         {
109                 name     = 'progressbar_height',
110                 required = true,
111                 default  = 19,
112                 validate = function(value) return delightful.utils.config_int(value) end
113         },
114         {
115                 name     = 'progressbar_width',
116                 required = true,
117                 default  = 8,
118                 validate = function(value) return delightful.utils.config_int(value) end
119         },
120         {
121                 name     = 'update_interval',
122                 required = true,
123                 default  = 20,
124                 validate = function(value) return delightful.utils.config_int(value) end
125         },
126 }
127
128 local icon_description = {
129         battery_ac     = { beautiful_name = 'delightful_battery_ac',     default_icon = 'battery-good-charging' },
130         battery_full   = { beautiful_name = 'delightful_battery_full',   default_icon = 'battery-good'          },
131         battery_medium = { beautiful_name = 'delightful_battery_medium', default_icon = 'battery-low'           },
132         battery_low    = { beautiful_name = 'delightful_battery_low',    default_icon = 'battery-caution'       },
133         not_found      = { beautiful_name = 'delightful_not_found',      default_icon = 'dialog-question'       },
134         error          = { beautiful_name = 'delightful_error',          default_icon = 'dialog-error'          },
135 }
136
137 -- Configuration handler
138 function handle_config(user_config)
139         local empty_config = delightful.utils.get_empty_config(config_description)
140         if not user_config then
141                 user_config = empty_config
142         end
143         local config_data = delightful.utils.normalize_config(user_config, config_description)
144         local validation_errors = delightful.utils.validate_config(config_data, config_description)
145         if validation_errors then
146                 fatal_error = 'Configuration errors: \n'
147                 for error_index, error_entry in pairs(validation_errors) do
148                         fatal_error = string.format('%s %s', fatal_error, error_entry)
149                         if error_index < #validation_errors then
150                                 fatal_error = string.format('%s \n', fatal_error)
151                         end
152                 end
153                 battery_config = empty_config
154                 return
155         end
156         battery_config = config_data
157 end
158
159 -- Initalization
160 function load(self, config)
161         handle_config(config)
162         if fatal_error then
163                 delightful.utils.print_error('battery', fatal_error)
164                 return nil, nil
165         end
166         if not battery_config.no_icon then
167                 icon_files = delightful.utils.find_icon_files(icon_description)
168         end
169         if icon_files.battery_ac and icon_files.battery_full and icon_files.battery_medium and icon_files.battery_low and icon_files.not_found and icon_files.error then
170                 local buttons = awful.button({}, 1, function()
171                                 if not fatal_error and battery_config.command then
172                                         awful.util.spawn(battery_config.command, true)
173                                 end
174                 end)
175                 icon = wibox.widget.imagebox()
176                 icon:buttons(buttons)
177                 icon_tooltip = awful.tooltip({ objects = { icon } })
178         end
179
180         local bg_color        = delightful.utils.find_theme_color({ 'bg_widget', 'bg_normal'                     })
181         local fg_color        = delightful.utils.find_theme_color({ 'fg_widget', 'fg_normal'                     })
182         local fg_center_color = delightful.utils.find_theme_color({ 'fg_center_widget', 'fg_widget', 'fg_normal' })
183         local fg_end_color    = delightful.utils.find_theme_color({ 'fg_end_widget', 'fg_widget', 'fg_normal'    })
184
185         local battery_widget = awful.widget.progressbar()
186         if bg_color then
187                 battery_widget:set_border_color(bg_color)
188                 battery_widget:set_background_color(bg_color)
189         end
190         local color_args = fg_color
191         local height = beautiful.progressbar_height or battery_config.progressbar_height
192         local width  = beautiful.progressbar_width  or battery_config.progressbar_width
193         if fg_color and fg_center_color and fg_end_color then
194                 color_args = {
195                         type = 'linear',
196                         from = { 0, 0 },
197                         to = { width, height },
198                         stops = {{ 0, fg_end_color }, { 0.5, fg_center_color }, { 1, fg_color }},
199                 }
200         end
201         battery_widget:set_color(color_args)
202         battery_widget:set_width(width)
203         battery_widget:set_height(height)
204         battery_widget:set_vertical(true)
205         vicious.register(battery_widget, vicious.widgets.bat, vicious_formatter, battery_config.update_interval, battery_config.battery)
206
207         return { battery_widget }, { icon }
208 end
209
210 -- Vicious display formatter, also update widget tooltip and icon
211 function vicious_formatter(widget, data)
212         -- update tooltip
213         local unknown = false
214         if icon_tooltip then
215                 local tooltip_text
216                 if fatal_error then
217                         tooltip_text = fatal_error
218                 elseif data[1] == '↯' then
219                         tooltip_text = 'Battery is charged'
220                 elseif data[1] == '+' then
221                         tooltip_text = string.format('Battery charge %d%% \n On AC power, %s until charged', data[2], data[3])
222                 elseif data[1] == '−' then
223                         tooltip_text = string.format('Battery charge %d%% \n On battery power, %s left', data[2], data[3])
224                 else
225                         tooltip_text = 'Battery status is unknown'
226                         unknown = true
227                 end
228                 icon_tooltip:set_text(string.format(' %s ', tooltip_text))
229         end
230         -- update icon
231         if icon then
232                 local icon_file
233                 if fatal_error then
234                         icon_file = icon_files.error
235                 elseif unknown then
236                         icon_file = icon_files.not_found
237                 elseif data[1] == '+' then
238                         icon_file = icon_files.battery_ac
239                 elseif data[2] >= 50 and data[2] <= 100 then
240                         icon_file = icon_files.battery_full
241                 elseif data[2] >= 15 and data[2] < 50   then
242                         icon_file = icon_files.battery_medium
243                 elseif data[2] >= 0  and data[2] < 15   then
244                         icon_file = icon_files.battery_low
245                 end
246                 if icon_file and (not prev_icon or prev_icon ~= icon_file) then
247                         prev_icon = icon_file
248                         icon:set_image(icon_file)
249                 end
250         end
251         if fatal_error then
252                 return 0
253         else
254                 return data[2]
255         end
256 end