refactor: eww notifications

This commit is contained in:
hesam-init 2024-07-02 17:45:31 +03:30
parent b73eb585af
commit caad2ba8ef
10 changed files with 498 additions and 245 deletions

View file

@ -73,10 +73,9 @@
(deflisten notifications :initial '{
"count": 0,
"dnd": false,
"notifications": [],
"popups": []
}'
"./scripts/notif.py"
"./scripts/notification/notifs.py"
)
; Playerctl

View file

@ -1,4 +1,4 @@
(include "./src/_definitions.yuck")
(include "./_definitions.yuck")
(include "./src/-components/_helpers.yuck")
(include "./src/-components/_separator.yuck")
@ -16,7 +16,7 @@
(include "./src/windows/_dashboard.yuck")
(include "./src/windows/_osd.yuck")
(include "./setups.yuck")
(include "./_setups.yuck")
(include "./src/wallpapers/main.yuck")
(include "./src/dock/main.yuck")

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

View file

@ -1,55 +0,0 @@
#!/usr/bin/bash
# Taken from Juminai
dismiss() {
dbus-send --session --type=method_call \
--dest=org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications.DismissPopup \
uint32:$1
}
close() {
dbus-send --session --type=method_call \
--dest=org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications.CloseNotification \
uint32:$1
}
action() {
dbus-send --session --type=method_call \
--dest=org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications.InvokeAction \
uint32:$1 string:$2
}
clear_all() {
dbus-send --session --type=method_call \
--dest=org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications.ClearAll
}
listen() {
dbus-send --session --type=method_call \
--dest=org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications.Listen
}
toggle_dnd() {
dbus-send --session --type=method_call \
--dest=org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications.ToggleDND
}
if [[ $1 == '--dismiss' ]]; then dismiss $2 $3; fi
if [[ $1 == '--close' ]]; then close $2; fi
if [[ $1 == '--action' ]]; then action $2 $3; fi
if [[ $1 == '--clear' ]]; then clear_all; fi
if [[ $1 == '--listen' ]]; then listen; fi
if [[ $1 == '--toggle' ]]; then toggle_dnd; fi

View file

@ -0,0 +1,59 @@
#!/usr/bin/bash
DBUS_CMD="dbus-send --session --type=method_call --dest=org.freedesktop.Notifications /org/freedesktop/Notifications"
dismiss() {
$DBUS_CMD org.freedesktop.Notifications.DismissPopup uint32:$1
}
close() {
$DBUS_CMD org.freedesktop.Notifications.CloseNotification uint32:$1
}
action() {
$DBUS_CMD org.freedesktop.Notifications.InvokeAction uint32:$1 string:$2
}
get_current() {
$DBUS_CMD org.freedesktop.Notifications.GetCurrent
}
clear_all() {
$DBUS_CMD org.freedesktop.Notifications.ClearAll
}
listen() {
$DBUS_CMD org.freedesktop.Notifications.Listen
}
toggle_dnd() {
$DBUS_CMD org.freedesktop.Notifications.ToggleDND
}
case "$1" in
--dismiss)
dismiss "$2"
;;
--close)
close "$2"
;;
--action)
action "$2" "$3"
;;
--current)
get_current
;;
--clear)
clear_all
;;
--listen)
listen
;;
--toggle)
toggle_dnd
;;
*)
echo "Usage: $0 {--dismiss|--close|--action|--clear|--listen|--toggle} [args]"
exit 1
;;
esac

View file

