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:
parent
14c94fca74
commit
19751af1b8
8 changed files with 243 additions and 3 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
6
data/gnome-contacts-search-provider.ini.in
Normal file
6
data/gnome-contacts-search-provider.ini.in
Normal 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
|
3
data/org.gnome.Contacts.SearchProvider.service.in
Normal file
3
data/org.gnome.Contacts.SearchProvider.service.in
Normal file
|
@ -0,0 +1,3 @@
|
|||
[D-BUS Service]
|
||||
Name=org.gnome.Contacts.SearchProvider
|
||||
Exec=@libexecdir@/gnome-contacts-search-provider
|
|
@ -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
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
155
src/contacts-shell-search-provider.vala
Normal file
155
src/contacts-shell-search-provider.vala
Normal 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 ();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue