eufon

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 463c1eea030921cb77c93dda680e43268fe81ca1
parent b6a8048c8bb2e9fdb46dd688173ac1c71f3924a0
Author: Daniel Barlow <dan@telent.net>
Date:   Thu,  7 Apr 2022 10:00:49 +0100

standardise interface to metrics

call as follows:

(let [m  (metricname.new)]
  (m:read)
  (m:read)
   ...
   )

Diffstat:
Mblinkenlicht/README.md | 10+++++-----
Mblinkenlicht/bl.fnl | 57+++++++++++++++++++++++++++++++--------------------------
Dblinkenlicht/metric.fnl | 42------------------------------------------
Ablinkenlicht/metric/battery.fnl | 15+++++++++++++++
Ablinkenlicht/metric/cpustat.fnl | 24++++++++++++++++++++++++
Ablinkenlicht/metric/loadavg.fnl | 9+++++++++
Ablinkenlicht/metric/uplink.fnl | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mblinkenlicht/modem.fnl | 32+++++++++++++++-----------------
Dblinkenlicht/uplink.fnl | 119-------------------------------------------------------------------------------
9 files changed, 218 insertions(+), 209 deletions(-)

diff --git a/blinkenlicht/README.md b/blinkenlicht/README.md @@ -17,9 +17,9 @@ Not quite dogfood-ready yet, but fast approaching. * blinkenlicht.fnl is the module that parses `bar` and `indicator` forms and does all the UI -* metric.fnl is a collection of functions that read metrics (load - average, battery status etc) from the system, for indicators to - display. +* metric/*.fnl is a collection of modules that read metrics (load + average, connectivity, battery status etc) from the system, for + indicators to display. Use the `default.nix` for guidance as to libraries and other setup required - or just use it, of course. @@ -43,10 +43,10 @@ Gtk will find it anyway. Magic. * [X] add some mechanism for indicators that wait for events instead of polling * [X] set indicator background colour (use css for this?) * [ ] update image/label widget instead of destroying -* [ ] allow height customisation +* [ ] allow image height customisation * [X] set poll interval based on indicators' requested intervals * [X] allow indicator to change styles based on status * [ ] add metrics for - - wireless connected/strength + - [X] wireless connected/strength - wwan connected/type (hspa, lte, etc)/signal - cpu % diff --git a/blinkenlicht/bl.fnl b/blinkenlicht/bl.fnl @@ -3,10 +3,12 @@ (local {: view} (require :fennel)) (local iostream (require :iostream)) -(local metric (require :metric)) -(local uplink (require :uplink)) (local modem (require :modem)) +(local uplink (require :metric.uplink)) +(local battery (require :metric.battery)) +(local cpustat (require :metric.cpustat)) + (stylesheet "licht.css") (fn battery-icon-codepoint [status percent] @@ -40,26 +42,27 @@ (indicator { :wait-for { :interval (* 1 500) } :refresh - #(let [current (. (metric.cpustat) :iowait) - delta (- current previous) - v (if (> delta 4) "" " ")] - (set previous current) - {:text v}) + (let [stat (cpustat.new)] + #(let [current (. (stat:read) :iowait) + delta (- current previous) + v (if (> delta 4) "" " ")] + (set previous current) + {:text v})) })) - (let [modem (modem.new)] - (indicator { - :wait-for { + (indicator { + :wait-for { :interval (* 4 1000) - } - :refresh + } + :refresh + (let [modem (modem.new)] #(let [{:m3gpp-operator-name operator - :signal-quality quality} (modem:value)] + :signal-quality quality} (modem:read)] {:text (.. operator - ;" " (. quality 1) "dBm" + ;;" " (. quality 1) "dBm" ) - }) - })) + })) + }) (let [uplink (uplink.new) input (iostream.from-descriptor uplink.fd)] @@ -68,7 +71,7 @@ :input [input] } :refresh - #(let [status (uplink:status)] + #(let [status (uplink:read)] (if status {:text (.. (or status.ssid status.name "?") " " @@ -85,15 +88,17 @@ (indicator { :wait-for { :interval (* 1000 10) } :refresh - #(let [{:power-supply-capacity percent - :power-supply-status status} - (metric.battery "axp20x-battery") - icon-code (battery-icon-codepoint - status (tonumber percent))] - {:text - (string.format "%s %d%%" (utf8.char icon-code) percent) - :classes ["battery" (if (< (tonumber percent) 20) "low" "ok")] - }) + (let [battery (battery.new (or (os.getenv "BLINKEN_BATTERY") + "axp20x-battery"))] + #(let [{:power-supply-capacity percent + :power-supply-status status} + (battery.read) + icon-code (battery-icon-codepoint + status (tonumber percent))] + {:text + (string.format "%s %d%%" (utf8.char icon-code) percent) + :classes ["battery" (if (< (tonumber percent) 20) "low" "ok")] + })) }) (indicator { :wait-for { :interval 4000 } diff --git a/blinkenlicht/metric.fnl b/blinkenlicht/metric.fnl @@ -1,42 +0,0 @@ -(local {: view} (require :fennel)) - -(fn loadavg [] - (with-open [f (io.open "/proc/loadavg" :r)] - (let [line (f:read "*a") - (one five fifteen) (line:match "([%d.]+) +([%d.]+) +([%d.]+)")] - (values (tonumber one) (tonumber five) (tonumber fifteen))))) - -(fn battery [name] - (let [name (.. "/sys/class/power_supply/" name "/uevent")] - (with-open [f (io.open name :r)] - (let [fields {}] - (each [line #(f:read "*l")] - (let [(name value) (line:match "([^=]+)=(.+)")] - (tset fields (: (name:gsub "_" "-") :lower) value))) - fields)))) - -(fn parse-cpu-stat-line [line] - (let [labels [:user :nice :system :idle :iowait - :irq :softirq :steal :guest :guest_nice] - vals (icollect [field (line:gmatch "([%d.]+)")] - (tonumber field))] - (collect [i label (ipairs labels)] - label (. vals i)))) - -(var proc-stat-handle nil) - -(fn cpustat [path] - (if proc-stat-handle - (proc-stat-handle:seek :set 0) - (set proc-stat-handle (io.open "/proc/stat" :r))) - (let [f proc-stat-handle] - (accumulate [ret nil - line #(f:read "*l") ] - (if (= (string.sub line 1 (# "cpu ")) "cpu ") - (parse-cpu-stat-line line) - ret)))) - -{: loadavg - : battery - : cpustat - } diff --git a/blinkenlicht/metric/battery.fnl b/blinkenlicht/metric/battery.fnl @@ -0,0 +1,15 @@ +(fn battery [name] + (let [name (.. "/sys/class/power_supply/" name "/uevent")] + (with-open [f (io.open name :r)] + (let [fields {}] + (each [line #(f:read "*l")] + (let [(name value) (line:match "([^=]+)=(.+)")] + (tset fields (: (name:gsub "_" "-") :lower) value))) + fields)))) + +{ :new + (fn [name] + { + :read #(battery name) + }) + } diff --git a/blinkenlicht/metric/cpustat.fnl b/blinkenlicht/metric/cpustat.fnl @@ -0,0 +1,24 @@ +(fn parse-cpu-stat-line [line] + (let [labels [:user :nice :system :idle :iowait + :irq :softirq :steal :guest :guest_nice] + vals (icollect [field (line:gmatch "([%d.]+)")] + (tonumber field))] + (collect [i label (ipairs labels)] + label (. vals i)))) + +(fn cpustat [proc-stat-handle] + (let [f proc-stat-handle] + (f:seek :set 0) + (accumulate [ret nil + line #(f:read "*l") + :until ret] + (if (= (string.sub line 1 (# "cpu ")) "cpu ") + (parse-cpu-stat-line line) + ret)))) +{ + :new + #(let [handle (io.open "/proc/stat" :r)] + { + :read #(cpustat handle) + }) + } diff --git a/blinkenlicht/metric/loadavg.fnl b/blinkenlicht/metric/loadavg.fnl @@ -0,0 +1,9 @@ +(fn loadavg [] + (with-open [f (io.open "/proc/loadavg" :r)] + (let [line (f:read "*a") + (one five fifteen) (line:match "([%d.]+) +([%d.]+) +([%d.]+)")] + (values (tonumber one) (tonumber five) (tonumber fifteen))))) + +{:new #{ + :read #(loadavg) + }) diff --git a/blinkenlicht/metric/uplink.fnl b/blinkenlicht/metric/uplink.fnl @@ -0,0 +1,119 @@ +(local nl (require :netlink)) +(local view (. (require :fennel) :view)) + +;; $ grep DEVTYPE /sys/class/net/*/uevent +;; /sys/class/net/docker0/uevent:DEVTYPE=bridge +;; /sys/class/net/wlp4s0/uevent:DEVTYPE=wlan +;; /sys/class/net/wwp0s20f0u2i12/uevent:DEVTYPE=wwan +;; (ethernet and loopback devices don't have DEVTYPE) + +(fn devtype [ifname] + (with-open [f (io.open (.. "/sys/class/net/" ifname "/uevent") :r)] + (accumulate [dtype nil + line #(f:read "*l") + :until dtype] + (let [(name value) (line:match "([^=]+)=(.+)")] + (if (= name "DEVTYPE") value dtype))))) + +;; if the type is wlan, we can get a signal strength indicator +;; from the "quality - link" column of /proc/net/wireless + +(fn wlan-link-quality [ifname] + (with-open [f (io.open "/proc/net/wireless" :r)] + (accumulate [strength nil + line #(f:read "*l") + :until strength] + ;; "%6s: %04x %3d%c %3d%c %3d%c %6d %6d %6d " + ;; "%6d %6d %6d\n", + ;; https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/wireless/wext-proc.c#n49 + + (let [(name status link level) + (line:match "(.-): +(%x+) +(%S-)[ .] +(%S-)[ .]")] + (if (= name ifname) + (tonumber level) + strength))))) + +(fn wlan-link-ssid [ifname] + ;; could do this directly using an ioctl + ;; http://papermint-designs.com/dmo-blog/2016-08-how-to-get-the-essid-of-the-wifi-network-you-are-connected-to-# + (with-open [f (io.popen (.. "iwgetid " ifname " --raw") :r)] + (f:read "*l"))) + +(fn get-network-info [event] + ;; augments a newlink event with relevant information, if it's + ;; sufficiently well-configured to have any + + ;; "up" => administratively up + ;; "running" => can actually exchange packets + (when (= event.running "yes") + (let [dtype (devtype event.name)] + (tset event :devtype dtype) + (when (= dtype "wlan") + (if (not event.ssid) + (tset event :ssid (wlan-link-ssid event.name))) + (tset event :quality (wlan-link-quality event.name))) + (when (= dtype "wwan") + ;; for wwan, need to determine how to get strength and carrier name + true) + )) + event) + +(fn uplink [] + (let [links {} + routes {} + sock (nl.socket)] + (fn handle-event [event] + (match event + {:event :newlink} + (tset links event.index + (match event.up + "yes" (get-network-info event) + "no" event)) + + {:event :newroute} + ;; XXX there may be >1 route to any given destination, + ;; (e.g wwan and wlan both have default route) + ;; we probably need to store all of them and + ;; distinguish by metric + (let [dst (or event.dst "default") + existing (. routes dst)] + (if (or (not existing) + (< event.metric existing.metric)) + (tset routes dst event))) + + {} (print :unhandled event.event) + )) + (each [_ event (ipairs (sock:query ))] + (handle-event event)) + + { + :refresh #(each [_ event (ipairs (sock:event))] + (handle-event event)) + :fd (sock:fd) + :read (fn [self] + (self:refresh) + (let [defaultroute routes.default + interface (and defaultroute + (. links defaultroute.index))] + (and interface (= interface.running "yes") + (get-network-info interface)))) + :wait #(sock:poll 1000) + :interface (fn [self ifnum] + (. links ifnum)) + } + )) + +(comment +(let [nl (netlunk)] + (while (or (nl:wait) true) + (nl:refresh) + (match (nl:uplink) + interface + (print "default route through " (view interface)) + nil + (print "no default route") + )))) + +{ + :new uplink + } diff --git a/blinkenlicht/modem.fnl b/blinkenlicht/modem.fnl @@ -5,25 +5,22 @@ (local variant dbus.variant) ;; https://www.freedesktop.org/software/ModemManager/api/latest/ref-dbus.html -(var the-modem-manager nil) + (fn modem-manager [] - (when (not the-modem-manager) - (set the-modem-manager - (dbus.Proxy:new - { - :bus dbus.Bus.SYSTEM - :name "org.freedesktop.ModemManager1" - :interface "org.freedesktop.DBus.ObjectManager" - :path "/org/freedesktop/ModemManager1" - }))) - the-modem-manager) + (dbus.Proxy:new + { + :bus dbus.Bus.SYSTEM + :name "org.freedesktop.ModemManager1" + :interface "org.freedesktop.DBus.ObjectManager" + :path "/org/freedesktop/ModemManager1" + })) ;; this is a function because the path to the modem may change ;; (e.g. due to suspend/resume cycles causing services to be stopped ;; and started) -(fn modem-interface [] - (let [modem-path (next (: (assert (modem-manager)) :GetManagedObjects))] +(fn modem-interface [manager] + (let [modem-path (next (: (assert manager) :GetManagedObjects))] (dbus.Proxy:new { :bus dbus.Bus.SYSTEM @@ -33,10 +30,11 @@ }))) (fn new-modem-status [] - { - :value #(let [m (modem-interface)] - (variant.strip (m:GetStatus))) - }) + (let [manager (modem-manager)] + { + :read #(let [m (modem-interface manager)] + (variant.strip (m:GetStatus))) + })) (comment (let [ctx (: (GLib.MainLoop) :get_context) diff --git a/blinkenlicht/uplink.fnl b/blinkenlicht/uplink.fnl @@ -1,119 +0,0 @@ -(local nl (require :netlink)) -(local view (. (require :fennel) :view)) - -;; $ grep DEVTYPE /sys/class/net/*/uevent -;; /sys/class/net/docker0/uevent:DEVTYPE=bridge -;; /sys/class/net/wlp4s0/uevent:DEVTYPE=wlan -;; /sys/class/net/wwp0s20f0u2i12/uevent:DEVTYPE=wwan -;; (ethernet and loopback devices don't have DEVTYPE) - -(fn devtype [ifname] - (with-open [f (io.open (.. "/sys/class/net/" ifname "/uevent") :r)] - (accumulate [dtype nil - line #(f:read "*l") - :until dtype] - (let [(name value) (line:match "([^=]+)=(.+)")] - (if (= name "DEVTYPE") value dtype))))) - -;; if the type is wlan, we can get a signal strength indicator -;; from the "quality - link" column of /proc/net/wireless - -(fn wlan-link-quality [ifname] - (with-open [f (io.open "/proc/net/wireless" :r)] - (accumulate [strength nil - line #(f:read "*l") - :until strength] - ;; "%6s: %04x %3d%c %3d%c %3d%c %6d %6d %6d " - ;; "%6d %6d %6d\n", - ;; https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/wireless/wext-proc.c#n49 - - (let [(name status link level) - (line:match "(.-): +(%x+) +(%S-)[ .] +(%S-)[ .]")] - (if (= name ifname) - (tonumber level) - strength))))) - -(fn wlan-link-ssid [ifname] - ;; could do this directly using an ioctl - ;; http://papermint-designs.com/dmo-blog/2016-08-how-to-get-the-essid-of-the-wifi-network-you-are-connected-to-# - (with-open [f (io.popen (.. "iwgetid " ifname " --raw") :r)] - (f:read "*l"))) - -(fn get-network-info [event] - ;; augments a newlink event with relevant information, if it's - ;; sufficiently well-configured to have any - - ;; "up" => administratively up - ;; "running" => can actually exchange packets - (when (= event.running "yes") - (let [dtype (devtype event.name)] - (tset event :devtype dtype) - (when (= dtype "wlan") - (if (not event.ssid) - (tset event :ssid (wlan-link-ssid event.name))) - (tset event :quality (wlan-link-quality event.name))) - (when (= dtype "wwan") - ;; for wwan, need to determine how to get strength and carrier name - true) - )) - event) - -(fn uplink [] - (let [links {} - routes {} - sock (nl.socket)] - (fn handle-event [event] - (match event - {:event :newlink} - (tset links event.index - (match event.up - "yes" (get-network-info event) - "no" event)) - - {:event :newroute} - ;; XXX there may be >1 route to any given destination, - ;; (e.g wwan and wlan both have default route) - ;; we probably need to store all of them and - ;; distinguish by metric - (let [dst (or event.dst "default") - existing (. routes dst)] - (if (or (not existing) - (< event.metric existing.metric)) - (tset routes dst event))) - - {} (print :unhandled event.event) - )) - (each [_ event (ipairs (sock:query ))] - (handle-event event)) - - { - :refresh #(each [_ event (ipairs (sock:event))] - (handle-event event)) - :fd (sock:fd) - :status (fn [self] - (self:refresh) - (let [defaultroute routes.default - interface (and defaultroute - (. links defaultroute.index))] - (and interface (= interface.running "yes") - (get-network-info interface)))) - :wait #(sock:poll 1000) - :interface (fn [self ifnum] - (. links ifnum)) - } - )) - -(comment -(let [nl (netlunk)] - (while (or (nl:wait) true) - (nl:refresh) - (match (nl:uplink) - interface - (print "default route through " (view interface)) - nil - (print "no default route") - )))) - -{ - :new uplink - }