@ -0,0 +1,236 @@
#!/usr/bin/python
import gi
gi.require_version("GdkPixbuf", "2.0")
gi.require_version("Gtk", "3.0")
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
import datetime
import os
import typing
import sys
import json
from gi.repository import Gtk, GdkPixbuf
import subprocess
cache_dir = f"{os.getenv('HOME')}/.cache/notify_img_data"
log_file = f"{os.getenv('HOME')}/.cache/notifications.json"
os.makedirs(cache_dir, exist_ok=True)
active_popups = {}
# RECIEVE NOTIFICATIONS
class NotificationDaemon(dbus.service.Object):
def __init__(self):
bus_name = dbus.service.BusName("org.freedesktop.Notifications", dbus.SessionBus())
dbus.service.Object.__init__(self, bus_name, "/org/freedesktop/Notifications")
self.dnd = False
@dbus.service.method("org.freedesktop.Notifications", in_signature="susssasa{sv}i", out_signature="u")
def Notify(self, app_name, replaces_id, app_icon, summary, body, actions, hints, timeout):
replaces_id = int(replaces_id)
actions = list(actions)
app_icon = str(app_icon)
app_name = str(app_name)
summary = str(summary)
body = str(body)
if replaces_id != 0:
id = replaces_id
else:
log_file = self.read_log_file()
if log_file['notifications'] != []:
id = log_file['notifications'][0]['id'] + 1
else:
id = 1
acts = []
for i in range(0, len(actions), 2):
acts.append([str(actions[i]), str(actions[i + 1])])
details = {
"id": id,
"app": app_name,
"summary": self.format_long_string(summary, 35),
"body": self.format_long_string(body, 35),
"time": datetime.datetime.now().strftime("%H:%M"),
"urgency": hints["urgency"] if "urgency" in hints else 1,
"actions": acts
}
if app_icon.strip():
if os.path.isfile(app_icon) or app_icon.startswith("file://"):
details["image"] = app_icon
else:
details["image"] = self.get_gtk_icon(app_icon)
else:
details["image"] = None
if "image-data" in hints:
details["image"] = f"{cache_dir}/{details['id']}.png"
self.save_img_byte(hints["image-data"], details["image"])
self.save_notifications(details)
if not self.dnd:
self.save_popup(details)
return id
def format_long_string(self, long_string, interval):
split_string = []
max_length = 256
for i in range(0, len(long_string), interval):
split_string.append(long_string[i:i+interval])
result = "-\n".join(split_string)
if len(result) > max_length:
result = result[:max_length] + "..."
return result
@dbus.service.method("org.freedesktop.Notifications", in_signature="", out_signature="ssss")
def GetServerInformation(self):
return ("linkfrg's notification daemon", "linkfrg", "1.0", "1.2")
@dbus.service.method("org.freedesktop.Notifications", in_signature="", out_signature="as")
def GetCapabilities(self):
return ('actions', 'body', 'icon-static', 'persistence')
@dbus.service.signal("org.freedesktop.Notifications", signature="us")
def ActionInvoked(self, id, action):
return (id, action)
@dbus.service.method("org.freedesktop.Notifications", in_signature="us", out_signature="")
def InvokeAction(self, id, action):
self.ActionInvoked(id, action)
@dbus.service.signal("org.freedesktop.Notifications", signature="uu")
def NotificationClosed(self, id, reason):
return (id, reason)
@dbus.service.method("org.freedesktop.Notifications", in_signature="u", out_signature="")
def CloseNotification(self, id):
current = self.read_log_file()
current["notifications"] = [n for n in current["notifications"] if n["id"] != id]
current["count"] = len(current["notifications"])
self.write_log_file(current)
self.NotificationClosed(id, 2)
self.DismissPopup(id)
@dbus.service.method("org.freedesktop.Notifications", in_signature="", out_signature="")
def ToggleDND(self):
match self.dnd:
case False:
self.dnd = True
case True:
self.dnd = False
@dbus.service.method("org.freedesktop.Notifications", in_signature="", out_signature="")
def GetDNDState(self):
subprocess.run(["eww", "update", f"do-not-disturb={json.dumps(self.dnd)}"])
def get_gtk_icon(self, icon_name):
theme = Gtk.IconTheme.get_default()
icon_info = theme.lookup_icon(icon_name, 128, 0)
if icon_info is not None:
return icon_info.get_filename()
def save_img_byte(self, px_args: typing.Iterable, save_path: str):
GdkPixbuf.Pixbuf.new_from_bytes(
width=px_args[0],
height=px_args[1],
has_alpha=px_args[3],
data=GLib.Bytes(px_args[6]),
colorspace=GdkPixbuf.Colorspace.RGB,
rowstride=px_args[2],
bits_per_sample=px_args[4],
).savev(save_path, "png")
def write_log_file(self, data):
output_json = json.dumps(data, indent=2)
subprocess.run(["eww", "update", f"notifications={output_json}"])
with open(log_file, "w") as log:
log.write(output_json)
def read_log_file(self):
empty = {"count": 0, "notifications": [], "popups": []}
try:
with open(log_file, "r") as log:
return json.load(log)
except FileNotFoundError:
with open(log_file, "w") as log:
json.dump(empty, log)
return empty
def save_notifications(self, notification):
current = self.read_log_file()
current["notifications"].insert(0, notification)
current["count"] = len(current["notifications"])
self.write_log_file(current)
@dbus.service.method("org.freedesktop.Notifications", in_signature="", out_signature="")
def ClearAll(self):
for notify in self.read_log_file()['notifications']:
self.NotificationClosed(notify['id'], 2)
data = {"count": 0, "notifications": [], "popups": []}
self.write_log_file(data)
# OPERATIONS WITH POPUPS
def save_popup(self, notification):
global active_popups
current = self.read_log_file()
if len(current["popups"]) >= 3:
oldest_popup = current["popups"].pop()
self.DismissPopup(oldest_popup["id"])
current["popups"].append(notification)
self.write_log_file(current)
popup_id = notification["id"]
active_popups[popup_id] = GLib.timeout_add_seconds(5, self.DismissPopup, popup_id)
@dbus.service.method("org.freedesktop.Notifications", in_signature="u", out_signature="")
def DismissPopup(self, id):
global active_popups
current = self.read_log_file()
current["popups"] = [n for n in current["popups"] if n["id"] != id]
self.write_log_file(current)
active_popups.pop(id, None)
@dbus.service.method("org.freedesktop.Notifications", in_signature="", out_signature="")
def GetCurrent(self):
subprocess.run(["eww", "update", f"notifications={json.dumps(self.read_log_file())}"])
# MAINLOOP
def main():
DBusGMainLoop(set_as_default=True)
loop = GLib.MainLoop()
NotificationDaemon()
try:
loop.run()
except KeyboardInterrupt:
exit(0)
if __name__ == "__main__":
main()

