notification-sidebar.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #!/usr/bin/env python3
  2. # Written by Adnan Shameem
  3. # License: CC0 - do whatever you want with it
  4. # This script relies on xfce-notifyd. It is lightweight and saves the
  5. # notifications in a ~/.cache/xfce4/notifyd/log file which is formatted
  6. # in INI type format.
  7. # To make this script work,
  8. # 1. Install notification-daemon
  9. # 2. Install xfce4-notifyd
  10. # 3. Run either:
  11. # setsid /usr/lib/xfce4/notifyd/xfce4-notifyd
  12. # or
  13. # systemctl --user start xfce4-notifyd
  14. # 4. Run xfce4-notifyd-config and go to Log and Turn ON Notifications
  15. # and then set Log notifications to "always"
  16. # Now get some notifications from your apps.
  17. # Run this script to show them as a sidebar list.
  18. # If you want to toggle the panel, run this with a toggle script like:
  19. # `python-invert.sh` in the current dir or https://gitlab.com/snippets/1994036
  20. # Something like ./python-invert.sh path/to/notification-sidebar.py
  21. # It will invert the panel everytime this is run.
  22. # You can then run this on a button on a panel.
  23. # Would be great for lightweight desktops, such as, jwm, openbox, i3 etc.
  24. # where DE does not have a notification list panel available.
  25. # Screenshot: https://imgur.com/wah27HZ
  26. ## -------- Imports -------- ##
  27. # Python GTK3
  28. import gi
  29. gi.require_version('Gtk', '3.0')
  30. from gi.repository import Gtk
  31. # Especially for getting screen dimensions
  32. from gi.repository import Gdk
  33. # For processing INI
  34. try:
  35. from configparser import ConfigParser
  36. except ImportError:
  37. from ConfigParser import ConfigParser # ver. < 3.0
  38. # For getting full path to home directory
  39. import os
  40. # For getting python version
  41. import sys
  42. # For processing time
  43. from datetime import datetime, timedelta
  44. # -------- Classes/Implementation -------- ##
  45. def deletefile(filename):
  46. if os.path.isfile(filename):
  47. os.remove(filename)
  48. # Get/expand real userpath from "~"
  49. # We will keep the xfce notofications log
  50. inipath = os.path.expanduser("~/.cache/xfce4/notifyd/log")
  51. # Panel width
  52. panel_width = 400
  53. # Class to handle multiline list items
  54. class ListBoxRowWithData(Gtk.ListBoxRow):
  55. def __init__(self, data1, data2):
  56. super(Gtk.ListBoxRow, self).__init__()
  57. self.data1 = data1
  58. self.data2 = data2
  59. box = Gtk.VBox(baseline_position=Gtk.BaselinePosition.TOP, spacing=0)
  60. item_line1 = Gtk.Label()
  61. item_line1.set_markup("<b>"+data1+"</b>")
  62. item_line1.set_line_wrap(True)
  63. box.pack_start(item_line1, False, False, 5)
  64. if data2 != '':
  65. item_line2 = Gtk.Label()
  66. item_line2.set_markup(data2)
  67. item_line2.set_line_wrap(True)
  68. box.pack_start(item_line2, False, False, 5)
  69. self.add(box)
  70. class MyWindow(Gtk.Window):
  71. def __init__(self):
  72. Gtk.Window.__init__(self, title="Notifications")
  73. # Get screen dimensions
  74. display = Gdk.Display.get_default()
  75. monitor = display.get_monitor(0)
  76. swidth = monitor.get_geometry().width
  77. sheight = monitor.get_geometry().height
  78. # Size the window
  79. # Remove the titlebar because having it would not let us set the height
  80. # past titlebar.
  81. self.set_decorated(False)
  82. self.move(swidth - panel_width,0)
  83. self.set_default_size(panel_width, sheight)
  84. main_grid = Gtk.Grid()
  85. self.add(main_grid)
  86. btnquit = Gtk.Button(label="Close")
  87. btnquit.connect("clicked", Gtk.main_quit)
  88. btnclear = Gtk.Button(label="Clear")
  89. btnclear.connect("clicked", self.on_btnclear_clicked)
  90. notif_list = Gtk.ListBox()
  91. notif_scroller = Gtk.ScrolledWindow()
  92. # Read INI file
  93. inidata = ConfigParser()
  94. # The check is temporary. See TODO below.
  95. if sys.version[0] == '2':
  96. inidata.read(inipath)
  97. else:
  98. # Sometimes some weird characters appear on the INI.
  99. # ", encoding="ISO-8859-1" stops it from crashing.
  100. # This is a temoporary fix. TODO: Fix this issue for real.
  101. inidata.read(inipath, encoding="ISO-8859-1")
  102. # Get all sections
  103. sections = inidata.sections()
  104. # Reverse to show the latest ones first
  105. sections.sort(reverse=True)
  106. for section in sections:
  107. itemdate = datetime.strptime(section, '%Y-%m-%dT%H:%M:%S.%f')
  108. if itemdate.date() == datetime.today().date(): # Today
  109. itemdate = itemdate.strftime('%I:%M%p')
  110. elif itemdate.date() == (datetime.today() - timedelta(days=1)).date(): # Yesterday
  111. itemdate = itemdate.strftime('Yesterday %I:%M%p')
  112. else: # Not today, not yesterday, so show full date and time
  113. itemdate = itemdate.strftime('%Y-%m-%d %I:%M%p')
  114. notif_list.add(ListBoxRowWithData(itemdate+': '+inidata.get(section, 'summary'), inidata.get(section, 'body')))
  115. main_grid.attach(notif_scroller, 1, 0, 2, 1)
  116. notif_scroller.add(notif_list)
  117. main_grid.attach(btnquit, 1, 2, 1, 1)
  118. main_grid.attach_next_to(btnclear, btnquit, Gtk.PositionType.RIGHT, 1, 1)
  119. notif_list.set_hexpand(True)
  120. notif_list.set_vexpand(True)
  121. def on_btnclear_clicked(self, widget):
  122. deletefile(inipath)
  123. Gtk.main_quit()
  124. win = MyWindow()
  125. win.connect("destroy", Gtk.main_quit)
  126. # Close this panel when mouse gets outside of window
  127. # TODO: Does not work with JWM panel button. Fix it or implement click outside window.
  128. #win.connect("leave-notify-event", Gtk.main_quit, "")
  129. # Remove taskbar button
  130. win.set_skip_taskbar_hint(True)
  131. win.show_all()
  132. # Remove titlebar but keep border
  133. win.get_window().set_decorations(Gdk.WMDecoration.BORDER)
  134. # Always on top
  135. win.set_keep_above(True);
  136. Gtk.main()