diff options
Diffstat (limited to 'input/manager.c')
| -rw-r--r-- | input/manager.c | 801 | 
1 files changed, 801 insertions, 0 deletions
diff --git a/input/manager.c b/input/manager.c new file mode 100644 index 00000000..2dfb9b2d --- /dev/null +++ b/input/manager.c @@ -0,0 +1,801 @@ +/* + * + *  BlueZ - Bluetooth protocol stack for Linux + * + *  Copyright (C) 2004-2008  Marcel Holtmann <marcel@holtmann.org> + * + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program; if not, write to the Free Software + *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/hidp.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "logging.h" +#include "textfile.h" + +#include "device.h" +#include "server.h" +#include "error.h" +#include "manager.h" +#include "storage.h" +#include "glib-helper.h" + +struct pending_req { +	char		*adapter_path;	/* Local adapter D-Bus path */ +	bdaddr_t	src;		/* Local adapter BT address */ +	bdaddr_t	dst;		/* Peer BT address */ +	DBusConnection	*conn; +	DBusMessage	*msg; +	sdp_list_t	*pnp_recs; +	sdp_list_t	*hid_recs; +	GIOChannel	*ctrl_channel; +}; + +static int idle_timeout = 0; + +static GSList *device_paths = NULL;	/* Input registered paths */ + +static DBusConnection *connection = NULL; + +static struct pending_req *pending_req_new(DBusConnection *conn, +			DBusMessage *msg, bdaddr_t *src, bdaddr_t *dst) +{ +	char adapter[18], adapter_path[32]; +	struct pending_req *pr; +	int dev_id; + +	pr = g_try_new0(struct pending_req, 1); +	if (!pr) +		return NULL; + +	ba2str(src, adapter); +	dev_id = hci_devid(adapter); +	snprintf(adapter_path, 32, "/org/bluez/hci%d", dev_id); + +	pr->adapter_path = g_strdup(adapter_path); +	bacpy(&pr->src, src); +	bacpy(&pr->dst, dst); +	pr->conn = dbus_connection_ref(conn); +	pr->msg = dbus_message_ref(msg); + +	return pr; +} + +static void pending_req_free(struct pending_req *pr) +{ +	if (!pr) +		return; + +	if (pr->adapter_path) +		g_free(pr->adapter_path); + +	if (pr->conn) +		dbus_connection_unref(pr->conn); + +	if (pr->msg) +		dbus_message_unref(pr->msg); + +	if (pr->pnp_recs) +		sdp_list_free(pr->pnp_recs, (sdp_free_func_t) sdp_record_free); + +	if (pr->hid_recs) +		sdp_list_free(pr->hid_recs, (sdp_free_func_t) sdp_record_free); + +	g_free(pr); +} + +static void epox_endian_quirk(unsigned char *data, int size) +{ +	/* USAGE_PAGE (Keyboard)	05 07 +	 * USAGE_MINIMUM (0)		19 00 +	 * USAGE_MAXIMUM (65280)	2A 00 FF   <= must be FF 00 +	 * LOGICAL_MINIMUM (0)		15 00 +	 * LOGICAL_MAXIMUM (65280)	26 00 FF   <= must be FF 00 +	 */ +	unsigned char pattern[] = { 0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff, +						0x15, 0x00, 0x26, 0x00, 0xff }; +	int i; + +	if (!data) +		return; + +	for (i = 0; i < size - sizeof(pattern); i++) { +		if (!memcmp(data + i, pattern, sizeof(pattern))) { +			data[i + 5] = 0xff; +			data[i + 6] = 0x00; +			data[i + 10] = 0xff; +			data[i + 11] = 0x00; +		} +	} +} + +static void extract_hid_record(sdp_record_t *rec, struct hidp_connadd_req *req) +{ +	sdp_data_t *pdlist, *pdlist2; +	uint8_t attr_val; + +	pdlist = sdp_data_get(rec, 0x0101); +	pdlist2 = sdp_data_get(rec, 0x0102); +	if (pdlist) { +		if (pdlist2) { +			if (strncmp(pdlist->val.str, pdlist2->val.str, 5)) { +				strncpy(req->name, pdlist2->val.str, 127); +				strcat(req->name, " "); +			} +			strncat(req->name, pdlist->val.str, 127 - strlen(req->name)); +		} else +			strncpy(req->name, pdlist->val.str, 127); +	} else { +		pdlist2 = sdp_data_get(rec, 0x0100); +		if (pdlist2) +			strncpy(req->name, pdlist2->val.str, 127); + 	} + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_PARSER_VERSION); +	req->parser = pdlist ? pdlist->val.uint16 : 0x0100; + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS); +	req->subclass = pdlist ? pdlist->val.uint8 : 0; + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE); +	req->country = pdlist ? pdlist->val.uint8 : 0; + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_VIRTUAL_CABLE); +	attr_val = pdlist ? pdlist->val.uint8 : 0; +	if (attr_val) +		req->flags |= (1 << HIDP_VIRTUAL_CABLE_UNPLUG); + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE); +	attr_val = pdlist ? pdlist->val.uint8 : 0; +	if (attr_val) +		req->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE); + +	pdlist = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST); +	if (pdlist) { +		pdlist = pdlist->val.dataseq; +		pdlist = pdlist->val.dataseq; +		pdlist = pdlist->next; + +		req->rd_data = g_try_malloc0(pdlist->unitSize); +		if (req->rd_data) { +			memcpy(req->rd_data, (unsigned char *) pdlist->val.str, +								pdlist->unitSize); +			req->rd_size = pdlist->unitSize; +			epox_endian_quirk(req->rd_data, req->rd_size); +		} +	} +} + +static void extract_pnp_record(sdp_record_t *rec, struct hidp_connadd_req *req) +{ +	sdp_data_t *pdlist; + +	pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); +	req->vendor = pdlist ? pdlist->val.uint16 : 0x0000; + +	pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); +	req->product = pdlist ? pdlist->val.uint16 : 0x0000; + +	pdlist = sdp_data_get(rec, SDP_ATTR_VERSION); +	req->version = pdlist ? pdlist->val.uint16 : 0x0000; +} + +static void interrupt_connect_cb(GIOChannel *chan, int err, +			const bdaddr_t *src, const bdaddr_t *dst, +			gpointer user_data) +{ +	struct pending_req *pr = user_data; +	struct hidp_connadd_req hidp; +	const char *path; + +	memset(&hidp, 0, sizeof(hidp)); + +	if (err < 0) { +		error("connect(): %s (%d)", strerror(-err), -err); +		goto failed; +	} + +	g_io_channel_close(chan); +	g_io_channel_unref(chan); + +	hidp.idle_to = idle_timeout * 60; + +	extract_hid_record(pr->hid_recs->data, &hidp); +	if (pr->pnp_recs) +		extract_pnp_record(pr->pnp_recs->data, &hidp); + +	store_device_info(&pr->src, &pr->dst, &hidp); + +	if (input_device_register(pr->conn, &pr->src, +					&pr->dst, &hidp, &path) < 0) { +		error_failed(pr->conn, pr->msg, "path registration failed"); +		goto cleanup; +	} + +	g_dbus_emit_signal(pr->conn, INPUT_PATH, +			INPUT_MANAGER_INTERFACE, "DeviceCreated", +			DBUS_TYPE_STRING, &path, +			DBUS_TYPE_INVALID); + +	device_paths = g_slist_append(device_paths, g_strdup(path)); + +	g_dbus_send_reply(pr->conn, pr->msg,  DBUS_TYPE_STRING, &path, +							DBUS_TYPE_INVALID); + +	goto cleanup; + +failed: +	error_connection_attempt_failed(pr->conn, pr->msg, err); + +cleanup: +	g_io_channel_close(pr->ctrl_channel); +	g_io_channel_unref(pr->ctrl_channel); +	pending_req_free(pr); + +	if (hidp.rd_data) +		g_free(hidp.rd_data); +} + +static void control_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src, +			const bdaddr_t *dst, gpointer user_data) +{ +	struct pending_req *pr = user_data; + +	if (err < 0) { +		error("connect(): %s (%d)", strerror(-err), -err); +		goto failed; +	} + +	/* Set HID control channel */ +	pr->ctrl_channel = chan; + +	/* Connect to the HID interrupt channel */ +	err = bt_l2cap_connect(&pr->src, &pr->dst, L2CAP_PSM_HIDP_INTR, 0, +			interrupt_connect_cb, pr); +	if (err < 0) { +		error("L2CAP connect failed:%s (%d)", strerror(-err), -err); +		goto failed; +	} + +	return; + +failed: +	error_connection_attempt_failed(pr->conn, pr->msg, -err); +	pending_req_free(pr); +} + +static void create_bonding_reply(DBusPendingCall *call, void *data) +{ +	DBusMessage *reply = dbus_pending_call_steal_reply(call); +	struct pending_req *pr = data; +	DBusError derr; +	int err; + +	dbus_error_init(&derr); +	if (dbus_set_error_from_message(&derr, reply)) { +		error("CreateBonding failed: %s(%s)", +					derr.name, derr.message); +		error_failed(pr->conn, pr->msg, "Authentication failed (CreateBonding)"); +		dbus_error_free(&derr); +		dbus_message_unref(reply); +		pending_req_free(pr); +		return; +	} + +	dbus_message_unref(reply); + +	err = bt_l2cap_connect(&pr->src, &pr->dst, L2CAP_PSM_HIDP_CTRL, 0, +			control_connect_cb, pr); +	if (err < 0) { +		error("L2CAP connect failed:%s (%d)", strerror(-err), -err); +		error_connection_attempt_failed(pr->conn, pr->msg, -err); +		pending_req_free(pr); +	} +} + +static int create_bonding(struct pending_req *pr) +{ +	DBusPendingCall *pending; +	DBusMessage *msg; +	char address[18], *addr_ptr = address; + +	msg = dbus_message_new_method_call("org.bluez", pr->adapter_path, +					"org.bluez.Adapter", "CreateBonding"); +	if (!msg) { +		error("Unable to allocate new method call"); +		return -1; +	} + +	ba2str(&pr->dst, address); +	dbus_message_append_args(msg, DBUS_TYPE_STRING, &addr_ptr, DBUS_TYPE_INVALID); +	if (dbus_connection_send_with_reply(pr->conn, msg, &pending, -1) == FALSE) { +		error("Can't send D-Bus message."); +		dbus_message_unref(msg); +		return -1; +	} +	dbus_pending_call_set_notify(pending, create_bonding_reply, pr, NULL); +	dbus_pending_call_unref(pending); +	dbus_message_unref(msg); +	return 0; +} + +static void hid_record_cb(sdp_list_t *recs, int err, gpointer user_data) +{ +	struct pending_req *pr = user_data; + +	if (err < 0) { +		error_not_supported(pr->conn, pr->msg); +		error("SDP search error: %s (%d)", strerror(-err), -err); +		goto fail; +	} + +	if (!recs || !recs->data) { +		error_not_supported(pr->conn, pr->msg); +		error("Invalid HID service record length"); +		goto fail; +	} + +	pr->hid_recs = recs; + +	if (strcmp("CreateSecureDevice", dbus_message_get_member(pr->msg)) == 0) { +		sdp_data_t *d; + +		/* Pairing mandatory for keyboard and combo */ +		d = sdp_data_get(pr->hid_recs->data, +				SDP_ATTR_HID_DEVICE_SUBCLASS); +		if (d && (d->val.uint8 & 0x40) && +				!has_bonding(&pr->src, &pr->dst)) { +			if (create_bonding(pr) < 0) { +				error_failed(pr->conn, pr->msg, +					"Unable to initialize bonding process"); +				goto fail; +			} +			/* Wait bonding reply */ +			return; +		} + +		/* Otherwise proceede L2CAP connection */ +	} + +	/* No encryption or link key already exists -- connect control channel */ +	err = bt_l2cap_connect(&pr->src, &pr->dst, L2CAP_PSM_HIDP_CTRL, 0, +			control_connect_cb, pr); +	if (err < 0) { +		error("L2CAP connect failed:%s (%d)", strerror(-err), -err); +		error_connection_attempt_failed(pr->conn, pr->msg, -err); +		goto fail; +	} + +	/* Wait L2CAP connect */ +	return; + +fail: +	pending_req_free(pr); +} + +static void pnp_record_cb(sdp_list_t *recs, int err, gpointer user_data) +{ +	struct pending_req *pr = user_data; +	uuid_t uuid; + +	if (err < 0) { +		error_not_supported(pr->conn, pr->msg); +		error("SDP search error: %s (%d)", strerror(-err), -err); +		goto fail; +	} + +	if (!recs || !recs->data) { +		error_not_supported(pr->conn, pr->msg); +		error("Invalid PnP service record length"); +		goto fail; +	} + +	pr->pnp_recs = recs; +	sdp_uuid16_create(&uuid, HID_SVCLASS_ID); +	err = bt_search_service(&pr->src, &pr->dst, &uuid, hid_record_cb, +			pr, NULL); +	if (err < 0) { +		error_not_supported(pr->conn, pr->msg); +		error("HID service search request failed"); +		goto fail; +	} + +	return; + +fail: +	pending_req_free(pr); +} + +static void headset_record_cb(sdp_list_t *recs, int err, gpointer user_data) +{ +	struct pending_req *pr = user_data; +	sdp_record_t *rec; +	sdp_list_t *protos; +	const char *path; +	uint8_t ch; + +	if (err < 0) { +		error_not_supported(pr->conn, pr->msg); +		error("SDP search error: %s (%d)", strerror(-err), -err); +		goto fail; +	} + +	if (!recs || !recs->data) { +		error_not_supported(pr->conn, pr->msg); +		error("Invalid headset service record length"); +		goto fail; +	} + +	rec = recs->data; + +	if (sdp_get_access_protos(rec, &protos) < 0) { +		error_not_supported(pr->conn, pr->msg); +		goto fail; +	} + +	ch = sdp_get_proto_port(protos, RFCOMM_UUID); +	sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); +	sdp_list_free(protos, NULL); +	sdp_record_free(rec); + +	if (ch <= 0) { +		error_not_supported(pr->conn, pr->msg); +		error("Invalid RFCOMM channel"); +		goto fail; +	} + +	/* FIXME: Store the fake input data */ + +	if (fake_input_register(pr->conn, &pr->src, &pr->dst, ch, &path) < 0) { +		error("D-Bus path registration failed:%s", path); +		error_failed(pr->conn, pr->msg, "Path registration failed"); +		goto fail; +	} + +	g_dbus_emit_signal(pr->conn, INPUT_PATH, +			INPUT_MANAGER_INTERFACE, "DeviceCreated", +			DBUS_TYPE_STRING, &path, +			DBUS_TYPE_INVALID); + +	device_paths = g_slist_append(device_paths, g_strdup(path)); + +	g_dbus_send_reply(pr->conn, pr->msg, DBUS_TYPE_STRING, &path, +							DBUS_TYPE_INVALID); + +fail: +	pending_req_free(pr); +} + +static inline DBusMessage *adapter_not_available(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", +						"Adapter not available"); +} + +static inline DBusMessage *already_exists(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", +						"Input Already exists"); +} + +static inline DBusMessage *not_supported(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported", +							"Not supported"); +} + +static inline DBusMessage *does_not_exist(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", +							"Input doesn't exist"); +} + +static DBusMessage *create_device(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct pending_req *pr; +	const char *addr; +	bdaddr_t src, dst; +	uint32_t cls = 0; +	int dev_id, err; +	uuid_t uuid; +	bt_callback_t cb; + +	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &addr, +							DBUS_TYPE_INVALID)) +		return NULL; + +	/* Get the default adapter */ +	dev_id = hci_get_route(NULL); +	if (dev_id < 0) { +		error("Bluetooth adapter not available"); +		return adapter_not_available(msg); +	} + +	if (hci_devba(dev_id, &src) < 0) { +		error("Can't get local adapter device info"); +		return adapter_not_available(msg); +	} + +	str2ba(addr, &dst); +	if (input_device_is_registered(&src, &dst)) +		return already_exists(msg); + +	if (read_device_class(&src, &dst, &cls) < 0) { +		error("Device class not available"); +		return not_supported(msg); +	} + +	pr = pending_req_new(conn, msg, &src, &dst); +	if (!pr) +		return NULL; + +	switch (cls & 0x1f00) { +		case 0x0500: /* Peripheral */ +		case 0x0200: /* Phone */ +			sdp_uuid16_create(&uuid, PNP_INFO_SVCLASS_ID); +			cb = pnp_record_cb; +			break; +		case 0x0400: /* Fake input */ +			sdp_uuid16_create(&uuid, HEADSET_SVCLASS_ID); +			cb = headset_record_cb; +			break; +		default: +			pending_req_free(pr); +			return not_supported(msg); +	} + +	err = bt_search_service(&src, &dst, &uuid, cb, pr, NULL); +	if (err < 0) { +		pending_req_free(pr); +		return not_supported(msg); +	} + +	return NULL; +} + +static DBusMessage *remove_device(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	GSList *l; +	const char *path; +	int err; + +	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &path, +							DBUS_TYPE_INVALID)) +		return NULL; + +	l = g_slist_find_custom(device_paths, path, (GCompareFunc) strcmp); +	if (!l) +		return does_not_exist(msg); + +	err = input_device_unregister(conn, path); +	if (err < 0) +		return create_errno_message(msg, -err); + +	g_free(l->data); +	device_paths = g_slist_remove(device_paths, l->data); + +	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *list_devices(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	DBusMessageIter iter, iter_array; +	DBusMessage *reply; +	GSList *paths; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_iter_init_append(reply, &iter); +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +			DBUS_TYPE_STRING_AS_STRING, &iter_array); + +	for (paths = device_paths; paths != NULL; paths = paths->next) { +		const char *ppath = paths->data; +		dbus_message_iter_append_basic(&iter_array, +					DBUS_TYPE_STRING, &ppath); +	} + +	dbus_message_iter_close_container(&iter, &iter_array); + +	return reply; +} + +static void manager_unregister(void *data) +{ +	info("Unregistered manager path"); + +	g_slist_foreach(device_paths, (GFunc) free, NULL); + +	g_slist_free(device_paths); +} + +/* + * Stored inputs registration functions + */ + +static void stored_input(char *key, char *value, void *data) +{ +	const char *path; +	struct hidp_connadd_req hidp; +	bdaddr_t dst, *src = data; + +	str2ba(key, &dst); + +	memset(&hidp, 0, sizeof(hidp)); + +	if (parse_stored_device_info(value, &hidp) < 0) +		return; + +	/* +	 * Repeated entries for the same remote device are +	 * acceptable since the source is different. +	 */ +	if (input_device_register(connection, src, &dst, &hidp, &path) < 0) +		goto cleanup; + +	device_paths = g_slist_append(device_paths, g_strdup(path)); +cleanup: +	if (hidp.rd_data) +		g_free(hidp.rd_data); +} + +/* hidd to input transition function */ +static void stored_hidd(char *key, char *value, void *data) +{ +	struct hidp_connadd_req hidp; +	char *str, filename[PATH_MAX + 1], addr[18]; +	bdaddr_t dst, *src = data; + +	ba2str(src, addr); +	create_name(filename, PATH_MAX, STORAGEDIR, addr, "input"); + +	str = textfile_get(filename, key); +	if (str) { +		/* Skip: entry found in input file */ +		free(str); +		return; +	} + +	memset(&hidp, 0, sizeof(hidp)); + +	if (parse_stored_hidd(value, &hidp) < 0) +		return; + +	str2ba(key, &dst); +	store_device_info(src, &dst, &hidp); +	if (hidp.rd_data) +		g_free(hidp.rd_data); +} + +static void register_stored_inputs(void) +{ +	char dirname[PATH_MAX + 1]; +	char filename[PATH_MAX + 1]; +	struct dirent *de; +	DIR *dir; +	bdaddr_t src; + +	snprintf(dirname, PATH_MAX, "%s", STORAGEDIR); + +	dir = opendir(dirname); +	if (!dir) +		return; + +	while ((de = readdir(dir)) != NULL) { +		if (!isdigit(de->d_name[0])) +			continue; + +		str2ba(de->d_name, &src); + +		/* move the hidd entries to the input storage */ +		create_name(filename, PATH_MAX, STORAGEDIR, +						de->d_name, "hidd"); +		textfile_foreach(filename, stored_hidd, &src); + +		/* load the input stored devices */ +		create_name(filename, PATH_MAX, STORAGEDIR, +						de->d_name, "input"); + +		textfile_foreach(filename, stored_input, &src); +	} + +	closedir(dir); +} + +static GDBusMethodTable manager_methods[] = { +	{ "ListDevices",	"",	"as",	list_devices }, +	{ "CreateDevice",	"s",	"s",	create_device, +						G_DBUS_METHOD_FLAG_ASYNC }, +	{ "CreateSecureDevice",	"s",	"s",	create_device, +						G_DBUS_METHOD_FLAG_ASYNC }, +	{ "RemoveDevice",	"s",	"",	remove_device }, +	{ } +}; + +static GDBusSignalTable manager_signals[] = { +	{ "DeviceCreated",	"s"	}, +	{ "DeviceRemoved",	"s"	}, +	{ } +}; + +int input_manager_init(DBusConnection *conn, GKeyFile *config) +{ +	GError *err = NULL; +  +	if (config) { +		idle_timeout = g_key_file_get_integer(config, "General", +						"IdleTimeout", &err); +		if (err) { +			debug("input.conf: %s", err->message); +			g_error_free(err); +		} +	} + +	if (g_dbus_register_interface(conn, INPUT_PATH, INPUT_MANAGER_INTERFACE, +					manager_methods, manager_signals, NULL, +					NULL, manager_unregister) == FALSE) { +		error("Failed to register %s interface to %s", +				INPUT_MANAGER_INTERFACE, INPUT_PATH); +		return -1; +	} + +	connection = dbus_connection_ref(conn); + +	info("Registered input manager path:%s", INPUT_PATH); + +	/* Register well known HID devices */ +	register_stored_inputs(); + +	server_start(); + +	return 0; +} + +void input_manager_exit(void) +{ +	g_dbus_unregister_interface(connection, INPUT_PATH, +						INPUT_MANAGER_INTERFACE); + +	server_stop(); + +	dbus_connection_unref(connection); + +	connection = NULL; +}  | 
