cpu-widget.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. local awful = require("awful")
  2. local watch = require("awful.widget.watch")
  3. local wibox = require("wibox")
  4. local beautiful = require("beautiful")
  5. local gears = require("gears")
  6. local CMD = [[sh -c "grep '^cpu.' /proc/stat; ps -eo 'pid:10,pcpu:5,pmem:5,comm:30,cmd' --sort=-pcpu ]]
  7. .. [[| grep -v [p]s | grep -v [g]rep | head -11 | tail -n +2"]]
  8. -- A smaller command, less resource intensive, used when popup is not shown.
  9. local CMD_slim = [[grep --max-count=1 '^cpu.' /proc/stat]]
  10. local HOME = os.getenv("HOME")
  11. local TERMINAL = os.getenv("TERMINAL") or "xterm"
  12. local SHELL = os.getenv("SHELL") or "/usr/bin/sh"
  13. local WIDGET_DIR = HOME.."/.config/awesome/awesome-wm-widgets/cpu-widget"
  14. local cpu_widget = {}
  15. local cpu_rows = {
  16. spacing = 4,
  17. layout = wibox.layout.fixed.vertical,
  18. }
  19. local is_update = true
  20. local process_rows = {
  21. layout = wibox.layout.fixed.vertical,
  22. }
  23. -- Remove spaces at end and beggining of a string
  24. local function trim(s)
  25. return (s:gsub("^%s*(.-)%s*$", "%1"))
  26. end
  27. -- Checks if a string starts with an another string
  28. local function starts_with(str, start)
  29. return str:sub(1, #start) == start
  30. end
  31. local function create_textbox(args)
  32. return wibox.widget {
  33. text = args.text,
  34. align = args.align or 'left',
  35. markup = args.markup,
  36. forced_width = args.forced_width or 40,
  37. widget = wibox.widget.textbox
  38. }
  39. end
  40. local function create_process_header(params)
  41. local res = wibox.widget{
  42. create_textbox{markup = '<b>PID</b>'},
  43. create_textbox{markup = '<b>Name</b>'},
  44. {
  45. create_textbox{markup = '<b>%CPU</b>'},
  46. create_textbox{markup = '<b>%MEM</b>'},
  47. params.with_action_column and create_textbox{forced_width = 20} or nil,
  48. layout = wibox.layout.align.horizontal
  49. },
  50. layout = wibox.layout.ratio.horizontal
  51. }
  52. res:ajust_ratio(2, 0.2, 0.47, 0.33)
  53. return res
  54. end
  55. local function create_kill_process_button()
  56. return wibox.widget{
  57. {
  58. id = "icon",
  59. image = WIDGET_DIR .. '/window-close-symbolic.svg',
  60. resize = false,
  61. opacity = 0.1,
  62. widget = wibox.widget.imagebox
  63. },
  64. widget = wibox.container.background
  65. }
  66. end
  67. local function get_cpu_freq()
  68. res = io.popen("cat /proc/cpuinfo | grep -i 'cpu MHz' | awk '{print $4}' | cut -d'.' -f1")
  69. freq_mhz = res:read("l")
  70. return string.format("%0.1f GHz", tonumber(freq_mhz/1000))
  71. end
  72. local function worker(user_args)
  73. local args = user_args or {}
  74. local width = args.width or 50
  75. local step_width = args.step_width or 2
  76. local step_spacing = args.step_spacing or 1
  77. local color = args.color or beautiful.fg_normal
  78. local fg_color = args.fg_color or beautiful.fg_normal
  79. local bg_color = args.bg_color or beautiful.bg_color or "#00000000"
  80. local popup_bg_color = args.popup_bg_color or beautiful.popup_bg_color or "#222222"
  81. local popup_border_width = args.popup_border_width or beautiful.popup_border_width or 1
  82. local popup_border_color = args.popup_border_color or beautiful.popup_border_color or "#7e7e7e"
  83. local enable_kill_button = args.enable_kill_button or false
  84. local process_info_max_length = args.process_info_max_length or -1
  85. local timeout = args.timeout or 1
  86. local font_name = args.font_name or beautiful.font
  87. local icon = args.icon or ""
  88. local icon_size = args.icon_size or 11
  89. local font_name_no_size = font_name:gsub("%s%d+$", " ")
  90. local font_size_icon = font_name_no_size .. icon_size or font_name_no_size .. icon_size
  91. local cpu_widget = wibox.widget {
  92. { -- the first inner widget in cpu_widget
  93. id = "txt_icon",
  94. text = icon,
  95. font = font_size_icon,
  96. widget = wibox.widget.textbox,
  97. },
  98. valign = "center",
  99. layout = wibox.layout.align.horizontal,
  100. -- layout = wibox.container.place,
  101. { -- the second (contains two widgets) inner widget in cpu_widget
  102. {
  103. id = "txt_cpu_freq",
  104. font = font_name,
  105. widget = wibox.widget.textbox
  106. },
  107. {
  108. id = "txt_cpu_usage",
  109. font = font_name,
  110. widget = wibox.widget.textbox
  111. },
  112. -- spacing = 5,
  113. layout = wibox.layout.align.vertical
  114. }
  115. }
  116. function cpu_widget.set(freq, cpu_usage)
  117. cpu_widget:get_children_by_id("txt_cpu_freq")[1]:set_text(freq)
  118. cpu_widget:get_children_by_id("txt_cpu_usage")[1]:set_text(cpu_usage)
  119. end
  120. -- This timer periodically executes the heavy command while the popup is open.
  121. -- It is stopped when the popup is closed and only the slim command is run then.
  122. -- This greatly improves performance while the popup is closed at the small cost
  123. -- of a slightly longer popup opening time.
  124. local popup_timer = gears.timer {
  125. timeout = timeout
  126. }
  127. local popup = awful.popup{
  128. ontop = true,
  129. visible = false,
  130. shape = gears.shape.rounded_rect,
  131. border_width = popup_border_width,
  132. border_color = popup_border_color,
  133. -- border_width = 1,
  134. -- border_color = beautiful.bg_normal,
  135. maximum_width = 300,
  136. offset = { y = 5 },
  137. widget = {}
  138. }
  139. popup:buttons(
  140. awful.util.table.join(
  141. awful.button({}, 3, function()
  142. popup.visible = not popup.visible
  143. end)
  144. )
  145. )
  146. -- Do not update process rows when mouse cursor is over the widget
  147. popup:connect_signal("mouse::enter", function() is_update = false end)
  148. popup:connect_signal("mouse::leave", function() is_update = true end)
  149. cpu_widget:buttons(
  150. awful.util.table.join(
  151. awful.button({}, 1, function()
  152. if popup.visible then
  153. popup.visible = not popup.visible
  154. -- When the popup is not visible, stop the timer
  155. popup_timer:stop()
  156. else
  157. popup:move_next_to(mouse.current_widget_geometry)
  158. -- Restart the timer, when the popup becomes visible
  159. -- Emit the signal to start the timer directly and not wait the timeout first
  160. popup_timer:start()
  161. popup_timer:emit_signal("timeout")
  162. end
  163. end),
  164. awful.button({}, 3, function()
  165. awful.spawn(TERMINAL.." -e "..SHELL.." -c htop")
  166. end)
  167. )
  168. )
  169. -- This part runs constantly, also when the popup is closed.
  170. -- It updates the graph widget in the bar.
  171. local maincpu = {}
  172. watch(CMD_slim, timeout, function(widget, stdout)
  173. local _, user, nice, system, idle, iowait, irq, softirq, steal, _, _ =
  174. stdout:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)')
  175. local total = user + nice + system + idle + iowait + irq + softirq + steal
  176. local diff_idle = idle - tonumber(maincpu['idle_prev'] == nil and 0 or maincpu['idle_prev'])
  177. local diff_total = total - tonumber(maincpu['total_prev'] == nil and 0 or maincpu['total_prev'])
  178. local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
  179. maincpu['total_prev'] = total
  180. maincpu['idle_prev'] = idle
  181. -- widget:add_value(diff_usage)
  182. -- local t = user + nice + system + irq +softirq
  183. -- local cpu_usage_perc = 100 * t / (t + idle)
  184. -- cpu_widget.set("2.6 GHz", cpu_usage_perc)
  185. -- cpu_widget.set("2.6 GHz", string.format(" %.0f%%", diff_usage))
  186. local freq = get_cpu_freq()
  187. cpu_widget.set(freq, string.format(" %.0f%%", diff_usage))
  188. end,
  189. cpu_widget
  190. )
  191. -- local maincpu = {}
  192. -- watch(CMD_slim, timeout, function(widget, stdout)
  193. --
  194. -- local _, user, nice, system, idle, iowait, irq, softirq, steal, _, _ =
  195. -- stdout:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)')
  196. --
  197. -- local total = user + nice + system + idle + iowait + irq + softirq + steal
  198. --
  199. -- local diff_idle = idle - tonumber(maincpu['idle_prev'] == nil and 0 or maincpu['idle_prev'])
  200. -- local diff_total = total - tonumber(maincpu['total_prev'] == nil and 0 or maincpu['total_prev'])
  201. -- local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
  202. --
  203. -- maincpu['total_prev'] = total
  204. -- maincpu['idle_prev'] = idle
  205. --
  206. -- widget:add_value(diff_usage)
  207. -- end,
  208. -- cpugraph_widget
  209. -- )
  210. -- This part runs whenever the timer is fired.
  211. -- It therefore only runs when the popup is open.
  212. local cpus = {}
  213. popup_timer:connect_signal('timeout', function()
  214. awful.spawn.easy_async(CMD, function(stdout, _, _, _)
  215. local i = 1
  216. local j = 1
  217. for line in stdout:gmatch("[^\r\n]+") do
  218. if starts_with(line, 'cpu') then
  219. if cpus[i] == nil then cpus[i] = {} end
  220. local name, user, nice, system, idle, iowait, irq, softirq, steal, _, _ =
  221. line:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)')
  222. local total = user + nice + system + idle + iowait + irq + softirq + steal
  223. local diff_idle = idle - tonumber(cpus[i]['idle_prev'] == nil and 0 or cpus[i]['idle_prev'])
  224. local diff_total = total - tonumber(cpus[i]['total_prev'] == nil and 0 or cpus[i]['total_prev'])
  225. local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
  226. cpus[i]['total_prev'] = total
  227. cpus[i]['idle_prev'] = idle
  228. local row = wibox.widget
  229. {
  230. create_textbox{text = name},
  231. create_textbox{text = math.floor(diff_usage) .. '%'},
  232. {
  233. max_value = 100,
  234. value = diff_usage,
  235. forced_height = 20,
  236. forced_width = 150,
  237. paddings = 1,
  238. margins = 4,
  239. border_width = 1,
  240. border_color = beautiful.bg_focus,
  241. background_color = beautiful.bg_normal,
  242. bar_border_width = 1,
  243. bar_border_color = beautiful.bg_focus,
  244. color = "linear:150,0:0,0:0,#D08770:0.3,#BF616A:0.6," .. beautiful.fg_normal,
  245. widget = wibox.widget.progressbar,
  246. },
  247. layout = wibox.layout.ratio.horizontal
  248. }
  249. row:ajust_ratio(2, 0.15, 0.15, 0.7)
  250. cpu_rows[i] = row
  251. i = i + 1
  252. else
  253. if is_update == true then
  254. local pid = trim(string.sub(line, 1, 10))
  255. local cpu = trim(string.sub(line, 12, 16))
  256. local mem = trim(string.sub(line, 18, 22))
  257. local comm = trim(string.sub(line, 24, 53))
  258. local cmd = trim(string.sub(line, 54))
  259. local kill_proccess_button = enable_kill_button and create_kill_process_button() or nil
  260. local pid_name_rest = wibox.widget{
  261. create_textbox{text = pid},
  262. create_textbox{text = comm},
  263. {
  264. create_textbox{text = cpu, align = 'center'},
  265. create_textbox{text = mem, align = 'center'},
  266. kill_proccess_button,
  267. layout = wibox.layout.fixed.horizontal
  268. },
  269. layout = wibox.layout.ratio.horizontal
  270. }
  271. pid_name_rest:ajust_ratio(2, 0.2, 0.47, 0.33)
  272. local row = wibox.widget {
  273. {
  274. pid_name_rest,
  275. top = 4,
  276. bottom = 4,
  277. widget = wibox.container.margin
  278. },
  279. widget = wibox.container.background
  280. }
  281. row:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) end)
  282. row:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) end)
  283. if enable_kill_button then
  284. row:connect_signal("mouse::enter", function() kill_proccess_button.icon.opacity = 1 end)
  285. row:connect_signal("mouse::leave", function() kill_proccess_button.icon.opacity = 0.1 end)
  286. kill_proccess_button:buttons(
  287. awful.util.table.join( awful.button({}, 1, function()
  288. row:set_bg('#ff0000')
  289. awful.spawn.with_shell('kill -9 ' .. pid)
  290. end) ) )
  291. end
  292. awful.tooltip {
  293. objects = { row },
  294. mode = 'outside',
  295. preferred_positions = {'bottom'},
  296. timer_function = function()
  297. local text = cmd
  298. if process_info_max_length > 0 and text:len() > process_info_max_length then
  299. text = text:sub(0, process_info_max_length - 3) .. '...'
  300. end
  301. return text
  302. :gsub('%s%-', '\n\t-') -- put arguments on a new line
  303. :gsub(':/', '\n\t\t:/') -- java classpath uses : to separate jars
  304. end,
  305. }
  306. process_rows[j] = row
  307. j = j + 1
  308. end
  309. end
  310. end
  311. popup:setup {
  312. {
  313. cpu_rows,
  314. {
  315. orientation = 'horizontal',
  316. forced_height = 15,
  317. color = beautiful.bg_focus,
  318. widget = wibox.widget.separator
  319. },
  320. create_process_header{with_action_column = enable_kill_button},
  321. process_rows,
  322. layout = wibox.layout.fixed.vertical,
  323. },
  324. margins = 8,
  325. widget = wibox.container.margin
  326. }
  327. end)
  328. end)
  329. -- Set fg and bg colors for ram_widget_icon
  330. local cpu_widget_clr = wibox.widget.background()
  331. cpu_widget_clr:set_widget(cpu_widget)
  332. cpu_widget_clr:set_fg(fg_color)
  333. return cpu_widget_clr
  334. end
  335. return setmetatable(cpu_widget, { __call = function(_, ...) return worker(...) end })