View file

@ -1,4 +1,3 @@
(defwindow notifypopup
:geometry (geometry
:x 0
@ -8,6 +7,7 @@
:anchor "top right")
:stacking "overlay"
:monitor 0
(revealer
:reveal { arraylength(notifications.popups) > 0 }
:transition "slidedown"
@ -40,7 +40,7 @@
:text {noti.app}))
(button
:halign "end"
:onclick "./scripts/notifManage --close ${noti.id}"
:onclick "./scripts/notification/manage --close ${noti.id}"
(label
:class "notifclose"
:text "")))
@ -54,8 +54,8 @@
(image :image-width 80 :image-height 80 :visible {noti.image != "null"} :path {noti.image != "null" ? noti.image : "./assets/image/idk.svg"})
(button
:onclick "./scripts/notifManage --dismiss ${noti.id}"
:onrightclick "./scripts/notifManage --close ${noti.id}"
:onclick "./scripts/notification/manage --dismiss ${noti.id}"
:onrightclick "./scripts/notification/manage --close ${noti.id}"
:tooltip "${noti.time}"
:hexpand true
:vexpand true
@ -83,7 +83,7 @@
:orientation "h"
(for action in {noti.actions}
(button
:onclick "./scripts/notifManage --action ${noti.id} ${action[0]} && ./scripts/notifManage --close ${noti.id}"
:onclick "./scripts/notification/manage --action ${noti.id} ${action[0]} && ./scripts/notification/manage --close ${noti.id}"
(label
:class "notifactions"
:text {action[1]}))

View file

@ -6,11 +6,11 @@
:valign "fill"
:vexpand true
(user)
(User )
(chooser)
; (weather)
(MediaPlayer :h 160 :permashow true)
; (MediaPlayer :h 160 :permashow true)
(box
:orientation "h"
@ -197,7 +197,7 @@
:width 55
(label :text icon))))
(defwidget user[]
(defwidget User []
(revealer
:reveal {!reveal4 && !reveal5 && !reveal6}
:transition "slideup"
@ -253,16 +253,16 @@
:hexpand {!revealWeather}
:transition "slideleft"
(notificationlog))
(NotificationsLogs))
(revealer
:reveal revealWeather
:hexpand revealWeather
:transition "slideleft"
(weather)))))
(Weather)))))
(defwidget weather[]
(defwidget Weather []
(overlay
(box
:orientation "v"
@ -272,6 +272,7 @@
:space-evenly false
(weathermain)
(scroll
:hscroll false
:vscroll true
@ -290,6 +291,64 @@
:class "fadeoutbox")
))
(defwidget NotificationsLogs []
(box
:halign "fill"
:valign "fill"
:width 320
:vexpand true
:space-evenly false
:orientation "v"
(overlay
:vexpand true
(box
:halign "fill"
:valign "fill"
:vexpand true
:space-evenly false
:orientation "v"
(scroll
:hscroll false
:vscroll true
:vexpand true
:valign "fill"
(box
:orientation "v"
:valign "start"
:space-evenly false
(for notif in {notifications.notifications}
(singlenotif :noti notif :initial false))))
)
(box
:valign "end"
:hexpand true
:vexpand false
:height 80
:class "fadeoutbox")
)
(box
:orientation "h"
:valign "end"
(button
:onclick "./scripts/notification/manage --clear"
(label
:class "title"
:text "Clear All"))
(button
:onclick "./scripts/notification/manage --toggle"
(label
:class { notifications.dnd ? "titlesel" : "title"}
:text "Do Not Disturb")))
)
)
(defwidget weatherhour[hour]
(box
:class "smallentry"
@ -313,7 +372,7 @@
(label :halign "start" :text "${hour.FeelsLikeC}°C")
(label :halign "start" :text "rain: ${hour.chanceofrain}%"))))
(defwidget weathermain[]
(defwidget weathermain []
(box
:class "mainentry"
:orientation "h"
@ -416,52 +475,6 @@
:valign "end"
:text "󰃞")))
(defwidget notificationlog []
(overlay
(box
:halign "fill"
:valign "fill"
:vexpand true
:space-evenly false
:orientation "v"
(scroll
:hscroll false
:vscroll true
:vexpand true
:valign "fill"
(box
:orientation "v"
:valign "start"
:space-evenly false
(for noti in {notifications.notifications}
(singlenotif :noti noti :initial false))))
(box
:valign "end"
:hexpand true
:vexpand false
:height 80
:class "fadeoutbox")
(box
:orientation "h"
(button
:onclick "./scripts/notifManage --clear"
(label
:class "title"
:text "Clear All"))
(button
:onclick "./scripts/notifManage --toggle"
(label
:class { notifications.dnd ? "titlesel" : "title"}
:text "Do Not Disturb")))
)
)
)
(defwidget quote []
(box
:class "quotewid panel-widget"