What are we making today?
The idea is an always-on-top persistent widget that integrates with my application Smoker and shows a live counter of how many cigarettes I have smoked today.
This is a nice addition to my existing Stream Deck hotkey which adds a cigarette to the app.
A simple counter in a box should suffice.

Word of advice
If you intend to follow along with this, take it carefully. Yea, I know it's just a widget, but still. I haven't tested anything of this on any other OS or desktop environment or whatever. I just did it, thought it was cool and wanted to share.
I'm using KDE Plasma on EndeavourOS.
Framework
Since I'm using KDE Plasma with Wayland, a fitting library is eww.
yay -S ewwSetup
We need two files, located in ~/.config/eww/ called eww.yuck (layout and functionality) and eww.scss (styling).
;; function that listens to a stdin stream
(deflisten smoker_count
:initial "--"
"/home/masco/.config/eww/scripts/smoker-stream.sh")
;; bounding box and counter, bound to the 'smoker_count' function
(defwidget counter []
(box :class "counter-box" :orientation "h"
(label :class "count" :text {smoker_count})))
;; layout definition on the monitor
(defwindow smoker
:monitor 0
:stacking "fg"
:wm-ignore true
:windowtype "normal"
:geometry (geometry :anchor "bottom right" :width "3%" :x "20px" :y "20px")
(counter))
Then of course we need to style it a bit, so it doesn't look as trash as plain HTML.
* {
all: unset; // removes all GTK (default) styling
}
.counter-box {
background-color: rgba(20, 20, 20, 0);
border: 1px solid #f97316;
border-radius: 10px;
padding: 6px 12px;
}
.count {
color: #f97316;
font-size: 24px;
font-weight: bold;
}Fetching Data
Now that we have the widget set up, we still need some way to get the actual data. One way would be to use a defpoll component and just let it run a curl task like every 10 seconds.
But that would be kinda inefficient and lazy, but we want ✨efficiency✨ right.
So, for exactly this purpose, the Smoker app already exposes an /api/v1/events endpoint that can be used to update dynamically. That endpoint sends data as text/event-stream so the connection is kept alive by the client. These updates are then captured by the deflisten function and passed into the component.
In other words, every time a cigarette is logged, that endpoint will send a new "refresh" event to the client and update the widget.
So the smoker-stream.sh file looks like this:
Warning
If you're recreating this, make sure the
prod.envfile containsAPI_KEYandBASEfields, or set them directly in the script.Specific to this application:
The base should be a valid Smoker endpoint. In my case that is
https://smoker.domain.com/api/v1.
#!/usr/bin/env bash
# load .env file
SCRIPT_DIR="/home/masco/.config/eww/scripts"
set -a; source "$SCRIPT_DIR/prod.env"; set +a
# fetch today's count
get_data () {
curl -s -H "Authorization: Bearer $API_KEY" "$BASE/stats" | jq -r '.data.session.count'
}
# run once on init
get_data
while true; do
# subscribe to events and read from stream
curl -sN -H "Authorization: Bearer $API_KEY" "$BASE/events" | while IFS= read -r line; do
case "$line" in # if line has data
data:*)
get_data # print data to stdout
;;
esac
done
sleep 2 # reconnect after 2s
done
Nice. We got the layout, styling and logic done. Last thing is to actually start the widget by running:
eww open smokerFinal words
I really like this implementation of a widget. It's easy to implement, doesn't take long and can be directly connected to bash scripts.
This was more or less a test if I can get a widget to run -- also kinda the first real blog post on here hihi -- and I'm pretty sure that I'm gonna do more stupid stuff with this in the future.
I hope you enjoyed this little adventure. Happy coding! :3