cpu-widget-icon.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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 "alacritty"
  12. local SHELL = os.getenv("SHELL") or "/usr/bin/fish"
  13. local WIDGET_DIR = HOME.."/.config/awesome/awesome-wm-widgets/cpu-widget"
  14. local cpu_widget_icon = {}
  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 worker(user_args)
  68. local args = user_args or {}
  69. local width = args.width or 50
  70. local step_width = args.step_width or 2
  71. local step_spacing = args.step_spacing or 1
  72. local color = args.color or beautiful.fg_normal
  73. local background_color = args.background_color or "#00000000"
  74. local enable_kill_button = args.enable_kill_button or false
  75. local process_info_max_length = args.process_info_max_length or -1
  76. local timeout = args.timeout or 1
  77. local cpu_widget_icon = wibox.widget {
  78. {
  79. id = "txt_icon",
  80. text = " ",
  81. font = args.font,
  82. widget = wibox.widget.textbox,
  83. },
  84. valign = "center",
  85. layout = wibox.layout.align.horizontal,
  86. -- layout = wibox.container.place,
  87. }
  88. -- This timer periodically executes the heavy command while the popup is open.
  89. -- It is stopped when the popup is closed and only the slim command is run then.
  90. -- This greatly improves performance while the popup is closed at the small cost
  91. -- of a slightly longer popup opening time.
  92. local popup_timer = gears.timer {
  93. timeout = timeout
  94. }
  95. local popup = awful.popup{
  96. ontop = true,
  97. visible = false,
  98. shape = gears.shape.rounded_rect,
  99. border_width = 1,
  100. border_color = beautiful.bg_normal,
  101. maximum_width = 300,
  102. offset = { y = 5 },
  103. widget = {}
  104. }
  105. popup:buttons(
  106. awful.util.table.join(
  107. awful.button({}, 3, function()
  108. popup.visible = not popup.visible
  109. end)
  110. )
  111. )
  112. -- Do not update process rows when mouse cursor is over the widget
  113. popup:connect_signal("mouse::enter", function() is_update = false end)
  114. popup:connect_signal("mouse::leave", function() is_update = true end)
  115. cpu_widget_icon:buttons(
  116. awful.util.table.join(
  117. awful.button({}, 1, function()
  118. if popup.visible then
  119. popup.visible = not popup.visible
  120. -- When the popup is not visible, stop the timer
  121. popup_timer:stop()
  122. else
  123. popup:move_next_to(mouse.current_widget_geometry)
  124. -- Restart the timer, when the popup becomes visible
  125. -- Emit the signal to start the timer directly and not wait the timeout first
  126. popup_timer:start()
  127. popup_timer:emit_signal("timeout")
  128. end
  129. end),
  130. awful.button({}, 3, function()
  131. awful.spawn(TERMINAL.." -e "..SHELL.." -c htop")
  132. end)
  133. )
  134. )
  135. -- -- This part runs constantly, also when the popup is closed.
  136. -- -- It updates the graph widget in the bar.
  137. -- local maincpu = {}
  138. -- watch(CMD_slim, timeout, function(widget, stdout)
  139. --
  140. -- local _, user, nice, system, idle, iowait, irq, softirq, steal, _, _ =
  141. -- stdout:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)')
  142. --
  143. -- local total = user + nice + system + idle + iowait + irq + softirq + steal
  144. --
  145. -- local diff_idle = idle - tonumber(maincpu['idle_prev'] == nil and 0 or maincpu['idle_prev'])
  146. -- local diff_total = total - tonumber(maincpu['total_prev'] == nil and 0 or maincpu['total_prev'])
  147. -- local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
  148. --
  149. -- maincpu['total_prev'] = total
  150. -- maincpu['idle_prev'] = idle
  151. --
  152. -- -- widget:add_value(diff_usage)
  153. -- -- local t = user + nice + system + irq +softirq
  154. -- -- local cpu_usage_perc = 100 * t / (t + idle)
  155. -- -- cpu_widget_icon.set("2.6 GHz", cpu_usage_perc)
  156. --
  157. -- cpu_widget_icon.set("2.6 GHz", string.format(" %.0f%%", diff_usage))
  158. -- end,
  159. -- cpu_widget_icon
  160. -- )
  161. -- local maincpu = {}
  162. -- watch(CMD_slim, timeout, function(widget, stdout)
  163. --
  164. -- local _, user, nice, system, idle, iowait, irq, softirq, steal, _, _ =
  165. -- stdout:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)')
  166. --
  167. -- local total = user + nice + system + idle + iowait + irq + softirq + steal
  168. --
  169. -- local diff_idle = idle - tonumber(maincpu['idle_prev'] == nil and 0 or maincpu['idle_prev'])
  170. -- local diff_total = total - tonumber(maincpu['total_prev'] == nil and 0 or maincpu['total_prev'])
  171. -- local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
  172. --
  173. -- maincpu['total_prev'] = total
  174. -- maincpu['idle_prev'] = idle
  175. --
  176. -- widget:add_value(diff_usage)
  177. -- end,
  178. -- cpugraph_widget
  179. -- )
  180. -- This part runs whenever the timer is fired.
  181. -- It therefore only runs when the popup is open.
  182. local cpus = {}
  183. popup_timer:connect_signal('timeout', function()
  184. awful.spawn.easy_async(CMD, function(stdout, _, _, _)
  185. local i = 1
  186. local j = 1
  187. for line in stdout:gmatch("[^\r\n]+") do
  188. if starts_with(line, 'cpu') then
  189. if cpus[i] == nil then cpus[i] = {} end
  190. local name, user, nice, system, idle, iowait, irq, softirq, steal, _, _ =
  191. line:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)')
  192. local total = user + nice + system + idle + iowait + irq + softirq + steal
  193. local diff_idle = idle - tonumber(cpus[i]['idle_prev'] == nil and 0 or cpus[i]['idle_prev'])
  194. local diff_total = total - tonumber(cpus[i]['total_prev'] == nil and 0 or cpus[i]['total_prev'])
  195. local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10
  196. cpus[i]['total_prev'] = total
  197. cpus[i]['idle_prev'] = idle
  198. local row = wibox.widget
  199. {
  200. create_textbox{text = name},
  201. create_textbox{text = math.floor(diff_usage) .. '%'},
  202. {
  203. max_value = 100,
  204. value = diff_usage,
  205. forced_height = 20,
  206. forced_width = 150,
  207. paddings = 1,
  208. margins = 4,
  209. border_width = 1,
  210. border_color = beautiful.bg_focus,
  211. background_color = beautiful.bg_normal,
  212. bar_border_width = 1,
  213. bar_border_color = beautiful.bg_focus,
  214. color = "linear:150,0:0,0:0,#D08770:0.3,#BF616A:0.6," .. beautiful.fg_normal,
  215. widget = wibox.widget.progressbar,
  216. },
  217. layout = wibox.layout.ratio.horizontal
  218. }
  219. row:ajust_ratio(2, 0.15, 0.15, 0.7)
  220. cpu_rows[i] = row
  221. i = i + 1
  222. else
  223. if is_update == true then
  224. local pid = trim(string.sub(line, 1, 10))
  225. local cpu = trim(string.sub(line, 12, 16))
  226. local mem = trim(string.sub(line, 18, 22))
  227. local comm = trim(string.sub(line, 24, 53))
  228. local cmd = trim(string.sub(line, 54))
  229. local kill_proccess_button = enable_kill_button and create_kill_process_button() or nil
  230. local pid_name_rest = wibox.widget{
  231. create_textbox{text = pid},
  232. create_textbox{text = comm},
  233. {
  234. create_textbox{text = cpu, align = 'center'},
  235. create_textbox{text = mem, align = 'center'},
  236. kill_proccess_button,
  237. layout = wibox.layout.fixed.horizontal
  238. },
  239. layout = wibox.layout.ratio.horizontal
  240. }
  241. pid_name_rest:ajust_ratio(2, 0.2, 0.47, 0.33)
  242. local row = wibox.widget {
  243. {
  244. pid_name_rest,
  245. top = 4,
  246. bottom = 4,
  247. widget = wibox.container.margin
  248. },
  249. widget = wibox.container.background
  250. }
  251. row:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) end)
  252. row:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) end)
  253. if enable_kill_button then
  254. row:connect_signal("mouse::enter", function() kill_proccess_button.icon.opacity = 1 end)
  255. row:connect_signal("mouse::leave", function() kill_proccess_button.icon.opacity = 0.1 end)
  256. kill_proccess_button:buttons(
  257. awful.util.table.join( awful.button({}, 1, function()
  258. row:set_bg('#ff0000')
  259. awful.spawn.with_shell('kill -9 ' .. pid)
  260. end) ) )
  261. end
  262. awful.tooltip {
  263. objects = { row },
  264. mode = 'outside',
  265. preferred_positions = {'bottom'},
  266. timer_function = function()
  267. local text = cmd
  268. if process_info_max_length > 0 and text:len() > process_info_max_length then
  269. text = text:sub(0, process_info_max_length - 3) .. '...'
  270. end
  271. return text
  272. :gsub('%s%-', '\n\t-') -- put arguments on a new line
  273. :gsub(':/', '\n\t\t:/') -- java classpath uses : to separate jars
  274. end,
  275. }
  276. process_rows[j] = row
  277. j = j + 1
  278. end
  279. end
  280. end
  281. popup:setup {
  282. {
  283. cpu_rows,
  284. {
  285. orientation = 'horizontal',
  286. forced_height = 15,
  287. color = beautiful.bg_focus,
  288. widget = wibox.widget.separator
  289. },
  290. create_process_header{with_action_column = enable_kill_button},
  291. process_rows,
  292. layout = wibox.layout.fixed.vertical,
  293. },
  294. margins = 8,
  295. widget = wibox.container.margin
  296. }
  297. end)
  298. end)
  299. -- Set fg and bg colors for ram_widget_icon
  300. local cpu_widget_icon_clr = wibox.widget.background()
  301. cpu_widget_icon_clr:set_widget(cpu_widget_icon)
  302. cpu_widget_icon_clr:set_fg("#89ddff")
  303. return cpu_widget_icon_clr
  304. end
  305. return setmetatable(cpu_widget_icon, { __call = function(_, ...) return worker(...) end })