diff options
| -rw-r--r-- | input/input-service.c | 186 | 
1 files changed, 171 insertions, 15 deletions
diff --git a/input/input-service.c b/input/input-service.c index 2e4c911b..06dcad77 100644 --- a/input/input-service.c +++ b/input/input-service.c @@ -28,10 +28,12 @@  #include <stdlib.h>  #include <errno.h>  #include <unistd.h> +#include <fcntl.h>  #include <bluetooth/bluetooth.h>  #include <bluetooth/hci.h>  #include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h>  #include <bluetooth/sdp.h>  #include <bluetooth/sdp_lib.h>  #include <bluetooth/hidp.h> @@ -50,6 +52,9 @@  #define INPUT_DEVICE_INTERFACE	"org.bluez.input.Device"  #define INPUT_ERROR_INTERFACE	"org.bluez.Error" +#define L2CAP_PSM_HIDP_CTRL		0x11 +#define L2CAP_PSM_HIDP_INTR		0x13 +  static DBusConnection *connection = NULL;  const char *pnp_uuid = "00001200-0000-1000-8000-00805f9b34fb";  const char *hid_uuid = "00001124-0000-1000-8000-00805f9b34fb"; @@ -68,6 +73,37 @@ struct pending_req {  	sdp_record_t *hid_rec;  }; +struct pending_connect { +	bdaddr_t sba; +	bdaddr_t dba; +	DBusConnection *conn; +	DBusMessage *msg; +}; + +struct input_device *input_device_new(const char *addr) +{ +	struct input_device *idev; + +	idev = malloc(sizeof(struct input_device)); +	if (!idev) +		return NULL; + +	memset(idev, 0, sizeof(struct input_device)); + +	memcpy(idev->addr, addr, 18); + +	return idev; +} + +void input_device_free(struct input_device *idev) +{ +	if (!idev) +		return; +	if (idev->hidp.rd_data) +		free(idev->hidp.rd_data); +	free(idev); +} +  struct pending_req *pending_req_new(DBusConnection *conn, DBusMessage *msg,  				const char *adapter_path, const char *peer)  { @@ -102,28 +138,32 @@ void pending_req_free(struct pending_req *pr)  	free(pr);  } -struct input_device *input_device_new(const char *addr) +static struct pending_connect *pending_connect_new(bdaddr_t *sba, bdaddr_t *dba, +					DBusConnection *conn, DBusMessage *msg)  { -	struct input_device *idev; - -	idev = malloc(sizeof(struct input_device)); -	if (!idev) +	struct pending_connect *pc; +	pc = malloc(sizeof(struct pending_connect)); +	if (!pc)  		return NULL; -	memset(idev, 0, sizeof(struct input_device)); +	memset(pc, 0, sizeof(struct pending_connect)); +	bacpy(&pc->sba, sba); +	bacpy(&pc->dba, dba); +	pc->conn = dbus_connection_ref(conn); +	pc->msg = dbus_message_ref(msg); -	memcpy(idev->addr, addr, 18); - -	return idev; +	return pc;  } -void input_device_free(struct input_device *idev) +static void pending_connect_free(struct pending_connect *pc)  { -	if (!idev) +	if (!pc)  		return; -	if (idev->hidp.rd_data) -		free(idev->hidp.rd_data); -	free(idev); +	if (pc->conn) +		dbus_connection_unref(pc->conn); +	if (pc->msg) +		dbus_message_unref(pc->msg); +	free(pc);  }  /* @@ -153,6 +193,15 @@ static DBusHandlerResult err_failed(DBusConnection *conn, DBusMessage *msg,  				INPUT_ERROR_INTERFACE ".Failed", str));  } +static DBusHandlerResult err_connection_failed(DBusConnection *conn, +					DBusMessage *msg, const char *str) +{ +	return send_message_and_unref(conn, +			dbus_message_new_error(msg, +				INPUT_ERROR_INTERFACE".ConnectionAttemptFailed", +				str)); +} +  static DBusHandlerResult err_already_exists(DBusConnection *conn,  				DBusMessage *msg, const char *str)  { @@ -371,13 +420,120 @@ static const char *create_input_path(uint8_t minor)  	return path;  } +static gboolean control_connect_cb(GIOChannel *chan, GIOCondition cond, +			struct pending_connect *pc) +{ +	info("FIXME: Control connect callback"); +	return FALSE; +} + +/* FIXME: Move to a common file. It is already used by audio and rfcomm */ +static int set_nonblocking(int fd) +{ +	long arg; + +	arg = fcntl(fd, F_GETFL); +	if (arg < 0) { +		error("fcntl(F_GETFL): %s (%d)", strerror(errno), errno); +		return -1; +	} + +	/* Return if already nonblocking */ +	if (arg & O_NONBLOCK) +		return 0; + +	arg |= O_NONBLOCK; +	if (fcntl(fd, F_SETFL, arg) < 0) { +		error("fcntl(F_SETFL, O_NONBLOCK): %s (%d)", +				strerror(errno), errno); +		return -1; +	} + +	return 0; +} + +static int l2cap_connect(struct pending_connect *pc, +		unsigned short psm, GIOFunc cb) +{ +	GIOChannel *io; +	struct sockaddr_l2 addr; +	struct l2cap_options opts; +	int sk, err; + +	if ((sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) +		return -1; + +	memset(&addr, 0, sizeof(addr)); +	addr.l2_family  = AF_BLUETOOTH; +	bacpy(&addr.l2_bdaddr, &pc->sba); + +	if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) +		goto failed; + +	if (set_nonblocking(sk) < 0) +		goto failed; + +	memset(&opts, 0, sizeof(opts)); +	opts.imtu = HIDP_DEFAULT_MTU; +	opts.omtu = HIDP_DEFAULT_MTU; +	opts.flush_to = 0xffff; + +	if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) +		goto failed; + +	memset(&addr, 0, sizeof(addr)); +	addr.l2_family  = AF_BLUETOOTH; +	bacpy(&addr.l2_bdaddr, &pc->dba); +	addr.l2_psm = htobs(psm); + +	/* FIXME: check leaking io channel */ +	io = g_io_channel_unix_new(sk); +	g_io_channel_set_close_on_unref(io, FALSE); + +	if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { +		if (!(errno == EAGAIN || errno == EINPROGRESS)) +			goto failed; + +		g_io_add_watch(io, G_IO_OUT, (GIOFunc) cb, pc); +	} else { +		cb(io, G_IO_OUT, pc); +	} + +	return 0; + +failed: +	err = errno; +	close(sk); +	errno = err; + +	return -1; +} +  /*   * Input Device methods   */  static DBusHandlerResult device_connect(DBusConnection *conn,  				DBusMessage *msg, void *data)  { -	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +	struct input_device *idev = data; +	struct pending_connect *pc; +	bdaddr_t dba; + +	/* FIXME: check if it is already connected */ +	str2ba(idev->addr, &dba); + +	pc = pending_connect_new(BDADDR_ANY, &dba, conn, msg); +	if (!pc) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	if (l2cap_connect(pc, L2CAP_PSM_HIDP_CTRL, +			(GIOFunc) control_connect_cb) < 0) { +		error("L2CAP connect failed: %s(%d)", strerror(errno), errno); +		pending_connect_free(pc); +		return err_connection_failed(conn, msg, strerror(errno)); +	} + +	return DBUS_HANDLER_RESULT_HANDLED;  }  static DBusHandlerResult device_disconnect(DBusConnection *conn,  | 
