/* * VBMouse - DOS mouse driver exec entry point * Copyright (C) 2022 Javier S. Pedro * * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "kitten.h" #include "version.h" #include "dlog.h" #include "int33.h" #include "int21dos.h" #include "int15ps2.h" #include "serial.h" #include "sermouse.h" #include "vbox.h" #include "vmware.h" #include "dostsr.h" #include "mousetsr.h" static nl_catd cat; #if USE_WHEEL static bool detect_ps2_wheel(LPTSRDATA data) { // Do a quick check for a mouse wheel here. // The TSR will do its own check when it is reset anyway if (ps2m_detect_imps2()) { if (ps2m_detect_imex()) { data->num_wheels = 2; data->num_buttons = 5; } else { data->num_wheels = 1; data->num_buttons = 3; } return true; } else { data->num_wheels = 0; data->num_buttons = 3; return false; } } static int set_wheel(LPTSRDATA data, bool enable) { printf(_(1, 1, "Setting wheel support to %s\n"), enable ? _(1, 2, "enabled") : _(1, 3, "disabled")); data->usewheel = enable; if (data->usewheel) { if (data->port == 0) { if (!detect_ps2_wheel(data)) { fprintf(stderr, _(3, 0, "Could not find PS/2 wheel mouse\n")); } } else { // TODO Serial does not support wheels yet fprintf(stderr, _(3, 0, "Could not find PS/2 wheel mouse\n")); } } else { // Force num_wheels to 0 even before the next mouse reset data->num_wheels = 0; } return 0; } static void set_wheel_keys(LPTSRDATA data, int wheel, uint8_t scancode_up, uint8_t scancode_down) { data->wheel_key[wheel][WHEEL_DIR_UP] = scancode_up; data->wheel_key[wheel][WHEEL_DIR_DOWN] = scancode_down; } static int set_wheel_key(LPTSRDATA data, const char *keyname) { if (!data->usewheel) { fprintf(stderr, _(3, 1, "Wheel not detected or support not enabled\n")); return EXIT_FAILURE; } if (keyname) { if (stricmp(keyname, "updn") == 0) { set_wheel_keys(data, 0, 0x48, 0x50); printf(_(1, 4, "Generate Up Arrow / Down Arrow key presses on wheel movement\n")); } else if (stricmp(keyname, "pageupdn") == 0) { set_wheel_keys(data, 0, 0x49, 0x51); printf(_(1, 5, "Generate PageUp / PageDown key presses on wheel movement\n")); } else { fprintf(stderr, _(3, 2, "Unknown key '%s'\n"), keyname); return EXIT_FAILURE; } } else { printf(_(1, 6, "Disabling wheel keystroke generation\n")); set_wheel_keys(data, 0, 0, 0); } return EXIT_SUCCESS; } static int set_hwheel_key(LPTSRDATA data, const char *keyname) { if (!data->usewheel) { fprintf(stderr, _(3, 1, "Wheel not detected or support not enabled\n")); return EXIT_FAILURE; } if (keyname) { if (stricmp(keyname, "lr") == 0) { set_wheel_keys(data, 1, 0x4B, 0x4D); printf(_(1, 4, "Generate Up Arrow / Down Arrow key presses on wheel movement\n")); } else { fprintf(stderr, _(3, 2, "Unknown key '%s'\n"), keyname); return EXIT_FAILURE; } } else { printf(_(1, 6, "Disabling wheel keystroke generation\n")); set_wheel_keys(data, 1, 0, 0); } return EXIT_SUCCESS; } #endif /* USE_WHEEL */ #if USE_VIRTUALBOX static int set_virtualbox_integration(LPTSRDATA data, bool enable, bool verbose) { if (enable) { int err; data->vbavail = false; // Reinitialize it even if already enabled err = vbox_init_device(&data->vb); if (err) { if (verbose) fprintf(stderr, _(3, 3, "Cannot find VirtualBox PCI device, err=%d\n"), err); return err; } err = vbox_init_buffer(&data->vb, VBOX_BUFFER_SIZE); if (err) { if (verbose) fprintf(stderr, _(3, 4, "Cannot lock buffer used for VirtualBox communication, err=%d\n"), err); return err; } err = vbox_report_guest_info(&data->vb, VBOXOSTYPE_DOS); if (err) { if (verbose) fprintf(stderr, _(3, 5, "VirtualBox communication is not working, err=%d\n"), err); return err; } printf(_(1, 7, "VirtualBox integration enabled\n")); data->vbavail = true; data->vbhaveabs = true; } else { if (data->vbavail) { vbox_set_mouse(&data->vb, false, false); vbox_release_buffer(&data->vb); printf(_(1, 8, "Disabled VirtualBox integration\n")); data->vbavail = false; data->vbhaveabs = false; } else { if (verbose) printf(_(1, 9, "VirtualBox integration already disabled or not available\n")); } } return 0; } static int set_virtualbox_host_cursor(LPTSRDATA data, bool enable) { printf(_(1, 10, "Setting host cursor to %s\n"), enable ? _(1, 2, "enabled") : _(1, 3, "disabled")); data->vbwantcursor = enable; return 0; } #endif #if USE_VMWARE static int set_vmware_integration(LPTSRDATA data, bool enable, bool verbose) { if (enable) { int32_t version; uint32_t status; uint16_t data_avail; data->vmwavail = false; version = vmware_get_version(); if (version < 0) { if (verbose) fprintf(stderr, _(3, 6, "Could not detect VMware, err=%ld\n"), version); return -1; } if (verbose) printf(_(1, 11, "Found VMware protocol version %ld\n"), version); vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_ENABLE); status = vmware_abspointer_status(); if ((status & VMWARE_ABSPOINTER_STATUS_MASK_ERROR) == VMWARE_ABSPOINTER_STATUS_MASK_ERROR) { fprintf(stderr, _(3, 7, "VMware absolute pointer error, err=0x%lx\n"), status & VMWARE_ABSPOINTER_STATUS_MASK_ERROR); return -1; } vmware_abspointer_data_clear(); // TSR part will enable the absolute mouse when reset printf(_(1, 12, "VMware integration enabled\n")); data->vmwavail = true; } else { if (data->vmwavail) { vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_REQUEST_RELATIVE); vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_DISABLE); data->vmwavail = false; printf(_(1, 13, "Disabled VMware integration\n")); } else { if (verbose) printf(_(1, 14, "VMware integration already disabled or not available\n")); } } return 0; } #endif static int set_integration(LPTSRDATA data, bool enable, bool verbose) { if (enable) { int err = -1; #if USE_VIRTUALBOX // First check if we can enable the VirtualBox integration, // since it's a PCI device it's easier to check if it's not present err = set_virtualbox_integration(data, true, verbose); if (!err) return 0; #endif #if USE_VMWARE // Afterwards try VMWare integration err = set_vmware_integration(data, true, verbose); if (!err) return 0; #endif printf(_(1, 15, "Neither VirtualBox nor VMware integration available\n")); return err; } else { #if USE_VIRTUALBOX if (data->vbavail) { set_virtualbox_integration(data, false, verbose); } #endif #if USE_VMWARE if (data->vmwavail) { set_vmware_integration(data, false, verbose); } #endif return 0; } } static int set_host_cursor(LPTSRDATA data, bool enable) { #if USE_VIRTUALBOX if (data->vbavail) { return set_virtualbox_host_cursor(data, enable); } #endif printf(_(1, 16, "VirtualBox integration not available\n")); return -1; } static int configure_driver_ps2(LPTSRDATA data) { int err; // Check for PS/2 mouse BIOS availability if ((err = ps2m_init(PS2M_PACKET_SIZE_STD))) { return err; } // OK, if BIOS gives no error, assume PS/2 mouse is present and use it data->port = 0; data->port_io = 0; data->port_irq = 0; #if USE_WHEEL // Let's utilize the wheel by default data->usewheel = true; detect_ps2_wheel(data); #else // Without ImEX/wheel detection, hardcode number of buttons data->num_buttons = 3; data->num_wheels = 0; #endif #if USE_INTEGRATION // Enable integration by default set_integration(data, true, false); #endif #if USE_VIRTUALBOX // Assume initially that we want host cursor data->vbwantcursor = data->vbavail; #endif return 0; } #if USE_SERIAL static int configure_driver_serial(LPTSRDATA data, int port, unsigned iobase) { struct serial_config serconfig; unsigned delay; char sig = 0; if (!iobase) { dprintf("Skip search on COM%u because no IO base found\n", port); return -1; } #if ENABLE_DLOG && DLOG_TARGET == DLOG_TARGET_SERIAL if (iobase == DLOG_TARGET_PORT) { dprintf("Skip search on COM%u because it would overlap with dlog %x\n", port, iobase); return -2; } #endif dprintf("Detecting mouse on COM%d, iobase %xh\n", port, iobase); serial_save_config(iobase, &serconfig); serial_configure_interrupts(iobase, SERIAL_IER_NONE); // disable interrupts for now // 1200 bps, word length = 7, parity = none serial_configure_line(iobase, SERIAL_DIVISOR_1200, SERIAL_LCR_WL_7|SERIAL_LCR_PAR_NONE); serial_set_fifo_control(iobase, SERIAL_FCR_FIFO_RESET|SERIAL_FCR_FIFO_ENABLE); // Enables DRT, but clears RTS, so that mouse enters reset state serial_set_modem_control(iobase, SERIAL_MCR_DTR); dputs("DTR ON, RTS OFF"); // Wait for 3 ticks (100-150ms) for mouse to power off bda_wait_ticks(3); // Now turn on the mouse by enabling RTS serial_set_modem_control(iobase, SERIAL_MCR_DTR | SERIAL_MCR_RTS); dputs("DTR ON, RTS ON"); // Wait up to 10 ticks (550ms) for the mouse to send something we understand for (delay = 10; delay > 0; delay--) { while (serial_data_ready(iobase)) { uint8_t car = serial_read_data(iobase); // Is this something we recognize ? dprintf("serial data after reset: 0x%hx '%c'\n", car, car); if (car == 'M') { // Ok, got the MS signature, break out of this wait sig = car; break; } } if (sig) break; bda_wait_tick(); dprintf("remaining ticks %u\n", delay); } switch (sig) { case 'M': // We have a valid MS-style serial mouse data->port = port; data->device_id = SERMOUSE_DEVICE_ID_MS; data->port_io = iobase; data->port_irq = serial_get_irq(port); data->num_buttons = 2; data->num_wheels = 0; data->packet_size = 3; // Wait for an extended signature for about 2 ticks (50-100ms) sig = 0; for (delay = 2; delay > 0; delay--) { while (serial_data_ready(iobase)) { uint8_t car = serial_read_data(iobase); dprintf("serial data after 'M': 0x%hx '%c'\n", car, car); if (car == '3' || car == 'Z') { sig = car; break; } } if (sig) break; bda_wait_tick(); dprintf("2nd loop remaining ticks %u\n", delay); } // Update the mouse details based on the extended signature // We don't change the device ID since we treat all the same. switch (sig) { case '3': data->packet_size = 4; data->num_buttons = 3; break; case 'Z': data->packet_size = 4; data->num_buttons = 3; #if USE_WHEEL data->num_wheels = 1; #endif break; } // In any case, we got ourselves a mouse. return 0; } // No mouse found on this port. serial_restore_config(iobase, &serconfig); return -3; } #endif /// Auto-configure the TSR. Try to detect which mouse we have connected and enable all possible integrations. static int configure_driver(LPTSRDATA data, unsigned num_ports, int ports[]) { int errs[MAX_PORTS+1] = {0}; unsigned portidx; bool found_mouse = false; // Configure the debug logging port dlog_init(); // Search for a mouse for (portidx = 0; portidx < num_ports; portidx++) { int port = ports[portidx]; if (port == 0) { // PS/2 int err = configure_driver_ps2(data); if (!err) { printf(_(1, 0, "Found PS/2 mouse with %u buttons, %u wheels\n"), data->num_buttons, data->num_wheels); found_mouse = true; break; } else { // Remember error to print out later only if no mouse was found errs[portidx] = err; } } else { #if USE_SERIAL int err = configure_driver_serial(data, port, serial_get_iobase(port)); if (!err) { printf(_(1, 22, "Found serial mouse on COM%u with %u buttons, %u wheels\n"), port, data->num_buttons, data->num_wheels); found_mouse = true; break; } else { errs[portidx] = err; } #endif } } if (!found_mouse) { // If we are here, we have neither serial nor PS/2. // Print all error messages now. for (portidx = 0; portidx < num_ports; portidx++) { int port = ports[portidx]; int err = errs[portidx]; if (err) { if (port == 0) { fprintf(stderr, _(3, 8, "Cannot init PS/2 mouse BIOS, err=%d\n"), err); } else { fprintf(stderr, _(3, 15, "Cannot find mouse in COM%u, err=%d\n"), port, err); } } } fprintf(stderr, _(3, 14, "No mouse found\n")); return -1; } // Otherwise, we found a mouse, go ahead with installation // No more interruptions from now on and until we TSR. // Inserting ourselves in the interrupt chain should be atomic. _disable(); // Hook hardware interrupts. Which interrupts depends on which mouse was found. data->prev_irq3_4_handler = 0; #if USE_SERIAL if (data->port_irq) { dprintf("Hooking irq%hu (int%x) for serial\n", data->port_irq, 0x8 + data->port_irq); data->prev_irq3_4_handler = _dos_getvect(0x8 + data->port_irq); _dos_setvect(0x8 + data->port_irq, data:>irq3_4_isr); } #endif return 0; } static int move_driver_to_umb(LPTSRDATA __far * data) { segment_t cur_seg = FP_SEG(*data); segment_t umb_seg = reallocate_to_umb(cur_seg, get_resident_size() + DOS_PSP_SIZE); if (umb_seg) { // Update the data pointer with the new segment *data = MK_FP(umb_seg, FP_OFF(*data)); return 0; } else { return -1; } } static __declspec(aborts) int install_driver(LPTSRDATA data, bool high) { const unsigned int resident_size = DOS_PSP_SIZE + get_resident_size(); _disable(); // Hook main interrupts data->prev_int33_handler = _dos_getvect(0x33); _dos_setvect(0x33, data:>int33_isr); #if USE_WIN386 data->prev_int2f_handler = _dos_getvect(0x2f); _dos_setvect(0x2f, data:>int2f_isr); #else data->prev_int2f_handler = 0; #endif printf(_(1, 17, "Driver installed\n")); // If we reallocated ourselves to UMB, // it's time to free our initial conventional memory allocation if (high) { finish_reallocation(_psp, FP_SEG(data)); } kittenclose(); _dos_keep(EXIT_SUCCESS, get_paragraphs(resident_size)); // Shouldn't reach this part return EXIT_FAILURE; } static bool check_if_driver_uninstallable(LPTSRDATA data) { // Compare the segment of the installed handler to see if its ours // or someone else's if (data->prev_int33_handler) { void (__interrupt __far *cur_int33_handler)() = _dos_getvect(0x33); if (FP_SEG(cur_int33_handler) != FP_SEG(data)) { fprintf(stderr, _(3, 9, "INT%X has been hooked by someone else, cannot safely remove\n"), 0x33); return false; } } if (data->port_irq && data->prev_irq3_4_handler) { void (__interrupt __far *cur_irq3_4_handler)() = _dos_getvect(0x8 + data->port_irq); if (FP_SEG(cur_irq3_4_handler) != FP_SEG(data)) { fprintf(stderr, _(3, 9, "INT%X has been hooked by someone else, cannot safely remove\n"), 0x8 + data->port_irq); return false; } } if (data->prev_int2f_handler) { void (__interrupt __far *cur_int2f_handler)() = _dos_getvect(0x2f); if (FP_SEG(cur_int2f_handler) != FP_SEG(data)) { fprintf(stderr, _(3, 9, "INT%X has been hooked by someone else, cannot safely remove\n"), 0x2F); return false; } } return true; } static void unconfigure_driver(LPTSRDATA data) { if (data->port == 0) { // PS/2 mode // Turn off PS2 BIOS, and remove callback ps2m_enable(false); ps2m_set_callback(0); #if USE_INTEGRATION set_integration(data, false, false); #endif } else { #if USE_SERIAL // Serial mode uint16_t iobase = data->port_io; // Disable interrupts, and clear RTS/DTS, which should completely power down mouse serial_configure_interrupts(iobase, 0); serial_set_modem_control(iobase, 0); // TODO Should I mask the serial IRQ on the PIC too? #endif } // Unhook only configured hardware-related interrupts _disable(); if (data->port_irq && data->prev_irq3_4_handler) { _dos_setvect(0x8 + data->port_irq, data->prev_irq3_4_handler); data->prev_irq3_4_handler = 0; } _enable(); } static void uninstall_driver(LPTSRDATA data) { // Unhook all interrupts _disable(); if (data->prev_int33_handler) { _dos_setvect(0x33, data->prev_int33_handler); data->prev_int33_handler = 0; } if (data->port_irq && data->prev_irq3_4_handler) { _dos_setvect(0x8 + data->port_irq, data->prev_irq3_4_handler); data->prev_irq3_4_handler = 0; } if (data->prev_int2f_handler) { _dos_setvect(0x2f, data->prev_int2f_handler); data->prev_int2f_handler = 0; } _enable(); // Find and deallocate the PSP (including the entire program), // it is always 256 bytes (16 paragraphs) before the TSR segment dos_free(FP_SEG(data) - (DOS_PSP_SIZE/16)); printf(_(1, 18, "Driver uninstalled\n")); } static int driver_reset(void) { printf(_(1, 19, "Reset mouse driver\n")); return int33_reset() == INT33_MOUSE_FOUND; } static int driver_not_found(void) { fprintf(stderr, _(3, 11, "Driver data not found (driver not installed?)\n")); return EXIT_FAILURE; } static void print_help(void) { putchar('\n'); puts(_(0, 0, "Usage: ")); puts(_(0, 1, " VBMOUSE ")); putchar('\n'); puts(_(0, 2, "Supported actions and options:")); puts(_(0, 3, " install Install the driver (default).")); puts(_(0, 4, " low Install in conventional memory (otherwise UMB).")); #if USE_SERIAL puts(_(0, 5, " ps2, com1, com2, ... Specify on which ports to search a mouse.")); #endif puts(_(0, 6, " uninstall Uninstall the driver from memory.")); #if USE_SERIAL puts(_(0, 7, " rescan Re-scan for mouse in specified ports.")); #endif #if USE_WHEEL puts(_(0, 8, " wheel Enable/disable wheel API support.")); puts(_(0, 9, " wheelkey Emulate a specific keystroke on wheel scroll.")); puts(_(0, 10, " Supported keys: updn, pageupdn.")); #if USE_IMEX puts(_(0, 11, " Supported keys: updn, pageupdn.")); puts(_(0, 12, " Supported keys: updn, pageupdn.")); #endif #endif #if USE_INTEGRATION puts(_(0, 13, " integ Enable/disable VirtualBox integration.")); puts(_(0, 14, " hostcur Enable/disable mouse cursor rendering in the host.")); #endif puts(_(0, 15, " reset Reset mouse driver.")); } static int invalid_arg(const char *s) { fprintf(stderr, _(3, 12, "Invalid argument '%s'\n"), s); print_help(); return EXIT_FAILURE; } static int arg_required(const char *s) { fprintf(stderr, _(3, 13, "Argument required for '%s'\n"), s); print_help(); return EXIT_FAILURE; } static bool is_true(const char *s) { return stricmp(s, "yes") == 0 || stricmp(s, "y") == 0 || stricmp(s, "on") == 0 || stricmp(s, "true") == 0 || stricmp(s, "enabled") == 0 || stricmp(s, "enable") == 0 || stricmp(s, "1") == 0; } static bool is_false(const char *s) { return stricmp(s, "no") == 0 || stricmp(s, "n") == 0 || stricmp(s, "off") == 0 || stricmp(s, "false") == 0 || stricmp(s, "disabled") == 0 || stricmp(s, "disable") == 0 || stricmp(s, "0") == 0; } static int parse_port(const char *s) { if (stricmp(s, "ps2") == 0) { return 0; #if USE_SERIAL } else if (strnicmp(s, "com", 3) == 0 && s[4] == '\0' && s[3] >= '0' && s[3] <= '9') { // "COMn" string return s[3] - '0'; #endif } else { return -1; } } static int default_port_order(int ports[]) { const unsigned num_com_ports = MIN(serial_num_ports(), MAX_PORTS); unsigned portidx = 0, port; if (ps2m_installed()) ports[portidx++] = 0; // 1st. PS2 // 2nd. COMn in order for (port = 1; port <= num_com_ports; port++) { ports[portidx++] = port; } return portidx; } int main(int argc, const char *argv[]) { LPTSRDATA data = get_tsr_data(true); int err, argi = 1; cat = kittenopen("vbmouse"); printf(_(1, 20, "\nVBMouse %x.%x (reporting as MSMOUSE %x.%x)\n"), VERSION_MAJOR, VERSION_MINOR, REPORTED_VERSION_MAJOR, REPORTED_VERSION_MINOR); if (argi >= argc || stricmp(argv[argi], "install") == 0) { bool high = true; int portidx = 0; int ports[1+MAX_PORTS] = {-1}; argi++; for (; argi < argc; argi++) { int port; if (stricmp(argv[argi], "low") == 0) { high = false; } else if (stricmp(argv[argi], "high") == 0) { high = true; } else if ((port = parse_port(argv[argi])) >= 0) { if (portidx > MAX_PORTS) return invalid_arg(argv[argi]); if (port < 0 || port > MAX_PORTS) return invalid_arg(argv[argi]); ports[portidx++] = port; } else { return invalid_arg(argv[argi]); } } if (!portidx) { // User has not specified custom search order, so init with default portidx = default_port_order(ports); } if (data) { printf(_(1, 21, "VBMouse already installed\n")); print_help(); return EXIT_SUCCESS; } data = get_tsr_data(false); if (high) { err = move_driver_to_umb(&data); if (err) high = false; // Not fatal } else { deallocate_environment(_psp); } err = configure_driver(data, portidx, ports); if (err) { if (high) cancel_reallocation(FP_SEG(data)); return EXIT_FAILURE; } return install_driver(data, high); } else if (stricmp(argv[argi], "uninstall") == 0) { if (!data) return driver_not_found(); if (!check_if_driver_uninstallable(data)) { return EXIT_FAILURE; } unconfigure_driver(data); uninstall_driver(data); return EXIT_SUCCESS; } else if (stricmp(argv[argi], "rescan") == 0) { int portidx = 0, port; int ports[1+MAX_PORTS] = {-1}; argi++; for (; argi < argc; argi++) { int port; if ((port = parse_port(argv[argi])) >= 0) { if (portidx > MAX_PORTS) return invalid_arg(argv[argi]); if (port < 0 || port > MAX_PORTS) return invalid_arg(argv[argi]); ports[portidx++] = port; } else { return invalid_arg(argv[argi]); } } if (!portidx) { portidx = default_port_order(ports); } // Unhook the driver if (!data) return driver_not_found(); if (!check_if_driver_uninstallable(data)) { return EXIT_FAILURE; } unconfigure_driver(data); // Configure the driver again err = configure_driver(data, portidx, ports); if (err) { return EXIT_FAILURE; } // Force a reset of the driver driver_reset(); return EXIT_SUCCESS; #if USE_WHEEL } else if (stricmp(argv[argi], "wheel") == 0) { bool enable = true; if (!data) return driver_not_found(); argi++; if (argi < argc) { if (is_false(argv[argi])) enable = false; } return set_wheel(data, enable); } else if (stricmp(argv[argi], "wheelkey") == 0) { bool enable = true; const char *key = 0; if (!data) return driver_not_found(); argi++; if (argi < argc) { if (is_false(argv[argi])) enable = false; else key = argv[argi]; } if (enable) { if (!key) return arg_required("wheelkey"); return set_wheel_key(data, key); } else { return set_wheel_key(data, 0); } } else if (stricmp(argv[argi], "hwheelkey") == 0) { bool enable = true; const char *key = 0; if (!data) return driver_not_found(); argi++; if (argi < argc) { if (is_false(argv[argi])) enable = false; else key = argv[argi]; } if (enable) { if (!key) return arg_required("hwheelkey"); return set_hwheel_key(data, key); } else { return set_hwheel_key(data, 0); } #endif #if USE_INTEGRATION } else if (stricmp(argv[argi], "integ") == 0) { bool enable = true; if (!data) return driver_not_found(); argi++; if (argi < argc) { if (is_false(argv[argi])) enable = false; } return set_integration(data, enable, true); } else if (stricmp(argv[argi], "hostcur") == 0) { bool enable = true; if (!data) return driver_not_found(); argi++; if (argi < argc) { if (is_false(argv[argi])) enable = false; } // Reset before changing this to ensure the cursor is not drawn int33_reset(); return set_host_cursor(data, enable); #endif } else if (stricmp(argv[argi], "reset") == 0) { return driver_reset(); } else { return invalid_arg(argv[argi]); } }