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() |