eufon/blinkenlicht/init.fnl

226 lines
6.4 KiB
Fennel

(local {: Gtk
: GtkLayerShell
: Gdk
: GdkPixbuf
: Gio
: GLib
: cairo } (require :lgi))
(local posix (require :posix))
(local {: view} (require :fennel))
(local icon-theme (Gtk.IconTheme.get_default))
(local HEIGHT 48)
(local css-provider
(let [p (Gtk.CssProvider)]
(Gtk.StyleContext.add_provider_for_screen
(Gdk.Screen.get_default)
p
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
p))
(fn load-styles [pathname]
(let [(success err) (css-provider:load_from_path pathname)]
(or success
(print "failed to load stylesheet" err))))
(var file-monitor nil)
(fn watch-stylesheet [pathname]
(let [f (Gio.File.new_for_path pathname)
(monitor err)
(Gio.File.monitor f Gio.FileMonitorFlags.NONE nil)]
(load-styles pathname)
(if (not monitor) (print :watch-stylesheet err))
(set file-monitor monitor)
(doto monitor
(tset
:on_changed
#(load-styles (: $2 :get_path))))))
(fn resolve [f]
(match (type f)
"string" f
"function" (f)))
(fn find-icon-pixbuf [name]
(var found nil)
(each [_ res (pairs [HEIGHT 128 64 48]) :until found]
(let [pixbuf (icon-theme:load_icon
name res
(+ Gtk.IconLookupFlags.FORCE_SVG
Gtk.IconLookupFlags.USE_BUILTIN))]
(when pixbuf
(set found (pixbuf:scale_simple
HEIGHT (* pixbuf.width (/ HEIGHT pixbuf.height))
GdkPixbuf.InterpType.BILINEAR)))))
found)
(local found-icons {})
(macro check-err [form]
`(let [(result# err#) ,form]
(or result#
(error err#))))
(fn load-icon [name]
(let [pixbuf
(if (= (name:sub 1 1) "/")
;; From a direct path
(check-err
(GdkPixbuf.Pixbuf.new_from_file_at_scale name HEIGHT -1 true))
;; From icon theme
(find-icon-pixbuf name))]
(Gtk.Image.new_from_pixbuf pixbuf)))
(fn find-icon [name]
(let [icon (. found-icons name)]
(or icon
(let [(icon err) (load-icon name)]
(if (not icon) (print err))
(tset found-icons name icon)
icon))))
(fn add-css-classes [widget classes]
(let [context (widget:get_style_context)]
(each [_ c (ipairs classes)]
(context:add_class c))))
(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}]
(let [button (Gtk.EventBox { })]
(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)
{
: button
:update update-indicator
:inputs (or wait-for.input [])
:interval wait-for.interval
}))
(fn make-layer-shell [window layer exclusive? anchors]
(let [s GtkLayerShell]
(s.init_for_window window)
(s.set_layer window (. {
:top GtkLayerShell.Layer.TOP
}
layer))
(if exclusive? (s.auto_exclusive_zone_enable window))
(each [edge margin (pairs anchors)]
(let [edge (. {:top GtkLayerShell.Edge.TOP
:bottom GtkLayerShell.Edge.BOTTOM
:left GtkLayerShell.Edge.LEFT
:right GtkLayerShell.Edge.RIGHT}
edge)]
(GtkLayerShell.set_margin window edge margin)
(GtkLayerShell.set_anchor window edge 1)))))
(local bars [])
(fn bar [{: anchor : orientation : indicators
: gravity
: classes }]
(let [window (Gtk.Window {} )
orientation (match orientation
:vertical Gtk.Orientation.VERTICAL
:horizontal Gtk.Orientation.HORIZONTAL)
box (Gtk.Box { :orientation orientation})]
(doto box
(add-css-classes ["bar"])
(add-css-classes (or classes [])))
(if (= gravity :end)
(box:pack_start (Gtk.EventBox) true true 0))
(table.insert bars { : window : anchor : indicators })
(each [_ i (ipairs indicators)]
(box:pack_start i.button false false 0))
(window:add box)))
(fn gsource-for-file-input [file cb]
(let [fd file.fileno]
(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 hcf [a b]
(let [remainder (% a b)]
(if (= remainder 0)
b
(hcf b remainder))))
(assert (= (hcf 198 360) 18))
(assert (= (hcf 10 15) 5))
(fn minimum-interval [intervals]
(accumulate [min (. intervals 1)
_ interval (ipairs intervals)]
(hcf min interval)))
(assert (= (minimum-interval [ 350 1000 5000 ]) 50))
(fn run []
(var intervals [])
(each [_ bar (ipairs bars)]
(each [_ indicator (ipairs bar.indicators)]
(if indicator.interval
(table.insert intervals indicator.interval))
(each [_ file (ipairs indicator.inputs)]
(GLib.Source.attach
(gsource-for-file-input
file
#(or (indicator:update) true))))))
(let [update-times {}
interval (minimum-interval intervals)]
(when (< interval 100)
(print (.. "required refresh interval is " interval "ms")))
(GLib.timeout_add
0
(minimum-interval intervals)
(fn []
(let [now (/ (GLib.get_monotonic_time) 1000)]
(each [_ bar (ipairs bars)]
(each [_ indicator (ipairs bar.indicators)]
(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)]
edge 0))
(b.window:show_all))
(Gtk.main))
{
: bar
: indicator
: run
:stylesheet watch-stylesheet
: file-monitor ;don't let this get GCed
}