You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
You are using Arch Linux and waybar. You have multiple display setup. Your waybar has a module which checks for system updates. (Though with some minor tinkering it may be applicable for other package managers as well, I'm not sure?)
Why am I posting this
I've been annoyed enough with this to make this script, so maybe I can help someone else. Also, I'm not sure how to properly present it/make it available, so I'm just putting it out there any way I can, and maybe someone will point me to a better solution!
Summary of the functionality
It will call checkupdates and yay -Qua to list all available updates. This response will be persistently stored with sqlite. It will output the number of available updates and an icon/color indicating whether "critical" updates are available.
I've made this script integration with waybar. It outputs the json based protocol for updating a custom module. It depends on (very lightweight, often found on distros by default):
python
sqlite
yay
checkupdates
libnotify (optional: for the notify-send cli tool)
alacritty (optional: you can use another terminal, change the on-click handler of the waybar config accordingly)
The problem it solves
With multiple display/bar setup in waybar, the program is designed such that any custom module "exec" invocation will be called once per bar. This will incur redundant AUR API calls and in worst case scenario can get user temporarily banned from executing any system upgrades. With this script "middle-man", it ensures that there are no redundant API calls by leveraging locking mechanisms and persistent storage.
Installing the script in your Arch Linux-based environment
Disclaimer about the script: it does the job, it ain't perfect, there's definitely room for improvements if you're willing to tinker with it. You might especially want to configure what is considered "critical" packages as those are hard-coded in the head of the script, I've been too lazy to expose CLI command to configure it.
Place the script accordingly to waybar config($HOME/.config/waybar/updates.py).
Ensure that the script is executable(chmod +x updates.py):
#!/usr/bin/env python3"""This script mainly aims to tackle an "issue" in waybar where modules are called once per monitor/bar (causing unneccessary duplicate checks of updates, which can cause user to get locked out of AUR).Summary of the script: - just-in-time DB/schema creation on first run - configurable interval in DB (default 2 h via --set-interval) - --set-interval SECONDS to change interval - --clear to reset cache timestamp (skips next interval) - --force to bypass interval and lock (always re-check) - using a file lock to serialize normal exexutions of updates (handling concurrent calls graciously) - skip checks when yay/pacman upgrade is in progress - desktop notification on new or critical updatesUsage:Intended to be used as part of your waybar configuration: "custom/updates": { "interval": 30, "return-type": "json", "exec": "~/.config/waybar/updates.py", "on-click": "alacritty -e yay --noconfirm && ~/.config/waybar/updates.py --clear && pkill -RTMIN+8 waybar", "escape": true, "signal": 8, },Using this configuration, the script will be called every 30 seconds, but will only check for updates based on the configured interval for the script, which is 2 hours by default.If you click the updates module on your waybar, it will upgrade the system using yay and then call a function in this script(--clear) that will simply clear any cached updates, andnext check will happen when the script is called again and the configured interval has passed."""importargparseimportfcntlimportjsonimportsqlite3importsubprocessimporttimefrompathlibimportPathDEFAULT_INTERVAL_SECONDS=2*3600CACHE_DIR=Path.home() /".cache"/"waybar"DB_PATH=CACHE_DIR/"updates.db"LOCK_PATH=CACHE_DIR/"updates.lock"KEY_UPD="updates"KEY_INT="interval"CRITICAL_PKGS= {"linux-cachyos", "librewolf-bin"}
defparse_args():
p=argparse.ArgumentParser()
p.add_argument("--force", action="store_true",
help="bypass lock & interval; always re-check")
p.add_argument("--clear", action="store_true",
help="under lock: reset cache timestamp to now")
p.add_argument("--set-interval", type=int, metavar="SECONDS",
help="set new cache interval (CLI only)")
returnp.parse_args()
defdb_init():
db_exists=DB_PATH.exists()
conn=sqlite3.connect(str(DB_PATH), timeout=10)
ifnotdb_exists:
db_create_schema(conn)
returnconndefdb_create_schema(conn):
"""Initialize DB schema and default interval on first run."""conn.execute("PRAGMA journal_mode=WAL;")
conn.execute("PRAGMA synchronous=NORMAL;")
conn.execute(""" CREATE TABLE IF NOT EXISTS cache ( key TEXT PRIMARY KEY, timestamp INTEGER NOT NULL, data TEXT NOT NULL ); """)
conn.execute(""" CREATE TABLE IF NOT EXISTS config ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); """)
conn.execute(
"INSERT OR IGNORE INTO config(key,value) VALUES(?,?);",
(KEY_INT, str(DEFAULT_INTERVAL_SECONDS))
)
conn.commit()
defensure_dirs():
"""Ensure cache directory exists."""CACHE_DIR.mkdir(parents=True, exist_ok=True)
defread_cache(conn):
returnconn.execute(
"SELECT timestamp,data FROM cache WHERE key=?;",
(KEY_UPD,)
).fetchone()
defwrite_cache(conn, ts, blob):
conn.execute(
"REPLACE INTO cache(key,timestamp,data) VALUES(?,?,?);",
(KEY_UPD, ts, blob)
)
conn.commit()
defread_interval(conn):
row=conn.execute(
"SELECT value FROM config WHERE key=?;",
(KEY_INT,)
).fetchone()
returnint(row[0]) ifrowandrow[0].isdigit() elseDEFAULT_INTERVAL_SECONDSdefwrite_interval(conn, sec):
conn.execute(
"REPLACE INTO config(key,value) VALUES(?,?);",
(KEY_INT, str(sec),)
)
conn.commit()
defrun_checks():
outs= []
forcmdin (["checkupdates"], ["yay", "-Qua"]):
try:
out=subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True)
ifout.strip():
outs.append(out.strip())
exceptsubprocess.CalledProcessError:
continuereturn"\n".join(outs)
defupgrade_in_progress():
fornamein ("yay", "pacman"):
try:
subprocess.check_output(["pgrep", "-x", name], stderr=subprocess.DEVNULL)
returnTrueexceptsubprocess.CalledProcessError:
continuereturnFalsedefclassify(lines):
forlinlines:
pkg=l.split()[0]
ifpkginCRITICAL_PKGS:
return"critical"return"ok"iflineselse""defrun_notify(msg, icon):
try:
subprocess.run(["notify-send", "-a", f"System updates {icon}", msg], check=False, stderr=subprocess.DEVNULL)
exceptsubprocess.CalledProcessError:
passdefhandle_set_interval(conn, args):
ifargs.set_intervalisnotNone:
write_interval(conn, args.set_interval)
returnTrue, f"Interval set to {args.set_interval} seconds"returnFalse, Nonedefhandle_clear(conn, args):
ifargs.clear:
now=int(time.time())
withopen(LOCK_PATH, "w") aslf:
fcntl.flock(lf, fcntl.LOCK_EX)
write_cache(conn, now, json.dumps({"ts": now, "out": ""}))
returnTrue, json.dumps({"text": "", "class": ""})
returnFalse, Nonedefhandle_updates(conn, args):
now=int(time.time())
updated=Falseifargs.force:
raw=run_checks()
write_cache(conn, now, json.dumps({"ts": now, "out": raw}))
data= {"ts": now, "out": raw}
updated=Trueelse:
interval=read_interval(conn)
withopen(LOCK_PATH, "w") aslf:
fcntl.flock(lf, fcntl.LOCK_EX)
row=read_cache(conn)
ifupgrade_in_progress():
data=json.loads(row[1]) ifrowelse {"ts": now, "out": ""}
else:
ifrowisNoneor (now-row[0]) >interval:
raw=run_checks()
write_cache(conn, now, json.dumps({"ts": now, "out": raw}))
data= {"ts": now, "out": raw}
updated=Trueelse:
data=json.loads(row[1])
lines=data["out"].splitlines() ifdata["out"].strip() else []
status=classify(lines)
num=len(lines)
icon=""ifstatus=="critical"else""text=f"{num}{icon} "ifnumelse""if (updatedorargs.force) andnum>0:
run_notify(f"{num} packages", icon)
returnjson.dumps({"text": text, "class": status})
defmain():
args=parse_args()
ensure_dirs()
conn=db_init()
handled, output=handle_set_interval(conn, args)
ifnothandled:
handled, output=handle_clear(conn, args)
ifnothandled:
output=handle_updates(conn, args)
conn.close()
ifoutputisnotNone:
print(output)
if__name__=="__main__":
main()
Finally here is the CSS that I am personally alongside with it, but you could probably make it a lot nicer than me:
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Who is this for
You are using Arch Linux and waybar. You have multiple display setup. Your waybar has a module which checks for system updates. (Though with some minor tinkering it may be applicable for other package managers as well, I'm not sure?)
Why am I posting this
I've been annoyed enough with this to make this script, so maybe I can help someone else. Also, I'm not sure how to properly present it/make it available, so I'm just putting it out there any way I can, and maybe someone will point me to a better solution!
Summary of the functionality
It will call
checkupdatesandyay -Quato list all available updates. This response will be persistently stored withsqlite. It will output the number of available updates and an icon/color indicating whether "critical" updates are available.I've made this script integration with waybar. It outputs the json based protocol for updating a custom module. It depends on (very lightweight, often found on distros by default):
notify-sendcli tool)The problem it solves
With multiple display/bar setup in waybar, the program is designed such that any custom module "exec" invocation will be called once per bar. This will incur redundant AUR API calls and in worst case scenario can get user temporarily banned from executing any system upgrades. With this script "middle-man", it ensures that there are no redundant API calls by leveraging locking mechanisms and persistent storage.
Installing the script in your Arch Linux-based environment
Ensure all dependencies are installed.
Put this custom module into your waybar config:
Disclaimer about the script: it does the job, it ain't perfect, there's definitely room for improvements if you're willing to tinker with it. You might especially want to configure what is considered "critical" packages as those are hard-coded in the head of the script, I've been too lazy to expose CLI command to configure it.
Place the script accordingly to waybar config(
$HOME/.config/waybar/updates.py).Ensure that the script is executable(
chmod +x updates.py):Finally here is the CSS that I am personally alongside with it, but you could probably make it a lot nicer than me:
Beta Was this translation helpful? Give feedback.
All reactions