{"id":19388364,"url":"https://github.com/mk-fg/notification-thing","last_synced_at":"2025-04-23T23:31:36.833Z","repository":{"id":2395452,"uuid":"3361962","full_name":"mk-fg/notification-thing","owner":"mk-fg","description":"Python-based implementation of Desktop Notifications Specification (notification-daemon)","archived":false,"fork":false,"pushed_at":"2025-03-18T18:40:23.000Z","size":471,"stargazers_count":34,"open_issues_count":0,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-02T22:23:12.137Z","etag":null,"topics":["audio-notifications","daemon","dbus-service","desktop-notifications","filtering","freedesktop","gobject-introspection","gtk3","libcanberra","notification-bubble","notification-daemon","pango","python","scheme","zeromq"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"wtfpl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mk-fg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2012-02-05T20:29:01.000Z","updated_at":"2025-03-18T18:40:27.000Z","dependencies_parsed_at":"2023-11-12T21:35:17.680Z","dependency_job_id":null,"html_url":"https://github.com/mk-fg/notification-thing","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mk-fg%2Fnotification-thing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mk-fg%2Fnotification-thing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mk-fg%2Fnotification-thing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mk-fg%2Fnotification-thing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mk-fg","download_url":"https://codeload.github.com/mk-fg/notification-thing/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250532051,"owners_count":21446107,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio-notifications","daemon","dbus-service","desktop-notifications","filtering","freedesktop","gobject-introspection","gtk3","libcanberra","notification-bubble","notification-daemon","pango","python","scheme","zeromq"],"created_at":"2024-11-10T10:12:32.545Z","updated_at":"2025-04-23T23:31:36.359Z","avatar_url":"https://github.com/mk-fg.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"notification-thing\n--------------------\n\nGtk3/Python (PyGI) notification daemon with flexible (scriptable) filtering,\nrate-limiting and misc other features, for linux desktop, not tied to any\nparticular desktop environment.\n\n![showcase image](showcase.png)\n\nFeatures:\n\n* Implements [Desktop Notification spec](https://specifications.freedesktop.org/notification-spec/latest/)\n  (current version 1.2).\n\n  Should be compatible with any dbus clients (e.g. libnotify, Gtk3, etc).\n  Supports icons and resizing of these (to a fixed/min/max box).\n\n* Tries hard to never drop any notifications silently by itself, even in case of\n  any unexpected errors.\n\n* [Pango markup](https://docs.gtk.org/Pango/pango_markup.html)\n  support in notification summary and body, option to enable/disable that for\n  individual messages via notification parameters, broken-markup-safe.\n  Start the thing with --test-message option to see how it works.\n\n* Configurable appearance via\n  [Gtk3 styles](https://docs.gtk.org/gtk3/css-overview.html)\n  (simple css files) and themes.\n\n* Rate-limiting using configurable \"leaky\" token-bucket algorithm.\n\n  When/if several messages get delayed, they will be displayed batched into one\n  \"digest\" message, up to a limit (number of last ones), and dropped with a\n  warning line (and a count of these) beyond that.\n\n* D-Bus interface has extra calls to pause passing notifications (but still\n  buffering these to \"digest\"), force-flushing such buffer, displaying previous\n  (cleaned-up) notifications, changing/pausing default cleanup timeout, etc.\n\n* Can send/receive json-serialized notifications via\n  [ZeroMQ](https://zeromq.org/) pub-sub queues.\n\n  This allows to e.g. link several machines together, to see all notifications\n  which are from any of them or send desktop notifications from a remote machine\n  (doesn't have to be a desktop one either, see included \"notify-net\" script and\n  zmq message format info below).\n\n* Filtering and matching using simple but very powerful scheme scripting\n  (based on [Peter Norvig's lispy2](https://norvig.com/lispy2.html)).\n\n* Can play any sounds from anywhere in the filtering scripts\n  (via [libcanberra](https://0pointer.net/lennart/projects/libcanberra/)).\n\n  I.e. only on specific occasions, like some regexp-match, not for every message\n  (though that is certainly possible as well), different sounds based on\n  notification message, or even multiple samples for the same one.\n\n* Optional logging for notifications with built-in file rotation.\n\n* All options/features are configurable and can be disabled entirely,\n  either via command-line options or a YAML configuration file.\n\n* Easy to change and debug - it's just a python script.\n\nSee below for detailed description of each particular feature.\n\nActual notification rendering is inspired (and based in part on)\n[notipy](https://gitlab.com/the_isz/notipy) project.\n\nWrote a few notes on the project a while ago when making it\n([link1](https://blog.fraggod.net/2010/2/libnotify-notification-daemon-shortcomings-and-my-solution),\n[link2](https://blog.fraggod.net/2010/12/Further-improvements-on-notification-daemon),\n[link3](https://blog.fraggod.net/2011/8/Notification-daemon-in-python)), but\nthese should be mostly summarized in this README anyway.\n\nIt's a somewhat old thing dating back to 2010 and python2, but I still use it myself,\nso should be supported and work perfectly fine on a reasonably-current linux desktop.\n\n\nInstallation\n--------------------\n\nIt's a regular package for Python 3.X, but not in pypi, so can be\ninstalled from a checkout with something like this:\n\n    % python setup.py install\n\n[pip](https://pip.pypa.io/) can be used to install it from a repo URL:\n\n    % python -m pip install --user 'git+https://github.com/mk-fg/notification-thing.git#egg=notification-thing'\n\nThis will install module to `~/.local/lib/`, use\n[venv](https://docs.python.org/3/library/venv.html)\nmodule for a more isolated/portable installation into some custom path.\n\nAlternatively, `./notification-thing` can be run right from the checkout tree,\nwithout any installation.\n\n### Requirements\n\n* [Python 3.X](https://python.org/).\n\n* [GObject-Introspection](https://gi.readthedocs.io/en/latest/)-enabled\n  [Gtk+](https://www.gtk.org/) 3.X (including Glib, Pango) and\n  [PyGObject](https://pygobject.readthedocs.io/en/latest/).\n\n* [libdbus / dbus-python](https://www.freedesktop.org/wiki/Software/DBusBindings/#libdbuspartofdbus).\n\n  Leftover dep from old python2 days, GDBus GI wrappers should probably provide\n  all necessary daemon interfaces as well these days.\n\n* (optional) [PyYAML](https://pyyaml.org/) - to configure daemon via YAML file,\n  not CLI (--conf option).\n\n* (optional) [pyzmq](https://pyzmq.readthedocs.io/en/latest/) -\n  to broadcast/receive notification messages over zeromq pub/sub sockets.\n\n* (optional) [libcanberra](https://0pointer.net/lennart/projects/libcanberra/) -\n  to play sounds (from XDG themes or files).\n\nNote that [libnotify](https://gitlab.gnome.org/GNOME/libnotify) and similar libs\nare not needed here - they are used to send the messages, not receive and\ndisplay these (latter is the job of notification-daemon, which this thing is).\n\n### Repository URLs\n\n- https://github.com/mk-fg/notification-thing\n- https://codeberg.org/mk-fg/notification-thing\n- https://fraggod.net/code/git/notification-thing\n\n\nUsage\n--------------------\n\nMake sure nothing else is already listening on the same dbus path/interface and\nstart the daemon by hand - should work.\n\nRun the script with -h/--help option to get the (rather long) list of all\npossible configuration options and tunables.\n\nAlternatively, dbus service file can be installed, so daemon can be started\nwhenever notifications arrive (and exiting during silence timeouts):\n\n    cp org.freedesktop.Notifications.service /usr/share/dbus-1/services/\n\nAnother way would be to start the thing from systemd user session or something\nlike that, e.g. ~/.xinitrc for older systems.\n\nTo enable startup with systemd user session for particular user, run following\nfrom that user's shell:\n\n    mkdir -p ~/.config/systemd/user/\n    cp notification-thing.service ~/.config/systemd/user/\n    systemctl --user enable notification-thing\n\nCan also start it manually then by `systemctl --user start notification-thing`\ncommand.\n\n\"notification-thing.service\" systemd unit from the repository makes daemon read\n`~/.notification.yaml` configuration file by default, if it exists, so any extra\noptions to the auto-started daemon instance can be specified there (see below).\n\nNote that both files are just an examples from dev setup, so feel free to tweak\nthem (e.g. add whatever options, change paths, dependencies, etc) to suit your\nsetup/distro (and ship adjusted file with the package, no need to pull these\nfrom repository).\n\n\n##### Configuration\n\nLots of tunable options are available (run the thing with \"--help\" option to see\nthe full list), but all-defaults should be the norm (naturally use defaults myself).\n\nUse --debug option to get a verbose log of all that's happening there, which\ndecisions are made and based on what data.\n\n[YAML](https://en.wikipedia.org/wiki/YAML) configuration file can be used to\nspecify a lot of options in a more convenient and brief format, see --conf\noption and \"notification_thing.example.yaml\" config in the repo.\n\n\n##### Filtering\n\nFile ~/.notification_filter (configurable via \"--filter-file\" option) can be\nused to control filtering mechanism at runtime and play sounds where necessary\n(see below).\n\nIt's the simple scheme script, see\n[scheme submodule](https://github.com/mk-fg/notification-thing/blob/master/notification_thing/scheme.py)\nor [original Peter Norvig's \"lispy2\" implementation](https://norvig.com/lispy2.html)\nfor details.\n\nGlobal definitions:\n\n- All the basic scheme stuff from lispy2:\n  [notification_thing/scheme.py#L164-L173](https://github.com/mk-fg/notification-thing/blob/0e8862c/notification_thing/scheme.py#L164-L173)\n\n- `(~ re msg)` - regexp search.\n- `(debug val-1 ...)` - print all arguments to --debug log.\n- `(sound-play name)`, `(sound-play-sync name)`, etc -\n  play sounds via libcanberra, if available - see note on sounds below.\n- `(props key value ...)` - set notification properties, like timeout, icon, hints.\n\nEvaluation of the filter script should return the function which will be called\nfor each notification and should return either #t or #f verdict value for\nwhether to display it or not.\n\nExample:\n\n    (define-macro define-matcher (lambda\n      (name op comp last rev-args)\n      `(define ,name (lambda args\n        (if (= (length args) 1) ,last\n          (let ((atom (car args)) (args (cdr args)))\n            (,comp\n              (,op ,@(if rev-args '((car args) atom) '(atom (car args))))\n              (apply ,name (cons atom (cdr args))))))))))\n\n    (define-matcher ~all ~ and #t #f) ; (~all re msg-1 ...)\n    (define-matcher all~ ~ and #t #t) ; (all~ msg re-1 ...)\n    (define-matcher ~any ~ or #f #f)  ; (~any re msg-1 ...)\n    (define-matcher any~ ~ or #f #t)  ; (any~ msg re-1 ...)\n\n    (define-macro log-kern~ (lambda (level pat)\n      `(~ ,(+ \"^kern\\.\" level \" kernel\\[-\\]:\\s+\\[[\\d.]+] \" pat) body)))\n\n    (lambda (summary body)\n      (not (or\n        ;; first section that returns #t suppresses notification\n\n        ;; --- irc\n        (and (~ \"^erc:\" summary) (or\n          ;; hl-only high-traffic channels\n          (and\n            (any~ summary\n              \"^erc: #(python|linux|bookz)$\"\n              \"^erc: (root|\\*status)\")\n            (not (~ \"mk-fg\" body)))\n          ;; irrelevant service messages\n          (~ \"Undefined CTCP query received. Silently ignored\" body)\n          (and\n            (~ \"^erc: #\\S+\" summary)\n            (or\n              (~ \"^\\*\\*\\* #\\S+ (was created on|modes:) \" body)\n              (~ \"^\\s*\\*\\*\\*\\s+\\S+\\s+\\(\\S+\\) is now known as \\S+$\" body)))\n          ;; make a sound\n          (sound-play (or\n            (and (~ \"mk-fg\" body) \"bell\") ;; nick highlight\n            (and (~ \"^erc: [^#]\" summary) \"phone-incoming-call\") ;; query\n            \"message-new-instant\"))))\n\n        ;; --- mail\n        (and (~ \"^New Mail:\" summary) (sound-play \"message\"))\n\n        ;; --- important notifications can have special properties set on them\n        (and (~ \"------------\\[ cut here \\]------------\" body)\n          (or (props \"hints.urgency\" 2 \"timeout\" 0 \"icon\" \"kernel-panic\")))\n\n        ;; --- sounds for log monitoring events\n        (sound-play\n          (and (= summary \"log:\") (or\n            (and (log-kern~ \"info\" \"input: \") \"device-added\")\n            (and (log-kern~ \"info\" \"usb [-\\d.]+: USB disconnect\") \"device-removed\")))))))\n\n~/.notification_filter is reloaded on-the-fly if updated, any errors will create\nadditional notification windows (with backtraces), as well as logged.\n\n\"--filter-test\" option can be used to test message summary + body\n(supplied after option) against filter file - will just print filtering verdict for\nsupplied summary/body and exit.\n\n\n##### Sounds\n\nSpecial \"sound-play\" function (see filtering example above) can plays specified\nsound sample (and always returns #f) via libcanberra from the filtering scripts.\nIf libcanberra is not available or failed to init, message will be logged to\nstderr on daemon start and sound-related stuff will simply be ignored.\n\n\"sound-play-sync\" function works same as \"sound-play\" but delays filtering until\nsound ends, and is intended for rare cases when e.g. one might want to play\nseveral different samples in sequence.\n\nThere's also \"sound-cache\" function to use libcanberra's \"cache\" function\n(to efficiently cache and reuse sample in audio daemon, e.g. pulseaudio).\n\nSounds are played only when and where these functions get invoked from the\nfiltering scripts, i.e. not played anywhere at all by default.\n\nThis allows to play sound samples selectively, using different samples based on\nnotification and possibly play multiple samples (in sequence or at the same time).\n\n\"--no-filter-sound\" cli/config option can be used to force-disable these,\ndon't init/touch libcanberra at all and make sound-* into a no-op functions.\n\n\"gtk-sound-theme-name\" setting is used to select\n[sound theme](https://freedesktop.org/wiki/Specifications/sound-theme-spec/)\nin libcanberra, \"gtk-enable-event-sounds\" is NOT used though (not implemented,\nas it's more dynamic and must be monitored for changes).\n\n\n##### Extra dbus commands\n\nD-Bus interface can be inspected via usual introspection methods (add \"--xml\" to\nget more canonical form):\n\n    gdbus introspect --session \\\n      --dest org.freedesktop.Notifications \\\n      --object-path /org/freedesktop/Notifications\n\nExtra non-spec methods:\n\n - \"Flush\" - no args, no returns - display all queued (due to rate-limiting)\n   notifications.\n\n - \"Cleanup\" - args: timeout (double), max\\_count (uint32), no returns - close\n   currently-displayed notifications older than passed timeout (seconds).\n   Notification bubbles are closed in oldest-first order up to \"max\\_count\" value\n   (0 - all), so to close one oldest note, one might pass timeout=0,\n   max\\_count=1.\n\n - \"List\" - no args, returns array of int32 - return list of currently-displayed\n   notification ids.\n\n - \"Redisplay\" - no args, returns uint32 id of notification - re-displays last\n   message again, then the one before it and so on (see --history-len option).\n\nDaemon also implements \"org.freedesktop.DBus.Properties\" interface.\nSupported properties (full list can be acquired via usual \"GetAll\" method) are:\n\n - \"plug\" (bool or \"toggle\") - block notification bubbles from displaying,\n   queueing them up to display when it will be disabled.\n\n - \"urgent\" (bool or \"toggle\") - display notifications with urgency=critical\n   immediately, regardless of rate-limiting or fullscreen-app check.\n\n - \"cleanup\" (bool or \"toggle\") - enable/disable cleanup timeout for\n   notification bubbles. Disabled timeout will mean that they will hang around\n   forever, until manually dismissed (either by clicking or via \"Flush\" method).\n\nFor example, to temporarily block/unblock all but the urgent notifications:\n\n    dbus-send --type=method_call \\\n      --dest=org.freedesktop.Notifications \\\n      /org/freedesktop/Notifications \\\n      org.freedesktop.DBus.Properties.Set \\\n      org.freedesktop.Notifications \\\n      string:plug variant:string:toggle\n\n\n##### Appearance / styles\n\nAppearance (and some behavior) of the popup windows is subject to\n[gtk3 styles](https://docs.gtk.org/gtk3/css-overview.html)\n(css files), with default being the light one\n(see actual code for up-to-date stylesheet though):\n\n    #notification { background: transparent; }\n    #notification #frame { background-color: #d4ded8; padding: 3px; }\n    #notification #hs { background-color: black; }\n\n    #notification #critical { background-color: #ffaeae; }\n    #notification #normal { background-color: #f0ffec; }\n    #notification #low { background-color: #bee3c6; }\n\n    #notification #summary {\n      padding-left: 5px;\n      font-size: 1.2em;\n      text-shadow: 1px 1px 0px gray;\n    }\n    #notification #body { font-size: 1em; }\n    #notification #body * { background-color: #d4ded8; }\n\nFull hierarchy of gtk3 widgets used (all have \"Gtk\" prefix in C code), how\nthey're placed and named (used as `#\u003cname\u003e` in gtk3 css):\n\n    Window #notification\n      Box #frame\n        HBox (only if icon is used)\n          Image (icon)  VBox\n                          EventBox #\u003curgency-level-name\u003e\n                            Label #summary\n                          HSeparator #hs\n                          TextView #body\n\n(to see tree of these for running app, find all style nodes, tweak stuff on the fly\nand such, use [Gtk-Inspector](https://wiki.gnome.org/Projects/GTK%2B/Inspector))\n\nFor example, if you have compositing wm that supports transparency, to make\nnotification popups have slightly rounded corners, one can put this to\n`~/.config/gtk-3.0/gtk.css`:\n\n    #notification #frame { border-radius: 7px; }\n\nOr, to use ~20% larger font size (wrt default size) in both notification summary\nand body text:\n\n    #notification #summary { font-size: 1.4em; }\n    #notification #body { font-size: 1.2em; }\n\n\n##### Text markup\n\n[Pango markup](https://docs.gtk.org/Pango/pango_markup.html)\nboth in message body and summary is supported and processing of it is enabled by\ndefault (can be disabled with --markup-disable).\n\nWhether it should be parsed can also be controlled on per-message basis by\npassing boolean \"x-nt-markup\" hint (true - enabled, false - disabled).\n\nOther --markup-* options are available to control what happens when pango fails\nto parse the tags in text - whether it should be stripped (--markup-strip-on-err\noption) and/or warning should be issued.\n\nTechnically GtkTextView widget used for message body doesn't allow pango markup\n(though GtkLabel holding summary does, see also\n[gnome bug 59390](https://bugzilla.gnome.org/show_bug.cgi?id=59390)) and uses\nit's own GtkTextTag-based markup, which is very similar to pango, so pango stuff\ngets ad-hoc converted to GtkTextTags (see `display.pango_markup_to_gtk`),\nand potentially (but unlikely) can get something wrong.\n\nStarting daemon with --test-message option will have display pango-markup-heavy\nnotification on startup, which can be used to test this stuff.\n\n\n##### Network broadcasting\n\nNeeds [pyzmq](https://pyzmq.readthedocs.io/en/latest/) module, if used.\n\nAllows to serialize notifications received from dbus interface and publish them\non zmq\\_pub socket(s), or receive them in a similar way from the network on\nzmq\\_sub socket(s).\n\nAny connected instances will receive notifications, and any transient network\nissues should be handled by zeromq - pub socket should keep messages queued\nfor subscribers it has seen (connected) at least once.\n\nFurthermore, it's not required that subscribers should connect to publishers or\nvice versa - any zeromq sockets can initiate connection to any other ones, so\nthat e.g. \"notify-net\" tool (included) can create \"pub\" socket and connect to a\nrunning daemon's \"sub\" socket on another machine - or any number of machines -\njust specify -d option as many times as needed, then publish messages there.\n\nSee --net-* options for all that.\n\nMake sure to link pub sockets with sub (in whatever direction), as linking\npub-pub or sub-sub won't do anything.\n\nWhen/if running this notification mechanism over public internet\n(and maybe even if not), I'd recommend using one of the\n[WireGuard mesh network tools](https://github.com/HarvsG/WireGuardMeshes)\nto have it done in a reliably secure manner.\n\nFormat for ZeroMQ messages used by this app:\n\n    message: byte(protocol_version) || json(notification_message)\n    notification_message: [hostname, posix_time, notification]\n    notification: { summary: \"\", body: \"\", timeout: -1,\n      icon: \"\", app_name: \"generic\", replaces_id: 0, actions: [], hints: {}, plain: False }\n\nWhich is basically a version-prefix byte followed by json-encoded payload:\n\n- \"protocol_version\" is 1 (resuting in \\x01 byte prefix).\n\n- \"hostname\" (String) will be displayed as prefix in message summary.\n\n- \"posix_time\" (Number) is a wall-clock timestamp, which can be indicated in\n  notification if message is reasonably old (e.g. older than 15min), to\n  highlight that it might be outdated or out of sequence with other notifications.\n\n- All fields in \"notification\" (Object) - except \"plain\" - are same as described in\n  [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/latest/)\n  \"Basic Design\" section, with default values presented in mapping above.\n\n- Extra \"plain\" field in \"notification\" object is a boolean that toggles disabling\n  [pango markup tags](https://docs.gtk.org/Pango/pango_markup.html) processing.\n\n  I.e. plain=true will make text in summary/body fields printed exactly as it is,\n  which might be useful if it can contain valid HTML/XML tags that'd parse as pango stuff.\n\nSimple notification-message example, represented in python string format:\n\n    '\\x01[\"myhost\", 1671650325.25, {\"summary\": \"hello\", \"body\": \"hello world!\"}]'\n\n(note that leading \\x01 gets translated in these strings to a single 01 byte)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmk-fg%2Fnotification-thing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmk-fg%2Fnotification-thing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmk-fg%2Fnotification-thing/lists"}