From 3148d188a02bdd35b80715ac4eb0e5635d9d5d92 Mon Sep 17 00:00:00 2001 From: Mark Finkle Date: Fri, 30 Jul 2010 10:40:26 -0400 Subject: [PATCH] Bug 578691 - Add contact support to form autocomplete (Maemo) [r=vingtetun] --- mobile/Makefile.in | 2 +- mobile/chrome/content/browser-ui.js | 9 +- mobile/chrome/tests/Makefile.in | 1 + mobile/chrome/tests/browser_contacts.js | 54 +++++++ mobile/components/FormAutoComplete.js | 171 ++++++++++++++++++++ mobile/components/Makefile.in | 1 + mobile/components/MobileComponents.manifest | 4 + mobile/makefiles.sh | 1 + mobile/modules/Makefile.in | 53 ++++++ mobile/modules/contacts.jsm | 154 ++++++++++++++++++ mobile/modules/linuxTypes.jsm | 116 +++++++++++++ 11 files changed, 564 insertions(+), 2 deletions(-) create mode 100644 mobile/chrome/tests/browser_contacts.js create mode 100644 mobile/components/FormAutoComplete.js create mode 100644 mobile/modules/Makefile.in create mode 100644 mobile/modules/contacts.jsm create mode 100644 mobile/modules/linuxTypes.jsm diff --git a/mobile/Makefile.in b/mobile/Makefile.in index 4bff2e43904..056d4999887 100644 --- a/mobile/Makefile.in +++ b/mobile/Makefile.in @@ -43,7 +43,7 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -DIRS = chrome locales components themes app +DIRS = chrome locales components modules themes app ifndef LIBXUL_SDK PARALLEL_DIRS += $(DEPTH)/xulrunner/tools/redit diff --git a/mobile/chrome/content/browser-ui.js b/mobile/chrome/content/browser-ui.js index 17df6691e83..2ba2b1f8307 100644 --- a/mobile/chrome/content/browser-ui.js +++ b/mobile/chrome/content/browser-ui.js @@ -49,6 +49,11 @@ XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() { return PlacesUtils; }); +XPCOMUtils.defineLazyGetter(this, "Contacts", function() { + Cu.import("resource:///modules/contacts.jsm"); + return Contacts; +}); + const TOOLBARSTATE_LOADING = 1; const TOOLBARSTATE_LOADED = 2; @@ -400,6 +405,8 @@ var BrowserUI = { FormHelperUI.init(); FindHelperUI.init(); + + Contacts.init(); }, uninit: function() { @@ -1679,7 +1686,7 @@ var FormHelperUI = { let suggestions = []; let autocompleteService = Cc["@mozilla.org/satchel/form-autocomplete;1"].getService(Ci.nsIFormAutoComplete); - let results = autocompleteService.autoCompleteSearch(aElement.name, aElement.value, aElement, null); + let results = autocompleteService.autoCompleteSearch(aElement.name, aElement.value, null, null); if (results.matchCount > 0) { for (let i = 0; i < results.matchCount; i++) { let value = results.getValueAt(i); diff --git a/mobile/chrome/tests/Makefile.in b/mobile/chrome/tests/Makefile.in index d72557bd428..a0eb08356c8 100644 --- a/mobile/chrome/tests/Makefile.in +++ b/mobile/chrome/tests/Makefile.in @@ -61,6 +61,7 @@ _BROWSER_FILES = \ browser_navigation.js \ browser_preferences_basic.js \ browser_sessionstore.js \ + browser_contacts.js \ browser_blank_01.html \ browser_blank_02.html \ browser_select.html \ diff --git a/mobile/chrome/tests/browser_contacts.js b/mobile/chrome/tests/browser_contacts.js new file mode 100644 index 00000000000..b453ceba854 --- /dev/null +++ b/mobile/chrome/tests/browser_contacts.js @@ -0,0 +1,54 @@ + +// pull in the Contacts service +Components.utils.import("resource:///modules/contacts.jsm"); + +function test() { + ok(Contacts, "Contacts class exists"); + for (var fname in tests) { + tests[fname](); + } +} + +let MockContactsProvider = { + getContacts: function() { + let contacts = [ + { + fullName: "-Billy One", + emails: [], + phoneNumbers: ["999-888-7777"] + }, + { + fullName: "-Billy Two", + emails: ["billy.two@fake.com", "btwo@work.com"], + phoneNumbers: ["111-222-3333", "123-123-1234"] + }, + { + fullName: "-Joe Schmo", + emails: ["joeschmo@foo.com"], + phoneNumbers: ["555-555-5555"] + } + ]; + + return contacts; + } +}; + +Contacts.addProvider(MockContactsProvider); + +let fac = Cc["@mozilla.org/satchel/form-autocomplete;1"].getService(Ci.nsIFormAutoComplete); + +let tests = { + testBasicMatch: function() { + let results = fac.autoCompleteSearch("email", "-Billy", null, null); + ok(results.matchCount == 2, "Found 2 emails '-Billy'"); + + results = fac.autoCompleteSearch("tel", "-Billy", null, null); + ok(results.matchCount == 3, "Found 3 phone numbers '-Billy'"); + + results = fac.autoCompleteSearch("skip", "-Billy", null, null); + ok(results.matchCount == 0, "Found nothing for a non-contact field"); + + results = fac.autoCompleteSearch("phone", "-Jo", null, null); + ok(results.matchCount == 1, "Found 1 phone number '-Jo'"); + } +}; diff --git a/mobile/components/FormAutoComplete.js b/mobile/components/FormAutoComplete.js new file mode 100644 index 00000000000..cae66589658 --- /dev/null +++ b/mobile/components/FormAutoComplete.js @@ -0,0 +1,171 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Form Autocomplete Plus. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle + * Dan Mills + * Justin Dolske + * Michael Hanson + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +function LOG() { + return; // comment out for verbose debugging + let msg = Array.join(arguments, " "); + dump(msg + "\n"); + Cu.reportError(msg); +} + +// Lazily get the base Form AutoComplete Search +XPCOMUtils.defineLazyGetter(this, "FAC", function() { + return Components.classesByID["{c11c21b2-71c9-4f87-a0f8-5e13f50495fd}"] + .getService(Ci.nsIFormAutoComplete); +}); + +XPCOMUtils.defineLazyGetter(this, "Contacts", function() { + Cu.import("resource:///modules/contacts.jsm"); + return Contacts; +}); + +function FormAutoComplete() { + LOG("new FAC"); +} + +FormAutoComplete.prototype = { + classDescription: "Form AutoComplete Plus", + classID: Components.ID("{cccd414c-3ec2-4cc5-9dc4-36c87cc3c4fe}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormAutoComplete]), + + // Specify the html5 types that we want and some values to guess + contactTypes: { + email: /^(?:.*(?:e-?mail|recipients?).*|(send_)?to(_b?cc)?)$/i, + tel: /^(?:tel(?:ephone)?|.*phone.*)$/i + }, + + checkQueryType: function checkQueryType(aName, aField) { + // If we have an input field with the desired html5 type, take it! + if (aField != null) { + let type = aField.getAttribute("type"); + if (this.contactTypes[type] != null) + return type; + } + + // Grab attributes to check for contact inputs + let props = [aName]; + if (aField != null) { + let attributes = ["class", "id", "rel"]; + attributes.forEach(function(attr) { + if (aField.hasAttribute(attr)) + props.push(aField.getAttribute(attr)); + }); + } + + // Check the gathered properties for contact-like values + for (let [type, regex] in Iterator(this.contactTypes)) { + if (props.some(function(prop) prop.search(regex) != -1)) + return type; + } + return null; + }, + + findContact: function findContact(aQuery, aType, aResult, aDupCheck) { + // Match the name and show the email for now.. + Contacts.find({ fullName: aQuery }).forEach(function(contact) { + // Might not have an email for some reason... ? + try { + LOG("findContact", "Contact " + contact.fullName); + + let suggestions; + switch (aType) { + case "email": + suggestions = contact.emails; + break; + case "tel": + suggestions = contact.phoneNumbers; + break; + default: + LOG("unknown type!", aType); + return; + } + + for each (let suggestion in suggestions) { + if (aDupCheck[suggestion]) + continue; + aDupCheck[suggestion] = true; + + let data = contact.fullName + " <" + suggestion + ">"; + aResult.appendMatch(suggestion, data, null, "contact"); + } + } + catch(ex) { + LOG("findContact error", ex); + } + }); + }, + + autoCompleteSearch: function autoCompleteSearch(aName, aQuery, aField, aPrev) { + LOG("autocomplete search", Array.slice(arguments)); + + let result = Cc["@mozilla.org/autocomplete/simple-result;1"].createInstance(Ci.nsIAutoCompleteSimpleResult); + result.setSearchString(aQuery); + + // Don't allow duplicates get merged into the final results + let dupCheck = {}; + + // Use the base form autocomplete for non-contact searches + let normal = FAC.autoCompleteSearch(aName, aQuery, aField, aPrev); + if (normal.matchCount > 0) { + for (let i = 0; i < normal.matchCount; i++) { + dupCheck[normal.getValueAt(i)] = true; + result.appendMatch(normal.getValueAt(i), normal.getCommentAt(i), normal.getImageAt(i), normal.getStyleAt(i)); + } + } + + // Do searches for certain input fields + let type = this.checkQueryType(aName, aField); + if (type != null) + this.findContact(aQuery, type, result, dupCheck); + + let resultCode = result.matchCount ? "RESULT_SUCCESS" : "RESULT_NOMATCH"; + result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]); + return result; + } +}; + +let components = [FormAutoComplete]; +const NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mobile/components/Makefile.in b/mobile/components/Makefile.in index 412ff7a3396..e33f607c9b7 100644 --- a/mobile/components/Makefile.in +++ b/mobile/components/Makefile.in @@ -70,6 +70,7 @@ EXTRA_COMPONENTS = \ ContentDispatchChooser.js \ AutoCompleteCache.js \ AddonUpdateService.js \ + FormAutoComplete.js \ $(NULL) DIRS = phone \ diff --git a/mobile/components/MobileComponents.manifest b/mobile/components/MobileComponents.manifest index b2aed3b898a..4e3fa7a7ffe 100644 --- a/mobile/components/MobileComponents.manifest +++ b/mobile/components/MobileComponents.manifest @@ -82,3 +82,7 @@ category bookmark-observers BrowserBookmarkObserver @mozilla.org/browser/autocom component {93c8824c-9b87-45ae-bc90-5b82a1e4d877} AddonUpdateService.js contract @mozilla.org/browser/addon-update-service;1 {93c8824c-9b87-45ae-bc90-5b82a1e4d877} category update-timer AddonUpdateService @mozilla.org/browser/addon-update-service;1,getService,auto-addon-background-update-timer,extensions.autoupdate.interval,86400 + +# FormAutoComplete.js +component {cccd414c-3ec2-4cc5-9dc4-36c87cc3c4fe} FormAutoComplete.js +contract @mozilla.org/satchel/form-autocomplete;1 {cccd414c-3ec2-4cc5-9dc4-36c87cc3c4fe} diff --git a/mobile/makefiles.sh b/mobile/makefiles.sh index 97ea2d74a66..11c8a353d38 100644 --- a/mobile/makefiles.sh +++ b/mobile/makefiles.sh @@ -47,6 +47,7 @@ mobile/chrome/Makefile mobile/chrome/tests/Makefile mobile/components/Makefile mobile/components/phone/Makefile +mobile/modules/Makefile mobile/installer/Makefile mobile/locales/Makefile mobile/Makefile diff --git a/mobile/modules/Makefile.in b/mobile/modules/Makefile.in new file mode 100644 index 00000000000..04d7eb9f352 --- /dev/null +++ b/mobile/modules/Makefile.in @@ -0,0 +1,53 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Mozilla Mobile Browser. +# +# The Initial Developer of the Original Code is +# the Mozilla Foundation . +# Portions created by the Initial Developer are Copyright (C) 2010 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Finkle +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +EXTRA_JS_MODULES = \ + linuxTypes.jsm \ + $(NULL) + +EXTRA_PP_JS_MODULES = \ + contacts.jsm \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/mobile/modules/contacts.jsm b/mobile/modules/contacts.jsm new file mode 100644 index 00000000000..5656dc83db9 --- /dev/null +++ b/mobile/modules/contacts.jsm @@ -0,0 +1,154 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Mobile Browser. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +let EXPORTED_SYMBOLS = ["Contacts"]; + +const Cu = Components.utils; + +Cu.import("resource://gre/modules/ctypes.jsm"); + +let Contacts = { + _providers: [], + _contacts: [], + + _load: function _load() { + this._contacts = []; + + this._providers.forEach(function(provider) { + this._contacts = this._contacts.concat(provider.getContacts()); + }, this) + }, + + init: function init() { + // Not much to do for now + this._load(); + }, + + refresh: function refresh() { + // Pretty simple for now + this._load(); + }, + + addProvider: function(aProvider) { + this._providers.push(aProvider); + this.refresh(); + }, + + find: function find(aMatch) { + let results = []; + + if (!this._contacts.length) + return results; + + for (let field in aMatch) { + // Simple string-only partial matching + let match = aMatch[field]; + this._contacts.forEach(function(aContact) { + if (field in aContact && aContact[field].indexOf(match) != -1) + results.push(aContact); + }); + } + return results; + } +}; + +#ifdef XP_UNIX +Cu.import("resource:///modules/linuxTypes.jsm"); + +function EBookProvider() { + EBook.init(); +} + +EBookProvider.prototype = { + getContacts: function() { + if (!EBook.lib) { + Cu.reportError("EBook not loaded") + return []; + } + + let gError = new GLib.GError.ptr; + let book = EBook.openSystem(gError.address()); + if (!book) { + Cu.reportError("EBook.openSystem: " + gError.contents.message.readString()) + return []; + } + + if (!EBook.openBook(book, false, gError.address())) { + Cu.reportError("EBook.openBook: " + gError.contents.message.readString()) + return []; + } + + let query = EBook.queryAnyFieldContains(""); + if (query) { + let gList = new GLib.GList.ptr(); + if (!EBook.getContacts(book, query, gList.address(), gError.address())) { + Cu.reportError("EBook.getContacts: " + gError.contents.message.readString()) + return []; + } + + let contacts = []; + while (gList && !gList.isNull()) { + let fullName = EBook.getContactField(gList.contents.data, EBook.E_CONTACT_FULL_NAME); + if (!fullName.isNull()) { + let contact = {}; + contact.fullName = fullName.readString(); + contact.emails = []; + contact.phoneNumbers = []; + + for (let emailIndex=EBook.E_CONTACT_EMAIL_FIRST; emailIndex<=EBook.E_CONTACT_EMAIL_LAST; emailIndex++) { + let email = EBook.getContactField(gList.contents.data, emailIndex); + if (!email.isNull()) + contact.emails.push(email.readString()); + } + + for (let phoneIndex=EBook.E_CONTACT_PHONE_FIRST; phoneIndex<=EBook.E_CONTACT_PHONE_LAST; phoneIndex++) { + let phone = EBook.getContactField(gList.contents.data, phoneIndex); + if (!phone.isNull()) + contact.phoneNumbers.push(phone.readString()); + } + + contacts.push(contact); + } + gList = ctypes.cast(gList.contents.next, GLib.GList.ptr); + } + return contacts; + } + return []; + } +}; + +Contacts.addProvider(new EBookProvider); +#endif diff --git a/mobile/modules/linuxTypes.jsm b/mobile/modules/linuxTypes.jsm new file mode 100644 index 00000000000..288670adeec --- /dev/null +++ b/mobile/modules/linuxTypes.jsm @@ -0,0 +1,116 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Mobile Browser. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Finkle + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +let EXPORTED_SYMBOLS = ["GLib", "EBook"]; + +const Cu = Components.utils; + +Cu.import("resource://gre/modules/ctypes.jsm"); + +let GLib = { + lib: null, + + init: function glib_init() { + if (this.lib) + return; + + this.lib = true; // TODO hook up the real glib + + this.GError = new ctypes.StructType("GError", [ + {"domain": ctypes.int32_t}, + {"code": ctypes.int32_t}, + {"message": ctypes.char.ptr} + ]); + + this.GList = new ctypes.StructType("GList", [ + {"data": ctypes.voidptr_t}, + {"next": ctypes.voidptr_t}, + {"prev": ctypes.voidptr_t} + ]); + } +}; + +let EBook = { + lib: null, + + E_CONTACT_FULL_NAME: 4, + E_CONTACT_EMAIL_1: 8, + E_CONTACT_EMAIL_2: 9, + E_CONTACT_EMAIL_3: 10, + E_CONTACT_EMAIL_4: 11, + E_CONTACT_PHONE_BUSINESS: 17, + E_CONTACT_PHONE_BUSINESS_2: 18, + E_CONTACT_PHONE_HOME: 23, + E_CONTACT_PHONE_HOME_2: 24, + E_CONTACT_PHONE_MOBILE: 27, + + E_CONTACT_EMAIL_FIRST: 8, + E_CONTACT_EMAIL_LAST: 11, + E_CONTACT_PHONE_FIRST: 16, + E_CONTACT_PHONE_LAST: 34, + + init: function ebook_init() { + if (this.lib) + return; + + GLib.init(); + + try { + // Shipping on N900 + this.lib = ctypes.open("libebook-1.2.so.5"); + } catch (e) { + try { + // Shipping on Ubuntu + this.lib = ctypes.open("libebook-1.2.so.9"); + } catch (e) { + Cu.reportError("EBook: couldn't load libebook:\n" + e) + this.lib = null; + return; + } + } + + this.EBook = new ctypes.StructType("EBook"); + this.EQuery = new ctypes.StructType("EQuery"); + + this.openSystem = this.lib.declare("e_book_new_system_addressbook", ctypes.default_abi, EBook.EBook.ptr, GLib.GError.ptr.ptr); + this.openBook = this.lib.declare("e_book_open", ctypes.default_abi, ctypes.bool, EBook.EBook.ptr, ctypes.bool, GLib.GError.ptr.ptr); + + this.queryAnyFieldContains = this.lib.declare("e_book_query_any_field_contains", ctypes.default_abi, EBook.EQuery.ptr, ctypes.char.ptr); + + this.getContacts = this.lib.declare("e_book_get_contacts", ctypes.default_abi, ctypes.bool, EBook.EBook.ptr, EBook.EQuery.ptr, GLib.GList.ptr.ptr, GLib.GError.ptr.ptr); + this.getContactField = this.lib.declare("e_contact_get_const", ctypes.default_abi, ctypes.char.ptr, ctypes.voidptr_t, ctypes.uint32_t); + } +};