/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Implementation of Serial Mouse devices. * * TODO: Add the Genius Serial Mouse. * * * * Authors: Fred N. van Kempen, */ #include #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/device.h> #include <86box/timer.h> #include <86box/serial.h> #include <86box/mouse.h> #include <86box/plat.h> #include <86box/plat_fallthrough.h> #include <86box/plat_unused.h> #define SERMOUSE_PORT 0 /* attach to Serial0 */ enum { STATE_RESET, STATE_BAUD_RATE, STATE_DORMANT, STATE_IDLE, STATE_COMMAND, STATE_DATA, STATE_TRANSMIT, STATE_TRANSMIT_REPORT, STATE_SKIP_REPORT }; enum { FORMAT_BP1_ABS = 0x01, FORMAT_BP1_REL, FORMAT_MM_SERIES = 0x13, FORMAT_PB_3BYTE, FORMAT_PB_5BYTE, FORMAT_MSYSTEMS = 0x15, /* Alias for FORMAT_PB_5BYTE. */ FORMAT_MS, FORMAT_HEX, FORMAT_MS_4BYTE, FORMAT_MS_WHEEL, FORMATS_NUM }; typedef struct mouse_t { const char *name; /* name of this device */ uint8_t id[252]; uint8_t buf[256]; uint8_t flags; /* device flags */ uint8_t but; uint8_t rts_toggle; uint8_t status; uint8_t format; uint8_t prompt; uint8_t continuous; uint8_t ib; uint8_t command; uint8_t buf_len; uint8_t report_mode; uint8_t id_len; uint8_t buf_pos; uint8_t rev; int8_t type; /* type of this device */ int8_t port; int abs_x; int abs_y; int old_buttons; int state; int bps; int rps; double transmit_period; double report_period; double cur_period; double min_bit_period; double acc_time; double host_transmit_period; pc_timer_t timer; serial_t * serial; } mouse_t; #define FLAG_INPORT 0x80 /* device is MS InPort */ #define FLAG_3BTN 0x20 /* enable 3-button mode */ #define FLAG_SCALED 0x10 /* enable delta scaling */ #define FLAG_INTR 0x04 /* dev can send interrupts */ #define FLAG_FROZEN 0x02 /* do not update counters */ #define FLAG_ENABLED 0x01 /* dev is enabled for use */ #ifdef ENABLE_MOUSE_SERIAL_LOG int mouse_serial_do_log = ENABLE_MOUSE_SERIAL_LOG; static void mouse_serial_log(const char *fmt, ...) { va_list ap; if (mouse_serial_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define mouse_serial_log(fmt, ...) #endif static void sermouse_set_period(mouse_t *dev, double period) { dev->cur_period = period; /* Needed for the recalculation of the timings. */ timer_stop(&dev->timer); if (period > 0.0) timer_on_auto(&dev->timer, 10000.0); } static void sermouse_transmit_byte(mouse_t *dev, int do_next) { if (dev->buf_pos == 0) dev->acc_time = 0.0; serial_write_fifo(dev->serial, dev->buf[dev->buf_pos]); if (do_next) { dev->buf_pos = (dev->buf_pos + 1) % dev->buf_len; if (dev->buf_pos != 0) sermouse_set_period(dev, dev->transmit_period); } } static void sermouse_transmit(mouse_t *dev, int len, int from_report, int to_report) { dev->state = to_report ? STATE_TRANSMIT_REPORT : STATE_TRANSMIT; dev->buf_pos = 0; dev->buf_len = len; if (from_report) { if (dev->acc_time > dev->report_period) dev->acc_time -= dev->report_period; /* We have too little time left, pretend it's zero and handle schedule the next report at byte period. */ if (dev->acc_time < dev->min_bit_period) sermouse_set_period(dev, dev->transmit_period); /* We have enough time, schedule the next report at report period, subtract the accumulated time from the total period, and add one byte period (the first byte delay). */ else sermouse_set_period(dev, dev->report_period - dev->acc_time + dev->transmit_period); } else sermouse_set_period(dev, dev->transmit_period); } /* It appears all host platforms give us y in the Microsoft format (positive to the south), so for all non-Microsoft report formsts, we have to invenrt that. */ static void sermouse_subtract_coords(mouse_t *dev, int *delta_x, int *delta_y, int min, int max, int invert, int abs) { int real_y = mouse_y; int abs_max = max + ABS(min); if (invert) real_y = -real_y; if (mouse_x > max) { if (abs) { dev->abs_x += max; *delta_x = dev->abs_x; } else *delta_x = max; mouse_x -= max; } else if (mouse_x < min) { if (abs) { dev->abs_x += min; *delta_x = dev->abs_x; } else *delta_x = min; mouse_x += ABS(min); } else { if (abs) { dev->abs_x += mouse_x; *delta_x = dev->abs_x; } else *delta_x = mouse_x; mouse_x = 0; } if (real_y > max) { if (abs) { dev->abs_y += max; *delta_y = dev->abs_y; } else *delta_y = max; real_y -= max; } else if (real_y < min) { if (abs) { dev->abs_y += min; *delta_y = dev->abs_y; } else *delta_y = min; real_y += ABS(min); } else { if (abs) { dev->abs_y += real_y; *delta_y = dev->abs_y; } else *delta_y = real_y; real_y = 0; } if (abs) { if (dev->abs_x < 0) *delta_x = 0; else if (dev->abs_x > abs_max) *delta_x = abs_max; if (dev->abs_y < 0) *delta_y = 0; else if (dev->abs_y > abs_max) *delta_y = abs_max; } if (invert) real_y = -real_y; mouse_y = real_y; } static uint8_t sermouse_report_msystems(mouse_t *dev) { int delta_x = 0; int delta_y = 0; sermouse_subtract_coords(dev, &delta_x, &delta_y, -128, 127, 1, 0); dev->buf[0] = 0x80; dev->buf[0] |= (mouse_buttons & 0x01) ? 0x00 : 0x04; /* left button */ if (dev->but >= 3) dev->buf[0] |= (mouse_buttons & 0x04) ? 0x00 : 0x02; /* middle button */ else dev->buf[0] |= 0x02; /* middle button */ dev->buf[0] |= (mouse_buttons & 0x02) ? 0x00 : 0x01; /* right button */ dev->buf[1] = delta_x; dev->buf[2] = delta_y; dev->buf[2] = delta_x; /* same as byte 1 */ dev->buf[3] = delta_y; /* same as byte 2 */ return 5; } static uint8_t sermouse_report_3bp(mouse_t *dev) { int delta_x = 0; int delta_y = 0; sermouse_subtract_coords(dev, &delta_x, &delta_y, -128, 127, 1, 0); dev->buf[0] = 0x80; dev->buf[0] |= (mouse_buttons & 0x01) ? 0x04 : 0x00; /* left button */ if (dev->but >= 3) dev->buf[0] |= (mouse_buttons & 0x04) ? 0x02 : 0x00; /* middle button */ dev->buf[0] |= (mouse_buttons & 0x02) ? 0x01 : 0x00; /* right button */ dev->buf[1] = delta_x; dev->buf[2] = delta_y; dev->buf[2] = delta_x; /* same as byte 1 */ dev->buf[3] = delta_y; /* same as byte 2 */ return 3; } static uint8_t sermouse_report_mmseries(mouse_t *dev) { int delta_x = 0; int delta_y = 0; sermouse_subtract_coords(dev, &delta_x, &delta_y, -127, 127, 1, 0); dev->buf[0] = 0x80; if (delta_x >= 0) dev->buf[0] |= 0x10; if (delta_y >= 0) dev->buf[0] |= 0x08; dev->buf[0] |= (mouse_buttons & 0x01) ? 0x04 : 0x00; /* left button */ if (dev->but >= 3) dev->buf[0] |= (mouse_buttons & 0x04) ? 0x02 : 0x00; /* middle button */ dev->buf[0] |= (mouse_buttons & 0x02) ? 0x01 : 0x00; /* right button */ dev->buf[1] = ABS(delta_x) & 0x7f; dev->buf[2] = ABS(delta_y) & 0x7f; mouse_serial_log("MM series mouse report: %02X %02X %02X\n", dev->buf[0], dev->buf[1], dev->buf[2]); return 3; } static uint8_t sermouse_report_bp1(mouse_t *dev, int abs) { int delta_x = 0; int delta_y = 0; sermouse_subtract_coords(dev, &delta_x, &delta_y, -2048, 2047, 1, abs); dev->buf[0] = 0x80; dev->buf[0] |= (mouse_buttons & 0x01) ? 0x10 : 0x00; /* left button */ if (dev->but >= 3) dev->buf[0] |= (mouse_buttons & 0x04) ? 0x08 : 0x00; /* middle button */ dev->buf[0] |= (mouse_buttons & 0x02) ? 0x04 : 0x00; /* right button */ dev->buf[1] = (delta_x & 0x3f); dev->buf[2] = ((delta_x >> 6) & 0x3f); dev->buf[3] = (delta_y & 0x3f); dev->buf[4] = ((delta_y >> 6) & 0x3f); return 5; } static uint8_t sermouse_report_ms(mouse_t *dev) { uint8_t len; int delta_x = 0; int delta_y = 0; int delta_z = 0; sermouse_subtract_coords(dev, &delta_x, &delta_y, -128, 127, 0, 0); dev->buf[0] = 0x40; dev->buf[0] |= (((delta_y >> 6) & 0x03) << 2); dev->buf[0] |= ((delta_x >> 6) & 0x03); if (mouse_buttons & 0x01) dev->buf[0] |= 0x20; if (mouse_buttons & 0x02) dev->buf[0] |= 0x10; dev->buf[1] = delta_x & 0x3f; dev->buf[2] = delta_y & 0x3f; mouse_serial_log("Microsoft serial mouse report: %02X %02X %02X\n", dev->buf[0], dev->buf[1], dev->buf[2]); if (dev->but == 3) { len = 3; if (dev->format == FORMAT_MS) { if (mouse_buttons & 0x04) { dev->buf[3] = 0x20; len++; } } else { if ((mouse_buttons ^ dev->old_buttons) & 0x04) { /* Microsoft 3-button mice send a fourth byte of 0x00 when the middle button has changed. */ dev->buf[3] = 0x00; len++; } } } else if (dev->but == 4) { len = 4; if (mouse_z > 7) { delta_z = 7; mouse_z -= 7; } else if (mouse_z < -8) { delta_z = -8; mouse_z += 8; } else { delta_z = mouse_z; mouse_z = 0; } dev->buf[3] = delta_z & 0x0f; if (mouse_buttons & 0x04) dev->buf[3] |= 0x10; } else len = 3; return len; } static uint8_t sermouse_report_hex(mouse_t *dev) { char ret[6] = { 0, 0, 0, 0, 0, 0 }; uint8_t but = 0x00; int delta_x = 0; int delta_y = 0; sermouse_subtract_coords(dev, &delta_x, &delta_y, -128, 127, 1, 0); but |= (mouse_buttons & 0x01) ? 0x04 : 0x00; /* left button */ if (dev->but >= 3) but |= (mouse_buttons & 0x04) ? 0x02 : 0x00; /* middle button */ but |= (mouse_buttons & 0x02) ? 0x01 : 0x00; /* right button */ sprintf(ret, "%01X%02X%02X", but & 0x0f, (int8_t) delta_x, (int8_t) delta_y); memcpy(dev->buf, ret, 5); return 5; } static int sermouse_report(mouse_t *dev) { int len = 0; memset(dev->buf, 0, 5); switch (dev->format) { case FORMAT_PB_5BYTE: len = sermouse_report_msystems(dev); break; case FORMAT_PB_3BYTE: len = sermouse_report_3bp(dev); break; case FORMAT_HEX: len = sermouse_report_hex(dev); break; case FORMAT_BP1_REL: len = sermouse_report_bp1(dev, 0); break; case FORMAT_MM_SERIES: len = sermouse_report_mmseries(dev); break; case FORMAT_BP1_ABS: len = sermouse_report_bp1(dev, 1); break; case FORMAT_MS: case FORMAT_MS_4BYTE: case FORMAT_MS_WHEEL: len = sermouse_report_ms(dev); break; default: break; } return len; } static void sermouse_transmit_report(mouse_t *dev, int from_report) { int z_changed = (dev->but == 4) ? mouse_z : 0; int b_changed = ((mouse_buttons ^ dev->old_buttons) & 0x07); if (dev->but < 3) b_changed &= ~0x04; if (mouse_capture && (mouse_x || mouse_y || z_changed || b_changed)) { sermouse_transmit(dev, sermouse_report(dev), from_report, 1); dev->old_buttons = mouse_buttons; } else { if (dev->prompt || dev->continuous) sermouse_set_period(dev, 0.0); else { dev->state = STATE_SKIP_REPORT; /* Not in prompt or continuous mode and there have been no changes, skip the next report entirely. */ if (from_report) { if (dev->acc_time > dev->report_period) dev->acc_time -= dev->report_period; if (dev->acc_time < dev->min_bit_period) sermouse_set_period(dev, dev->report_period); else sermouse_set_period(dev, (dev->report_period * 2.0) - dev->acc_time); } else sermouse_set_period(dev, dev->report_period); } } } static int sermouse_poll(int x, int y, int z, int b, UNUSED(double abs_x), UNUSED(double abs_y), void *priv) { mouse_t *dev = (mouse_t *) priv; if (!mouse_capture || dev->prompt || !dev->continuous || (dev->state != STATE_IDLE)) return 1; sermouse_transmit_report(dev, 0); return (dev->cur_period == 0.0) ? 1 : 0; } static void ltsermouse_set_prompt_mode(mouse_t *dev, int prompt) { dev->prompt = prompt; if (prompt || dev->continuous) sermouse_set_period(dev, 0.0); else sermouse_set_period(dev, dev->transmit_period); } static void ltsermouse_set_report_period(mouse_t *dev, int rps) { /* Limit the reports rate according to the baud rate. */ if (rps == 0) { sermouse_set_period(dev, 0.0); dev->report_period = 0.0; dev->continuous = 1; } else { /* if (rps > dev->max_rps) rps = dev->max_rps; */ dev->continuous = 0; dev->report_period = 1000000.0 / ((double) rps); /* Actual spacing between reports. */ } } static void ltsermouse_update_report_period(mouse_t *dev) { ltsermouse_set_report_period(dev, dev->rps); ltsermouse_set_prompt_mode(dev, 0); mouse_serial_log("ltsermouse_update_report_period(): %i, %i\n", dev->continuous, dev->prompt); if (dev->continuous) dev->state = STATE_IDLE; else { sermouse_transmit_report(dev, 0); dev->state = STATE_TRANSMIT_REPORT; } } static void ltsermouse_switch_baud_rate(mouse_t *dev, int next_state) { double word_lens[FORMATS_NUM] = { [FORMAT_BP1_ABS] = 7.0 + 1.0, /* 7 data bits + even parity */ [FORMAT_BP1_REL] = 7.0 + 1.0, /* 7 data bits + even parity */ [FORMAT_MM_SERIES] = 8.0 + 1.0, /* 8 data bits + odd parity */ [FORMAT_PB_3BYTE] = 8.0, /* 8 data bits + no parity */ [FORMAT_PB_5BYTE] = 8.0, /* 8 data bits + no parity */ [FORMAT_MS] = 7.0, /* 7 datas bits + no parity */ [FORMAT_HEX] = 8.0, /* 8 data bits + no parity */ [FORMAT_MS_4BYTE] = 7.0, /* 7 datas bits + no parity */ [FORMAT_MS_WHEEL] = 7.0 }; /* 7 datas bits + no parity */ double word_len = word_lens[dev->format]; word_len += 1.0 + 2.0; /* 1 start bit + 2 stop bits */ // dev->max_rps = (int) floor(((double) dev->bps) / (word_len * num_words)); if (next_state == STATE_BAUD_RATE) dev->transmit_period = dev->host_transmit_period; else dev->transmit_period = (1000000.0) / ((double) dev->bps); dev->min_bit_period = dev->transmit_period; dev->transmit_period *= word_len; /* The transmit period for the entire report, we're going to need this in ltsermouse_set_report_period(). */ // dev->report_transmit_period = dev->transmit_period * num_words; ltsermouse_set_report_period(dev, dev->rps); if (!dev->continuous && (next_state != STATE_BAUD_RATE)) { if (dev->prompt) ltsermouse_set_prompt_mode(dev, 0); sermouse_transmit_report(dev, 0); } dev->state = next_state; } static int sermouse_next_state(mouse_t *dev) { int ret = STATE_IDLE; if (dev->prompt || (dev->rps == 0)) ret = STATE_IDLE; else ret = STATE_TRANSMIT; return ret; } static void ltsermouse_process_command(mouse_t *dev) { int cmd_to_rps[9] = { 10, 20, 35, 70, 150, 0, -1, 100, 50 }; uint8_t format_codes[FORMATS_NUM] = { [FORMAT_BP1_ABS] = 0x0c, [FORMAT_BP1_REL] = 0x06, [FORMAT_MM_SERIES] = 0x0a, [FORMAT_PB_3BYTE] = 0x00, [FORMAT_PB_5BYTE] = 0x02, [FORMAT_MS] = 0x0e, [FORMAT_HEX] = 0x04, [FORMAT_MS_4BYTE] = 0x08, /* Guess */ [FORMAT_MS_WHEEL] = 0x08 }; /* Guess */ char *copr = "\r\n(C) 2023 86Box, Revision 3.0"; mouse_serial_log("ltsermouse_process_command(): %02X\n", dev->ib); dev->command = dev->ib; switch (dev->command) { case 0x20: /* Auto Baud Selection */ dev->bps = (int) floor(1000000.0 / dev->host_transmit_period); dev->transmit_period = dev->host_transmit_period; dev->buf[0] = 0x06; sermouse_transmit(dev, 1, 0, 0); ltsermouse_switch_baud_rate(dev, STATE_BAUD_RATE); break; case 0x4a: /* Report Rate Selection commands */ case 0x4b: case 0x4c: case 0x52: case 0x4d: case 0x51: case 0x4e: case 0x4f: dev->report_mode = dev->command; dev->rps = cmd_to_rps[dev->command - 0x4a]; ltsermouse_update_report_period(dev); break; case 0x44: /* Select Prompt Mode */ dev->report_mode = dev->command; ltsermouse_set_prompt_mode(dev, 1); dev->state = STATE_IDLE; break; case 0x50: /* Promopt to send a report (also enters Prompt Mode). */ if (!dev->prompt) { dev->report_mode = 0x44; ltsermouse_set_prompt_mode(dev, 1); } sermouse_transmit_report(dev, 0); dev->state = STATE_TRANSMIT_REPORT; break; case 0x41: /* Absolute Bit Pad One Packed Binary Format */ dev->abs_x = dev->abs_y = 0; #ifdef FALLTHROUGH_ANNOTATION [[fallthrough]]; #endif case 0x42: /* Relative Bit Pad One Packed Binary Format */ case 0x53: /* MM Series Data Format */ case 0x54: /* Three Byte Packed Binary Format */ case 0x55: /* Five Byte Packed Binary Format (Mouse Systems-compatible) */ case 0x56: /* Microsoft Compatible Format */ case 0x57: /* Hexadecimal Format */ case 0x58: /* Microsoft Compatible Format (3+1 byte 3-button, from the FreeBSD source code) */ if ((dev->rev >= 0x02) && ((dev->command != 0x58) || (dev->rev > 0x04))) { dev->format = dev->command & 0x1f; ltsermouse_switch_baud_rate(dev, sermouse_next_state(dev)); } break; case 0x2a: if (dev->rev >= 0x03) { /* Programmable Baud Rate Selection */ dev->state = STATE_DATA; } break; case 0x73: /* Status */ dev->buf[0] = dev->prompt ? 0x4f : 0x0f; sermouse_transmit(dev, 1, 0, 0); break; case 0x05: /* Diagnostic */ dev->buf[0] = ((mouse_buttons & 0x01) << 2) | ((mouse_buttons & 0x06) >> 1); dev->buf[1] = dev->buf[2] = 0x00; sermouse_transmit(dev, 3, 0, 0); break; case 0x66: if (dev->rev >= 0x20) { /* Format and Revision Number */ dev->buf[0] = format_codes[dev->format]; dev->buf[0] |= 0x10; /* Revision 3.0, 0x00 would be Revision 2.0 */ sermouse_transmit(dev, 1, 0, 0); } break; case 0x74: /* Format and Mode in ASCII */ if (dev->rev >= 0x03) { dev->buf[0] = dev->format | 0x40; dev->buf[1] = dev->report_mode; sermouse_transmit(dev, 2, 0, 0); } break; case 0x63: /* Copyright and Revision in ASCII */ if (dev->rev >= 0x03) { memcpy(&(dev->buf[0]), copr, strlen(copr) + 1); sermouse_transmit(dev, strlen(copr) + 1, 0, 0); } else { memcpy(&(dev->buf[0]), copr, strlen(copr)); sermouse_transmit(dev, strlen(copr), 0, 0); } dev->buf[29] = dev->rev | 0x30; break; case 0x64: /* Dormant State */ dev->state = STATE_DORMANT; break; case 0x6b: /* Buttons - 86Box-specific command. */ dev->state = dev->but; break; } } static void ltsermouse_process_data(mouse_t *dev) { mouse_serial_log("ltsermouse_process_data(): %02X (command = %02X)\n", dev->ib, dev->command); switch(dev->command) { case 0x2a: switch (dev->ib) { default: mouse_serial_log("Serial mouse: Invalid period %02X, using 1200 bps\n", data); #ifdef FALLTHROUGH_ANNOTATION [[fallthrough]]; #endif case 0x6e: dev->bps = 1200; break; case 0x6f: dev->bps = 2400; break; case 0x70: dev->bps = 4800; break; case 0x71: dev->bps = 9600; break; } ltsermouse_switch_baud_rate(dev, (dev->prompt || dev->continuous) ? STATE_IDLE : STATE_TRANSMIT_REPORT); break; default: dev->state = STATE_IDLE; break; } } static void sermouse_reset(mouse_t *dev, int callback) { sermouse_set_period(dev, 0.0); dev->bps = 1200; dev->rps = 0; dev->prompt = 0; if (dev->id[0] == 'H') dev->format = FORMAT_MSYSTEMS; else switch (dev->but) { default: case 2: dev->format = FORMAT_MS; break; case 3: dev->format = (dev->type == MOUSE_TYPE_LT3BUTTON) ? FORMAT_MS : FORMAT_MS_4BYTE; break; case 4: dev->format = FORMAT_MS_WHEEL; break; } ltsermouse_switch_baud_rate(dev, callback ? STATE_TRANSMIT : STATE_IDLE); } static void sermouse_timer(void *priv) { mouse_t *dev = (mouse_t *) priv; #ifdef ENABLE_MOUSE_SERIAL_LOG int old_state = dev->state; #endif switch (dev->state) { case STATE_RESET: /* All three mice default to continuous reporting. */ sermouse_reset(dev, 0); break; case STATE_DATA: ltsermouse_process_data(dev); break; case STATE_COMMAND: ltsermouse_process_command(dev); break; case STATE_SKIP_REPORT: if (!dev->prompt && !dev->continuous) sermouse_transmit_report(dev, (dev->state == STATE_TRANSMIT_REPORT)); else dev->state = STATE_IDLE; break; case STATE_TRANSMIT_REPORT: case STATE_TRANSMIT: case STATE_BAUD_RATE: sermouse_transmit_byte(dev, 1); if (dev->buf_pos == 0) { if (!dev->prompt && !dev->continuous) sermouse_transmit_report(dev, (dev->state == STATE_TRANSMIT_REPORT)); else dev->state = STATE_IDLE; } break; default: break; } mouse_serial_log("sermouse_timer(): %02i -> %02i\n", old_state, dev->state); } static void ltsermouse_write(UNUSED(struct serial_s *serial), void *priv, uint8_t data) { mouse_t *dev = (mouse_t *) priv; mouse_serial_log("ltsermouse_write(): %02X\n", data); dev->ib = data; switch (dev->state) { case STATE_RESET: case STATE_BAUD_RATE: break; case STATE_TRANSMIT_REPORT: case STATE_TRANSMIT: case STATE_SKIP_REPORT: sermouse_set_period(dev, 0.0); #ifdef FALLTHROUGH_ANNOTATION [[fallthrough]]; #endif default: dev->state = STATE_COMMAND; #ifdef FALLTHROUGH_ANNOTATION [[fallthrough]]; #endif case STATE_DATA: sermouse_timer(dev); break; } } /* Callback from serial driver: RTS was toggled. */ static void sermouse_callback(UNUSED(struct serial_s *serial), void *priv) { mouse_t *dev = (mouse_t *) priv; sermouse_reset(dev, 1); memcpy(dev->buf, dev->id, dev->id_len); sermouse_transmit(dev, dev->id_len, 0, 0); } static void ltsermouse_transmit_period(UNUSED(serial_t *serial), void *priv, double transmit_period) { mouse_t *dev = (mouse_t *) priv; dev->host_transmit_period = transmit_period; } static void sermouse_speed_changed(void *priv) { mouse_t *dev = (mouse_t *) priv; if (dev->cur_period != 0.0) sermouse_set_period(dev, dev->cur_period); } static void sermouse_close(void *priv) { mouse_t *dev = (mouse_t *) priv; /* Detach serial port from the mouse. */ if (dev && dev->serial && dev->serial->sd) memset(dev->serial->sd, 0, sizeof(serial_device_t)); free(dev); } /* Initialize the device for use by the user. */ static void * sermouse_init(const device_t *info) { mouse_t *dev; void (*rcr_callback)(struct serial_s *serial, void *p); void (*dev_write)(struct serial_s *serial, void *p, uint8_t data); void (*transmit_period_callback)(struct serial_s *serial, void *p, double transmit_period); dev = (mouse_t *) malloc(sizeof(mouse_t)); memset(dev, 0x00, sizeof(mouse_t)); dev->name = info->name; dev->but = device_get_config_int("buttons"); dev->rev = device_get_config_int("revision"); if (info->local == 0) dev->rts_toggle = 1; else dev->rts_toggle = device_get_config_int("rts_toggle"); if (dev->but > 2) dev->flags |= FLAG_3BTN; if (info->local == MOUSE_TYPE_MSYSTEMS) { dev->format = 0; dev->type = info->local; dev->id_len = 1; dev->id[0] = 'H'; } else { dev->format = 7; dev->status = 0x0f; dev->id_len = 1; dev->id[0] = 'M'; if (info->local) dev->rev = device_get_config_int("revision"); switch (dev->but) { default: case 2: dev->type = info->local ? MOUSE_TYPE_LOGITECH : MOUSE_TYPE_MICROSOFT; break; case 3: dev->type = info->local ? MOUSE_TYPE_LT3BUTTON : MOUSE_TYPE_MS3BUTTON; dev->id_len = 2; dev->id[1] = '3'; break; case 4: dev->type = MOUSE_TYPE_MSWHEEL; dev->id_len = 6; dev->id[1] = 'Z'; dev->id[2] = '@'; break; } } dev->port = device_get_config_int("port"); /* Attach a serial port to the mouse. */ rcr_callback = dev->rts_toggle ? sermouse_callback : NULL; dev_write = (info->local == 1) ? ltsermouse_write : NULL; transmit_period_callback = (info->local == 1) ? ltsermouse_transmit_period : NULL; dev->serial = serial_attach_ex(dev->port, rcr_callback, dev_write, transmit_period_callback, NULL, dev); mouse_serial_log("%s: port=COM%d\n", dev->name, dev->port + 1); timer_add(&dev->timer, sermouse_timer, dev, 0); /* The five second delay allows the mouse to execute internal initializations. */ sermouse_set_period(dev, 5000000.0); /* Tell them how many buttons we have. */ mouse_set_buttons((dev->flags & FLAG_3BTN) ? 3 : 2); /* Return our private data to the I/O layer. */ return dev; } static const device_config_t msssermouse_config[] = { // clang-format off { .name = "port", .description = "Serial Port", .type = CONFIG_SELECTION, .default_string = "", .default_int = 0, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "COM1", .value = 0 }, { .description = "COM2", .value = 1 }, { .description = "COM3", .value = 2 }, { .description = "COM4", .value = 3 }, { .description = "" } } }, { .name = "buttons", .description = "Buttons", .type = CONFIG_SELECTION, .default_string = "", .default_int = 2, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "Two", .value = 2 }, { .description = "Three", .value = 3 }, { .description = "" } } }, { .name = "rts_toggle", .description = "RTS toggle", .type = CONFIG_BINARY, .default_string = "", .default_int = 0 }, { .name = "", .description = "", .type = CONFIG_END } // clang-format on }; static const device_config_t mssermouse_config[] = { // clang-format off { .name = "port", .description = "Serial Port", .type = CONFIG_SELECTION, .default_string = "", .default_int = 0, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "COM1", .value = 0 }, { .description = "COM2", .value = 1 }, { .description = "COM3", .value = 2 }, { .description = "COM4", .value = 3 }, { .description = "" } } }, { .name = "buttons", .description = "Buttons", .type = CONFIG_SELECTION, .default_string = "", .default_int = 2, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "Two", .value = 2 }, { .description = "Three", .value = 3 }, { .description = "Wheel", .value = 4 }, { .description = "" } } }, { .name = "", .description = "", .type = CONFIG_END } // clang-format on }; static const device_config_t ltsermouse_config[] = { // clang-format off { .name = "port", .description = "Serial Port", .type = CONFIG_SELECTION, .default_string = "", .default_int = 0, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "COM1", .value = 0 }, { .description = "COM2", .value = 1 }, { .description = "COM3", .value = 2 }, { .description = "COM4", .value = 3 }, { .description = "" } } }, { .name = "buttons", .description = "Buttons", .type = CONFIG_SELECTION, .default_string = "", .default_int = 2, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "Two", .value = 2 }, { .description = "Three", .value = 3 }, { .description = "" } } }, { .name = "revision", .description = "Revision", .type = CONFIG_SELECTION, .default_string = "", .default_int = 3, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "LOGIMOUSE R7 1.0", .value = 1 }, { .description = "LOGIMOUSE R7 2.0", .value = 2 }, { .description = "LOGIMOUSE C7 3.0", .value = 3 }, { .description = "Logitech MouseMan", .value = 4 }, { .description = "" } } }, { .name = "rts_toggle", .description = "Microsoft-compatible RTS toggle", .type = CONFIG_BINARY, .default_string = "", .default_int = 0 }, { .name = "", .description = "", .type = CONFIG_END } // clang-format on }; const device_t mouse_mssystems_device = { .name = "Mouse Systems Serial Mouse", .internal_name = "mssystems", .flags = DEVICE_COM, .local = MOUSE_TYPE_MSYSTEMS, .init = sermouse_init, .close = sermouse_close, .reset = NULL, { .poll = sermouse_poll }, .speed_changed = sermouse_speed_changed, .force_redraw = NULL, .config = msssermouse_config }; const device_t mouse_msserial_device = { .name = "Microsoft Serial Mouse", .internal_name = "msserial", .flags = DEVICE_COM, .local = 0, .init = sermouse_init, .close = sermouse_close, .reset = NULL, { .poll = sermouse_poll }, .speed_changed = sermouse_speed_changed, .force_redraw = NULL, .config = mssermouse_config }; const device_t mouse_ltserial_device = { .name = "Logitech Serial Mouse", .internal_name = "ltserial", .flags = DEVICE_COM, .local = 1, .init = sermouse_init, .close = sermouse_close, .reset = NULL, { .poll = sermouse_poll }, .speed_changed = sermouse_speed_changed, .force_redraw = NULL, .config = ltsermouse_config };