~jconti/recent-notifications/trunk

200 by Jason Conti
After discovering unity-books-lens, decided to try to make a unity-notifications-lens, so adding experimental support.
1
"""
2
Lens.py
3
April 20, 2011
4
"""
5
6
import logging
7
8
from gi.repository import Dee, Gio, GLib, GObject, Unity
9
10
from Notification import Notification
11
12
import Timestamp
13
14
BUS_NAME = "net.launchpad.RecentNotifications"
15
OBJECT_PATH = "/net/launchpad/RecentNotifications"
16
DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x4
17
DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1
18
19
logging.basicConfig(level=logging.DEBUG)
20
logger = logging.getLogger("Lens")
21
22
class Group:
23
  RECENT_NOTIFICATIONS = 0
24
25
class LensException(Exception):
26
  pass
27
28
class Lens(object):
29
  def __init__(self):
30
    self._connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
31
    if not self.request_name(BUS_NAME):
32
      raise LensException("Failed to acquire bus name: {0}".format(BUS_NAME))
33
34
    self._messages = []
35
36
    self._entry = Unity.PlaceEntryInfo.new(OBJECT_PATH + "/mainentry")
37
38
    self._sections_model = Dee.SharedModel.new(BUS_NAME + ".SectionsModel")
39
    self._sections_model.set_schema("s", "s")
40
    self._entry.props.sections_model = self._sections_model
41
42
    self._groups_model = Dee.SharedModel.new(BUS_NAME + ".GroupsModel")
43
    self._groups_model.set_schema("s", "s", "s")
44
    self._entry.props.entry_renderer_info.props.groups_model = self._groups_model
45
46
    self._results_model = Dee.SharedModel.new(BUS_NAME + ".ResultsModel")
47
    self._results_model.set_schema("s", "s", "u", "s", "s", "s")
48
    self._entry.props.entry_renderer_info.props.results_model = self._results_model
49
50
    self._sections_model.connect("notify::synchronized", self.on_sections_synchronized)
51
    self._groups_model.connect("notify::synchronized", self.on_groups_synchronized)
52
    self._results_model.connect("notify::synchronized", self.on_results_synchronized)
53
54
    self._entry.connect("notify::active-search", self.on_search_changed)
55
    self._entry.connect("notify::active-section", self.on_section_changed)
56
57
    self._notify = Notification()
58
    self._notify.connect("message-received", self.on_message_received)
59
    
60
    self._controller = Unity.PlaceController.new(OBJECT_PATH)
61
    self._controller.add_entry(self._entry)
62
    self._controller.export()
63
64
  def append_message_to_results(self, message):
65
    if message.app_icon == "":
66
      icon = "file:///home/jconti/Projects/bzr/recent-notifications/icons/notification-normal.svg"
67
    else:
68
      icon = message.app_icon
69
    self._results_model.prepend("", icon, Group.RECENT_NOTIFICATIONS,
70
        "text/html", message.summary, "{0}\n{1} from {2}".format(message.body,
71
          Timestamp.locale_datetime(message.timestamp), message.app_name))
72
73
  def on_message_received(self, monitor, message):
74
    self._messages.append(message)
75
    if self._results_model.get_property("synchronized"):
76
      self.append_message_to_results(message)
77
      self._results_model.flush_revision_queue()
78
79
  def on_groups_synchronized(self, groups_model, *args):
80
    groups_model.clear()
81
    groups_model.append("UnityHorizontalTileRenderer",
82
      "Recent Notifications",
83
      "file:///home/jconti/Projects/bzr/recent-notifications/icons/notification-unread.svg")
84
85
  def on_results_synchronized(self, results_model, *args):
86
    results_model.clear()
87
    for message in self._messages:
88
      self.append_message_to_results(message)
89
    results_model.flush_revision_queue()
90
91
  def on_sections_synchronized(self, sections_model, *args):
92
    sections_model.clear()
93
    sections_model.append("All Applications",
94
        Gio.ThemedIcon.new("document").to_string())
95
96
  def on_search_changed(self, entry, *args):
97
    entry.freeze_notify()
98
    active_search = entry.get_property("active_search")
99
    search_string = active_search.get_search_string()
100
    logger.debug("Searching for: {0}".format(search_string))
101
    active_search.finished()
102
    GObject.idle_add(self.thaw, entry)
103
104
  def on_section_changed(self, entry, section):
105
    active_section = entry.get_property("active_section")
106
    logger.debug("Changed section: {0}".format(active_section))
107
    
108
  def request_name(self, name):
109
    proxy = Gio.DBusProxy.new_sync(self._connection, 0, None, 
110
        "org.freedesktop.DBus", "/org/freedesktop/DBus",
111
        "org.freedesktop.DBus", None)
112
    result = proxy.call_sync("RequestName", 
113
        GLib.Variant("(su)", (BUS_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE)), 
114
        Gio.DBusCallFlags.NONE, -1, None)
115
    value = result.unpack()
116
    if len(value) < 1 or value[0] != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
117
      logger.debug("RequestName returned value: {0}".format(value))
118
      return False
119
    else:
120
      return True
121
122
  def thaw(self, entry):
123
    entry.thaw_notify()
124
    return False
125
126
def main():
127
  lens = Lens()
128
  loop = GObject.MainLoop()
129
  loop.run()
130
131
if __name__ == "__main__":
132
  main()