Make the progress bars and graphs more configurable and themable
[delightful.git] / delightful / widgets / memory.lua
1 -------------------------------------------------------------------------------
2 --
3 -- Memory 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 -- Displays memory and swap usage statistics as a vertical progress bars.
11 -- Also displays a memory icon next to the memory graph. Clicking the icon
12 -- launches an external application (if configured).
13 --
14 -- Widget extends vicious.widgets.mem from Vicious widget framework.
15 --
16 -- Widget tries to use an icon from the package mate-sensors-applet-common
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 -- -- Command to execute when left-clicking the widget icon.
26 -- -- Empty by default.
27 --        command            = 'gnome-system-monitor',
28 -- -- Don't try to display any icons. Default is false (i.e. display icons).
29 --        no_icon            = true,
30 -- -- Height of the progress bar in pixels. Default is 19.
31 --        progressbar_height = 19,
32 -- -- Width of the progress bar in pixels. Default is 8.
33 --        progressbar_width  = 12,
34 -- -- How often update the widget data. Default is 10 second.
35 --        update_interval    = 20
36 -- }
37 --
38 --
39 -- Theme:
40 --
41 -- The widget uses following settings, colors and icons if available in
42 -- the Awesome theme.
43 --
44 -- theme.progressbar_height - height of the memory usage progress bar in pixels
45 -- theme.progressbar_width  - width of the memory usage progress bar in pixels
46 -- theme.bg_widget          - widget background color
47 -- theme.fg_widget          - widget foreground color
48 -- theme.fg_center_widget   - widget gradient color, middle
49 -- theme.fg_end_widget      - widget gradient color, end
50 -- theme.delightful_mem     - icon shown next to the memory progress bars
51 --
52 -------------------------------------------------------------------------------
53
54 local awful      = require('awful')
55 local wibox      = require('wibox')
56 local beautiful  = require('beautiful')
57
58 local delightful = { utils = require('delightful.utils') }
59 local vicious    = require('vicious')
60
61 local io       = { lines = io.lines }
62 local math     = { floor = math.floor }
63 local pairs    = pairs
64 local string   = { format = string.format }
65 local table    = { insert = table.insert }
66 local tonumber = tonumber
67
68 module('delightful.widgets.memory')
69
70 -- Helper
71 function is_swap_available()
72         for line in io.lines('/proc/meminfo') do
73                 local total_swap = tonumber(line:match('^SwapTotal:%s+(%d+)%s+kB4'))
74                 if total_swap and total_swap > 0 then
75                         return true
76                 end
77         end
78         return false
79 end
80
81 local memory_config
82 local fatal_error
83 local icon_tooltip
84 local has_swap = is_swap_available()
85
86 local config_description = {
87         {
88                 name     = 'command',
89                 validate = function(value) return delightful.utils.config_string(value) end
90         },
91         {
92                 name     = 'no_icon',
93                 validate = function(value) return delightful.utils.config_boolean(value) end
94         },
95         {
96                 name     = 'progressbar_height',
97                 required = true,
98                 default  = 19,
99                 validate = function(value) return delightful.utils.config_int(value) end
100         },
101         {
102                 name     = 'progressbar_width',
103                 required = true,
104                 default  = 8,
105                 validate = function(value) return delightful.utils.config_int(value) end
106         },
107         {
108                 name     = 'update_interval',
109                 required = true,
110                 default  = 10,
111                 validate = function(value) return delightful.utils.config_int(value) end
112         },
113 }
114
115 local icon_description = {
116         memory = { beautiful_name = 'delightful_mem', default_icon = 'mate-sensors-applet-memory' },
117 }
118
119 -- Configuration handler
120 function handle_config(user_config)
121         local empty_config = delightful.utils.get_empty_config(config_description)
122         if not user_config then
123                 user_config = empty_config
124         end
125         local config_data = delightful.utils.normalize_config(user_config, config_description)
126         local validation_errors = delightful.utils.validate_config(config_data, config_description)
127         if validation_errors then
128                 fatal_error = 'Configuration errors: \n'
129                 for error_index, error_entry in pairs(validation_errors) do
130                         fatal_error = string.format('%s %s', fatal_error, error_entry)
131                         if error_index < #validation_errors then
132                                 fatal_error = string.format('%s \n', fatal_error)
133                         end
134                 end
135                 memory_config = empty_config
136                 return
137         end
138         memory_config = config_data
139 end
140
141 -- Initalization
142 function load(self, config)
143         handle_config(config)
144         if fatal_error then
145                 delightful.utils.print_error('memory', fatal_error)
146                 return nil, nil
147         end
148         local icon
149         local icon_files
150         if not memory_config.no_icon then
151                 icon_files = delightful.utils.find_icon_files(icon_description)
152         end
153         local icon_file = icon_files and icon_files.memory
154         if icon_file then
155                 local buttons = awful.button({}, 1, function()
156                         if not fatal_error and memory_config.command then
157                                 awful.util.spawn(memory_config.command, true)
158                         end
159                 end)
160                 icon = wibox.widget.imagebox()
161                 icon:buttons(buttons)
162                 icon:set_image(icon_file)
163                 icon_tooltip = awful.tooltip({ objects = { icon } })
164         end
165         local icons = { icon }
166
167         local bg_color        = delightful.utils.find_theme_color({ 'bg_widget', 'bg_normal'                     })
168         local fg_color        = delightful.utils.find_theme_color({ 'fg_widget', 'fg_normal'                     })
169         local fg_center_color = delightful.utils.find_theme_color({ 'fg_center_widget', 'fg_widget', 'fg_normal' })
170         local fg_end_color    = delightful.utils.find_theme_color({ 'fg_end_widget', 'fg_widget', 'fg_normal'    })
171
172         local memory_widget = awful.widget.progressbar()
173         if bg_color then
174                 memory_widget:set_background_color(bg_color)
175                 memory_widget:set_border_color(bg_color)
176         end
177         local color_args = fg_color
178         local height = beautiful.progressbar_height or memory_config.progressbar_height
179         local width  = beautiful.progressbar_width  or memory_config.progressbar_width
180         if fg_color and fg_center_color and fg_end_color then
181                 color_args = {
182                         type = 'linear',
183                         from = { 0, 0 },
184                         to = { width, height },
185                         stops = {{ 0, fg_end_color }, { 0.5, fg_center_color }, { 1, fg_color }},
186                 }
187         end
188         memory_widget:set_color(color_args)
189         memory_widget:set_width(width)
190         memory_widget:set_height(height)
191         memory_widget:set_vertical(true)
192         vicious.register(memory_widget, vicious.widgets.mem, vicious_formatter_memory, memory_config.update_interval)
193         local widgets = { memory_widget }
194
195
196         if has_swap then
197                 local swap_widget = awful.widget.progressbar()
198                 if bg_color then
199                         swap_widget:set_background_color(bg_color)
200                         swap_widget:set_border_color(bg_color)
201                 end
202                 swap_widget:set_color(color_args)
203                 swap_widget:set_width(width)
204                 swap_widget:set_height(height)
205                 swap_widget:set_vertical(true)
206                 vicious.register(swap_widget, vicious.widgets.mem, vicious_formatter_swap, memory_config.update_interval)
207                 table.insert(widgets, swap_widget)
208         end
209
210         vicious.cache(vicious.widgets.mem)
211
212         return widgets, icons
213 end
214
215 -- Vicious display formatter for memory graph, also update widget tooltip
216 function vicious_formatter_memory(widget, data)
217         local memory_percentage = math.floor(((data[2] / data[3]) * 100) + 0.5)
218         if icon_tooltip then
219                 local swap_string = ''
220                 if has_swap then
221                         swap_string = 'and swap '
222                 end
223                 local tooltip_text = string.format(' Memory %susage statistics \n Current memory usage: %d%% (%dMB out of %dMB) ', swap_string, memory_percentage, data[2], data[3])
224                 if has_swap then
225                         local swap_percentage = math.floor(((data[6] / data[7]) * 100) + 0.5)
226                         tooltip_text = string.format('%s\n Current swap usage: %d%% (%dMB out of %dMB) ', tooltip_text, swap_percentage, data[6], data[7])
227                 end
228                 icon_tooltip:set_text(tooltip_text)
229         end
230         return memory_percentage
231 end
232
233 -- Vicious display formatter for swap graph
234 function vicious_formatter_swap(widget, data)
235         return (data[6] / data[7]) * 100
236 end