dc7de1176f2be03b6684fc29234648b7ae49e015
[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 -- -- How often update the widget data. Default is 20 seconds.
35 --            update_interval = 30
36 -- }
37 --
38 --
39 -- Theme:
40 --
41 -- The widget uses following colors and icons if available in
42 -- the Awesome theme.
43 --
44 -- theme.bg_widget                 - widget background color
45 -- theme.fg_widget                 - widget foreground color
46 -- theme.fg_center_widget          - widget gradient color, middle
47 -- theme.fg_end_widget             - widget gradient color, end
48 -- theme.delightful_battery_ac     - icon shown when the machine is connected to AC adapter
49 -- theme.delightful_battery_full   - icon shown when battery has 50%-99% charge
50 -- theme.delightful_battery_medium - icon shown when battery has 15%-49% charge
51 -- theme.delightful_battery_low    - icon shown when battery has less than 15% charge
52 -- theme.delightful_not_found      - icon shown when battery status is unknown
53 -- theme.delightful_error          - icon shown when critical error has occurred
54 --
55 -------------------------------------------------------------------------------
56
57 local awful      = require('awful')
58 local wibox      = require('wibox')
59
60 local delightful = { utils = require('delightful.utils') }
61 local vicious    = require('vicious')
62
63 local pairs  = pairs
64 local string = { format = string.format }
65
66 module('delightful.widgets.battery')
67
68 local battery_config
69 local fatal_error
70 local icon_tooltip
71 local icon_files        = {}
72 local icon
73 local prev_icon
74
75 local config_description = {
76         {
77                 name     = 'battery',
78                 required = true,
79                 default  = 'BAT0',
80                 validate = function(value)
81                         local status, errors = delightful.utils.config_string(value)
82                         if not status then
83                                 return status, errors
84                         end
85                         local battery_path = string.format('/sys/class/power_supply/%s/status', value)
86                         if not awful.util.file_readable(battery_path) then
87                                 return false, string.format('Battery not found: %s', value)
88                         end
89                         return true
90                 end
91         },
92         {
93                 name     = 'command',
94                 default  = 'gnome-power-preferences',
95                 validate = function(value) return delightful.utils.config_string(value) end
96         },
97         {
98                 name     = 'no_icon',
99                 validate = function(value) return delightful.utils.config_boolean(value) end
100         },
101         {
102                 name     = 'update_interval',
103                 required = true,
104                 default  = 20,
105                 validate = function(value) return delightful.utils.config_int(value) end
106         },
107 }
108
109 local icon_description = {
110         battery_ac     = { beautiful_name = 'delightful_battery_ac',     default_icon = 'battery-good-charging' },
111         battery_full   = { beautiful_name = 'delightful_battery_full',   default_icon = 'battery-good'          },
112         battery_medium = { beautiful_name = 'delightful_battery_medium', default_icon = 'battery-low'           },
113         battery_low    = { beautiful_name = 'delightful_battery_low',    default_icon = 'battery-caution'       },
114         not_found      = { beautiful_name = 'delightful_not_found',      default_icon = 'dialog-question'       },
115         error          = { beautiful_name = 'delightful_error',          default_icon = 'dialog-error'          },
116 }
117
118 -- Configuration handler
119 function handle_config(user_config)
120         local empty_config = delightful.utils.get_empty_config(config_description)
121         if not user_config then
122                 user_config = empty_config
123         end
124         local config_data = delightful.utils.normalize_config(user_config, config_description)
125         local validation_errors = delightful.utils.validate_config(config_data, config_description)
126         if validation_errors then
127                 fatal_error = 'Configuration errors: \n'
128                 for error_index, error_entry in pairs(validation_errors) do
129                         fatal_error = string.format('%s %s', fatal_error, error_entry)
130                         if error_index < #validation_errors then
131                                 fatal_error = string.format('%s \n', fatal_error)
132                         end
133                 end
134                 battery_config = empty_config
135                 return
136         end
137         battery_config = config_data
138 end
139
140 -- Initalization
141 function load(self, config)
142         handle_config(config)
143         if fatal_error then
144                 delightful.utils.print_error('battery', fatal_error)
145                 return nil, nil
146         end
147         if not battery_config.no_icon then
148                 icon_files = delightful.utils.find_icon_files(icon_description)
149         end
150         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
151                 local buttons = awful.button({}, 1, function()
152                                 if not fatal_error and battery_config.command then
153                                         awful.util.spawn(battery_config.command, true)
154                                 end
155                 end)
156                 icon = wibox.widget.imagebox()
157                 icon:buttons(buttons)
158                 icon_tooltip = awful.tooltip({ objects = { icon } })
159         end
160
161         local bg_color        = delightful.utils.find_theme_color({ 'bg_widget', 'bg_normal'                     })
162         local fg_color        = delightful.utils.find_theme_color({ 'fg_widget', 'fg_normal'                     })
163         local fg_center_color = delightful.utils.find_theme_color({ 'fg_center_widget', 'fg_widget', 'fg_normal' })
164         local fg_end_color    = delightful.utils.find_theme_color({ 'fg_end_widget', 'fg_widget', 'fg_normal'    })
165
166         local battery_widget = awful.widget.progressbar()
167         if bg_color then
168                 battery_widget:set_border_color(bg_color)
169                 battery_widget:set_background_color(bg_color)
170         end
171         local color_args = fg_color
172         local width  = 8
173         local height = 19
174         if fg_color and fg_center_color and fg_end_color then
175                 color_args = {
176                         type = 'linear',
177                         from = { 0, 0 },
178                         to = { width, height },
179                         stops = {{ 0, fg_end_color }, { 0.5, fg_center_color }, { 1, fg_color }},
180                 }
181         end
182         battery_widget:set_color(color_args)
183         battery_widget:set_width(width)
184         battery_widget:set_height(height)
185         battery_widget:set_vertical(true)
186         vicious.register(battery_widget, vicious.widgets.bat, vicious_formatter, battery_config.update_interval, battery_config.battery)
187
188         return { battery_widget }, { icon }
189 end
190
191 -- Vicious display formatter, also update widget tooltip and icon
192 function vicious_formatter(widget, data)
193         -- update tooltip
194         local unknown = false
195         if icon_tooltip then
196                 local tooltip_text
197                 if fatal_error then
198                         tooltip_text = fatal_error
199                 elseif data[1] == '↯' then
200                         tooltip_text = 'Battery is charged'
201                 elseif data[1] == '+' then
202                         tooltip_text = string.format('Battery charge %d%% \n On AC power, %s until charged', data[2], data[3])
203                 elseif data[1] == '−' then
204                         tooltip_text = string.format('Battery charge %d%% \n On battery power, %s left', data[2], data[3])
205                 else
206                         tooltip_text = 'Battery status is unknown'
207                         unknown = true
208                 end
209                 icon_tooltip:set_text(string.format(' %s ', tooltip_text))
210         end
211         -- update icon
212         if icon then
213                 local icon_file
214                 if fatal_error then
215                         icon_file = icon_files.error
216                 elseif unknown then
217                         icon_file = icon_files.not_found
218                 elseif data[1] == '+' then
219                         icon_file = icon_files.battery_ac
220                 elseif data[2] >= 50 and data[2] <= 100 then
221                         icon_file = icon_files.battery_full
222                 elseif data[2] >= 15 and data[2] < 50   then
223                         icon_file = icon_files.battery_medium
224                 elseif data[2] >= 0  and data[2] < 15   then
225                         icon_file = icon_files.battery_low
226                 end
227                 if icon_file and (not prev_icon or prev_icon ~= icon_file) then
228                         prev_icon = icon_file
229                         icon:set_image(icon_file)
230                 end
231         end
232         if fatal_error then
233                 return 0
234         else
235                 return data[2]
236         end
237 end