Merge lp:~3v1n0/unity/zeitgeist-quicklists into lp:unity

Proposed by Marco Trevisan (Treviño)
Status: Work in progress
Proposed branch: lp:~3v1n0/unity/zeitgeist-quicklists
Merge into: lp:unity
Diff against target: 935 lines (+701/-57)
8 files modified
CMakeLists.txt (+1/-1)
UnityCore/CMakeLists.txt (+5/-1)
UnityCore/ZeitgeistApplication.cpp (+269/-0)
UnityCore/ZeitgeistApplication.h (+86/-0)
UnityCore/ZeitgeistSubject.cpp (+91/-0)
UnityCore/ZeitgeistSubject.h (+64/-0)
plugins/unityshell/src/BamfLauncherIcon.cpp (+174/-55)
plugins/unityshell/src/BamfLauncherIcon.h (+11/-0)
To merge this branch: bzr merge lp:~3v1n0/unity/zeitgeist-quicklists
Reviewer Review Type Date Requested Status
Tim Penhey (community) Needs Fixing
Review via email: mp+93126@code.launchpad.net

Description of the change

Implemented design backlog bug #893652 adding quicklist entries with recently used files to launcher icons.

This adds Zeitgeist support to unity, using lp:libzeitgeist through an UnityCore wrapper where I've added two main classes, ZeitgeistApplication and ZeitgeistSubject:

ZeitgeistSubject is a kind of a mix between a pure zeitgeist-event and a native zeitgeist-subject. In fact each ZeitgeistSubject is actually wrapping a native subject, but it has also the references to the native (parent) event id.

ZeitgeistApplication is the main class to be used in unity and allows to define some kinds of event monitors for the given application.
Each application can store also a defined number of subjects received by the monitor and these elements are stored into a deque that acts like a circular-buffer cache.

BamfLauncherIcon use now ZeitgeistApplication to receive the events and to generate the quicklist items with the last recent items. Each .desktop file can disable this list of recent files setting to false the "X-Ayatana-Show-Recent-Files" key.

To post a comment you must log in.
lp:~3v1n0/unity/zeitgeist-quicklists updated
1978. By Marco Trevisan (Treviño)

ZeitgeistApplication: some string optimization...

1979. By Marco Trevisan (Treviño)

ZeitgeistApplication: share the instance of ZeitgeistLog, fix memory leaks

1980. By Marco Trevisan (Treviño)

BamfLauncherIcon: don't open the content of a quicklist if the uri is empty

1981. By Marco Trevisan (Treviño)

ZeitgeistApplication: don't use a pointer to handle the shared log...

1982. By Marco Trevisan (Treviño)

ZeitgeistApplication: use manifestation and reorder the received items

Revision history for this message
Tim Penhey (thumper) wrote :

> std::string const& basename = glib::String(g_path_get_basename(desktop_file.c_str())).Str();

This takes a reference to a temporary. Remove the "const&" bit.

If you have a shared log, why delete it when the last reference was there? why not let the log just live on?

> UnityCore/ZeitgeistSubject.cpp

Has the same two lines about 6 times. Consider a simple function.

The ZeitgeistSubject is obviously not meant to be inherited from, so please don't add a protected section.
Secondly, please don't use friend. What is wrong with having a public constructor?

As you mentioned, this branch is missing tests. A key question is how to ensure it is working.

What needs to exist for this to work properly? How can we minimally test it?

We need to export the relevant bits through autopilot, then we can have some functional tests that show when we do "stuff" we get "other stuff" showing in the quicklists.

review: Needs Fixing
Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote :

> > std::string const& basename =
> glib::String(g_path_get_basename(desktop_file.c_str())).Str();
>
> This takes a reference to a temporary. Remove the "const&" bit.

Ok.

> If you have a shared log, why delete it when the last reference was there?
> why not let the log just live on?
>
> > UnityCore/ZeitgeistSubject.cpp
>
> Has the same two lines about 6 times. Consider a simple function.

