Add search provider for GNOME Shell

GNOME Shell provides a DBus interface for external search provider
implementations; add a small auto-activated service which implements
that interface, to replace the Shell's built-in contact search with
results provided directly by GNOME Contacts.

https://bugzilla.gnome.org/show_bug.cgi?id=679002
This commit is contained in:
Florian Müllner 2012-06-19 23:39:06 +02:00 committed by Alexander Larsson
parent 14c94fca74
commit 19751af1b8
8 changed files with 243 additions and 3 deletions

View file

@ -17,7 +17,7 @@ AC_CONFIG_FILES([Makefile
LT_INIT
AC_PROG_CC
AM_PROG_VALAC([0.14.0])
AM_PROG_VALAC([0.17.2])
AC_PROG_INSTALL
GLIB_GSETTINGS
@ -48,7 +48,7 @@ pkg_modules="gtk+-3.0 >= 3.4.0
"
PKG_CHECK_MODULES(CONTACTS, [$pkg_modules])
CONTACTS_PACKAGES="--pkg gtk+-3.0 --pkg gio-2.0 --pkg folks --pkg folks-telepathy --pkg folks-eds --pkg libnotify"
CONTACTS_PACKAGES="--pkg gtk+-3.0 --pkg gio-2.0 --pkg gio-unix-2.0 --pkg folks --pkg folks-telepathy --pkg folks-eds --pkg libnotify"
AC_SUBST(CONTACTS_PACKAGES)
gstreamers_modules="gdk-x11-3.0

View file

@ -3,12 +3,32 @@ NULL=
desktopdir = $(datadir)/applications
desktop_in_files = gnome-contacts.desktop.in
desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
searchproviderdir = $(datadir)/gnome-shell/search-providers
searchprovider_DATA = gnome-contacts-search-provider.ini
searchprovider_in_files = gnome-contacts-search-provider.ini.in
%.ini: %.ini.in
LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
@INTLTOOL_DESKTOP_RULE@
service_in_files = org.gnome.Contacts.SearchProvider.service.in
servicedir = $(datadir)/dbus-1/services
service_DATA = $(service_in_files:.service.in=.service)
%.service: %.service.in Makefile
$(AM_V_GEN) [ -d $(@D) ] || $(mkdir_p) $(@D) ; \
sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@.tmp && mv $@.tmp $@
EXTRA_DIST = \
gnome-contacts.desktop.in.in \
$(searchprovider_DATA) \
$(service_in_files) \
$(NULL)
CLEANFILES = $(service_DATA)
DISTCLEANFILES = \
gnome-contacts.desktop \
gnome-contacts.desktop.in

View file

@ -0,0 +1,6 @@
[Shell Search Provider]
_Title=Gnome Contacts
Icon=x-office-address-book
DesktopId=gnome-contacts.desktop
BusName=org.gnome.Contacts.SearchProvider
ObjectPath=/org/gnome/Contacts/SearchProvider

View file

@ -0,0 +1,3 @@
[D-BUS Service]
Name=org.gnome.Contacts.SearchProvider
Exec=@libexecdir@/gnome-contacts-search-provider

View file

@ -1,3 +1,4 @@
data/gnome-contacts-search-provider.ini.in
data/gnome-contacts.desktop.in.in
[type: gettext/glade]src/app-menu.ui
src/contacts-app.vala

View file

@ -75,6 +75,19 @@ gnome_contacts_SOURCES += \
$(NULL)
endif
libexec_PROGRAMS = gnome-contacts-search-provider
gnome_contacts_search_provider_SOURCES = \
contacts-contact.vala \
contacts-esd-setup.c \
contacts-shell-search-provider.vala \
contacts-store.vala \
contacts-types.vala \
contacts-utils.vala \
$(NULL)
gnome_contacts_search_provider_LDADD = $(CONTACTS_LIBS)
CLEANFILES = \
$(vala_sources:.vala=.c) \
$(gsettings_SCHEMAS) \

View file

@ -142,6 +142,45 @@ public class Contacts.Contact : GLib.Object {
}
}
public Icon? serializable_avatar_icon {
get {
if (individual.avatar != null && individual.avatar.to_string () != null)
return individual.avatar;
return null;
}
}
private Variant? _avatar_icon_data;
public Variant? avatar_icon_data {
get {
if (individual.avatar == null)
return null;
if (individual.avatar.to_string () != null)
return null;
if (_avatar_icon_data == null) {
if (small_avatar == null)
return null;
var pixel_data = Variant.new_from_data (VariantType.BYTESTRING,
small_avatar.get_pixels_with_length (),
true, small_avatar);
_avatar_icon_data = new Variant ("(iiibii@ay)",
small_avatar.get_width (),
small_avatar.get_height (),
small_avatar.get_rowstride (),
small_avatar.get_has_alpha (),
small_avatar.get_bits_per_sample (),
small_avatar.get_n_channels (),
pixel_data);
}
return _avatar_icon_data;
}
}
public string display_name {
get {
unowned string? name = individual.full_name;
@ -436,6 +475,7 @@ public class Contacts.Contact : GLib.Object {
connect_persona (p);
}
_small_avatar = null;
_avatar_icon_data = null;
individual.notify.connect(notify_cb);
queue_changed (true);
}
@ -820,8 +860,10 @@ public class Contacts.Contact : GLib.Object {
}
private void notify_cb (ParamSpec pspec) {
if (pspec.get_name () == "avatar")
if (pspec.get_name () == "avatar") {
_small_avatar = null;
_avatar_icon_data = null;
}
queue_changed (false);
}

View file

@ -0,0 +1,155 @@
using Gee;
[DBus (name = "org.gnome.Shell.SearchProvider")]
public class Contacts.SearchProvider : Object {
SearchProviderApp app;
Store store;
Gee.HashMap<string, Contact> contacts_map;
private uint next_id;
public SearchProvider (SearchProviderApp app) {
this.app = app;
ensure_eds_accounts ();
store = new Store ();
contacts_map = new Gee.HashMap<string, Contact> ();
next_id = 0;
store.changed.connect ( (c) => {
contacts_map.set(c.get_data<string> ("search-id"), c);
});
store.added.connect ( (c) => {
var id = next_id++.to_string ();
c.set_data ("search-id", id);
contacts_map.set(id, c);
});
store.removed.connect ( (c) => {
contacts_map.unset(c.get_data<string> ("search-id"));
});
}
private static int compare_contacts (Contact a, Contact b) {
int a_prio = a.is_main ? 0 : -1;
int b_prio = b.is_main ? 0 : -1;
if (a_prio > b_prio)
return -1;
if (a_prio < b_prio)
return 1;
if (is_set (a.display_name) && is_set (b.display_name))
return a.display_name.collate (b.display_name);
// Sort empty names last
if (is_set (a.display_name))
return -1;
if (is_set (b.display_name))
return 1;
return 0;
}
private string[] do_search (string[] terms) {
app.hold ();
string[] normalized_terms =
Utils.canonicalize_for_search (string.joinv(" ", terms)).split(" ");
var matches = new ArrayList<Contact> ();
foreach (var c in store.get_contacts ()) {
if (c.is_hidden)
continue;
if (c.contains_strings (normalized_terms))
matches.add (c);
}
matches.sort((CompareFunc<Contact>) compare_contacts);
var results = new string[matches.size];
for (int i = 0; i < matches.size; i++)
results[i] = matches[i].get_data ("search-id");
app.release ();
return results;
}
public string[] GetInitialResultSet (string[] terms) {
return do_search (terms);
}
public string[] GetSubsearchResultSet (string[] previous_results,
string[] new_terms) {
return do_search (new_terms);
}
public HashTable<string, Variant>[] GetResultMetas (string[] ids) {
app.hold ();
var results = new ArrayList<HashTable> ();
foreach (var id in ids) {
var contact = contacts_map.get (id);
if (contact == null)
continue;
var meta = new HashTable<string, Variant> (str_hash, str_equal);
meta.insert ("id", new Variant.string (id));
meta.insert ("name", new Variant.string (contact.display_name));
if (contact.serializable_avatar_icon != null)
meta.insert ("gicon", new Variant.string (contact.serializable_avatar_icon.to_string ()));
else if (contact.avatar_icon_data != null)
meta.insert ("icon-data", contact.avatar_icon_data);
else
meta.insert ("gicon", new Variant.string (new ThemedIcon ("avatar-default").to_string ()));
results.add (meta);
}
app.release ();
return results.to_array ();
}
public void ActivateResult (string search_id) {
app.hold ();
var contact = contacts_map.get (search_id);
if (contact == null)
return;
string id = contact.individual.id;
try {
if (!Process.spawn_command_line_async ("gnome-contacts -i " + id))
stderr.printf ("Failed to launch contact with id '%s'\n", id);
} catch (SpawnError e) {
stderr.printf ("Failed to launch contact with id '%s'\n", id);
}
app.release ();
}
}
public class Contacts.SearchProviderApp : GLib.Application {
public SearchProviderApp () {
Object (application_id: "org.gnome.Contacts.SearchProvider",
flags: ApplicationFlags.IS_SERVICE,
inactivity_timeout: 10000);
}
public override bool dbus_register (GLib.DBusConnection connection, string object_path) {
try {
connection.register_object (object_path, new SearchProvider (this));
} catch (IOError error) {
stderr.printf ("Could not register service: %s", error.message);
quit ();
}
return true;
}
public override void startup () {
if (Environment.get_variable ("CONTACTS_SEARCH_PROVIDER_PERSIST") != null)
hold ();
base.startup ();
}
}
int main () {
return new Contacts.SearchProviderApp ().run ();
}