commit c587817907f3f6788c51e9582bef6a75625145fa
parent 26f0f46c7172519c650dffde0b6df7d727e23da0
Author: Daniel Barlow <dan@telent.net>
Date: Sat, 26 Mar 2022 18:08:25 +0000
rework updates to allow file input readiness as well as timers
Diffstat:
2 files changed, 96 insertions(+), 49 deletions(-)
diff --git a/blinkenlicht/bl.fnl b/blinkenlicht/bl.fnl
@@ -1,6 +1,9 @@
(local {: bar : indicator : stylesheet : run} (require :blinkenlicht))
(local {: view} (require :fennel))
+(local posix (require :posix))
+(local fcntl (require :posix.fcntl))
+
(local metric (require :metric))
(stylesheet "licht.css")
@@ -26,28 +29,56 @@
:classes ["hey"]
:indicators
[
+ (let [f (io.open "/tmp/statuspipe" :r)]
+ (fcntl.fcntl (posix.stdio.fileno f)
+ fcntl.F_SETFL fcntl.O_NONBLOCK)
+
+ (indicator {
+ ;; this is a guide to tell blinkenlicht when it might
+ ;; be worth calling your `content` function. Your
+ ;; function may be called at other times too
+ :wait-for { :input [f] }
+
+ ;; the `content` function should not block, so e.g
+ ;; don't read from files unless you know there's data
+ ;; available. it returns a hash
+ ;; { :text "foo" } - render "foo" as a label
+ ;; { :icon "face-sad" } - render icon from theme or pathname
+ ;; { :classes ["foo" "bar"] - add CSS classes to widget
+ :refresh
+ #(let [l (posix.unistd.read (posix.stdio.fileno f) 1024)]
+ (if l
+ {:text l}))
+ }))
+
+ (indicator {
+ :wait-for { :interval 2000 }
+ :refresh
+ #{:icon (if (> (metric.loadavg) 2) "face-sad" "face-smile")}
+ })
+
(indicator {
- :interval 200
- :icon #(if (> (metric.loadavg) 2) "face-sad" "face-smile")
+ :wait-for { :interval (* 1000 10) }
+ :refresh
+ #(let [{:power-supply-energy-full full
+ :power-supply-energy-now now
+ :power-supply-status status} (metric.battery)
+ percent (math.floor
+ (* 100
+ (/ (tonumber now) (tonumber full))))
+ icon-code (battery-icon-codepoint status percent)]
+ {:text
+ (string.format "%s %d%%" (utf8.char icon-code) percent)
+ :classes ["yellow"]
+ })
})
- ;; (let [f (io.open "/tmp/statuspipe" "r")]
- ;; (indicator {
- ;; :poll [f]
- ;; :text #((f:read):sub 1 10)
- ;; }))
(indicator {
- :interval (* 10 1000)
- :classes ["yellow"]
- :text #(let [{:power-supply-energy-full full
- :power-supply-energy-now now
- :power-supply-status status} (metric.battery)
- percent (math.floor (* 100 (/ (tonumber now) (tonumber full))))
- icon-code (battery-icon-codepoint status percent)]
- (string.format "%s %d%%" (utf8.char icon-code) percent))
+ :wait-for { :interval 1000 }
+ :refresh #{:text (os.date "%H:%M:%S")}
})
(indicator {
- :interval 1000
- :text #(os.date "%H:%M")
+ :wait-for { :interval 4000 }
+ :refresh #{:text (os.date "%H:%M:%S")}
})
]})
diff --git a/blinkenlicht/blinkenlicht.fnl b/blinkenlicht/blinkenlicht.fnl
@@ -5,6 +5,8 @@
: GLib
: cairo } (require :lgi))
+(local posix (require :posix))
+
(local {: view} (require :fennel))
(local icon-theme (Gtk.IconTheme.get_default))
@@ -58,40 +60,39 @@
(tset found-icons name icon)
icon))))
-(fn update-button [button icon text]
- (match (button:get_child) it (button:remove it))
- (let [i (resolve icon)]
- (if i
- (button:add (find-icon i))
- (button:add (Gtk.Label {:label (resolve text)})))
- (button:show_all)
- ))
-
(fn add-css-classes [widget classes]
(let [context (widget:get_style_context)]
(each [_ c (ipairs classes)]
(context:add_class c))))
-(fn indicator [{: interval
- : icon
- : poll
- : text
- : classes
+(fn clear-css-classes [widget]
+ (let [context (widget:get_style_context)]
+ (each [_ c (ipairs (context:list_classes))]
+ (context:remove_class c))))
+
+(fn indicator [{: wait-for
+ : refresh
: on-click}]
- (var last-update -1)
- (let [button (doto (Gtk.Button { :relief Gtk.ReliefStyle.NONE})
- (add-css-classes ["indicator"])
- (add-css-classes (or classes [])))
- update (fn [now]
- (when (and interval (> now (+ last-update interval)))
- (update-button button icon text)
- (set last-update now)))]
- (update 0)
+ (let [button (Gtk.Button { :relief Gtk.ReliefStyle.NONE})]
+ (fn update-indicator []
+ (let [content (resolve refresh)]
+ (when content
+ (match (button:get_child) it (button:remove it))
+ (match content
+ {:icon icon} (button:add (find-icon icon))
+ {:text text} (button:add (Gtk.Label {:label text})))
+ (clear-css-classes button)
+ (add-css-classes button ["indicator"])
+ (match content
+ {:classes classes} (add-css-classes button classes))
+ (button:show_all))))
+ (update-indicator)
+
{
- : interval
- : poll
: button
- :update #(update $2)
+ :update update-indicator
+ :inputs (or wait-for.input [])
+ :interval wait-for.interval
}))
(fn make-layer-shell [window layer exclusive? anchors]
@@ -129,20 +130,35 @@
(box:pack_start i.button false false 0))
(window:add box)))
-;; we want to run each indicator's update function only when
-;; more than `interval` ms has elapsed since it last ran
+(fn gsource-for-file-input [file cb]
+ (let [fd (posix.stdio.fileno file)]
+ (doto (GLib.unix_fd_source_new fd GLib.IOCondition.IN)
+ (: :set_callback cb))))
+(fn ready-to-update? [indicator now update-times]
+ (if indicator.interval
+ (> now (or (. update-times indicator) 0))))
(fn run []
- (GLib.timeout_add
+ (each [_ bar (ipairs bars)]
+ (each [_ indicator (ipairs bar.indicators)]
+ (each [_ file (ipairs indicator.inputs)]
+ (GLib.Source.attach
+ (gsource-for-file-input
+ file
+ #(or (indicator:update) true))))))
+ (let [update-times {}]
+ (GLib.timeout_add
0
- 1000
+ 100
(fn []
(let [now (/ (GLib.get_monotonic_time) 1000)]
(each [_ bar (ipairs bars)]
(each [_ indicator (ipairs bar.indicators)]
- (indicator:update now))))
- true))
+ (when (ready-to-update? indicator now update-times)
+ (indicator:update)
+ (tset update-times indicator (+ now indicator.interval))))))
+ true)))
(each [_ b (ipairs bars)]
(make-layer-shell b.window :top true
(collect [_ edge (ipairs b.anchor)]