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
|
LT_INIT
|
||||||
AC_PROG_CC
|
AC_PROG_CC
|
||||||
AM_PROG_VALAC([0.14.0])
|
AM_PROG_VALAC([0.17.2])
|
||||||
AC_PROG_INSTALL
|
AC_PROG_INSTALL
|
||||||
|
|
||||||
GLIB_GSETTINGS
|
GLIB_GSETTINGS
|
||||||
|
@ -48,7 +48,7 @@ pkg_modules="gtk+-3.0 >= 3.4.0
|
||||||
"
|
"
|
||||||
PKG_CHECK_MODULES(CONTACTS, [$pkg_modules])
|
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)
|
AC_SUBST(CONTACTS_PACKAGES)
|
||||||
|
|
||||||
gstreamers_modules="gdk-x11-3.0
|
gstreamers_modules="gdk-x11-3.0
|
||||||
|
|
|
@ -3,12 +3,32 @@ NULL=
|
||||||
desktopdir = $(datadir)/applications
|
desktopdir = $(datadir)/applications
|
||||||
desktop_in_files = gnome-contacts.desktop.in
|
desktop_in_files = gnome-contacts.desktop.in
|
||||||
desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
|
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@
|
@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 = \
|
EXTRA_DIST = \
|
||||||
gnome-contacts.desktop.in.in \
|
gnome-contacts.desktop.in.in \
|
||||||
|
$(searchprovider_DATA) \
|
||||||
|
$(service_in_files) \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
CLEANFILES = $(service_DATA)
|
||||||
DISTCLEANFILES = \
|
DISTCLEANFILES = \
|
||||||
gnome-contacts.desktop \
|
gnome-contacts.desktop \
|
||||||
gnome-contacts.desktop.in
|
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
|
data/gnome-contacts.desktop.in.in
|
||||||
[type: gettext/glade]src/app-menu.ui
|
[type: gettext/glade]src/app-menu.ui
|
||||||
src/contacts-app.vala
|
src/contacts-app.vala
|
||||||
|
|
|
@ -75,6 +75,19 @@ gnome_contacts_SOURCES += \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
endif
|
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 = \
|
CLEANFILES = \
|
||||||
$(vala_sources:.vala=.c) \
|
$(vala_sources:.vala=.c) \
|
||||||
$(gsettings_SCHEMAS) \
|
$(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 {
|
public string display_name {
|
||||||
get {
|
get {
|
||||||
unowned string? name = individual.full_name;
|
unowned string? name = individual.full_name;
|
||||||
|
@ -436,6 +475,7 @@ public class Contacts.Contact : GLib.Object {
|
||||||
connect_persona (p);
|
connect_persona (p);
|
||||||
}
|
}
|
||||||
_small_avatar = null;
|
_small_avatar = null;
|
||||||
|
_avatar_icon_data = null;
|
||||||
individual.notify.connect(notify_cb);
|
individual.notify.connect(notify_cb);
|
||||||
queue_changed (true);
|
queue_changed (true);
|
||||||
}
|
}
|
||||||
|
@ -820,8 +860,10 @@ public class Contacts.Contact : GLib.Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notify_cb (ParamSpec pspec) {
|
private void notify_cb (ParamSpec pspec) {
|
||||||
if (pspec.get_name () == "avatar")
|
if (pspec.get_name () == "avatar") {
|
||||||
_small_avatar = null;
|
_small_avatar = null;
|
||||||
|
_avatar_icon_data = null;
|
||||||
|
}
|
||||||
queue_changed (false);
|
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