danieljon.es

index posts opinions portfolio

Posts

My posts about programming and things.
Date format is day/month/year because I'm sane.

Userspace driver for Microsoft serial mouse

9/1/2022

To learn the basics of serial programming in UNIX I created a simple userspace driver for a Micrsoft serial mouse.

I use uinput to emulate an input device.

/*
 * Microsoft mouse
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <ctype.h>
#include <stdint.h>
#include <linux/uinput.h>

#define DOWN 		1
#define UP 		0

#define PORT 		"/dev/ttyUSB0"
#define BAUD 		B1200
#define LBIT 		1 << 5
#define RBIT		1 << 4
#define SYNCBIT 	1 << 7
#define XBYTE0MASK	0x03
#define XBYTE1MASK 	0x7F
#define YBYTE0MASK	0x0C
#define YBYTE2MASK 	0x7F

void process(void);
void emit(int fd, int type, int code, int val);

uint8_t packet[3];

struct 
{
	uint8_t left;
	uint8_t right;
} mouse;

struct uinput_setup usetup;
int ud;

int
main(void)
{
	int fd, flags, onbyte;
	ssize_t byte;
	struct termios options;
	fd = open(PORT, O_RDWR | O_NOCTTY);
	if (fd == -1)
	{
		perror("cannot open serial device");
		exit(EXIT_FAILURE);
	}
	flags = fcntl(fd, F_GETFL);			// get current flags
	fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);	// mask out O_NONBLOCK = blocking

	// get port options and set flags
	tcgetattr(fd, &options);
	cfsetispeed(&options, BAUD);
	cfsetospeed(&options, BAUD);
	options.c_cflag |= (CLOCAL | CREAD);	// local line, enable read
	// set character size
	options.c_cflag &= ~CSIZE;		// mask out the character size bits
	options.c_cflag |= CS7;			// 7 bits
	// no parity
	options.c_cflag &= ~PARENB;		// mask out parity bit
	options.c_cflag &= ~CSTOPB;		// mask out stop bit (unset = 1 bit)
	// raw input
	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);	
	options.c_iflag &= ~INPCK; 		// mask out parity checking
	options.c_oflag &= ~OPOST;		// mask out postprocessing - raw output

	options.c_cc[VMIN] = 1;			// minimum of 1 byte read
	options.c_cc[VTIME] = 0;		// wait indefinitely
	// apply options
	tcsetattr(fd, TCSANOW, &options);

	// setup uevent device

	ud = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
	if (ud == -1)
	{
		perror("cannot open /dev/uinput");
		exit(EXIT_FAILURE);
	}


	// enable mouse left/right and relative events
	ioctl(ud, UI_SET_EVBIT, EV_KEY);
	ioctl(ud, UI_SET_KEYBIT, BTN_LEFT);
	ioctl(ud, UI_SET_KEYBIT, BTN_RIGHT);

	ioctl(ud, UI_SET_EVBIT, EV_REL);
	ioctl(ud, UI_SET_RELBIT, REL_X);
	ioctl(ud, UI_SET_RELBIT, REL_Y);

	memset(&usetup, 0, sizeof(usetup));
	usetup.id.bustype = BUS_USB;
	usetup.id.vendor = 0x1234; 
	usetup.id.product = 0x5678;
	strcpy(usetup.name, "Userspace Microsoft serial mouse");

	ioctl(ud, UI_DEV_SETUP, &usetup);
	ioctl(ud, UI_DEV_CREATE);

	mouse.left = 0x00;
	mouse.right = 0x00;
	onbyte = 0;
	byte = 0x00;

	while(1)
	{
		read(fd, &byte, 1);
		if (byte == 0xcd) 		// some kind of init?
			continue;
		// is byte first of packet?
		if (byte & SYNCBIT && onbyte == 0)
		{
			packet[0] = byte;
			onbyte = 1;
		}
		else if (onbyte == 1)
		{
			packet[1] = byte;
			onbyte = 2;
		}
		else if (onbyte == 2)
		{
			packet[2] = byte;
			onbyte = 0;
			process();
		}
		fflush(stdout);
	}
	close(fd);
	ioctl(ud, UI_DEV_DESTROY);
	close(ud);
	return 1;
}

void
process(void)
{
	int8_t relx, rely;
	printf("0x%02x\t", packet[0]);
	printf("0x%02x\t", packet[1]);
	printf("0x%02x\t\n\n", packet[2]);
	if (packet[0] & LBIT && !mouse.left)
	{
		mouse.left = DOWN;
		puts("left down");
		emit(ud, EV_KEY, BTN_LEFT, 1);
		emit(ud, EV_SYN, SYN_REPORT, 0);
	}
	else if (mouse.left && !(packet[0] & LBIT))
	{
		mouse.left = UP;
		puts("left up");
		emit(ud, EV_KEY, BTN_LEFT, 0);
		emit(ud, EV_SYN, SYN_REPORT, 0);
	}
	if (packet[0] & RBIT && !mouse.right)
	{
		mouse.right = DOWN;
		puts("right down");
		emit(ud, EV_KEY, BTN_RIGHT, 1);
		emit(ud, EV_SYN, SYN_REPORT, 0);
	}
	else if (mouse.right && !(packet[0] & RBIT))
	{
		mouse.right = UP;
		emit(ud, EV_KEY, BTN_RIGHT, 0);
		emit(ud, EV_SYN, SYN_REPORT, 0);
	}

	relx |= ((packet[0] & XBYTE0MASK)<<6) | (packet[1] & XBYTE1MASK);
	rely |= ((packet[0] & YBYTE0MASK)<<4) | (packet[2] & YBYTE2MASK);
	emit(ud, EV_REL, REL_X, relx);
	emit(ud, EV_REL, REL_Y, rely);
	emit(ud, EV_SYN, SYN_REPORT, 0);


}

void
emit(int fd, int type, int code, int val)
{
	struct input_event ie;
	ie.type = type;
	ie.code = code;
	ie.value = val;
	/* timestamp values below are ignored */
	ie.time.tv_sec = 0;
	ie.time.tv_usec = 0;

	write(fd, &ie, sizeof(ie));
}



RSS feed
FSF member

page generated 10/4/2023 using websitegenerator in C