That's something I was thinking on, but I didn't like to do something like that just for this case (also considering that I'm repeating something that is not so different from calling a shared function).
I think that this a reason why can make sense supporting const strings in glib::String as I told some time ago... Wouldn't be better something like this: http://paste.ubuntu.com/860690/ ?

> The ZeitgeistSubject is obviously not meant to be inherited from, so please
> don't add a protected section.

Sure, I added it for another reason... Then I forgot to change it.

> Secondly, please don't use friend. What is wrong with having a public
> constructor?

I know that friend classes aren't the best thing, but I neither loved the idea of allowing everybody to construct a ZeitgeistSubject, as that's something that only a ZetigeistApplication should be able to provide. But if you prefer to avoid to use friends, I'll move it out.

> As you mentioned, this branch is missing tests. A key question is how to
> ensure it is working.
>
> What needs to exist for this to work properly? How can we minimally test it?

In the real world we'd need to create a new zeitgeist application, install a monitor on it, and check that we get some subjects both when the monitor is installed (if there are events for that) and when we open a new file with the given application.

Testing it through a mock would be maybe useless to really check that this is working.

> We need to export the relevant bits through autopilot, then we can have some
> functional tests that show when we do "stuff" we get "other stuff" showing in
> the quicklists.

I see what I can do on this.

Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote :

Ah, I forgot to reply at:

> If you have a shared log, why delete it when the last reference was there?
> why not let the log just live on?

Well, why should we leak memory if no one really needs a log?
When all the applications that have used a log are destroyed, we make sure that our log is removed... Maybe I could just switch to zetigeist_log_get_default, but it wouldn't be destroyed anyway when all the Application instances would be removed.

lp:~3v1n0/unity/zeitgeist-quicklists updated
1983. By Marco Trevisan (Treviño)

Merging with trunk

1984. By Marco Trevisan (Treviño)

glib::String: add support for constant strings

1985. By Marco Trevisan (Treviño)

ZeitgeistSubject: moving to glib::String with const support.

1986. By Marco Trevisan (Treviño)

ZeitgeistSubject: setting the constructor private.

Revision history for this message
Michal Hruby (mhr3) wrote :

112 + explicit String(const gchar* str);

Woooo, no, no, no, the glib::String class is meant to own the string, this way it can easily break if the real owner goes out of scope, we definitely don't want that kind of breakage.

lp:~3v1n0/unity/zeitgeist-quicklists updated
1987. By Marco Trevisan (Treviño)

Removing debug test code

1988. By Marco Trevisan (Treviño)

ZeitgeistSubject: add GetConstString to stafely convert gchar* to std::string

Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote :

> 112 + explicit String(const gchar* str);
>
> Woooo, no, no, no, the glib::String class is meant to own the string, this way
> it can easily break if the real owner goes out of scope, we definitely don't
> want that kind of breakage.

It didn't look so bad to me as I don't think it would introduce any breakage that using a simple const gchar* wouldn't, but if that's your thinking, I go back to the Tim's advice, even if it's out of scope there, imho.

Unmerged revisions

1988. By Marco Trevisan (Treviño)

ZeitgeistSubject: add GetConstString to stafely convert gchar* to std::string

1987. By Marco Trevisan (Treviño)

Removing debug test code

1986. By Marco Trevisan (Treviño)

ZeitgeistSubject: setting the constructor private.

1985. By Marco Trevisan (Treviño)

ZeitgeistSubject: moving to glib::String with const support.

1984. By Marco Trevisan (Treviño)

glib::String: add support for constant strings

1983. By Marco Trevisan (Treviño)

Merging with trunk

1982. By Marco Trevisan (Treviño)

ZeitgeistApplication: use manifestation and reorder the received items

1981. By Marco Trevisan (Treviño)

ZeitgeistApplication: don't use a pointer to handle the shared log...

1980. By Marco Trevisan (Treviño)

BamfLauncherIcon: don't open the content of a quicklist if the uri is empty

1979. By Marco Trevisan (Treviño)

ZeitgeistApplication: share the instance of ZeitgeistLog, fix memory leaks

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2012-02-23 07:43:46 +0000
3+++ CMakeLists.txt 2012-02-28 17:40:24 +0000
4@@ -122,7 +122,7 @@
5 #
6 # Compiz Plugins
7 #
8-set (UNITY_PLUGIN_DEPS "compiz;nux-2.0>=2.0.0;libbamf3;dee-1.0;gio-2.0;gio-unix-2.0;dbusmenu-glib-0.4;x11;libstartup-notification-1.0;gthread-2.0;indicator3-0.4;atk;unity-misc>=0.4.0;gconf-2.0;libutouch-geis;gtk+-3.0>=3.1;sigc++-2.0;json-glib-1.0;libnotify;gnome-desktop-3.0;gdu")
9+set (UNITY_PLUGIN_DEPS "compiz;nux-2.0>=2.0.0;libbamf3;dee-1.0;gio-2.0;gio-unix-2.0;dbusmenu-glib-0.4;x11;libstartup-notification-1.0;gthread-2.0;indicator3-0.4;atk;unity-misc>=0.4.0;gconf-2.0;libutouch-geis;gtk+-3.0>=3.1;sigc++-2.0;json-glib-1.0;libnotify;gnome-desktop-3.0;gdu;zeitgeist-1.0")
10
11 add_subdirectory(plugins/unityshell)
12 add_subdirectory(plugins/gtkloader)
13
14=== modified file 'UnityCore/CMakeLists.txt'
15--- UnityCore/CMakeLists.txt 2012-02-07 07:42:12 +0000
16+++ UnityCore/CMakeLists.txt 2012-02-28 17:40:24 +0000
17@@ -1,5 +1,5 @@
18 find_package (PkgConfig)
19-pkg_check_modules (CORE_DEPS REQUIRED glib-2.0 gio-2.0 dee-1.0 sigc++-2.0 nux-core-2.0 gdk-pixbuf-2.0 unity)
20+pkg_check_modules (CORE_DEPS REQUIRED glib-2.0 gio-2.0 dee-1.0 sigc++-2.0 nux-core-2.0 gdk-pixbuf-2.0 unity zeitgeist-1.0)
21
22 execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} unity --variable lensesdir OUTPUT_VARIABLE _lensesdir OUTPUT_STRIP_TRAILING_WHITESPACE)
23
24@@ -40,6 +40,8 @@
25 Result.h
26 Results.h
27 Variant.h
28+ ZeitgeistApplication.h
29+ ZeitgeistSubject.h
30 )
31
32 set (CORE_SOURCES
33@@ -70,6 +72,8 @@
34 Result.cpp
35 Results.cpp
36 Variant.cpp
37+ ZeitgeistApplication.cpp
38+ ZeitgeistSubject.cpp
39 )
40
41 #
42
43=== added file 'UnityCore/ZeitgeistApplication.cpp'
44--- UnityCore/ZeitgeistApplication.cpp 1970-01-01 00:00:00 +0000
45+++ UnityCore/ZeitgeistApplication.cpp 2012-02-28 17:40:24 +0000
46@@ -0,0 +1,269 @@
47+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
48+/*
49+ * Copyright (C) 2012 Canonical Ltd
50+ *
51+ * This program is free software: you can redistribute it and/or modify
52+ * it under the terms of the GNU General Public License version 3 as
53+ * published by the Free Software Foundation.
54+ *
55+ * This program is distributed in the hope that it will be useful,
56+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
57+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
58+ * GNU General Public License for more details.
59+ *
60+ * You should have received a copy of the GNU General Public License
61+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
62+ *
63+ * Authored by: Marco Trevisan (Treviño) <3v1n0@ubuntu.com>
64+ */
65+
66+#include "ZeitgeistApplication.h"
67+
68+namespace unity
69+{
70+namespace zeitgeist
71+{
72+static glib::Object<ZeitgeistLog> shared_log_;
73+
74+Application::Application(std::string const& desktop_file)
75+ : application_event_(zeitgeist_event_new())
76+{
77+ if (!shared_log_ || !ZEITGEIST_IS_LOG(shared_log_.RawPtr()))
78+ {
79+ shared_log_ = glib::Object<ZeitgeistLog>(zeitgeist_log_new());
80+ }
81+
82+ log_ = shared_log_;
83+
84+ if (!desktop_file.empty())
85+ {
86+ std::string basename = glib::String(g_path_get_basename(desktop_file.c_str())).Str();
87+
88+ if (!basename.empty())
89+ {
90+ const std::string prefix = "application://";
91+ application_uri_ = prefix + basename;
92+
93+ ZeitgeistSubject* subj = zeitgeist_subject_new();
94+ glib::Object<ZeitgeistSubject> application_subject(subj, glib::AddRef());
95+ zeitgeist_subject_set_manifestation(application_subject, "!" ZEITGEIST_NFO_SOFTWARE_ITEM);
96+ zeitgeist_event_add_subject(application_event_, application_subject);
97+ zeitgeist_event_set_actor(application_event_, application_uri_.c_str());
98+ }
99+ }
100+}
101+
102+Application::~Application()
103+{
104+ if (ZEITGEIST_IS_LOG(shared_log_.RawPtr()))
105+ {
106+ auto glog = reinterpret_cast<GObject*>(shared_log_.RawPtr());
107+
108+ /* If the log has 2 references we can safely nullify the shared instance
109+ * In fact, at this point both the application log_ member and the
110+ * shared member owns a reference, that's why of this "magic" number */
111+ if (glog->ref_count == 2)
112+ {
113+ shared_log_ = nullptr;
114+ }
115+ }
116+ else if (shared_log_)
117+ {
118+ shared_log_ = nullptr;
119+ }
120+}
121+
122+std::string const& Application::Uri() const
123+{
124+ return application_uri_;
125+}
126+
127+ZeitgeistResultType Application::GetResultTypeByMonitorType(MonitorType mtype)
128+{
129+ switch (mtype)
130+ {
131+ case MOST_RECENT_EVENTS:
132+ return ZEITGEIST_RESULT_TYPE_MOST_RECENT_EVENTS;
133+ case LEAST_RECENT_EVENTS:
134+ return ZEITGEIST_RESULT_TYPE_LEAST_RECENT_EVENTS;
135+ case MOST_RECENT_SUBJECTS:
136+ return ZEITGEIST_RESULT_TYPE_MOST_RECENT_SUBJECTS;
137+ case LEAST_RECENT_SUBJECTS:
138+ return ZEITGEIST_RESULT_TYPE_LEAST_RECENT_SUBJECTS;
139+ case MOST_POPULAR_SUBJECTS:
140+ return ZEITGEIST_RESULT_TYPE_MOST_POPULAR_SUBJECTS;
141+ case LEAST_POPULAR_SUBJECTS:
142+ return ZEITGEIST_RESULT_TYPE_LEAST_POPULAR_SUBJECTS;
143+ default:
144+ return ZEITGEIST_RESULT_TYPE_RELEVANCY;
145+ }
146+}
147+
148+void Application::HandleResultSet(glib::Object<ZeitgeistResultSet> const& results, MonitorType mtype)
149+{
150+ Subjects& subjects = subjects_[mtype];
151+ std::deque<std::pair<ZeitgeistEvent*, ZeitgeistSubject*>> new_subjects;
152+ unsigned int max_items = subjects_pools_[mtype];
153+
154+ // Reversing the list of the new items.
155+ while (zeitgeist_result_set_has_next(results))
156+ {
157+ ZeitgeistEvent* event = zeitgeist_result_set_next(results);
158+
159+ for (int i = 0; i < zeitgeist_event_num_subjects(event); ++i)
160+ {
161+ ZeitgeistSubject* subj = zeitgeist_event_get_subject(event, i);
162+ if (!ZEITGEIST_IS_SUBJECT(subj))
163+ continue;
164+
165+ std::pair<ZeitgeistEvent*, ZeitgeistSubject*> new_event(event, subj);
166+ new_subjects.push_front(new_event);
167+ }
168+ }
169+
170+ for (auto new_pair : new_subjects)
171+ {
172+ ZeitgeistEvent* event = new_pair.first;
173+ guint32 event_id = zeitgeist_event_get_id(event);
174+ gint64 timestamp = zeitgeist_event_get_timestamp(event);
175+
176+ glib::Object<ZeitgeistSubject> gsubject(new_pair.second, glib::AddRef());
177+ Subject::Ptr subject_ptr(new Subject(gsubject, event_id, timestamp));
178+
179+ for (auto it = subjects.begin(); it != subjects.end(); ++it)
180+ {
181+ if ((*it)->Uri() == subject_ptr->Uri())
182+ {
183+ subject_removed.emit(mtype, *it);
184+ subjects.erase(it);
185+ break;
186+ }
187+ }
188+
189+ if (subjects.size() == max_items)
190+ {
191+ subject_removed.emit(mtype, subjects.front());
192+ subjects.pop_front();
193+ }
194+
195+ subjects.push_back(subject_ptr);
196+ subject_added.emit(mtype, subject_ptr);
197+ }
198+}
199+
200+void Application::AddMonitor(MonitorType mtype, unsigned int pool_size)
201+{
202+ if (monitors_.find(mtype) != monitors_.end() || pool_size == 0)
203+ {
204+ return;
205+ }
206+
207+ glib::Object<ZeitgeistTimeRange> time_range(zeitgeist_time_range_new_to_now());
208+
209+ // This must not be free'd, libzeitgeist will take care of it.
210+ GPtrArray* event_template = g_ptr_array_sized_new(1);
211+ glib::Object<ZeitgeistEvent> event(application_event_, glib::AddRef());
212+ g_ptr_array_add(event_template, event);
213+
214+ subjects_pools_[mtype] = pool_size;
215+
216+ struct FindEventsData
217+ {
218+ Application* self;
219+ Application::MonitorType type;
220+ };
221+
222+ auto data = new FindEventsData();
223+ data->self = this;
224+ data->type = mtype;
225+
226+ zeitgeist_log_find_events(log_, time_range, event_template, ZEITGEIST_STORAGE_STATE_AVAILABLE,
227+ pool_size, GetResultTypeByMonitorType(mtype), nullptr,
228+ [] (GObject* object, GAsyncResult* res, gpointer data)
229+ {
230+ glib::Error error;
231+ glib::Object<ZeitgeistResultSet> results;
232+ auto find_data = static_cast<FindEventsData*>(data);
233+ auto log = reinterpret_cast<ZeitgeistLog*>(object);
234+
235+ results = zeitgeist_log_find_events_finish(log, res, &error);
236+
237+ if (!error)
238+ find_data->self->HandleResultSet(results, find_data->type);
239+
240+ delete find_data;
241+ },
242+ data);
243+
244+ GPtrArray* monitor_template = g_ptr_array_sized_new(1);
245+ g_ptr_array_add(monitor_template, event);
246+
247+ glib::Object<ZeitgeistTimeRange> monitor_time_range(zeitgeist_time_range_new_from_now(), glib::AddRef());
248+ glib::Object<ZeitgeistMonitor> monitor(zeitgeist_monitor_new(monitor_time_range, monitor_template), glib::AddRef());
249+ g_object_set_data(glib::object_cast<GObject>(monitor), "monitor-type", GUINT_TO_POINTER(mtype));
250+
251+ monitors_[mtype] = monitor;
252+ zeitgeist_log_install_monitor(log_, monitor);
253+
254+ typedef glib::Signal<void, ZeitgeistMonitor*, ZeitgeistTimeRange*, ZeitgeistResultSet*> EventsAddedSignal;
255+
256+ signal_manager_.Add(new EventsAddedSignal(monitor, "events-inserted",
257+ [&] (ZeitgeistMonitor* zmon, ZeitgeistTimeRange* tm, ZeitgeistResultSet* events)
258+ {
259+ glib::Object<ZeitgeistTimeRange> time(tm);
260+ glib::Object<ZeitgeistResultSet> results(events);
261+ gpointer data = g_object_get_data(reinterpret_cast<GObject*>(zmon), "monitor-type");
262+ HandleResultSet(results, static_cast<MonitorType>(GPOINTER_TO_UINT(data)));
263+ }));
264+
265+ typedef glib::Signal<void, ZeitgeistMonitor*, ZeitgeistTimeRange*, GArray*> EventsDeletedSignal;
266+
267+ signal_manager_.Add(new EventsDeletedSignal(monitor, "events-deleted",
268+ [&] (ZeitgeistMonitor* zmon, ZeitgeistTimeRange* tm, GArray* events)
269+ {
270+ glib::Object<ZeitgeistTimeRange> time(tm);
271+ gpointer data = g_object_get_data(reinterpret_cast<GObject*>(zmon), "monitor-type");
272+ MonitorType mon_type = static_cast<MonitorType>(GPOINTER_TO_UINT(data));
273+
274+ Subjects& subjects = subjects_[mon_type];
275+ std::vector<Subjects::iterator> to_rm;
276+
277+ for (unsigned int i = 0; events && i < events->len; ++i)
278+ {
279+ guint32 event_id = g_array_index(events, guint32, i);
280+
281+ for (auto it = subjects.begin(); it != subjects.end(); ++it)
282+ {
283+ if ((*it)->EventId() == event_id)
284+ {
285+ subject_removed.emit(mon_type, *it);
286+ to_rm.push_back(it);
287+ }
288+ }
289+ }
290+
291+ for (auto subject_it : to_rm)
292+ subjects.erase(subject_it);
293+
294+ g_array_unref(events);
295+ }));
296+}
297+
298+void Application::RemoveMonitor(MonitorType mtype)
299+{
300+ if (monitors_.find(mtype) == monitors_.end())
301+ return;
302+
303+ glib::Object<ZeitgeistMonitor> &monitor = monitors_[mtype];
304+ signal_manager_.Disconnect(monitor.RawPtr(), "events-inserted");
305+ signal_manager_.Disconnect(monitor.RawPtr(), "events-deleted");
306+
307+ for (auto subject : subjects_[mtype])
308+ subject_removed.emit(mtype, subject);
309+
310+ subjects_.erase(mtype);
311+ monitors_.erase(mtype);
312+}
313+
314+}
315+}
316
317=== added file 'UnityCore/ZeitgeistApplication.h'
318--- UnityCore/ZeitgeistApplication.h 1970-01-01 00:00:00 +0000
319+++ UnityCore/ZeitgeistApplication.h 2012-02-28 17:40:24 +0000
320@@ -0,0 +1,86 @@
321+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
322+/*
323+ * Copyright (C) 2012 Canonical Ltd
324+ *
325+ * This program is free software: you can redistribute it and/or modify
326+ * it under the terms of the GNU General Public License version 3 as
327+ * published by the Free Software Foundation.
328+ *
329+ * This program is distributed in the hope that it will be useful,
330+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
331+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
332+ * GNU General Public License for more details.
333+ *
334+ * You should have received a copy of the GNU General Public License
335+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
336+ *
337+ * Authored by: Marco Trevisan (Treviño) <3v1n0@ubuntu.com>
338+ */
339+
340+#ifndef UNITY_ZEITGEIST_APPLICATION_H
341+#define UNITY_ZEITGEIST_APPLICATION_H
342+
343+#include <zeitgeist.h>
344+
345+#include <sigc++/signal.h>
346+#include <deque>
347+#include <map>
348+
349+#include "ZeitgeistSubject.h"
350+#include "GLibSignal.h"
351+
352+namespace unity
353+{
354+namespace zeitgeist
355+{
356+
357+class Application : public sigc::trackable, boost::noncopyable
358+{
359+public:
360+ typedef boost::shared_ptr<Application> Ptr;
361+ typedef std::deque<Subject::Ptr> Subjects;
362+
363+ Application(std::string const& desktop_file);
364+ ~Application();
365+
366+ enum MonitorType
367+ {
368+ MOST_RECENT_EVENTS = 1,
369+ LEAST_RECENT_EVENTS,
370+ MOST_RECENT_SUBJECTS,
371+ LEAST_RECENT_SUBJECTS,
372+ MOST_POPULAR_SUBJECTS,
373+ LEAST_POPULAR_SUBJECTS,
374+ RELEVANCY
375+ };
376+
377+ std::string const& Uri() const;
378+
379+ void AddMonitor(MonitorType type, unsigned int pool_size = 10);
380+ void RemoveMonitor(MonitorType type);
381+
382+ Subjects GetSubjectsForMonitor(MonitorType type) const;
383+
384+ // Signals
385+ sigc::signal<void, MonitorType, Subject::Ptr const&> subject_added;
386+ sigc::signal<void, MonitorType, Subject::Ptr const&> subject_removed;
387+
388+private:
389+ ZeitgeistResultType GetResultTypeByMonitorType(MonitorType mtype);
390+ void HandleResultSet(glib::Object<ZeitgeistResultSet> const& results, MonitorType mtype);
391+
392+ glib::Object<ZeitgeistLog> log_;
393+ glib::Object<ZeitgeistEvent> application_event_;
394+ std::string application_uri_;
395+ glib::SignalManager signal_manager_;
396+
397+ std::map<MonitorType, glib::Object<ZeitgeistMonitor>> monitors_;
398+ std::map<MonitorType, Subjects> subjects_;
399+ std::map<MonitorType, unsigned int> subjects_pools_;
400+};
401+
402+
403+}
404+}
405+
406+#endif // UNITY_ZEITGEIST_APPLICATION_H
407
408=== added file 'UnityCore/ZeitgeistSubject.cpp'
409--- UnityCore/ZeitgeistSubject.cpp 1970-01-01 00:00:00 +0000
410+++ UnityCore/ZeitgeistSubject.cpp 2012-02-28 17:40:24 +0000
411@@ -0,0 +1,91 @@
412+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
413+/*
414+ * Copyright (C) 2012 Canonical Ltd
415+ *
416+ * This program is free software: you can redistribute it and/or modify
417+ * it under the terms of the GNU General Public License version 3 as
418+ * published by the Free Software Foundation.
419+ *
420+ * This program is distributed in the hope that it will be useful,
421+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
422+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
423+ * GNU General Public License for more details.
424+ *
425+ * You should have received a copy of the GNU General Public License
426+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
427+ *
428+ * Authored by: Marco Trevisan (Treviño) <3v1n0@ubuntu.com>
429+ */
430+
431+#include "ZeitgeistSubject.h"
432+
433+namespace unity
434+{
435+namespace zeitgeist
436+{
437+
438+Subject::Subject(glib::Object<ZeitgeistSubject> const& subject, unsigned int id, long long timestamp)
439+ : subject_(subject, glib::AddRef()),
440+ event_id_(id),
441+ event_timestamp_(timestamp)
442+{}
443+
444+unsigned int Subject::EventId() const
445+{
446+ return event_id_;
447+}
448+
449+long long Subject::EventTimestamp() const
450+{
451+ return event_timestamp_;
452+}
453+
454+std::string Subject::GetConstString(const gchar* str)
455+{
456+ return (str) ? str : "";
457+}
458+
459+std::string Subject::Interpretation() const
460+{
461+ const gchar* str = zeitgeist_subject_get_interpretation(subject_);
462+ return GetConstString(str);
463+}
464+
465+std::string Subject::Manifestation() const
466+{
467+ const gchar* str = zeitgeist_subject_get_manifestation(subject_);
468+ return GetConstString(str);
469+}
470+
471+std::string Subject::MimeType() const
472+{
473+ const gchar* str = zeitgeist_subject_get_mimetype(subject_);
474+ return GetConstString(str);
475+}
476+
477+std::string Subject::Origin() const
478+{
479+ const gchar* str = zeitgeist_subject_get_origin(subject_);
480+ return GetConstString(str);
481+}
482+
483+std::string Subject::Storage() const
484+{
485+ const gchar* str = zeitgeist_subject_get_storage(subject_);
486+ return GetConstString(str);
487+}
488+
489+std::string Subject::Text() const
490+{
491+ const gchar* str = zeitgeist_subject_get_text(subject_);
492+ return GetConstString(str);
493+}
494+
495+std::string Subject::Uri() const
496+{
497+ const gchar* str = zeitgeist_subject_get_uri(subject_);
498+ return GetConstString(str);
499+}
500+
501+} // namespace zeitgeist
502+} // namespace unity
503
504=== added file 'UnityCore/ZeitgeistSubject.h'
505--- UnityCore/ZeitgeistSubject.h 1970-01-01 00:00:00 +0000
506+++ UnityCore/ZeitgeistSubject.h 2012-02-28 17:40:24 +0000
507@@ -0,0 +1,64 @@
508+// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
509+/*
510+ * Copyright (C) 2012 Canonical Ltd
511+ *
512+ * This program is free software: you can redistribute it and/or modify
513+ * it under the terms of the GNU General Public License version 3 as
514+ * published by the Free Software Foundation.
515+ *
516+ * This program is distributed in the hope that it will be useful,
517+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
518+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
519+ * GNU General Public License for more details.
520+ *
521+ * You should have received a copy of the GNU General Public License
522+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
523+ *
524+ * Authored by: Marco Trevisan (Treviño) <3v1n0@ubuntu.com>
525+ */
526+
527+#ifndef UNITY_ZEITGEIST_SUBJECT_H
528+#define UNITY_ZEITGEIST_SUBJECT_H
529+
530+#include <zeitgeist.h>
531+#include <boost/utility.hpp>
532+#include <boost/shared_ptr.hpp>
533+
534+#include "GLibWrapper.h"
535+
536+namespace unity
537+{
538+namespace zeitgeist
539+{
540+
541+class Subject : public boost::noncopyable
542+{
543+public:
544+ typedef boost::shared_ptr<Subject> Ptr;
545+
546+ unsigned int EventId() const;
547+ long long EventTimestamp() const;
548+
549+ std::string Interpretation() const;
550+ std::string Manifestation() const;
551+ std::string MimeType() const;
552+ std::string Origin() const;
553+ std::string Storage() const;
554+ std::string Text() const;
555+ std::string Uri() const;
556+
557+private:
558+ Subject(glib::Object<ZeitgeistSubject> const& subject, unsigned int id, long long timestamp);
559+ static std::string GetConstString(const gchar* str);
560+
561+ glib::Object<ZeitgeistSubject> subject_;
562+ unsigned int event_id_;
563+ long long event_timestamp_;
564+
565+ friend class Application;
566+};
567+
568+}
569+}
570+
571+#endif // UNITY_ZEITGEIST_SUBJECT_H
572
573=== modified file 'plugins/unityshell/src/BamfLauncherIcon.cpp'
574--- plugins/unityshell/src/BamfLauncherIcon.cpp 2012-02-22 02:17:08 +0000
575+++ plugins/unityshell/src/BamfLauncherIcon.cpp 2012-02-28 17:40:24 +0000
576@@ -70,52 +70,52 @@
577 glib::SignalBase* sig;
578
579 sig = new glib::Signal<void, BamfView*, BamfView*>(bamf_view, "child-added",
580- [&] (BamfView*, BamfView*) {
581- EnsureWindowState();
582- UpdateMenus();
583- UpdateIconGeometries(GetCenters());
584- });
585+ [&] (BamfView*, BamfView*) {
586+ EnsureWindowState();
587+ UpdateMenus();
588+ UpdateIconGeometries(GetCenters());
589+ });
590 _gsignals.Add(sig);
591
592 sig = new glib::Signal<void, BamfView*, BamfView*>(bamf_view, "child-removed",
593 [&] (BamfView*, BamfView*) { EnsureWindowState(); });
594 _gsignals.Add(sig);
595
596- sig = new glib::Signal<void, BamfView*, gboolean>(bamf_view, "urgent-changed",
597- [&] (BamfView*, gboolean urgent) {
598- SetQuirk(QUIRK_URGENT, urgent);
599- });
600- _gsignals.Add(sig);
601-
602- sig = new glib::Signal<void, BamfView*, gboolean>(bamf_view, "active-changed",
603- [&] (BamfView*, gboolean active) {
604- SetQuirk(QUIRK_ACTIVE, active);
605- });
606- _gsignals.Add(sig);
607-
608- sig = new glib::Signal<void, BamfView*, gboolean>(bamf_view, "running-changed",
609- [&] (BamfView*, gboolean running) {
610- SetQuirk(QUIRK_RUNNING, running);
611- if (running)
612- {
613- EnsureWindowState();
614- UpdateIconGeometries(GetCenters());
615- }
616- });
617- _gsignals.Add(sig);
618-
619- sig = new glib::Signal<void, BamfView*, gboolean>(bamf_view, "user-visible-changed",
620- [&] (BamfView*, gboolean visible) {
621- if (!IsSticky())
622- SetQuirk(QUIRK_VISIBLE, visible);
623- });
624+ sig = new BamfPropertyChangedSignal(bamf_view, "urgent-changed",
625+ [&] (BamfView*, gboolean urgent) {
626+ SetQuirk(QUIRK_URGENT, urgent);
627+ });
628+ _gsignals.Add(sig);
629+
630+ sig = new BamfPropertyChangedSignal(bamf_view, "active-changed",
631+ [&] (BamfView*, gboolean active) {
632+ SetQuirk(QUIRK_ACTIVE, active);
633+ });
634+ _gsignals.Add(sig);
635+
636+ sig = new BamfPropertyChangedSignal(bamf_view, "running-changed",
637+ [&] (BamfView*, gboolean running) {
638+ SetQuirk(QUIRK_RUNNING, running);
639+ if (running)
640+ {
641+ EnsureWindowState();
642+ UpdateIconGeometries(GetCenters());
643+ }
644+ });
645+ _gsignals.Add(sig);
646+
647+ sig = new BamfPropertyChangedSignal(bamf_view, "user-visible-changed",
648+ [&] (BamfView*, gboolean visible) {
649+ if (!IsSticky())
650+ SetQuirk(QUIRK_VISIBLE, visible);
651+ });
652 _gsignals.Add(sig);
653
654 sig = new glib::Signal<void, BamfView*>(bamf_view, "closed",
655- [&] (BamfView*) {
656- if (!IsSticky())
657- Remove();
658- });
659+ [&] (BamfView*) {
660+ if (!IsSticky())
661+ Remove();
662+ });
663 _gsignals.Add(sig);
664
665 WindowManager::Default()->window_minimized.connect(sigc::mem_fun(this, &BamfLauncherIcon::OnWindowMinimized));
666@@ -126,6 +126,7 @@
667 EnsureWindowState();
668 UpdateMenus();
669 UpdateDesktopFile();
670+ SetupZeitgeistMonitor();
671
672 // hack
673 SetProgress(0.0f);
674@@ -416,6 +417,7 @@
675 break;
676 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
677 UpdateDesktopQuickList();
678+ SetupZeitgeistMonitor();
679 break;
680 default:
681 break;
682@@ -707,12 +709,12 @@
683 dbusmenu_menuitem_property_set_bool(item, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE);
684 dbusmenu_menuitem_property_set(item, "shortcut-nick", nicks[index]);
685
686- auto sig = new glib::Signal<void, DbusmenuMenuitem*, gint>(item, "item-activated",
687- [&] (DbusmenuMenuitem* item, gint) {
688- const gchar *nick;
689- nick = dbusmenu_menuitem_property_get(item, "shortcut-nick");
690- indicator_desktop_shortcuts_nick_exec(_desktop_shortcuts, nick);
691- });
692+ auto sig = new MenuitemActivateSignal(item, "item-activated",
693+ [&] (DbusmenuMenuitem* item, gint) {
694+ const gchar *nick;
695+ nick = dbusmenu_menuitem_property_get(item, "shortcut-nick");
696+ indicator_desktop_shortcuts_nick_exec(_desktop_shortcuts, nick);
697+ });
698 _gsignals.Add(sig);
699
700 dbusmenu_menuitem_child_append(_menu_desktop_shortcuts, item);
701@@ -837,10 +839,10 @@
702 dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_ENABLED, true);
703 dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_VISIBLE, true);
704
705- _gsignals.Add(new glib::Signal<void, DbusmenuMenuitem*, int>(menu_item, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
706- [&] (DbusmenuMenuitem*, int) {
707- ToggleSticky();
708- }));
709+ _gsignals.Add(new MenuitemActivateSignal(menu_item, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
710+ [&] (DbusmenuMenuitem*, int) {
711+ ToggleSticky();
712+ }));
713
714 _menu_items["Pin"] = glib::Object<DbusmenuMenuitem>(menu_item);
715 }
716@@ -858,10 +860,10 @@
717 dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_ENABLED, true);
718 dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_VISIBLE, true);
719
720- _gsignals.Add(new glib::Signal<void, DbusmenuMenuitem*, int>(menu_item, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
721- [&] (DbusmenuMenuitem*, int) {
722- Quit();
723- }));
724+ _gsignals.Add(new MenuitemActivateSignal(menu_item, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
725+ [&] (DbusmenuMenuitem*, int) {
726+ Quit();
727+ }));
728
729 _menu_items["Quit"] = glib::Object<DbusmenuMenuitem>(menu_item);
730 }
731@@ -901,6 +903,7 @@
732 }
733 }
734 }
735+
736
737 // FIXME: this should totally be added as a _menu_client
738 if (DBUSMENU_IS_MENUITEM(_menu_desktop_shortcuts.RawPtr()))
739@@ -960,10 +963,10 @@
740 "unity-use-markup",
741 true);
742
743- _gsignals.Add(new glib::Signal<void, DbusmenuMenuitem*, int>(item, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
744- [&] (DbusmenuMenuitem*, int) {
745- ActivateLauncherIcon(ActionArg(ActionArg::OTHER, 0));
746- }));
747+ _gsignals.Add(new MenuitemActivateSignal(item, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
748+ [&] (DbusmenuMenuitem*, int) {
749+ ActivateLauncherIcon(ActionArg(ActionArg::OTHER, 0));
750+ }));
751
752 _menu_items_extra["AppName"] = glib::Object<DbusmenuMenuitem>(item);
753 }
754@@ -984,6 +987,29 @@
755 }
756 result.push_back(item);
757
758+ for (auto it = _menu_items_zeitgeist.begin(); it != _menu_items_zeitgeist.end(); ++it)
759+ {
760+ result.push_back(it->second.RawPtr());
761+ }
762+
763+ if (_menu_items_zeitgeist.size() > 0)
764+ {
765+ auto third_separator = _menu_items_extra.find("ThirdSeparator");
766+ if (third_separator != _menu_items_extra.end())
767+ {
768+ item = third_separator->second;
769+ }
770+ else
771+ {
772+ item = dbusmenu_menuitem_new();
773+ dbusmenu_menuitem_property_set(item,
774+ DBUSMENU_MENUITEM_PROP_TYPE,
775+ DBUSMENU_CLIENT_TYPES_SEPARATOR);
776+ _menu_items_extra["ThirdSeparator"] = glib::Object<DbusmenuMenuitem>(item);
777+ }
778+ result.push_back(item);
779+ }
780+
781 EnsureMenuItemsReady();
782
783 for (auto it_m = _menu_items.begin(); it_m != _menu_items.end(); ++it_m)
784@@ -1231,6 +1257,99 @@
785 }
786 }
787
788+void BamfLauncherIcon::SetupZeitgeistMonitor()
789+{
790+ const std::string keyname = "X-Ayatana-Show-Recent-Files";
791+ bool enabled = true;
792+
793+ if (_desktop_file.empty())
794+ {
795+ enabled = false;
796+ }
797+ else
798+ {
799+ glib::Error error;
800+ GKeyFile* keyfile = g_key_file_new();
801+
802+ g_key_file_load_from_file(keyfile, _desktop_file.c_str(), G_KEY_FILE_NONE, &error);
803+
804+ if (error)
805+ {
806+ enabled = false;
807+ g_key_file_free(keyfile);
808+ }
809+ else if (g_key_file_has_key(keyfile, G_KEY_FILE_DESKTOP_GROUP, keyname.c_str(), nullptr))
810+ {
811+ enabled = g_key_file_get_boolean(keyfile, G_KEY_FILE_DESKTOP_GROUP, keyname.c_str(), nullptr);
812+ }
813+
814+ g_key_file_free(keyfile);
815+ }
816+
817+ if (!enabled && _zeitgeist_app.get())
818+ {
819+ _zeitgeist_app->RemoveMonitor(zeitgeist::Application::MOST_RECENT_SUBJECTS);
820+ _zeitgeist_app.reset();
821+ }
822+ else if (enabled && !_zeitgeist_app.get())
823+ {
824+ _zeitgeist_app.reset(new zeitgeist::Application(_desktop_file));
825+ _zeitgeist_app->subject_added.connect(sigc::mem_fun(this, &BamfLauncherIcon::OnZeitgeistSubjectAdded));
826+ _zeitgeist_app->subject_removed.connect(sigc::mem_fun(this, &BamfLauncherIcon::OnZeitgeistSubjectRemoved));
827+ _zeitgeist_app->AddMonitor(zeitgeist::Application::MOST_RECENT_SUBJECTS, 5);
828+ }
829+}
830+
831+void BamfLauncherIcon::OnZeitgeistSubjectAdded(zeitgeist::Application::MonitorType mtype,
832+ zeitgeist::Subject::Ptr const& subject)
833+{
834+ if (mtype != zeitgeist::Application::MOST_RECENT_SUBJECTS)
835+ return;
836+
837+ glib::Object<DbusmenuMenuitem> menu_item(dbusmenu_menuitem_new());
838+ dbusmenu_menuitem_property_set(menu_item, DBUSMENU_MENUITEM_PROP_LABEL, subject->Text().c_str());
839+ dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_ENABLED, true);
840+ dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_VISIBLE, true);
841+
842+ _gsignals.Add(new MenuitemActivateSignal(menu_item, DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
843+ [&] (DbusmenuMenuitem* item, int)
844+ {
845+ for (auto zmenuitem : _menu_items_zeitgeist)
846+ {
847+ if (zmenuitem.second == item)
848+ {
849+ std::string const& uri = zmenuitem.first->Uri();
850+ if (!uri.empty())
851+ {
852+ std::set<std::string> uris;
853+ uris.insert(uri);
854+ OpenInstanceWithUris(uris);
855+ }
856+ break;
857+ }
858+ }
859+ }));
860+
861+ _menu_items_zeitgeist.push_front(ZeitgeistMenuitem(subject, menu_item));
862+}
863+
864+void BamfLauncherIcon::OnZeitgeistSubjectRemoved(zeitgeist::Application::MonitorType mtype,
865+ zeitgeist::Subject::Ptr const& subject)
866+{
867+ if (mtype != zeitgeist::Application::MOST_RECENT_SUBJECTS)
868+ return;
869+
870+ for (auto it = _menu_items_zeitgeist.begin(); it != _menu_items_zeitgeist.end(); ++it)
871+ {
872+ if (it->first == subject)
873+ {
874+ _gsignals.Disconnect(it->second.RawPtr(), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED);
875+ _menu_items_zeitgeist.erase(it);
876+ break;
877+ }
878+ }
879+}
880+
881 std::string BamfLauncherIcon::GetName() const
882 {
883 return "BamfLauncherIcon";
884
885=== modified file 'plugins/unityshell/src/BamfLauncherIcon.h'
886--- plugins/unityshell/src/BamfLauncherIcon.h 2012-02-12 10:43:11 +0000
887+++ plugins/unityshell/src/BamfLauncherIcon.h 2012-02-28 17:40:24 +0000
888@@ -23,6 +23,7 @@
889
890 #include <UnityCore/GLibSignal.h>
891 #include <UnityCore/GLibWrapper.h>
892+#include <UnityCore/ZeitgeistApplication.h>
893
894 #include <libbamf/libbamf.h>
895 #include <libindicator/indicator-desktop-shortcuts.h>
896@@ -93,6 +94,7 @@
897 void UpdateMenus();
898 void UpdateDesktopQuickList();
899 void FillSupportedTypes();
900+ void SetupZeitgeistMonitor();
901
902 void OpenInstanceWithUris(std::set<std::string> uris);
903 void Focus(ActionArg arg);
904@@ -100,11 +102,18 @@
905
906 void OnWindowMinimized(guint32 xid);
907 void OnWindowMoved(guint32 xid);
908+ void OnZeitgeistSubjectAdded(zeitgeist::Application::MonitorType mtype,
909+ zeitgeist::Subject::Ptr const& subject);
910+ void OnZeitgeistSubjectRemoved(zeitgeist::Application::MonitorType mtype,
911+ zeitgeist::Subject::Ptr const& subject);
912
913 bool OwnsWindow(Window w) const;
914
915 const std::set<std::string>& GetSupportedTypes();
916
917+ typedef glib::Signal<void, BamfView*, gboolean> BamfPropertyChangedSignal;
918+ typedef glib::Signal<void, DbusmenuMenuitem*, int> MenuitemActivateSignal;
919+ typedef std::pair<zeitgeist::Subject::Ptr, glib::Object<DbusmenuMenuitem>> ZeitgeistMenuitem;
920
921 glib::Object<BamfApplication> _bamf_app;
922 bool _dnd_hovered;
923@@ -116,10 +125,12 @@
924
925 std::string _remote_uri;
926 std::string _desktop_file;
927+ zeitgeist::Application::Ptr _zeitgeist_app;
928 std::set<std::string> _supported_types;
929 std::map<std::string, glib::Object<DbusmenuClient>> _menu_clients;
930 std::map<std::string, glib::Object<DbusmenuMenuitem>> _menu_items;
931 std::map<std::string, glib::Object<DbusmenuMenuitem>> _menu_items_extra;
932+ std::deque<ZeitgeistMenuitem> _menu_items_zeitgeist;
933 glib::Object<IndicatorDesktopShortcuts> _desktop_shortcuts;
934 glib::Object<DbusmenuMenuitem> _menu_desktop_shortcuts;
935 glib::Object<GFileMonitor> _desktop_file_monitor;