net.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. -- @author cedlemo
  2. local setmetatable = setmetatable
  3. local ipairs = ipairs
  4. local math = math
  5. local table = table
  6. local type = type
  7. local string = string
  8. local io = require("io")
  9. local os = require("os")
  10. local awful = require("awful")
  11. local naughty = require("naughty")
  12. local tonumber = tonumber
  13. local color = require("gears.color")
  14. local base = require("wibox.widget.base")
  15. local helpers = require("blingbling.helpers")
  16. local superproperties = require('blingbling.superproperties')
  17. local lgi = require("lgi")
  18. local pangocairo = lgi.PangoCairo
  19. local pango = lgi.Pango
  20. ---Net widget displays two arrows as graph for download/upload activities
  21. --@module blingbling.widget
  22. ---Set the net interface used to get information, default is eth0.
  23. --@usage mynet:set_interface(string)
  24. --@name set_interface
  25. --@class function
  26. --@param interface a string
  27. ---Define the top and bottom margin for the graph area.
  28. --@usage mynet:set_v_margin(integer)
  29. --@name set_v_margin
  30. --@class function
  31. --@param margin an integer for top and bottom margin
  32. ---Define the left and right margin for the graph area.
  33. --@usage mynet:set_h_margin()
  34. --@name set_h_margin
  35. --@class function
  36. --@param margin an integer for left and right margin
  37. ---Fill all the widget (width * height) with this color (default is none ).
  38. --@usage mynet:set_background_color(string) -->"#rrggbbaa"
  39. --@name set_background_color
  40. --@class function
  41. --@param color a string "#rrggbbaa" or "#rrggbb"
  42. ---Fill the graph area background with this color (default is none).
  43. --@usage mynet:set_background_graph_color(string) -->"#rrggbbaa"
  44. --@name set_background_graph_color
  45. --@class function
  46. --@param color a string "#rrggbbaa" or "#rrggbb"
  47. ---Define the graph/arrows color.
  48. --@usage mynet:set_graph_color(string) -->"#rrggbbaa"
  49. --@name set_graph_color
  50. --@class function
  51. --@param color a string "#rrggbbaa" or "#rrggbb"
  52. ---Set an outline on the arrows with this color.
  53. --@usage mynet:set_graph_line_color(string) -->"#rrggbbaa"
  54. --@name set_graph_line_color
  55. --@class function
  56. --@param color a string "#rrggbbaa" or "#rrggbb"
  57. ---Display or not upload/download informations.
  58. --@usage mynet:set_show_text(boolean) --> true or false
  59. --@name set_show_text
  60. --@class function
  61. --@param boolean true or false default is false
  62. ---Define the text color.
  63. --@usage mynet:set_text_color(string) -->"#rrggbbaa"
  64. --@name set_text_color
  65. --@class function
  66. --@param color a string "#rrggbbaa" or "#rrggbb"
  67. ---Set a color behind the text.
  68. --@usage mynet:set_text_background_color(string) -->"#rrggbbaa"
  69. --@name set_text_background_color
  70. --@class function
  71. --@param color a string "#rrggbbaa" or "#rrggbb"
  72. ---Set the size of the font to use.
  73. --@usage mynet:set_font_size(integer)
  74. --@name set_font_size
  75. --@class function
  76. --@param size the font size
  77. ---Set the font to use.
  78. --@usage mynet:set_font(string)
  79. --@name set_font
  80. --@class function
  81. --@param font a string that contains the font name family and weight
  82. ---Set the URL used for getting the external IP, default is http://ipecho.net/plain
  83. --@usage mynet:set_url_for_ext_ip(string)
  84. --@name set_url_for_ext_ip
  85. --@class function
  86. --@param URL outputting your external IP in plain text
  87. local net = { mt = {} }
  88. local data = setmetatable({}, { __mode = "k" })
  89. local properties = { "interface", "width", "height", "v_margin", "h_margin",
  90. "background_color", "graph_background_color","graph_color",
  91. "graph_line_color","show_text", "text_color",
  92. "text_background_color" , "font_size","font","url_for_ext_ip" }
  93. local function format_output_value(value)
  94. local unit = { "b", "kb","mb","gb"}
  95. local unit_range = { 1, 1024, 1024^2, 1024^3 }
  96. local output_value = 0
  97. local output_unit = "b"
  98. local text = ""
  99. if value ~= nil then
  100. for i,v in ipairs(unit_range) do
  101. if value >= v then
  102. output_value = value / v
  103. output_unit = unit[i]
  104. end
  105. end
  106. end
  107. if output_value >= 0 and output_value < 10 then
  108. text = string.format("%.2f", output_value).. output_unit
  109. end
  110. if output_value >= 10 and output_value < 100 then
  111. text = string.format("%.1f", output_value).. output_unit
  112. end
  113. if output_value >= 100 then
  114. text = string.format("%d", math.ceil(output_value)).. output_unit
  115. end
  116. return output_value, text
  117. end
  118. local function draw_a_red_cross(cr, x , y, width, height)
  119. cr:move_to(x, y)
  120. cr:line_to(width - x, height - y)
  121. cr:move_to(width - x, y)
  122. cr:line_to(x, height - y)
  123. cr:set_source_rgb(1,0,0)
  124. cr:set_line_width(2)
  125. cr:stroke()
  126. end
  127. function net.draw(n_graph, wibox, cr, width, height)
  128. local props = helpers.load_properties(properties, data, n_graph, superproperties)
  129. local interface= data[n_graph].interface or "eth0"
  130. local font = nil
  131. if props.show_text then
  132. if type(props.font) == "string" then
  133. font = props.font .. " " .. props.font_size
  134. elseif type(props.font) == "table" then
  135. font = (props.font.family or "Sans") .. " " .. (props.font.slang or "normal") .. " " .. (props.font.weight or "normal") .. " " .. props.font_size
  136. end
  137. --search the good width to display all text and graph and modify the widget width if necessary
  138. --Adapt widget width with max lenght text
  139. --local text_reference = "1.00mb"
  140. local layout = pangocairo.create_layout(cr)
  141. local font_desc = pango.FontDescription.from_string(font)
  142. layout:set_font_description(font_desc)
  143. layout.text = "1.00mb"
  144. local _, logical = layout:get_pixel_extents()
  145. local text_width = logical.width
  146. local arrow_width = 6
  147. local arrows_separator = 2
  148. local total_width = (2* text_width) +(2*arrow_width) + arrows_separator + (2*props.h_margin)
  149. data[n_graph].width = total_width
  150. else
  151. local arrow_width = 8
  152. local arrows_separator = 2
  153. data[n_graph].width = (arrow_width * 2) + arrows_separator + (2* props.h_margin)
  154. end
  155. --TODO manage widget background
  156. ----Generate Background (background widget)
  157. if props.background_color then
  158. local r,g,b,a = helpers.hexadecimal_to_rgba_percent(props.background_color)
  159. cr:set_source_rgba(r,g,b,a)
  160. cr:paint()
  161. end
  162. up_value, up_text = format_output_value(data[n_graph][interface.."_up"])
  163. down_value, down_text = format_output_value(data[n_graph][interface.."_down"])
  164. --Drawn up arrow
  165. helpers.draw_up_down_arrows(
  166. cr,
  167. math.floor(data[n_graph].width/2 -1),
  168. height - props.v_margin,
  169. props.v_margin,
  170. up_value,
  171. props.graph_background_color,
  172. props.graph_color,
  173. props.graph_line_color ,
  174. true)
  175. --Drawn down arrow
  176. helpers.draw_up_down_arrows(
  177. cr,
  178. math.floor(data[n_graph].width/2)+1,
  179. props.v_margin,
  180. height - props.v_margin,
  181. down_value,
  182. props.graph_background_color,
  183. props.graph_color,
  184. props.graph_line_color ,
  185. false)
  186. if data[n_graph][interface.."_state"] ~= "up" or data[n_graph][interface.."_carrier"] ~= "1" then
  187. draw_a_red_cross(cr, props.h_margin, props.v_margin, data[n_graph].width, height)
  188. end
  189. if props.show_text == true then
  190. --Draw Text and it's background
  191. helpers.draw_layout_and_background(cr,
  192. down_text,
  193. data[n_graph].width - props.h_margin,
  194. props.v_margin ,
  195. font,
  196. props.text_background_color,
  197. props.text_color,
  198. "end",
  199. "start")
  200. helpers.draw_layout_and_background(cr,
  201. up_text,
  202. props.h_margin,
  203. height - props.v_margin ,
  204. font,
  205. props.text_background_color,
  206. props.text_color,
  207. "start",
  208. "end")
  209. end
  210. end
  211. function net.fit(n_graph, width, height)
  212. return data[n_graph].width, data[n_graph].height
  213. end
  214. local function get_net_infos(n_graph)
  215. -- Variable definitions
  216. for line in io.lines("/proc/net/dev") do
  217. local device = string.match(line, "^[%s]?[%s]?[%s]?[%s]?([%w]+):")
  218. if device ~= nil then
  219. -- Received bytes, first value after the name
  220. local recv = tonumber(string.match(line, ":[%s]*([%d]+)"))
  221. -- Transmited bytes, 7 fields from end of the line
  222. local send = tonumber(string.match(line,
  223. "([%d]+)%s+%d+%s+%d+%s+%d+%s+%d+%s+%d+%s+%d+%s+%d$"))
  224. --check if interface is up or down
  225. local state
  226. for line in io.lines("/sys/class/net/"..string.gsub(device,"%s","").."/operstate") do
  227. state = line
  228. end
  229. data[n_graph][device.."_state"]=state
  230. --check if wire is connected
  231. local carrier
  232. if data[n_graph][device.."_state"] == "up" then
  233. for line in io.lines("/sys/class/net/"..string.gsub(device,"%s","").."/carrier") do
  234. carrier = line
  235. end
  236. data[n_graph][device.."_carrier"]=carrier
  237. else
  238. data[n_graph][device.."_carrier"]="0"
  239. end
  240. local now =os.time()
  241. if data[n_graph][device.."_down"] == nil or data[n_graph][device.."_up"] == nil then
  242. data[n_graph][device.."_down"] = 0
  243. data[n_graph][device.."_up"] = 0
  244. else
  245. local interval = now - data[n_graph][device.."_time"]
  246. if interval <= 0 then interval =1 end
  247. local down = (recv -data[n_graph][device.."_last_recv"]) / interval
  248. local up = (send - data[n_graph][device.."_last_send"]) / interval
  249. data[n_graph][device.."_down" ] = down
  250. data[n_graph][device.."_up"] = up
  251. end
  252. data[n_graph][device.."_time"] = now
  253. data[n_graph][device.."_last_recv"] = recv
  254. data[n_graph][device.."_last_send"] = send
  255. end
  256. end
  257. end
  258. local function update_net(n_graph)
  259. if not n_graph then return end
  260. data[n_graph].timer_update = timer({timeout = 2})
  261. data[n_graph].timer_update:connect_signal("timeout", function()
  262. get_net_infos(n_graph);
  263. n_graph:emit_signal("widget::updated")
  264. end)
  265. data[n_graph].timer_update:start()
  266. return n_graph
  267. end
  268. local function hide_ippopup_infos(n_graph)
  269. if data[n_graph].ippopup ~= nil then
  270. naughty.destroy(data[n_graph].ippopup)
  271. data[n_graph].ippopup = nil
  272. end
  273. end
  274. local function show_ippopup_infos(n_graph)
  275. local ip_addr
  276. local gateway
  277. local all_infos=awful.util.pread("ip route show")
  278. local interface = data[n_graph].interface
  279. if data[n_graph][interface.."_state"] == "up" then
  280. if data[n_graph][interface.."_carrier"] == "1" then --get local ip configuration
  281. ip_addr=string.match(string.match(all_infos, "%sdev%s".. interface .. "%s+proto%skernel.*" .."%ssrc%s[%d]+%.[d%]+%.[%d]+%.[%d]+"), "[%d]+%.[d%]+%.[%d]+%.[%d]+")
  282. --get gateway
  283. gateway= string.match(string.match(all_infos,"default%svia%s[%d]+%.[d%]+%.[%d]+%.[%d]+"), "[%d]+%.[d%]+%.[%d]+%.[%d]+")
  284. --get external ip configuration
  285. local url_for_ext_ip = ""
  286. if data[n_graph].url_for_ext_ip == nil then
  287. data[n_graph].url_for_ext_ip = "http://ipecho.net/plain"
  288. end
  289. url_for_ext_ip = data[n_graph].url_for_ext_ip
  290. local ext_ip = awful.util.pread("curl --silent --connect-timeout 3 -S " .. url_for_ext_ip .. " 2>&1")
  291. --if time out then no external ip
  292. if string.match(ext_ip,"timed%sout%!") then
  293. data[n_graph].ext_ip = "n/a"
  294. else
  295. data[n_graph].ext_ip = ext_ip
  296. end
  297. --get tor external configuration
  298. local tor_ext_ip
  299. --we check that the tor address have not been checked or that the elapsed time from the last request is not < 300 sec. whereas whatsmyip block the request
  300. if (data[n_graph].tor_ext_ip_timer == nil or data[n_graph].tor_ext_ip_timer + 300 < os.time()) and data[n_graph].ext_ip ~= "n/a" then
  301. if awful.util.pread("pgrep -x tor") ~= "" then
  302. tor_ext_ip = awful.util.pread("curl --silent -S -x socks4a://localhost:9050 http://ipecho.net/plain 2>&1")
  303. else
  304. tor_ext_ip = "No tor"
  305. end
  306. data[n_graph].tor_ext_ip=tor_ext_ip
  307. data[n_graph].tor_ext_ip_timer=os.time()
  308. --if local ip is ok but not the external ip, then we can't get external tor ip
  309. elseif data[n_graph].ext_ip == "n/a" then
  310. tor_ext_ip="n/a"
  311. --we get the last value of tor_ext_ip of the last recent check.
  312. else
  313. tor_ext_ip= data[n_graph].tor_ext_ip
  314. end
  315. local separator ="\n|\n"
  316. text="Local Ip:\t"..ip_addr..separator.."Gateway:\t".. gateway..separator .."External Ip:\t"..data[n_graph].ext_ip .. separator .. "Tor External Ip:\t" .. tor_ext_ip
  317. else
  318. text="Wire is not connected on " .. interface
  319. end
  320. else
  321. text ="Interface : "..interface .. " is down."
  322. end
  323. data[n_graph].ippopup=naughty.notify({
  324. title = interface .. " informations:",
  325. text = text,
  326. timeout= 0,
  327. hover_timeout = 0.5
  328. })
  329. end
  330. ---Add a popup on the widget that displays informations on the current network connection.
  331. --@usage mynet:set_ippopup()
  332. function net:set_ippopup()
  333. self:connect_signal("mouse::enter", function()
  334. show_ippopup_infos(self)
  335. end)
  336. self:connect_signal("mouse::leave", function()
  337. hide_ippopup_infos(self)
  338. end)
  339. end
  340. --- Set the n_graph height.
  341. -- @param height The height to set.
  342. function net:set_height( height)
  343. if height >= 5 then
  344. data[self].height = height
  345. self:emit_signal("widget::updated")
  346. end
  347. return self
  348. end
  349. --- Set the graph width.
  350. -- @param width The width to set.
  351. function net:set_width( width)
  352. if width >= 5 then
  353. data[self].width = width
  354. self:emit_signal("widget::updated")
  355. end
  356. return self
  357. end
  358. -- Build properties function
  359. for _, prop in ipairs(properties) do
  360. if not net["set_" .. prop] then
  361. net["set_" .. prop] = function(n_graph, value)
  362. data[n_graph][prop] = value
  363. n_graph:emit_signal("widget::updated")
  364. return n_graph
  365. end
  366. end
  367. end
  368. --- Create a n_graph widget.
  369. --@usage mynet = blingbling.net({width = 100, height = 20, interface = "eth0"})
  370. -- @param args Standard widget() arguments. You should add width and height and interface
  371. -- @return A graph widget.
  372. function net.new(args)
  373. local args = args or {}
  374. args.width = args.width or 100
  375. args.height = args.height or 20
  376. if args.width < 5 or args.height < 5 then return end
  377. local n_graph = base.make_widget()
  378. data[n_graph] = {}
  379. for _, v in ipairs(properties) do
  380. data[n_graph][v] = args[v]
  381. end
  382. if args.interface then
  383. data[n_graph].interface = args.interface
  384. end
  385. data[n_graph].nets = {}
  386. --or
  387. data[n_graph].value = 0
  388. data[n_graph].max_value = 1
  389. n_graph.draw = net.draw
  390. n_graph.fit = net.fit
  391. n_graph.set_ippopup = net.set_ippopup
  392. for _, prop in ipairs(properties) do
  393. n_graph["set_" .. prop] = net["set_" .. prop]
  394. end
  395. update_net(n_graph)
  396. return n_graph
  397. end
  398. function net.mt:__call(...)
  399. return net.new(...)
  400. end
  401. return setmetatable(net, net.mt)