From 970f90228a6978712c28529437721caffec76202 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 9 Apr 2022 14:55:06 +0200 Subject: rename source files for consistency --- dosmain.c | 646 -------------------------- dosmouse.lnk | 9 - dostsr.c | 1468 ---------------------------------------------------------- dostsr.h | 217 --------- makefile | 21 +- mousetsr.c | 1468 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ mousetsr.h | 217 +++++++++ mousew16.c | 187 ++++++++ mousew16.h | 53 +++ mousew16.lnk | 11 + mousmain.c | 646 ++++++++++++++++++++++++++ vbmouse.lnk | 9 + w16mouse.c | 187 -------- w16mouse.h | 53 --- w16mouse.lnk | 11 - 15 files changed, 2602 insertions(+), 2601 deletions(-) delete mode 100644 dosmain.c delete mode 100644 dosmouse.lnk delete mode 100644 dostsr.c delete mode 100644 dostsr.h create mode 100644 mousetsr.c create mode 100644 mousetsr.h create mode 100644 mousew16.c create mode 100644 mousew16.h create mode 100644 mousew16.lnk create mode 100644 mousmain.c create mode 100644 vbmouse.lnk delete mode 100644 w16mouse.c delete mode 100644 w16mouse.h delete mode 100644 w16mouse.lnk diff --git a/dosmain.c b/dosmain.c deleted file mode 100644 index 9a802c6..0000000 --- a/dosmain.c +++ /dev/null @@ -1,646 +0,0 @@ -/* - * 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 - -#include "dlog.h" -#include "int33.h" -#include "int21dos.h" -#include "ps2.h" -#include "vbox.h" -#include "vmware.h" -#include "dostsr.h" - -#if USE_WHEEL -static void detect_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 (data->haswheel = ps2m_detect_wheel()) { - printf("Wheel mouse found and enabled\n"); - } -} - -static int set_wheel(LPTSRDATA data, bool enable) -{ - printf("Setting wheel support to %s\n", enable ? "enabled" : "disabled"); - data->usewheel = enable; - - if (data->usewheel) { - detect_wheel(data); - } else { - data->haswheel = false; - } - - return 0; -} - -static int set_wheel_key(LPTSRDATA data, const char *keyname) -{ - if (!data->usewheel || !data->haswheel) { - fprintf(stderr, "Wheel not detected or support not enabled\n"); - return EXIT_FAILURE; - } - if (keyname) { - if (stricmp(keyname, "updn") == 0) { - data->wheel_up_key = 0x4800; - data->wheel_down_key = 0x5000; - printf("Generate Up Arrow / Down Arrow key presses on wheel movement\n"); - } else if (stricmp(keyname, "pageupdn") == 0) { - data->wheel_up_key = 0x4900; - data->wheel_down_key = 0x5100; - printf("Generate PageUp / PageDown key presses on wheel movement\n"); - } else { - fprintf(stderr, "Unknown key '%s'\n", keyname); - return EXIT_FAILURE; - } - } else { - printf("Disabling wheel keystroke generation\n"); - data->wheel_up_key = 0; - data->wheel_down_key = 0; - } - return EXIT_SUCCESS; -} -#endif /* USE_WHEEL */ - -#if USE_VIRTUALBOX -static int set_virtualbox_integration(LPTSRDATA data, bool enable) -{ - if (enable) { - int err; - - data->vbavail = false; // Reinitialize it even if already enabled - - err = vbox_init_device(&data->vb); - if (err) { - fprintf(stderr, "Cannot find VirtualBox PCI device, err=%d\n", err); - return err; - } - - printf("Found VirtualBox device at IO 0x%x\n", data->vb.iobase); - - err = vbox_init_buffer(&data->vb); - if (err) { - fprintf(stderr, "Cannot lock buffer used for VirtualBox communication, err=%d\n", err); - return err; - } - - err = vbox_report_guest_info(&data->vb, VBOXOSTYPE_DOS); - if (err) { - fprintf(stderr, "VirtualBox communication is not working, err=%d\n", err); - return err; - } - - printf("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("Disabled VirtualBox integration\n"); - data->vbavail = false; - data->vbhaveabs = false; - } else { - printf("VirtualBox integration already disabled or not available\n"); - } - } - - return 0; -} - -static int set_virtualbox_host_cursor(LPTSRDATA data, bool enable) -{ - printf("Setting host cursor to %s\n", enable ? "enabled" : "disabled"); - data->vbwantcursor = enable; - - return 0; -} -#endif - -#if USE_VMWARE -static int set_vmware_integration(LPTSRDATA data, bool enable) -{ - if (enable) { - int32_t version; - uint32_t status; - uint16_t data_avail; - - data->vmwavail = false; - - version = vmware_get_version(); - if (version < 0) { - fprintf(stderr, "Could not detect VMware, err=%ld\n", version); - return -1; - } - - printf("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, "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("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("Disabled VMware integration\n"); - } else { - printf("VMware integration already disabled or not available\n"); - } - } - - return 0; -} -#endif - -static int set_integration(LPTSRDATA data, bool enable) -{ - 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); - if (!err) return 0; -#endif - -#if USE_VMWARE - // Afterwards try VMWare integration - err = set_vmware_integration(data, true); - if (!err) return 0; -#endif - - printf("Neither VirtualBox nor VMware integration available\n"); - return err; - } else { -#if USE_VIRTUALBOX - if (data->vbavail) { - set_virtualbox_integration(data, false); - } -#endif -#if USE_VMWARE - if (data->vmwavail) { - set_vmware_integration(data, false); - } -#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("VirtualBox integration not available\n"); - return -1; -} - -static int configure_driver(LPTSRDATA data) -{ - int err; - - // Configure the debug logging port - dlog_init(); - - // Check for PS/2 mouse BIOS availability - if ((err = ps2m_init(PS2M_PACKET_SIZE_PLAIN))) { - fprintf(stderr, "Cannot init PS/2 mouse BIOS, err=%d\n", err); - // Can't do anything without PS/2 - return err; - } - -#if USE_WHEEL - // Let's utilize the wheel by default - data->usewheel = true; - data->wheel_up_key = 0; - data->wheel_down_key = 0; - detect_wheel(data); -#endif - -#if USE_INTEGRATION - // Enable integration by default - set_integration(data, true); -#endif - -#if USE_VIRTUALBOX - // Assume initially that we want host cursor - data->vbwantcursor = data->vbavail; -#endif - - return 0; -} - -/** Converts bytes to MS-DOS "paragraphs" (16 bytes), rounding up. */ -static inline unsigned get_paragraphs(unsigned bytes) -{ - return (bytes + 15) / 16; -} - -/** Gets the size of the resident part of this program, including the PSP. */ -static inline unsigned get_resident_program_size() -{ - return get_resident_size() + DOS_PSP_SIZE; -} - -/** Deallocates the environment block from the passed PSP segment. */ -static void deallocate_environment(__segment psp) -{ - // TODO : Too lazy to make PSP struct; - // 0x2C is offsetof the environment block field on the PSP - uint16_t __far *envblockP = (uint16_t __far *) MK_FP(psp, 0x2C); - dos_free(*envblockP); - *envblockP = 0; -} - -/** Copies a program to another location. - * @param new_seg PSP segment for the new location - * @param old_seg PSP segment for the old location - * @param size size of the program to copy including PSP size. */ -static void copy_program(__segment new_seg, __segment old_seg, unsigned size) -{ - // The MCB is always 1 segment before. - uint8_t __far *new_mcb = MK_FP(new_seg - 1, 0); - uint8_t __far *old_mcb = MK_FP(old_seg - 1, 0); - uint16_t __far *new_mcb_owner = (uint16_t __far *) &new_mcb[1]; - char __far *new_mcb_owner_name = &new_mcb[8]; - char __far *old_mcb_owner_name = &old_mcb[8]; - - // Copy entire resident segment including PSP - _fmemcpy(MK_FP(new_seg, 0), MK_FP(old_seg, 0), size); - - // Make the new MCB point to itself as owner - *new_mcb_owner = new_seg; - - // Copy the program name, too. - _fmemcpy(new_mcb_owner_name, old_mcb_owner_name, 8); -} - -/** Allocates a UMB of the given size. - * If no UMBs are available, this may still return a block in conventional memory. */ -static __segment allocate_umb(unsigned size) -{ - bool old_umb_link = dos_query_umb_link_state(); - unsigned int old_strategy = dos_query_allocation_strategy(); - __segment new_segment; - - dos_set_umb_link_state(true); - dos_set_allocation_strategy(DOS_FIT_BEST | DOS_FIT_HIGHONLY); - - new_segment = dos_alloc(get_paragraphs(size)); - - dos_set_umb_link_state(old_umb_link); - dos_set_allocation_strategy(old_strategy); - - return new_segment; -} - -static int reallocate_to_umb(LPTSRDATA __far * data) -{ - const unsigned int resident_size = get_resident_program_size(); - LPTSRDATA old_data = *data; - __segment old_psp_segment = FP_SEG(old_data) - (DOS_PSP_SIZE/16); - __segment new_psp_segment; - - deallocate_environment(_psp); - - // If we are already in UMA, don't bother - if (old_psp_segment >= 0xA000) { - return -1; - } - - new_psp_segment = allocate_umb(resident_size); - - if (new_psp_segment && new_psp_segment >= 0xA000) { - __segment new_segment = new_psp_segment + (DOS_PSP_SIZE/16); - printf("Moving to upper memory\n"); - - // Create a new program instance including PSP at the new_segment - copy_program(new_psp_segment, old_psp_segment, resident_size); - - // Tell DOS to "switch" to the new program - dos_set_psp(new_psp_segment); - - // Now update the data pointer to the new segment - *data = MK_FP(new_segment, FP_OFF(old_data)); - - return 0; - } else { - printf("No upper memory available\n"); - if (new_psp_segment) { - // In case we got another low-memory segment... - dos_free(new_psp_segment); - } - - return -1; - } -} - -static __declspec(aborts) int install_driver(LPTSRDATA data, bool high) -{ - const unsigned int resident_size = DOS_PSP_SIZE + get_resident_size(); - - // No more interruptions from now on and until we TSR. - // Inserting ourselves in the interrupt chain should be atomic. - _disable(); - - 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); -#endif - - printf("Driver installed\n"); - - // If we reallocated ourselves to UMB, - // it's time to free our initial conventional memory allocation - if (high) { - // We are about to free() our own code segment. - // Nothing should try to allocate memory between this and the TSR call - // below, since it could overwrite our code... - dos_free(_psp); - } - - _dos_keep(EXIT_SUCCESS, get_paragraphs(resident_size)); - - // Shouldn't reach this part - return EXIT_FAILURE; -} - -static bool check_if_driver_uninstallable(LPTSRDATA data) -{ - void (__interrupt __far *cur_int33_handler)() = _dos_getvect(0x33); - - // Compare the segment of the installed handler to see if its ours - // or someone else's - if (FP_SEG(cur_int33_handler) != FP_SEG(data)) { - fprintf(stderr, "INT33 has been hooked by someone else, cannot safely remove\n"); - return false; - } - -#if USE_WIN386 - { - void (__interrupt __far *cur_int2f_handler)() = _dos_getvect(0x2f); - - if (FP_SEG(cur_int2f_handler) != FP_SEG(data)) { - fprintf(stderr, "INT2F has been hooked by someone else, cannot safely remove\n"); - return false; - } - } -#endif - - return true; -} - -static int unconfigure_driver(LPTSRDATA data) -{ -#if USE_INTEGRATION - set_integration(data, false); -#endif - - ps2m_enable(false); - ps2m_set_callback(0); - - return 0; -} - -static int uninstall_driver(LPTSRDATA data) -{ - _dos_setvect(0x33, data->prev_int33_handler); - -#if USE_WIN386 - _dos_setvect(0x2f, data->prev_int2f_handler); -#endif - - // 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("Driver uninstalled\n"); - - return 0; -} - -static int driver_reset(void) -{ - printf("Reset mouse driver\n"); - return int33_reset() == 0xFFFF; -} - -static int driver_not_found(void) -{ - fprintf(stderr, "Driver data not found (driver not installed?)\n"); - return EXIT_FAILURE; -} - -static void print_help(void) -{ - printf("\n" - "Usage: \n" - " VBMOUSE \n\n" - "Supported actions:\n" - " install install the driver (default)\n" - " low install in conventional memory (otherwise UMB)\n" - " uninstall uninstall the driver from memory\n" -#if USE_WHEEL - " wheel enable/disable wheel API support\n" - " wheelkey emulate a specific keystroke on wheel scroll\n" - " supported keys: updn, pageupdn\n" -#endif -#if USE_INTEGRATION - " integ enable/disable virtualbox integration\n" - " hostcur enable/disable mouse cursor rendering in host\n" -#endif - " reset reset mouse driver settings\n" - ); -} - -static int invalid_arg(const char *s) -{ - fprintf(stderr, "Invalid argument '%s'\n", s); - print_help(); - return EXIT_FAILURE; -} - -static int arg_required(const char *s) -{ - fprintf(stderr, "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; -} - -int main(int argc, const char *argv[]) -{ - LPTSRDATA data = get_tsr_data(true); - int err, argi = 1; - - printf("\nVBMouse %x.%x (like 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; - - argi++; - for (; argi < argc; argi++) { - if (stricmp(argv[argi], "low") == 0) { - high = false; - } else if (stricmp(argv[argi], "high") == 0) { - high = true; - } else { - return invalid_arg(argv[argi]); - } - } - - if (data) { - printf("VBMouse already installed\n"); - print_help(); - return EXIT_SUCCESS; - } - - data = get_tsr_data(false); - if (high) { - err = reallocate_to_umb(&data); - if (err) high = false; // Not fatal - } else { - deallocate_environment(_psp); - } - err = configure_driver(data); - if (err) 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; - } - err = unconfigure_driver(data); - if (err) { - return EXIT_FAILURE; - } - return uninstall_driver(data); -#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); - } -#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); - } 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]); - } -} diff --git a/dosmouse.lnk b/dosmouse.lnk deleted file mode 100644 index bc1596f..0000000 --- a/dosmouse.lnk +++ /dev/null @@ -1,9 +0,0 @@ -system dos -option map=dosmouse.map -# Put the resident text & data first, then the rest of standard classses -order clname RES_CODE - clname FAR_DATA - clname CODE segment BEGTEXT segment _TEXT - clname BEGDATA - clname DATA - clname BSS diff --git a/dostsr.c b/dostsr.c deleted file mode 100644 index a9319d3..0000000 --- a/dostsr.c +++ /dev/null @@ -1,1468 +0,0 @@ -/* - * VBMouse - DOS mouse driver resident part - * 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 "dlog.h" -#include "ps2.h" -#include "int10vga.h" -#include "int16kbd.h" -#include "int2fwin.h" -#include "int33.h" -#include "vbox.h" -#include "vmware.h" -#include "dostsr.h" - -#define MSB_MASK 0x8000U - -TSRDATA data; - -static const uint16_t default_cursor_graphic[] = { - 0x3FFF, 0x1FFF, 0x0FFF, 0x07FF, - 0x03FF, 0x01FF, 0x00FF, 0x007F, - 0x003F, 0x001F, 0x01FF, 0x00FF, - 0x30FF, 0xF87F, 0xF87F, 0xFCFF, - 0x0000, 0x4000, 0x6000, 0x7000, - 0x7800, 0x7C00, 0x7E00, 0x7F00, - 0x7F80, 0x7C00, 0x6C00, 0x4600, - 0x0600, 0x0300, 0x0300, 0x0000 -}; - -/** Constraint current mouse position to the user-set window. */ -static void bound_position_to_window(void) -{ - if (data.pos.x < data.min.x) data.pos.x = data.min.x; - if (data.pos.x > data.max.x) data.pos.x = data.max.x; - if (data.pos.y < data.min.y) data.pos.y = data.min.y; - if (data.pos.y > data.max.y) data.pos.y = data.max.y; -} - -/** Constraints coordinate value to the desired granularity, - * which must be a power of two. */ -static inline int16_t snap_to_grid(int16_t val, int16_t granularity) -{ - // Build a bitmask that masks away the undesired low-order bits - return val & (-granularity); -} - -static void hide_text_cursor(void) -{ - // Restore the character under the old position of the cursor - uint16_t __far *ch = get_video_char(&data.video_mode, - data.cursor_pos.x / 8, data.cursor_pos.y / 8); - *ch = data.cursor_prev_char; - data.cursor_visible = false; -} - -static void show_text_cursor(void) -{ - uint16_t __far *ch = get_video_char(&data.video_mode, - data.pos.x / 8, data.pos.y / 8); - data.cursor_prev_char = *ch; - data.cursor_pos = data.pos; - *ch = (*ch & data.cursor_text_and_mask) ^ data.cursor_text_xor_mask; - data.cursor_visible = true; -} - -/** Given the new position of the cursor, compute the both our source - * (i.e. the mouse cursor shape) and target clipping areas. - * @param cursor_pos cursor position - * @param start top left corner of clipping box in screen - * @param size size of the clipping box in screen as well as cursor shape - * @param offset top left corner of clipping box in mouse cursor shape */ -static bool get_graphic_cursor_area(struct point __far *cursor_pos, - struct point __far *start, - struct point __far *size, - struct point __far *offset) -{ - start->x = (cursor_pos->x / data.screen_scale.x) - data.cursor_hotspot.x; - start->y = (cursor_pos->y / data.screen_scale.y) - data.cursor_hotspot.y; - size->x = GRAPHIC_CURSOR_WIDTH; - size->y = GRAPHIC_CURSOR_HEIGHT; - offset->x = 0; - offset->y = 0; - - // Start clipping around - // Cursor is left/top of visible area - if (start->x <= -size->x) { - return false; - } else if (start->x < 0) { - offset->x += -start->x; - size->x -= -start->x; - start->x = 0; - } - if (start->y <= -size->y) { - return false; - } else if (start->y < 0) { - offset->y += -start->y; - size->y -= -start->y; - start->y = 0; - } - // Cursor is right/bottom of visible area - if (start->x > data.video_mode.pixels_width) { - return false; // Don't render cursor - } else if (start->x + size->x > data.video_mode.pixels_width) { - size->x -= (start->x + size->x) - data.video_mode.pixels_width; - } - if (start->y > data.video_mode.pixels_height) { - return false; - } else if (start->y + size->y > data.video_mode.pixels_height) { - size->y -= (start->y + size->y) - data.video_mode.pixels_height; - } - - return true; -} - -static inline uint8_t * get_prev_graphic_cursor_scanline(unsigned bytes_per_line, - unsigned num_lines, - unsigned plane, - unsigned y) -{ - return &data.cursor_prev_graphic[((plane * num_lines) + y) * bytes_per_line]; -} - -static inline uint16_t get_graphic_cursor_and_mask_line(unsigned y) -{ - return data.cursor_graphic[y]; -} - -static inline uint16_t get_graphic_cursor_xor_mask_line(unsigned y) -{ - return data.cursor_graphic[GRAPHIC_CURSOR_HEIGHT + y]; -} - -/** Compute the total number of bytes between start and end pixels, - * rounding up as necessary to cover all bytes. */ -static inline unsigned get_scanline_segment_bytes(unsigned bits_per_pixel, unsigned start, unsigned size) -{ - // Get starting byte (round down) - unsigned start_byte = (start * bits_per_pixel) / 8; - // Get end byte (round up) - unsigned end_byte = (((start + size) * bits_per_pixel) + (8-1)) / 8; - - return end_byte - start_byte; -} - -/** Creates a bitmask that extracts the topmost N bits of a byte. */ -static inline uint8_t build_pixel_mask(unsigned bits_per_pixel) -{ - if (bits_per_pixel == 1) return 0x80; - else if (bits_per_pixel == 2) return 0xC0; - else if (bits_per_pixel == 4) return 0xF0; - else return 0xFF; -} - -/** Hides the graphical mouse cursor, by restoring the contents of - * data.cursor_prev_graphic (i.e. what was below the cursor before we drew it) - * to video memory. */ -static void hide_graphic_cursor(void) -{ - struct modeinfo *info = &data.video_mode; - struct point start, size, offset; - unsigned cursor_bytes_per_line; - unsigned plane, y; - - // Compute the area where the cursor is currently positioned - if (!get_graphic_cursor_area(&data.cursor_pos, &start, &size, &offset)) { - return; - } - - // For each scanline, we will copy this amount of bytes - cursor_bytes_per_line = get_scanline_segment_bytes(info->bits_per_pixel, - start.x, size.x); - - for (plane = 0; plane < info->num_planes; plane++) { - if (info->num_planes > 1) vga_select_plane(plane); - - for (y = 0; y < size.y; y++) { - uint8_t __far *line = get_video_scanline(info, start.y + y) - + (start.x * info->bits_per_pixel) / 8; - uint8_t *prev = get_prev_graphic_cursor_scanline(cursor_bytes_per_line, size.y, - plane, y); - - // Restore this scaline from cursor_prev - _fmemcpy(line, prev, cursor_bytes_per_line); - } - } - - data.cursor_visible = false; -} - -/** Renders the graphical cursor. - * It will also backup whatever pixels are below - * the cursor area to cursor_prev_graphic. */ -static void show_graphic_cursor(void) -{ - const struct modeinfo *info = &data.video_mode; - struct point start, size, offset; - unsigned cursor_bytes_per_line; - const uint8_t msb_pixel_mask = build_pixel_mask(info->bits_per_pixel); - unsigned plane, y; - - // Compute the area where the cursor is supposed to be drawn - if (!get_graphic_cursor_area(&data.pos, &start, &size, &offset)) { - return; - } - - // For each scanline, we will copy this amount of bytes - cursor_bytes_per_line = get_scanline_segment_bytes(info->bits_per_pixel, - start.x, size.x); - - for (plane = 0; plane < info->num_planes; plane++) { - if (info->num_planes > 1) vga_select_plane(plane); - - for (y = 0; y < size.y; y++) { - uint8_t __far *line = get_video_scanline(info, start.y + y) - + (start.x * info->bits_per_pixel) / 8; - uint8_t *prev = get_prev_graphic_cursor_scanline(cursor_bytes_per_line, size.y, - plane, y); - uint16_t cursor_and_mask = get_graphic_cursor_and_mask_line(offset.y + y) - << offset.x; - uint16_t cursor_xor_mask = get_graphic_cursor_xor_mask_line(offset.y + y) - << offset.x; - unsigned x; - - // First, backup this scanline to prev before any changes - _fmemcpy(prev, line, cursor_bytes_per_line); - - if (info->bits_per_pixel < 8) { - uint8_t pixel_mask = msb_pixel_mask; - uint8_t pixel = *line; - - // when start.x is not pixel aligned, - // scaline points the previous multiple of pixels_per_byte; - // and the initial pixel will not be at the MSB of it. - // advance the pixel_mask accordingly - pixel_mask >>= (start.x * info->bits_per_pixel) % 8; - - for (x = 0; x < size.x; x++) { - // The MSBs of each mask correspond to the current pixel - if (!(cursor_and_mask & MSB_MASK)) { - pixel &= ~pixel_mask; - } - if (cursor_xor_mask & MSB_MASK) { - pixel ^= pixel_mask; - } - - // Advance to the next pixel - pixel_mask >>= info->bits_per_pixel; - if (!pixel_mask) { - // Time to advance to the next byte - *line = pixel; // Save current byte first - pixel = *(++line); - pixel_mask = msb_pixel_mask; - } - - // Advance to the next bit in the cursor mask - cursor_and_mask <<= 1; - cursor_xor_mask <<= 1; - } - - if (pixel_mask != msb_pixel_mask) { - // We ended up in the middle of a byte, save it - *line = pixel; - } - } else if (info->bits_per_pixel == 8) { - // Simplified version for byte-aligned pixels - for (x = 0; x < size.x; x++) { - uint8_t pixel = 0; - - if (cursor_and_mask & MSB_MASK) { - pixel = *line; - } - if (cursor_xor_mask & MSB_MASK) { - pixel ^= 0x0F; // Use 0x0F as "white pixel" - } - - // Advance to the next pixel - *line = pixel; - ++line; - - // Advance to the next bit in the cursor mask - cursor_and_mask <<= 1; - cursor_xor_mask <<= 1; - } - } - } - } - - data.cursor_pos = data.pos; - data.cursor_visible = true; -} - -/** Refreshes cursor position and visibility. */ -static void refresh_cursor(void) -{ - bool should_show = data.visible_count >= 0; - bool pos_changed, needs_refresh; - -#if USE_WIN386 - // Windows 386 is already rendering the cursor for us. - // Hide our own. - if (data.w386cursor) should_show = false; -#endif - -#if USE_VIRTUALBOX - if (data.vbavail && data.vbwantcursor) { - // We want to use the VirtualBox host cursor. - // See if we have to update its visibility. - int err = 0; - if (should_show != data.cursor_visible) { - err = vbox_set_pointer_visible(&data.vb, should_show); - if (err == 0 && data.vbhaveabs) { - data.cursor_visible = should_show; - } - } - if (err == 0 && data.vbhaveabs) { - // No need to refresh the cursor; VirtualBox is already showing it for us. - return; - } - } -#endif - - pos_changed = data.cursor_pos.x != data.pos.x || data.cursor_pos.y != data.pos.y; - needs_refresh = should_show && pos_changed || should_show != data.cursor_visible; - - if (!needs_refresh) { - // Nothing to do - return; - } - - if (data.video_mode.type == VIDEO_TEXT) { - // Text video mode - if (data.cursor_visible) { - // Hide the cursor at the old position if any - hide_text_cursor(); - } - if (should_show) { - // Show the cursor at the new position - show_text_cursor(); - } - } else if (data.video_mode.type != VIDEO_UNKNOWN) { - // Graphic video modes - - bool video_planar = data.video_mode.num_planes > 1; - struct videoregs regs; - // If current video mode is planar, - // we will have to play with the VGA registers - // so let's save and restore them. - if (video_planar) { - vga_save_registers(®s); - vga_set_graphics_mode(®s, 0, 0); - } - - if (data.cursor_visible) { - hide_graphic_cursor(); - } - if (should_show) { - show_graphic_cursor(); - } - - if (video_planar) { - vga_restore_register(®s); - } - } else { - // Unknown video mode, don't render cursor. - } -} - -/** Forcefully hides the mouse cursor if shown. */ -static void hide_cursor(void) -{ -#if USE_VIRTUALBOX - if (data.vbavail && data.vbwantcursor) { - vbox_set_pointer_visible(&data.vb, false); - if (data.vbhaveabs) { - data.cursor_visible = false; - } - } -#endif - - if (data.cursor_visible) { - if (data.video_mode.type == VIDEO_TEXT) { - hide_text_cursor(); - } else if (data.video_mode.type != VIDEO_UNKNOWN) { - hide_graphic_cursor(); - } - } -} - -/** Loads the current graphic cursor, - * which in this case means uploading it to the host. */ -static void load_cursor(void) -{ -#if USE_VIRTUALBOX - if (data.vbavail && data.vbwantcursor) { - VMMDevReqMousePointer *req = (VMMDevReqMousePointer *) data.vb.buf; - const unsigned width = GRAPHIC_CURSOR_WIDTH, height = GRAPHIC_CURSOR_HEIGHT; - uint8_t *output = req->pointerData; - unsigned int y, x; - - memset(req, 0, sizeof(VMMDevReqMousePointer)); - - req->header.size = vbox_req_mouse_pointer_size(width, height); - req->header.version = VMMDEV_REQUEST_HEADER_VERSION; - req->header.requestType = VMMDevReq_SetPointerShape; - req->header.rc = -1; - - req->fFlags = VBOX_MOUSE_POINTER_SHAPE; - req->xHot = BOUND(data.cursor_hotspot.x, 0, width); - req->yHot = BOUND(data.cursor_hotspot.y, 0, height); - req->width = width; - req->height = height; - - // AND mask - // int33 format is 1-bit per pixel packed into 16-bit LE values, - // while VirtualBox wants 1-bit per pixel packed into 8-bit. - // All we have to do is byteswap 16-bit values. - for (y = 0; y < height; ++y) { - uint16_t cursor_line = get_graphic_cursor_and_mask_line(y); - output[0] = (cursor_line >> 8) & 0xFF; - output[1] = cursor_line & 0xFF; - output += GRAPHIC_CURSOR_SCANLINE_LEN; - } - - // XOR mask - // int33 format is again 1-bit per pixel packed into 16-bit LE values, - // however VirtualBox wants 4-byte per pixel packed "RGBA". - for (y = 0; y < height; ++y) { - uint16_t cursor_line = get_graphic_cursor_xor_mask_line(y); - - for (x = 0; x < width; ++x) { - // MSB of line is current mask bit, we shift it on each iteration - uint8_t val = (cursor_line & MSB_MASK) ? 0xFF : 0; - - output[0] = val; - output[1] = val; - output[2] = val; - output[3] = 0; - - cursor_line <<= 1; - output += 4; - } - } - - dlog_puts("Loading cursor to VBox"); - - vbox_send_request(data.vb.iobase, data.vb.dds.physicalAddress); - - if (req->header.rc != 0) { - dlog_puts("Could not send cursor to VirtualBox"); - return; - } - - // After we send this message, it looks like VirtualBox shows the cursor - // even if we didn't actually want it to be visible at this point. - vbox_set_pointer_visible(&data.vb, false); - } -#endif -} - -/** Reloads the information about the current video mode. */ -static void reload_video_info(void) -{ - get_current_video_mode_info(&data.video_mode); - - data.screen_max.x = data.video_mode.pixels_width - 1; - data.screen_max.y = data.video_mode.pixels_height - 1; - data.screen_scale.x = 1; - data.screen_scale.y = 1; - data.screen_granularity.x = 1; - data.screen_granularity.y = 1; - - // The actual range of coordinates expected by int33 clients - // is, for some reason, different than real resolution in some modes. - // For example, 320x... modes are mapped to 640x... pixels. - if (data.video_mode.pixels_width == 320) { - data.screen_max.x = 640 - 1; - data.screen_scale.x = 640 / 320; - } - - // In text modes, we are supposed to always round the mouse cursor - // to character boundaries. - if (data.video_mode.type == VIDEO_TEXT) { - // Always assume 8x8 character size irregardless of true font - data.screen_granularity.x = 8; - data.screen_granularity.y = 8; - } - - dlog_print("Current video mode="); - dlog_printx(data.video_mode.mode); - dlog_print(" screen_max="); - dlog_printd(data.screen_max.x); - dlog_putc(','); - dlog_printd(data.screen_max.y); - dlog_endline(); -} - -/** True if the current video is different from what we have stored - * and we probably need to recompute video mode info. */ -static inline bool video_mode_changed(void) -{ - uint8_t cur_mode = bda_get_video_mode() & ~0x80; - - if (cur_mode != data.video_mode.mode) - return true; - - if (data.video_mode.type == VIDEO_TEXT) { - // Also check to see if the font size was changed.. - return data.video_mode.pixels_height != (bda_get_last_row()+1) * 8; - } else { - return false; - } -} - -/** Checks if the video mode has changed and if so - * refreshes the information about the current video mode. */ -static void refresh_video_info(void) -{ - if (video_mode_changed()) { - if (data.cursor_visible) { - // Assume cursor is lost with no way to restore prev contents - data.cursor_visible = false; - } - - reload_video_info(); - - if (data.video_mode.type != VIDEO_UNKNOWN) { - // If we know the screen size for this mode, then reset the window to it - data.min.x = 0; - data.min.y = 0; - data.max.x = data.screen_max.x; - data.max.y = data.screen_max.y; - } - } -} - -/** Calls the application-registered event handler. */ -static void call_event_handler(void (__far *handler)(), uint16_t events, - uint16_t buttons, int16_t x, int16_t y, - int16_t delta_x, int16_t delta_y) -{ -#if TRACE_EVENTS - dlog_print("calling event handler events="); - dlog_printx(events); - dlog_print(" buttons="); - dlog_printx(buttons); - dlog_print(" x="); - dlog_printd(x); - dlog_print(" y="); - dlog_printd(y); - dlog_print(" dx="); - dlog_printd(delta_x); - dlog_print(" dy="); - dlog_printd(delta_y); - dlog_endline(); -#endif - - __asm { - mov ax, [events] - mov bx, [buttons] - mov cx, [x] - mov dx, [y] - mov si, [delta_x] - mov di, [delta_y] - - call dword ptr [handler] - } -} - -/** Process a mouse event internally. - * @param buttons currently pressed buttons as a bitfield - * @param absolute whether mouse coordinates are an absolute value - * @param x y if absolute, then absolute coordinates in screen pixels - * if relative, then relative coordinates in mickeys - * @param z relative wheel mouse movement - */ -static void handle_mouse_event(uint16_t buttons, bool absolute, int x, int y, int z) -{ - uint16_t events = 0; - int i; - -#if TRACE_EVENTS - dlog_print("handle mouse event"); - if (absolute) dlog_print(" absolute"); - dlog_print(" buttons="); - dlog_printx(buttons); - dlog_print(" x="); - dlog_printd(x); - dlog_print(" y="); - dlog_printd(y); - dlog_print(" z="); - dlog_printd(z); - dlog_endline(); -#endif - - if (absolute) { - // Absolute movement: x,y are in screen pixels units - events |= INT33_EVENT_MASK_ABSOLUTE; - - if (x != data.pos.x || y != data.pos.y) { - events |= INT33_EVENT_MASK_MOVEMENT; - - // Simulate a fake relative movement in mickeys - // This is almost certainly broken. - // Programs that expect relative movement data - // will almost never set a mickeyPerPixel value. - // So all we can do is guess. - if (data.abs_pos.x >= 0 && data.abs_pos.y >= 0) { - data.delta.x += (x - data.abs_pos.x) * 8; - data.delta.y += (y - data.abs_pos.y) * 8; - } - data.abs_pos.x = x; - data.abs_pos.y = y; - - // Set the new absolute position - data.pos.x = x; - data.pos.y = y; - data.pos_frac.x = 0; - data.pos_frac.y = 0; - } - } else if (x || y) { - // Relative movement: x,y are in mickeys - uint16_t ticks = bda_get_tick_count_lo(); - unsigned ax = abs(x), ay = abs(y); - - events |= INT33_EVENT_MASK_MOVEMENT; - - // Check if around one second has passed - if ((ticks - data.last_ticks) >= 18) { - data.total_motion = 0; - data.last_ticks = ticks; - } - - // If more than the double speed threshold has been moved in the last second, - // double the speed - data.total_motion += ax * ax + ay * ay; - if (data.total_motion > data.doubleSpeedThreshold * data.doubleSpeedThreshold) { - x *= 2; - y *= 2; - } - - data.delta.x += x; - data.delta.y += y; - data.delta_frac.x = 0; - data.delta_frac.y = 0; - data.abs_pos.x = -1; - data.abs_pos.y = -1; - - // Convert mickeys into pixels - data.pos.x += scalei_rem(x, data.mickeysPerLine.x, 8, &data.pos_frac.x); - data.pos.y += scalei_rem(y, data.mickeysPerLine.y, 8, &data.pos_frac.y); - } - - bound_position_to_window(); - -#if USE_WHEEL - if (data.haswheel && z) { - if (!data.usewheelapi && (data.wheel_up_key || data.wheel_down_key)) { - // Emulate keystrokes on wheel movement - if (z < 0 && data.wheel_up_key) { - for (; z < 0; z++) { - int16_store_keystroke(data.wheel_up_key); - } - } else if (z > 0 && data.wheel_up_key) { - for (; z > 0; z--) { - int16_store_keystroke(data.wheel_down_key); - } - } - } else { - events |= INT33_EVENT_MASK_WHEEL_MOVEMENT; - // Higher byte of buttons contains wheel movement - buttons |= (z & 0xFF) << 8; - // Accumulate delta wheel movement - data.wheel_delta += z; - data.wheel_last.x = data.pos.x; - data.wheel_last.y = data.pos.y; - } - } -#endif - - // Update button status - for (i = 0; i < NUM_BUTTONS; ++i) { - uint8_t btn = 1 << i; - uint8_t evt = 0; - if ((buttons & btn) && !(data.buttons & btn)) { - // Button pressed - evt = 1 << (1 + (i * 2)); // Press event mask - data.button[i].pressed.count++; - data.button[i].pressed.last.x = data.pos.x; - data.button[i].pressed.last.y = data.pos.y; - } else if (!(buttons & btn) && (data.buttons & btn)) { - // Button released - evt = 1 << (2 + (i * 2)); // Release event mask - data.button[i].released.count++; - data.button[i].released.last.x = data.pos.x; - data.button[i].released.last.y = data.pos.y; - } - events |= evt; - } - data.buttons = buttons; - - refresh_cursor(); - - events &= data.event_mask; - if (data.event_handler && events) { - x = snap_to_grid(data.pos.x, data.screen_granularity.x); - y = snap_to_grid(data.pos.y, data.screen_granularity.y); - - call_event_handler(data.event_handler, events, - buttons, x, y, data.delta.x, data.delta.y); - } -} - -/** PS/2 BIOS calls this routine to notify mouse events. */ -static void ps2_mouse_handler(uint16_t word1, uint16_t word2, uint16_t word3, uint16_t word4) -{ -#pragma aux ps2_mouse_handler "*" parm caller [ax] [bx] [cx] [dx] modify [ax bx cx dx si di es fs gs] - - unsigned status; - int x, y, z; - bool abs = false; - -#if TRACE_EVENTS - dlog_print("ps2 callback "); - dlog_printx(word1); - dlog_putc(' '); - dlog_printx(word2); - dlog_putc(' '); - dlog_printx(word3); - dlog_putc(' '); - dlog_printx(word4); - dlog_endline(); -#endif /* TRACE_EVENTS */ - - // Decode the PS2 event args - // In a normal IBM PS/2 BIOS (incl. VirtualBox/Bochs/qemu/SeaBIOS): - // word1 low byte = status (following PS2M_STATUS_*) - // word2 low byte = x - // word3 low byte = y - // word4 = always zero - // In a PS/2 BIOS with wheel support (incl. VMware/DOSBox-X): - // taken from CuteMouse/KoKo: - // word1 high byte = x - // word1 low byte = status - // word2 low byte = y - // word3 low byte = z - // word4 = always zero - // VirtualBox/Bochs/qemu/SeaBIOS behave like a normal one, - // but they also store the raw contents of all mouse packets in the EBDA (starting 0x28 = packet 0). - // Other BIOSes don't do that so it is not a reliable option either. - // So, how to detect which BIOS we have? - // For now we are always assuming "normal" PS/2 BIOS. - // But with VirtualBox integration on we'll get the wheel packet from the EBDA, - // and with VMWare integration on we'll get it from the VMware protocol. - status = (uint8_t) word1; - x = (uint8_t) word2; - y = (uint8_t) word3; - z = 0; - (void) word4; - - // Sign-extend X, Y as per the status byte - x = status & PS2M_STATUS_X_NEG ? 0xFF00 | x : x; - y = -(status & PS2M_STATUS_Y_NEG ? 0xFF00 | y : y); - -#if USE_VIRTUALBOX - if (data.vbavail) { - uint16_t vbx, vby; - if ((vbox_get_mouse(&data.vb, &abs, &vbx, &vby) == 0) && abs) { - // VirtualBox gives unsigned coordinates from 0...0xFFFFU, - // scale to 0..screen_size (in pixels). - refresh_video_info(); - // If the user is using a window larger than the screen, use it. - x = scaleu(vbx, 0xFFFFU, MAX(data.max.x, data.screen_max.x)); - y = scaleu(vby, 0xFFFFU, MAX(data.max.y, data.screen_max.y)); - data.vbhaveabs = true; - } else { - // VirtualBox does not support absolute coordinates, - // or user has disabled them. - data.vbhaveabs = false; - // Rely on PS/2 relative coordinates. - } - -#if USE_WHEEL - // VirtualBox/Bochs BIOS does not pass wheel data to the callback, - // so we will fetch it directly from the BIOS data segment. - if (data.haswheel) { - int8_t __far * mouse_packet = MK_FP(bda_get_ebda_segment(), 0x28); - z = mouse_packet[3]; - } -#endif - } -#endif /* USE_VIRTUALBOX */ - -#if USE_VMWARE - if (data.vmwavail) { - uint32_t vmwstatus = vmware_abspointer_status(); - uint16_t data_avail = vmwstatus & VMWARE_ABSPOINTER_STATUS_MASK_DATA; - -#if TRACE_EVENTS - dlog_print("vmware status=0x"); - dlog_printx(vmwstatus >> 16); - dlog_print(" "); - dlog_printx(vmwstatus & 0xFFFF); - dlog_endline(); -#endif - - if (data_avail >= VMWARE_ABSPOINTER_DATA_PACKET_SIZE) { - struct vmware_abspointer_data vmw; - vmware_abspointer_data(VMWARE_ABSPOINTER_DATA_PACKET_SIZE, &vmw); - -#if TRACE_EVENTS - dlog_print("vmware pstatus=0x"); - dlog_printx(status); - dlog_print(" x=0x"); - dlog_printx(vmw.x); - dlog_print(" z="); - dlog_printd((int8_t) (uint8_t) vmw.z); - dlog_endline(); -#endif - - if (vmw.status & VMWARE_ABSPOINTER_STATUS_RELATIVE) { - x = (int16_t) vmw.x; - y = (int16_t) vmw.y; - z = (int8_t) (uint8_t) vmw.z; - } else { - abs = true; - // Scale to screen coordinates - refresh_video_info(); - x = scaleu(vmw.x & 0xFFFFU, 0xFFFFU, - MAX(data.max.x, data.screen_max.x)); - y = scaleu(vmw.y & 0xFFFFU, 0xFFFFU, - MAX(data.max.y, data.screen_max.y)); - z = (int8_t) (uint8_t) vmw.z; - } - - if (vmw.status & VMWARE_ABSPOINTER_STATUS_BUTTON_LEFT) { - status |= PS2M_STATUS_BUTTON_1; - } - if (vmw.status & VMWARE_ABSPOINTER_STATUS_BUTTON_RIGHT) { - status |= PS2M_STATUS_BUTTON_2; - } - if (vmw.status & VMWARE_ABSPOINTER_STATUS_BUTTON_MIDDLE) { - status |= PS2M_STATUS_BUTTON_3; - } - } else { - return; // Ignore the PS/2 packet otherwise, it is likely garbage - } - } -#endif /* USE_VMWARE */ - - handle_mouse_event(status & (PS2M_STATUS_BUTTON_1 | PS2M_STATUS_BUTTON_2 | PS2M_STATUS_BUTTON_3), - abs, x, y, z); -} - -void __declspec(naked) __far ps2_mouse_callback() -{ - __asm { - pusha - push ds - push es - push fs - push gs - - ; 8 + 4 saved registers, 24 bytes - ; plus 4 bytes for retf address - ; = 28 bytes of stack before callback args - - mov bp, sp - push cs - pop ds - - mov ax,[bp+28+6] ; Status - mov bx,[bp+28+4] ; X - mov cx,[bp+28+2] ; Y - mov dx,[bp+28+0] ; Z - - call ps2_mouse_handler - - pop gs - pop fs - pop es - pop ds - popa - - retf - } -} - -#if USE_INTEGRATION -static void set_absolute(bool enable) -{ -#if USE_VIRTUALBOX - data.vbhaveabs = false; - if (data.vbavail) { - int err = vbox_set_mouse(&data.vb, enable, false); - if (enable && !err) { - dlog_puts("VBox absolute mouse enabled"); - data.vbhaveabs = true; - } else if (!enable) { - dlog_puts("VBox absolute mouse disabled"); - } - } -#endif /* USE_VIRTUALBOX */ -#if USE_VMWARE - if (data.vmwavail) { - if (enable) { - uint16_t data_avail; - // It looks like a reset of the PS/2 mouse completely disables - // the vmware interface, so we have to reenable it from scratch. - vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_ENABLE); - vmware_abspointer_data_clear(); - vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_REQUEST_ABSOLUTE); - - dlog_puts("VMware absolute mouse enabled"); - } else { - vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_REQUEST_RELATIVE); - vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_DISABLE); - - dlog_puts("VMware absolute mouse disabled"); - } - } -#endif /* USE_VMWARE */ -} -#endif /* USE_INTEGRATION */ - -static void reset_mouse_hardware() -{ - ps2m_enable(false); - -#if USE_WHEEL - if (data.usewheel && ps2m_detect_wheel()) { - // Detect wheel also reinitializes the mouse to the proper packet size - data.haswheel = true; - } else { - // Otherwise do an extra reset to return back to initial state, just in case - data.haswheel = false; - ps2m_init(PS2M_PACKET_SIZE_PLAIN); - } -#else - ps2m_init(PS2M_PACKET_SIZE_PLAIN); -#endif - - ps2m_set_resolution(3); // 3 = 200 dpi, 8 counts per millimeter - ps2m_set_sample_rate(4); // 4 = 80 reports per second - ps2m_set_scaling_factor(1); // 1 = 1:1 scaling - - ps2m_set_callback(get_cs():>ps2_mouse_callback); - -#if USE_INTEGRATION - // By default, enable absolute mouse - set_absolute(true); - // Reload hardware cursor - load_cursor(); -#endif - - ps2m_enable(true); -} - -/** Reset "software" mouse settings, i.e. those configurable by the client program. */ -static void reset_mouse_settings() -{ - data.event_mask = 0; - data.event_handler = 0; - - data.mickeysPerLine.x = 8; - data.mickeysPerLine.y = 16; - data.doubleSpeedThreshold = 64; - data.min.x = 0; - data.max.x = data.screen_max.x; - data.min.y = 0; - data.max.y = data.screen_max.y; - data.visible_count = -1; - data.cursor_text_type = 0; - data.cursor_text_and_mask = 0xFFFFU; - data.cursor_text_xor_mask = 0x7700U; - data.cursor_hotspot.x = 0; - data.cursor_hotspot.y = 0; - memcpy(data.cursor_graphic, default_cursor_graphic, sizeof(data.cursor_graphic)); - -#if USE_WHEEL - data.usewheelapi = false; -#endif - - refresh_cursor(); // This will hide the cursor and update data.cursor_visible -} - -/** Reset the current mouse state and throw away past events. */ -static void reset_mouse_state() -{ - int i; - data.pos.x = data.min.x; - data.pos.y = data.min.y; - data.pos_frac.x = 0; - data.pos_frac.y = 0; - data.delta.x = 0; - data.delta.y = 0; - data.delta_frac.x = 0; - data.delta_frac.y = 0; - data.abs_pos.x = -1; - data.abs_pos.y = -1; - data.buttons = 0; - for (i = 0; i < NUM_BUTTONS; i++) { - data.button[i].pressed.count = 0; - data.button[i].pressed.last.x = 0; - data.button[i].pressed.last.y = 0; - data.button[i].released.count = 0; - data.button[i].released.last.x = 0; - data.button[i].released.last.y = 0; - } - data.wheel_delta = 0; - data.cursor_visible = false; - data.cursor_pos.x = 0; - data.cursor_pos.y = 0; - data.cursor_prev_char = 0; - memset(data.cursor_prev_graphic, 0, sizeof(data.cursor_prev_graphic)); -} - -static void return_clear_wheel_counter(union INTPACK __far *r) -{ - r->x.cx = snap_to_grid(data.wheel_last.x, data.screen_granularity.x); - r->x.dx = snap_to_grid(data.wheel_last.y, data.screen_granularity.y); - r->x.bx = data.wheel_delta; - data.wheel_last.x = 0; - data.wheel_last.y = 0; - data.wheel_delta = 0; -} - -static void return_clear_button_counter(union INTPACK __far *r, struct buttoncounter *c) -{ - r->x.cx = snap_to_grid(c->last.x, data.screen_granularity.x); - r->x.dx = snap_to_grid(c->last.y, data.screen_granularity.y); - r->x.bx = c->count; - c->last.x = 0; - c->last.y = 0; - c->count = 0; -} - -/** Entry point for our int33 API. */ -static void int33_handler(union INTPACK r) -#pragma aux int33_handler "*" parm caller [] modify [ax bx cx dx si di es fs gs] -{ - switch (r.x.ax) { - case INT33_RESET_MOUSE: - dlog_puts("Mouse reset"); - reload_video_info(); - reset_mouse_settings(); - reset_mouse_hardware(); - reset_mouse_state(); - r.x.ax = INT33_MOUSE_FOUND; - r.x.bx = NUM_BUTTONS; - break; - case INT33_SHOW_CURSOR: - data.visible_count++; - refresh_cursor(); - break; - case INT33_HIDE_CURSOR: - data.visible_count--; - refresh_cursor(); - break; - case INT33_GET_MOUSE_POSITION: -#if TRACE_EVENTS - dlog_puts("Mouse get position"); -#endif - r.x.cx = snap_to_grid(data.pos.x, data.screen_granularity.x); - r.x.dx = snap_to_grid(data.pos.y, data.screen_granularity.y); - r.x.bx = data.buttons; -#if USE_WHEEL - if (data.haswheel) { - r.h.bh = data.wheel_delta; - data.wheel_delta = 0; - } -#endif - break; - case INT33_SET_MOUSE_POSITION: -#if TRACE_EVENTS - dlog_puts("Mouse set position"); -#endif - data.pos.x = r.x.cx; - data.pos.y = r.x.dx; - data.pos_frac.x = 0; - data.pos_frac.y = 0; - data.delta.x = 0; - data.delta.y = 0; - data.delta_frac.x = 0; - data.delta_frac.y = 0; - bound_position_to_window(); - break; - case INT33_GET_BUTTON_PRESSED_COUNTER: - r.x.ax = data.buttons; -#if USE_WHEEL - if (data.haswheel) { - r.h.bh = data.wheel_delta; - if (r.x.bx == -1) { - // Asked for wheel information - return_clear_wheel_counter(&r); - break; - } - } -#endif - // Regular button information - return_clear_button_counter(&r, - &data.button[MIN(r.x.bx, NUM_BUTTONS - 1)].pressed); - break; - case INT33_GET_BUTTON_RELEASED_COUNTER: - r.x.ax = data.buttons; -#if USE_WHEEL - if (data.haswheel) { - r.h.bh = data.wheel_delta; - if (r.x.bx == -1) { - // Asked for wheel information - return_clear_wheel_counter(&r); - break; - } - } -#endif - return_clear_button_counter(&r, - &data.button[MIN(r.x.bx, NUM_BUTTONS - 1)].released); - break; - case INT33_SET_HORIZONTAL_WINDOW: - dlog_print("Mouse set horizontal window ["); - dlog_printd(r.x.cx); - dlog_putc(','); - dlog_printd(r.x.dx); - dlog_puts("]"); - // Recheck in case someone changed the video mode - refresh_video_info(); - data.min.x = r.x.cx; - data.max.x = r.x.dx; - bound_position_to_window(); - break; - case INT33_SET_VERTICAL_WINDOW: - dlog_print("Mouse set vertical window ["); - dlog_printd(r.x.cx); - dlog_putc(','); - dlog_printd(r.x.dx); - dlog_puts("]"); - refresh_video_info(); - data.min.y = r.x.cx; - data.max.y = r.x.dx; - bound_position_to_window(); - break; - case INT33_SET_GRAPHICS_CURSOR: - dlog_puts("Mouse set graphics cursor"); - hide_cursor(); - data.cursor_hotspot.x = r.x.bx; - data.cursor_hotspot.y = r.x.cx; - _fmemcpy(data.cursor_graphic, MK_FP(r.x.es, r.x.dx), sizeof(data.cursor_graphic)); - load_cursor(); - refresh_cursor(); - break; - case INT33_SET_TEXT_CURSOR: - dlog_print("Mouse set text cursor "); - dlog_printd(r.x.bx); - dlog_endline(); - hide_cursor(); - data.cursor_text_type = r.x.bx; - data.cursor_text_and_mask = r.x.cx; - data.cursor_text_xor_mask = r.x.dx; - refresh_cursor(); - break; - case INT33_GET_MOUSE_MOTION: -#if TRACE_EVENTS - dlog_puts("Mouse get motion"); -#endif - r.x.cx = data.delta.x; - r.x.dx = data.delta.y; - data.delta.x = 0; - data.delta.y = 0; - break; - case INT33_SET_EVENT_HANDLER: - dlog_puts("Mouse set event handler"); - data.event_mask = r.x.cx; - data.event_handler = MK_FP(r.x.es, r.x.dx); - break; - case INT33_SET_MOUSE_SPEED: - dlog_print("Mouse set speed x="); - dlog_printd(r.x.cx); - dlog_print(" y="); - dlog_printd(r.x.dx); - dlog_endline(); - data.mickeysPerLine.x = r.x.cx; - data.mickeysPerLine.y = r.x.dx; - break; - case INT33_SET_SPEED_DOUBLE_THRESHOLD: - dlog_print("Mouse set speed double threshold="); - dlog_printd(r.x.dx); - dlog_endline(); - data.doubleSpeedThreshold = r.x.dx; - break; - case INT33_EXCHANGE_EVENT_HANDLER: - dlog_puts("Mouse exchange event handler"); - data.event_mask = r.x.cx; - { - void (__far *prev_event_handler)() = data.event_handler; - data.event_handler = MK_FP(r.x.es, r.x.dx); - r.x.es = FP_SEG(prev_event_handler); - r.x.dx = FP_OFF(prev_event_handler); - } - break; - case INT33_GET_MOUSE_STATUS_SIZE: - dlog_puts("Mouse get status size"); - r.x.bx = sizeof(TSRDATA); - break; - case INT33_SAVE_MOUSE_STATUS: - dlog_puts("Mouse save status"); - _fmemcpy(MK_FP(r.x.es, r.x.dx), &data, sizeof(TSRDATA)); - break; - case INT33_LOAD_MOUSE_STATUS: - dlog_puts("Mouse load status"); - _fmemcpy(&data, MK_FP(r.x.es, r.x.dx), sizeof(TSRDATA)); - break; - case INT33_SET_MOUSE_SENSITIVITY: - dlog_print("Mouse set speed x="); - dlog_printd(r.x.bx); - dlog_print(" y="); - dlog_printd(r.x.cx); - dlog_print(" threshold="); - dlog_printd(r.x.dx); - dlog_endline(); - data.mickeysPerLine.x = r.x.bx; - data.mickeysPerLine.y = r.x.cx; - data.doubleSpeedThreshold = r.x.dx; - break; - case INT33_GET_MOUSE_SENSITIVITY: - r.x.bx = data.mickeysPerLine.x; - r.x.cx = data.mickeysPerLine.y; - r.x.dx = data.doubleSpeedThreshold; - break; - case INT33_RESET_SETTINGS: - dlog_puts("Mouse reset settings"); - reload_video_info(); - reset_mouse_settings(); - reset_mouse_state(); - r.x.ax = INT33_MOUSE_FOUND; - r.x.bx = NUM_BUTTONS; - break; - case INT33_GET_LANGUAGE: - r.x.bx = 0; - break; - case INT33_GET_DRIVER_INFO: - dlog_puts("Mouse get driver info"); - r.h.bh = REPORTED_VERSION_MAJOR; - r.h.bl = REPORTED_VERSION_MINOR; - r.h.ch = INT33_MOUSE_TYPE_PS2; - r.h.cl = 0; - break; - case INT33_GET_MAX_COORDINATES: - r.x.bx = 0; - r.x.cx = MAX(data.screen_max.x, data.max.x); - r.x.dx = MAX(data.screen_max.y, data.max.y); - break; - case INT33_GET_WINDOW: - r.x.ax = data.min.x; - r.x.bx = data.min.y; - r.x.cx = data.max.x; - r.x.dx = data.max.y; - break; -#if USE_WHEEL - // Wheel API extensions: - case INT33_GET_CAPABILITIES: - dlog_puts("Mouse get capabitilies"); - r.x.ax = INT33_WHEEL_API_MAGIC; // Driver supports wheel API - r.x.bx = 0; - r.x.cx = data.haswheel ? INT33_CAPABILITY_MOUSE_API : 0; - data.usewheelapi = true; // Someone calling this function likely wants to use wheel API - break; -#endif - // Our internal API extensions: - case INT33_GET_TSR_DATA: - dlog_puts("Get TSR data"); - r.x.es = FP_SEG(&data); - r.x.di = FP_OFF(&data); - break; - default: - dlog_print("Unknown mouse function ax="); - dlog_printx(r.x.ax); - dlog_endline(); - break; - } -} - -// Can't use __interrupt, it makes a call to GETDS on the runtime -void __declspec(naked) __far int33_isr(void) -{ - __asm { - pusha - push ds - push es - push fs - push gs - - mov bp, sp - push cs - pop ds - - call int33_handler - - pop gs - pop fs - pop es - pop ds - popa - iret - } -} - -#if USE_WIN386 -/** Windows will call this function to notify events when we are inside a DOS box. */ -static void windows_mouse_handler(int action, int x, int y, int buttons, int events) -#pragma aux windows_mouse_handler "*" parm [ax] [bx] [cx] [dx] [si] modify [ax bx cx dx es] -{ - switch (action) { - case VMD_ACTION_MOUSE_EVENT: - (void) events; - // Forward event to our internal system - handle_mouse_event(buttons, true, x, y, 0); - break; - case VMD_ACTION_HIDE_CURSOR: - dlog_puts("VMD_ACTION_HIDE_CURSOR"); - data.w386cursor = true; - refresh_cursor(); - break; - case VMD_ACTION_SHOW_CURSOR: - dlog_puts("VMD_ACTION_SHOW_CURSOR"); - data.w386cursor = false; - refresh_cursor(); - break; - } -} - -void __declspec(naked) __far windows_mouse_callback() -{ - __asm { - pusha - push ds - push es - push fs - push gs - - mov bp, sp - push cs - pop ds - - call windows_mouse_handler - - pop gs - pop fs - pop es - pop ds - popa - - retf - } -} - -static void int2f_handler(union INTPACK r) -#pragma aux int2f_handler "*" parm caller [] modify [ax bx cx dx es] -{ - switch (r.x.ax) { - case INT2F_NOTIFY_WIN386_STARTUP: - dlog_print("Windows is starting, version="); - dlog_printx(r.x.di); - dlog_endline(); - data.w386_startup.version = 3; - data.w386_startup.next = MK_FP(r.x.es, r.x.bx); - data.w386_startup.device_driver = 0; - data.w386_startup.device_driver_data = 0; - data.w386_startup.instance_data = &data.w386_instance; - data.w386_instance[0].ptr = &data; - data.w386_instance[0].size = sizeof(data); - data.w386_instance[1].ptr = 0; - data.w386_instance[1].size = 0; - r.x.es = FP_SEG(&data.w386_startup); - r.x.bx = FP_OFF(&data.w386_startup); - break; - case INT2F_NOTIFY_DEVICE_CALLOUT: - switch (r.x.bx) { - case VMD_DEVICE_ID: - switch (r.x.cx) { - case VMD_CALLOUT_TEST: - r.x.cx = 1; // Yes, we are here! - break; - case VMD_CALLOUT_GET_DOS_MOUSE_API: - // Windows is asking our mouse driver for the hook function address - r.x.ds = get_cs(); - r.x.si = FP_OFF(windows_mouse_callback); - r.x.ax = 0; // Yes, we are here! - break; - } - break; - } - break; - } -} - -void __declspec(naked) __far int2f_isr(void) -{ - __asm { - pusha - push ds - push es - push fs - push gs - - mov bp, sp - push cs - pop ds - - call int2f_handler - - pop gs - pop fs - pop es - pop ds - popa - - ; Jump to the next handler in the chain - jmp dword ptr cs:[data + 4] ; wasm doesn't support structs, this is data.prev_int2f_handler - } -} -#endif - -static LPTSRDATA int33_get_tsr_data(void); -#pragma aux int33_get_tsr_data = \ - "xor ax, ax" \ - "mov es, ax" \ - "mov di, ax" \ - "mov ax, 0x73" \ - "int 0x33" \ - __value [es di] \ - __modify [ax] - -static LPTSRDATA local_get_tsr_data(void); -#pragma aux local_get_tsr_data = \ - "mov ax, cs" \ - "mov es, ax" \ - "mov di, offset data" \ - __value [es di] \ - __modify [ax] - -LPTSRDATA __far get_tsr_data(bool installed) -{ - if (installed) { - return int33_get_tsr_data(); - } else { - return local_get_tsr_data(); - } -} - -int resident_end; diff --git a/dostsr.h b/dostsr.h deleted file mode 100644 index a025c92..0000000 --- a/dostsr.h +++ /dev/null @@ -1,217 +0,0 @@ -/* - * VBMouse - DOS mouse driver resident part - * 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. - */ - -#ifndef DOSTSR_H -#define DOSTSR_H - -#include -#include - -#include "int2fwin.h" -#include "int10vga.h" - -// User customizable defines - -/** Enable the VirtualBox integration */ -#define USE_VIRTUALBOX 1 -/** Enable the VMware integration */ -#define USE_VMWARE 1 -/** Enable the Windows 386/protected mode integration */ -#define USE_WIN386 1 -/** Enable the wheel. */ -#define USE_WHEEL 1 -/** Trace events verbosily */ -#define TRACE_EVENTS 0 - -// End of user customizable defines - -#define USE_INTEGRATION (USE_VIRTUALBOX || USE_VMWARE) - -#define NUM_BUTTONS 3 - -#define GRAPHIC_CURSOR_WIDTH 16 -#define GRAPHIC_CURSOR_HEIGHT 16 -#define GRAPHIC_CURSOR_SCANLINE_LEN 2 -#define GRAPHIC_CURSOR_MASK_LEN (GRAPHIC_CURSOR_HEIGHT * GRAPHIC_CURSOR_SCANLINE_LEN) -#define GRAPHIC_CURSOR_DATA_LEN (2 * GRAPHIC_CURSOR_MASK_LEN) - -#define VERSION_MAJOR 0 -#define VERSION_MINOR 4 -#define REPORTED_VERSION_MAJOR 6 -#define REPORTED_VERSION_MINOR 0x30 - -#if USE_VIRTUALBOX -#include "vbox.h" -#endif - -struct point { - int16_t x, y; -}; - -typedef struct tsrdata { - // TSR installation data - /** Previous int33 ISR, storing it for uninstall. */ - void (__interrupt __far *prev_int33_handler)(); -#if USE_WIN386 - void (__interrupt __far *prev_int2f_handler)(); -#endif -#if USE_WHEEL - /** Whether to enable & use wheel mouse. */ - bool usewheel; - /** Key (scancode) to generate on wheel scroll up/down, or 0 for none. */ - uint16_t wheel_up_key, wheel_down_key; -#endif - - // Video settings - /** Information of the current video mode. */ - struct modeinfo video_mode; - /** Max (virtual) coordinates of full screen in the current mode. - * Used for rendering graphic cursor, mapping absolute coordinates, - * and initializing the default min/max window. */ - struct point screen_max; - /** In some modes, the virtual coordinates are larger than the - * physical screen coordinates. - * real coordinates = virtual coordinates * screen_scale. */ - struct point screen_scale; - /** In text modes, we want to snap the cursor position to the cell grid. - * This stores the desired grid granularity. */ - struct point screen_granularity; - - // Detected mouse hardware -#if USE_WHEEL - /** Whether the current mouse has a wheel (and support is enabled). */ - bool haswheel; -#endif - - // Current mouse settings - /** Mouse sensitivity/speed. */ - struct point mickeysPerLine; // mickeys per 8 pixels - /** Mouse acceleration "double-speed threshold". */ - uint16_t doubleSpeedThreshold; // mickeys - /** Current window min coordinates. */ - struct point min; - /** Current window max coordinates. */ - struct point max; - /** Current cursor visible counter. If >= 0, cursor should be shown. */ - int16_t visible_count; - /** For text cursor, whether this is a software or hardware cursor. */ - uint8_t cursor_text_type; - /** Masks for the text cursor. */ - uint16_t cursor_text_and_mask, cursor_text_xor_mask; - /** Hotspot for the graphic cursor. */ - struct point cursor_hotspot; - /** Masks for the graphic cursor. */ - uint16_t cursor_graphic[GRAPHIC_CURSOR_DATA_LEN/sizeof(uint16_t)]; -#if USE_WHEEL - /** Whether someone asked for the int33 wheel API, in which case we - * should send them wheel movement rather than fake keypresses. */ - bool usewheelapi; -#endif - - // Current mouse status - /** Current cursor position (in pixels). */ - struct point pos; - /** Current remainder of movement that does not yet translate to an entire pixel - * (8ths of pixel). */ - struct point pos_frac; - /** Current delta movement (in mickeys) since the last report. */ - struct point delta; - /** Current remainder of delta movement that does not yet translate to an entire mickey - * Usually only when mickeysPerLine is not a multiple of 8. */ - struct point delta_frac; - /** Last absolute position (to compute a delta for relative motion emulation). Using -1 for "none". */ - struct point abs_pos; - /** Total mickeys moved in the last second. */ - uint16_t total_motion; - /** Ticks when the above value was last reset. */ - uint16_t last_ticks; - /** Current status of buttons (as bitfield). */ - uint16_t buttons; - struct { - struct buttoncounter { - struct point last; - uint16_t count; - } pressed, released; - } button[NUM_BUTTONS]; - /** Total delta movement of the wheel since the last wheel report. */ - int16_t wheel_delta; - /** Last position where the wheel was moved. */ - struct point wheel_last; - - // Cursor information - /** Whether the cursor is currently displayed or not. */ - bool cursor_visible; - /** The current position at which the cursor is displayed. */ - struct point cursor_pos; - /** For text mode cursor, the character data that was displayed below the cursor. */ - uint16_t cursor_prev_char; - /** For graphical mode cursor, contents of the screen that were displayed below - * the cursor before the cursor was drawn. */ - uint8_t cursor_prev_graphic[GRAPHIC_CURSOR_WIDTH * GRAPHIC_CURSOR_HEIGHT]; - - // Current handlers - /** Address of the event handler. */ - void (__far *event_handler)(); - /** Events for which we should call the event handler. */ - uint16_t event_mask; - -#if USE_WIN386 - /** Information that we pass to Windows 386 on startup. */ - win386_startup_info w386_startup; - win386_instance_item w386_instance[2]; - /** Whether Windows 386 is rendering the cursor for us, - * and therefore we should hide our own. */ - bool w386cursor : 1; -#endif - -#if USE_VIRTUALBOX - /** VirtualBox is available. */ - bool vbavail : 1; - /** Want to use the VirtualBox "host" cursor. */ - bool vbwantcursor : 1; - /** Have VirtualBox absolute coordinates. */ - bool vbhaveabs : 1; - struct vboxcomm vb; -#endif - -#if USE_VMWARE - /** VMware is available. */ - bool vmwavail : 1; -#endif -} TSRDATA; - -typedef TSRDATA * PTSRDATA; -typedef TSRDATA __far * LPTSRDATA; - -extern void __declspec(naked) __far int33_isr(void); - -extern void __declspec(naked) __far int2f_isr(void); - -extern LPTSRDATA __far get_tsr_data(bool installed); - -/** This symbol is always at the end of the TSR segment */ -extern int resident_end; - -/** This is not just data, but the entire segment. */ -static inline unsigned get_resident_size(void) -{ - return FP_OFF(&resident_end); -} - -#endif diff --git a/makefile b/makefile index a10e671..1eb22d5 100644 --- a/makefile +++ b/makefile @@ -1,7 +1,9 @@ # This is an Open Watcom wmake makefile, not GNU make. # Assuming you have sourced `owsetenv` beforehand. -# -dosobjs = dostsr.obj dosmain.obj vbox.obj + +mousedosobjs = mousetsr.obj mousmain.obj vbox.obj +mousew16objs = mousew16.obj + doscflags = -bt=dos -ms -6 -osi -w3 -wcd=202 # -ms to use small memory model (though sometimes ss != ds...) # -osi to optimize for size, put intrinsics inline (to avoid runtime calls) @@ -11,7 +13,6 @@ dostsrcflags = -zu -s -g=RES_GROUP -nd=RES -nt=RES_TEXT -nc=RES_CODE # -s to disable stack checks, since it inserts calls to the runtime from the TSR part # -zu since ss != ds on the TSR -w16objs = w16mouse.obj w16cflags = -bt=windows -bd -mc -zu -s -6 -oi -w3 -wcd=202 # -bd to build DLL # -mc to use compact memory model (far data pointers, ss != ds in a DLL) @@ -23,22 +24,22 @@ w16cflags = -bt=windows -bd -mc -zu -s -6 -oi -w3 -wcd=202 set include=$(%watcom)/h/win;$(%watcom)/h # Main DOS driver file -vbmouse.exe: dosmouse.lnk $(dosobjs) - wlink @$[@ name $@ file { $(dosobjs) } +vbmouse.exe: vbmouse.lnk $(mousedosobjs) + wlink @$[@ name $@ file { $(mousedosobjs) } -dostsr.obj: dostsr.c .AUTODEPEND +mousetsr.obj: mousetsr.c .AUTODEPEND wcc -fo=$^@ $(doscflags) $(dostsrcflags) $[@ -dosmain.obj: dosmain.c .AUTODEPEND +mousmain.obj: mousmain.c .AUTODEPEND wcc -fo=$^@ $(doscflags) $[@ vbox.obj: vbox.c .AUTODEPEND wcc -fo=$^@ $(doscflags) $[@ -vbmouse.drv: w16mouse.lnk $(w16objs) - wlink @$[@ name $@ file { $(w16objs) } +vbmouse.drv: mousew16.lnk $(mousew16objs) + wlink @$[@ name $@ file { $(mousew16objs) } -w16mouse.obj: w16mouse.c .AUTODEPEND +mousew16.obj: mousew16.c .AUTODEPEND wcc -fo=$^@ $(w16cflags) $[@ clean: .SYMBOLIC diff --git a/mousetsr.c b/mousetsr.c new file mode 100644 index 0000000..fed2c9b --- /dev/null +++ b/mousetsr.c @@ -0,0 +1,1468 @@ +/* + * VBMouse - DOS mouse driver resident part + * 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 "dlog.h" +#include "ps2.h" +#include "int10vga.h" +#include "int16kbd.h" +#include "int2fwin.h" +#include "int33.h" +#include "vbox.h" +#include "vmware.h" +#include "mousetsr.h" + +#define MSB_MASK 0x8000U + +TSRDATA data; + +static const uint16_t default_cursor_graphic[] = { + 0x3FFF, 0x1FFF, 0x0FFF, 0x07FF, + 0x03FF, 0x01FF, 0x00FF, 0x007F, + 0x003F, 0x001F, 0x01FF, 0x00FF, + 0x30FF, 0xF87F, 0xF87F, 0xFCFF, + 0x0000, 0x4000, 0x6000, 0x7000, + 0x7800, 0x7C00, 0x7E00, 0x7F00, + 0x7F80, 0x7C00, 0x6C00, 0x4600, + 0x0600, 0x0300, 0x0300, 0x0000 +}; + +/** Constraint current mouse position to the user-set window. */ +static void bound_position_to_window(void) +{ + if (data.pos.x < data.min.x) data.pos.x = data.min.x; + if (data.pos.x > data.max.x) data.pos.x = data.max.x; + if (data.pos.y < data.min.y) data.pos.y = data.min.y; + if (data.pos.y > data.max.y) data.pos.y = data.max.y; +} + +/** Constraints coordinate value to the desired granularity, + * which must be a power of two. */ +static inline int16_t snap_to_grid(int16_t val, int16_t granularity) +{ + // Build a bitmask that masks away the undesired low-order bits + return val & (-granularity); +} + +static void hide_text_cursor(void) +{ + // Restore the character under the old position of the cursor + uint16_t __far *ch = get_video_char(&data.video_mode, + data.cursor_pos.x / 8, data.cursor_pos.y / 8); + *ch = data.cursor_prev_char; + data.cursor_visible = false; +} + +static void show_text_cursor(void) +{ + uint16_t __far *ch = get_video_char(&data.video_mode, + data.pos.x / 8, data.pos.y / 8); + data.cursor_prev_char = *ch; + data.cursor_pos = data.pos; + *ch = (*ch & data.cursor_text_and_mask) ^ data.cursor_text_xor_mask; + data.cursor_visible = true; +} + +/** Given the new position of the cursor, compute the both our source + * (i.e. the mouse cursor shape) and target clipping areas. + * @param cursor_pos cursor position + * @param start top left corner of clipping box in screen + * @param size size of the clipping box in screen as well as cursor shape + * @param offset top left corner of clipping box in mouse cursor shape */ +static bool get_graphic_cursor_area(struct point __far *cursor_pos, + struct point __far *start, + struct point __far *size, + struct point __far *offset) +{ + start->x = (cursor_pos->x / data.screen_scale.x) - data.cursor_hotspot.x; + start->y = (cursor_pos->y / data.screen_scale.y) - data.cursor_hotspot.y; + size->x = GRAPHIC_CURSOR_WIDTH; + size->y = GRAPHIC_CURSOR_HEIGHT; + offset->x = 0; + offset->y = 0; + + // Start clipping around + // Cursor is left/top of visible area + if (start->x <= -size->x) { + return false; + } else if (start->x < 0) { + offset->x += -start->x; + size->x -= -start->x; + start->x = 0; + } + if (start->y <= -size->y) { + return false; + } else if (start->y < 0) { + offset->y += -start->y; + size->y -= -start->y; + start->y = 0; + } + // Cursor is right/bottom of visible area + if (start->x > data.video_mode.pixels_width) { + return false; // Don't render cursor + } else if (start->x + size->x > data.video_mode.pixels_width) { + size->x -= (start->x + size->x) - data.video_mode.pixels_width; + } + if (start->y > data.video_mode.pixels_height) { + return false; + } else if (start->y + size->y > data.video_mode.pixels_height) { + size->y -= (start->y + size->y) - data.video_mode.pixels_height; + } + + return true; +} + +static inline uint8_t * get_prev_graphic_cursor_scanline(unsigned bytes_per_line, + unsigned num_lines, + unsigned plane, + unsigned y) +{ + return &data.cursor_prev_graphic[((plane * num_lines) + y) * bytes_per_line]; +} + +static inline uint16_t get_graphic_cursor_and_mask_line(unsigned y) +{ + return data.cursor_graphic[y]; +} + +static inline uint16_t get_graphic_cursor_xor_mask_line(unsigned y) +{ + return data.cursor_graphic[GRAPHIC_CURSOR_HEIGHT + y]; +} + +/** Compute the total number of bytes between start and end pixels, + * rounding up as necessary to cover all bytes. */ +static inline unsigned get_scanline_segment_bytes(unsigned bits_per_pixel, unsigned start, unsigned size) +{ + // Get starting byte (round down) + unsigned start_byte = (start * bits_per_pixel) / 8; + // Get end byte (round up) + unsigned end_byte = (((start + size) * bits_per_pixel) + (8-1)) / 8; + + return end_byte - start_byte; +} + +/** Creates a bitmask that extracts the topmost N bits of a byte. */ +static inline uint8_t build_pixel_mask(unsigned bits_per_pixel) +{ + if (bits_per_pixel == 1) return 0x80; + else if (bits_per_pixel == 2) return 0xC0; + else if (bits_per_pixel == 4) return 0xF0; + else return 0xFF; +} + +/** Hides the graphical mouse cursor, by restoring the contents of + * data.cursor_prev_graphic (i.e. what was below the cursor before we drew it) + * to video memory. */ +static void hide_graphic_cursor(void) +{ + struct modeinfo *info = &data.video_mode; + struct point start, size, offset; + unsigned cursor_bytes_per_line; + unsigned plane, y; + + // Compute the area where the cursor is currently positioned + if (!get_graphic_cursor_area(&data.cursor_pos, &start, &size, &offset)) { + return; + } + + // For each scanline, we will copy this amount of bytes + cursor_bytes_per_line = get_scanline_segment_bytes(info->bits_per_pixel, + start.x, size.x); + + for (plane = 0; plane < info->num_planes; plane++) { + if (info->num_planes > 1) vga_select_plane(plane); + + for (y = 0; y < size.y; y++) { + uint8_t __far *line = get_video_scanline(info, start.y + y) + + (start.x * info->bits_per_pixel) / 8; + uint8_t *prev = get_prev_graphic_cursor_scanline(cursor_bytes_per_line, size.y, + plane, y); + + // Restore this scaline from cursor_prev + _fmemcpy(line, prev, cursor_bytes_per_line); + } + } + + data.cursor_visible = false; +} + +/** Renders the graphical cursor. + * It will also backup whatever pixels are below + * the cursor area to cursor_prev_graphic. */ +static void show_graphic_cursor(void) +{ + const struct modeinfo *info = &data.video_mode; + struct point start, size, offset; + unsigned cursor_bytes_per_line; + const uint8_t msb_pixel_mask = build_pixel_mask(info->bits_per_pixel); + unsigned plane, y; + + // Compute the area where the cursor is supposed to be drawn + if (!get_graphic_cursor_area(&data.pos, &start, &size, &offset)) { + return; + } + + // For each scanline, we will copy this amount of bytes + cursor_bytes_per_line = get_scanline_segment_bytes(info->bits_per_pixel, + start.x, size.x); + + for (plane = 0; plane < info->num_planes; plane++) { + if (info->num_planes > 1) vga_select_plane(plane); + + for (y = 0; y < size.y; y++) { + uint8_t __far *line = get_video_scanline(info, start.y + y) + + (start.x * info->bits_per_pixel) / 8; + uint8_t *prev = get_prev_graphic_cursor_scanline(cursor_bytes_per_line, size.y, + plane, y); + uint16_t cursor_and_mask = get_graphic_cursor_and_mask_line(offset.y + y) + << offset.x; + uint16_t cursor_xor_mask = get_graphic_cursor_xor_mask_line(offset.y + y) + << offset.x; + unsigned x; + + // First, backup this scanline to prev before any changes + _fmemcpy(prev, line, cursor_bytes_per_line); + + if (info->bits_per_pixel < 8) { + uint8_t pixel_mask = msb_pixel_mask; + uint8_t pixel = *line; + + // when start.x is not pixel aligned, + // scaline points the previous multiple of pixels_per_byte; + // and the initial pixel will not be at the MSB of it. + // advance the pixel_mask accordingly + pixel_mask >>= (start.x * info->bits_per_pixel) % 8; + + for (x = 0; x < size.x; x++) { + // The MSBs of each mask correspond to the current pixel + if (!(cursor_and_mask & MSB_MASK)) { + pixel &= ~pixel_mask; + } + if (cursor_xor_mask & MSB_MASK) { + pixel ^= pixel_mask; + } + + // Advance to the next pixel + pixel_mask >>= info->bits_per_pixel; + if (!pixel_mask) { + // Time to advance to the next byte + *line = pixel; // Save current byte first + pixel = *(++line); + pixel_mask = msb_pixel_mask; + } + + // Advance to the next bit in the cursor mask + cursor_and_mask <<= 1; + cursor_xor_mask <<= 1; + } + + if (pixel_mask != msb_pixel_mask) { + // We ended up in the middle of a byte, save it + *line = pixel; + } + } else if (info->bits_per_pixel == 8) { + // Simplified version for byte-aligned pixels + for (x = 0; x < size.x; x++) { + uint8_t pixel = 0; + + if (cursor_and_mask & MSB_MASK) { + pixel = *line; + } + if (cursor_xor_mask & MSB_MASK) { + pixel ^= 0x0F; // Use 0x0F as "white pixel" + } + + // Advance to the next pixel + *line = pixel; + ++line; + + // Advance to the next bit in the cursor mask + cursor_and_mask <<= 1; + cursor_xor_mask <<= 1; + } + } + } + } + + data.cursor_pos = data.pos; + data.cursor_visible = true; +} + +/** Refreshes cursor position and visibility. */ +static void refresh_cursor(void) +{ + bool should_show = data.visible_count >= 0; + bool pos_changed, needs_refresh; + +#if USE_WIN386 + // Windows 386 is already rendering the cursor for us. + // Hide our own. + if (data.w386cursor) should_show = false; +#endif + +#if USE_VIRTUALBOX + if (data.vbavail && data.vbwantcursor) { + // We want to use the VirtualBox host cursor. + // See if we have to update its visibility. + int err = 0; + if (should_show != data.cursor_visible) { + err = vbox_set_pointer_visible(&data.vb, should_show); + if (err == 0 && data.vbhaveabs) { + data.cursor_visible = should_show; + } + } + if (err == 0 && data.vbhaveabs) { + // No need to refresh the cursor; VirtualBox is already showing it for us. + return; + } + } +#endif + + pos_changed = data.cursor_pos.x != data.pos.x || data.cursor_pos.y != data.pos.y; + needs_refresh = should_show && pos_changed || should_show != data.cursor_visible; + + if (!needs_refresh) { + // Nothing to do + return; + } + + if (data.video_mode.type == VIDEO_TEXT) { + // Text video mode + if (data.cursor_visible) { + // Hide the cursor at the old position if any + hide_text_cursor(); + } + if (should_show) { + // Show the cursor at the new position + show_text_cursor(); + } + } else if (data.video_mode.type != VIDEO_UNKNOWN) { + // Graphic video modes + + bool video_planar = data.video_mode.num_planes > 1; + struct videoregs regs; + // If current video mode is planar, + // we will have to play with the VGA registers + // so let's save and restore them. + if (video_planar) { + vga_save_registers(®s); + vga_set_graphics_mode(®s, 0, 0); + } + + if (data.cursor_visible) { + hide_graphic_cursor(); + } + if (should_show) { + show_graphic_cursor(); + } + + if (video_planar) { + vga_restore_register(®s); + } + } else { + // Unknown video mode, don't render cursor. + } +} + +/** Forcefully hides the mouse cursor if shown. */ +static void hide_cursor(void) +{ +#if USE_VIRTUALBOX + if (data.vbavail && data.vbwantcursor) { + vbox_set_pointer_visible(&data.vb, false); + if (data.vbhaveabs) { + data.cursor_visible = false; + } + } +#endif + + if (data.cursor_visible) { + if (data.video_mode.type == VIDEO_TEXT) { + hide_text_cursor(); + } else if (data.video_mode.type != VIDEO_UNKNOWN) { + hide_graphic_cursor(); + } + } +} + +/** Loads the current graphic cursor, + * which in this case means uploading it to the host. */ +static void load_cursor(void) +{ +#if USE_VIRTUALBOX + if (data.vbavail && data.vbwantcursor) { + VMMDevReqMousePointer *req = (VMMDevReqMousePointer *) data.vb.buf; + const unsigned width = GRAPHIC_CURSOR_WIDTH, height = GRAPHIC_CURSOR_HEIGHT; + uint8_t *output = req->pointerData; + unsigned int y, x; + + memset(req, 0, sizeof(VMMDevReqMousePointer)); + + req->header.size = vbox_req_mouse_pointer_size(width, height); + req->header.version = VMMDEV_REQUEST_HEADER_VERSION; + req->header.requestType = VMMDevReq_SetPointerShape; + req->header.rc = -1; + + req->fFlags = VBOX_MOUSE_POINTER_SHAPE; + req->xHot = BOUND(data.cursor_hotspot.x, 0, width); + req->yHot = BOUND(data.cursor_hotspot.y, 0, height); + req->width = width; + req->height = height; + + // AND mask + // int33 format is 1-bit per pixel packed into 16-bit LE values, + // while VirtualBox wants 1-bit per pixel packed into 8-bit. + // All we have to do is byteswap 16-bit values. + for (y = 0; y < height; ++y) { + uint16_t cursor_line = get_graphic_cursor_and_mask_line(y); + output[0] = (cursor_line >> 8) & 0xFF; + output[1] = cursor_line & 0xFF; + output += GRAPHIC_CURSOR_SCANLINE_LEN; + } + + // XOR mask + // int33 format is again 1-bit per pixel packed into 16-bit LE values, + // however VirtualBox wants 4-byte per pixel packed "RGBA". + for (y = 0; y < height; ++y) { + uint16_t cursor_line = get_graphic_cursor_xor_mask_line(y); + + for (x = 0; x < width; ++x) { + // MSB of line is current mask bit, we shift it on each iteration + uint8_t val = (cursor_line & MSB_MASK) ? 0xFF : 0; + + output[0] = val; + output[1] = val; + output[2] = val; + output[3] = 0; + + cursor_line <<= 1; + output += 4; + } + } + + dlog_puts("Loading cursor to VBox"); + + vbox_send_request(data.vb.iobase, data.vb.dds.physicalAddress); + + if (req->header.rc != 0) { + dlog_puts("Could not send cursor to VirtualBox"); + return; + } + + // After we send this message, it looks like VirtualBox shows the cursor + // even if we didn't actually want it to be visible at this point. + vbox_set_pointer_visible(&data.vb, false); + } +#endif +} + +/** Reloads the information about the current video mode. */ +static void reload_video_info(void) +{ + get_current_video_mode_info(&data.video_mode); + + data.screen_max.x = data.video_mode.pixels_width - 1; + data.screen_max.y = data.video_mode.pixels_height - 1; + data.screen_scale.x = 1; + data.screen_scale.y = 1; + data.screen_granularity.x = 1; + data.screen_granularity.y = 1; + + // The actual range of coordinates expected by int33 clients + // is, for some reason, different than real resolution in some modes. + // For example, 320x... modes are mapped to 640x... pixels. + if (data.video_mode.pixels_width == 320) { + data.screen_max.x = 640 - 1; + data.screen_scale.x = 640 / 320; + } + + // In text modes, we are supposed to always round the mouse cursor + // to character boundaries. + if (data.video_mode.type == VIDEO_TEXT) { + // Always assume 8x8 character size irregardless of true font + data.screen_granularity.x = 8; + data.screen_granularity.y = 8; + } + + dlog_print("Current video mode="); + dlog_printx(data.video_mode.mode); + dlog_print(" screen_max="); + dlog_printd(data.screen_max.x); + dlog_putc(','); + dlog_printd(data.screen_max.y); + dlog_endline(); +} + +/** True if the current video is different from what we have stored + * and we probably need to recompute video mode info. */ +static inline bool video_mode_changed(void) +{ + uint8_t cur_mode = bda_get_video_mode() & ~0x80; + + if (cur_mode != data.video_mode.mode) + return true; + + if (data.video_mode.type == VIDEO_TEXT) { + // Also check to see if the font size was changed.. + return data.video_mode.pixels_height != (bda_get_last_row()+1) * 8; + } else { + return false; + } +} + +/** Checks if the video mode has changed and if so + * refreshes the information about the current video mode. */ +static void refresh_video_info(void) +{ + if (video_mode_changed()) { + if (data.cursor_visible) { + // Assume cursor is lost with no way to restore prev contents + data.cursor_visible = false; + } + + reload_video_info(); + + if (data.video_mode.type != VIDEO_UNKNOWN) { + // If we know the screen size for this mode, then reset the window to it + data.min.x = 0; + data.min.y = 0; + data.max.x = data.screen_max.x; + data.max.y = data.screen_max.y; + } + } +} + +/** Calls the application-registered event handler. */ +static void call_event_handler(void (__far *handler)(), uint16_t events, + uint16_t buttons, int16_t x, int16_t y, + int16_t delta_x, int16_t delta_y) +{ +#if TRACE_EVENTS + dlog_print("calling event handler events="); + dlog_printx(events); + dlog_print(" buttons="); + dlog_printx(buttons); + dlog_print(" x="); + dlog_printd(x); + dlog_print(" y="); + dlog_printd(y); + dlog_print(" dx="); + dlog_printd(delta_x); + dlog_print(" dy="); + dlog_printd(delta_y); + dlog_endline(); +#endif + + __asm { + mov ax, [events] + mov bx, [buttons] + mov cx, [x] + mov dx, [y] + mov si, [delta_x] + mov di, [delta_y] + + call dword ptr [handler] + } +} + +/** Process a mouse event internally. + * @param buttons currently pressed buttons as a bitfield + * @param absolute whether mouse coordinates are an absolute value + * @param x y if absolute, then absolute coordinates in screen pixels + * if relative, then relative coordinates in mickeys + * @param z relative wheel mouse movement + */ +static void handle_mouse_event(uint16_t buttons, bool absolute, int x, int y, int z) +{ + uint16_t events = 0; + int i; + +#if TRACE_EVENTS + dlog_print("handle mouse event"); + if (absolute) dlog_print(" absolute"); + dlog_print(" buttons="); + dlog_printx(buttons); + dlog_print(" x="); + dlog_printd(x); + dlog_print(" y="); + dlog_printd(y); + dlog_print(" z="); + dlog_printd(z); + dlog_endline(); +#endif + + if (absolute) { + // Absolute movement: x,y are in screen pixels units + events |= INT33_EVENT_MASK_ABSOLUTE; + + if (x != data.pos.x || y != data.pos.y) { + events |= INT33_EVENT_MASK_MOVEMENT; + + // Simulate a fake relative movement in mickeys + // This is almost certainly broken. + // Programs that expect relative movement data + // will almost never set a mickeyPerPixel value. + // So all we can do is guess. + if (data.abs_pos.x >= 0 && data.abs_pos.y >= 0) { + data.delta.x += (x - data.abs_pos.x) * 8; + data.delta.y += (y - data.abs_pos.y) * 8; + } + data.abs_pos.x = x; + data.abs_pos.y = y; + + // Set the new absolute position + data.pos.x = x; + data.pos.y = y; + data.pos_frac.x = 0; + data.pos_frac.y = 0; + } + } else if (x || y) { + // Relative movement: x,y are in mickeys + uint16_t ticks = bda_get_tick_count_lo(); + unsigned ax = abs(x), ay = abs(y); + + events |= INT33_EVENT_MASK_MOVEMENT; + + // Check if around one second has passed + if ((ticks - data.last_ticks) >= 18) { + data.total_motion = 0; + data.last_ticks = ticks; + } + + // If more than the double speed threshold has been moved in the last second, + // double the speed + data.total_motion += ax * ax + ay * ay; + if (data.total_motion > data.doubleSpeedThreshold * data.doubleSpeedThreshold) { + x *= 2; + y *= 2; + } + + data.delta.x += x; + data.delta.y += y; + data.delta_frac.x = 0; + data.delta_frac.y = 0; + data.abs_pos.x = -1; + data.abs_pos.y = -1; + + // Convert mickeys into pixels + data.pos.x += scalei_rem(x, data.mickeysPerLine.x, 8, &data.pos_frac.x); + data.pos.y += scalei_rem(y, data.mickeysPerLine.y, 8, &data.pos_frac.y); + } + + bound_position_to_window(); + +#if USE_WHEEL + if (data.haswheel && z) { + if (!data.usewheelapi && (data.wheel_up_key || data.wheel_down_key)) { + // Emulate keystrokes on wheel movement + if (z < 0 && data.wheel_up_key) { + for (; z < 0; z++) { + int16_store_keystroke(data.wheel_up_key); + } + } else if (z > 0 && data.wheel_up_key) { + for (; z > 0; z--) { + int16_store_keystroke(data.wheel_down_key); + } + } + } else { + events |= INT33_EVENT_MASK_WHEEL_MOVEMENT; + // Higher byte of buttons contains wheel movement + buttons |= (z & 0xFF) << 8; + // Accumulate delta wheel movement + data.wheel_delta += z; + data.wheel_last.x = data.pos.x; + data.wheel_last.y = data.pos.y; + } + } +#endif + + // Update button status + for (i = 0; i < NUM_BUTTONS; ++i) { + uint8_t btn = 1 << i; + uint8_t evt = 0; + if ((buttons & btn) && !(data.buttons & btn)) { + // Button pressed + evt = 1 << (1 + (i * 2)); // Press event mask + data.button[i].pressed.count++; + data.button[i].pressed.last.x = data.pos.x; + data.button[i].pressed.last.y = data.pos.y; + } else if (!(buttons & btn) && (data.buttons & btn)) { + // Button released + evt = 1 << (2 + (i * 2)); // Release event mask + data.button[i].released.count++; + data.button[i].released.last.x = data.pos.x; + data.button[i].released.last.y = data.pos.y; + } + events |= evt; + } + data.buttons = buttons; + + refresh_cursor(); + + events &= data.event_mask; + if (data.event_handler && events) { + x = snap_to_grid(data.pos.x, data.screen_granularity.x); + y = snap_to_grid(data.pos.y, data.screen_granularity.y); + + call_event_handler(data.event_handler, events, + buttons, x, y, data.delta.x, data.delta.y); + } +} + +/** PS/2 BIOS calls this routine to notify mouse events. */ +static void ps2_mouse_handler(uint16_t word1, uint16_t word2, uint16_t word3, uint16_t word4) +{ +#pragma aux ps2_mouse_handler "*" parm caller [ax] [bx] [cx] [dx] modify [ax bx cx dx si di es fs gs] + + unsigned status; + int x, y, z; + bool abs = false; + +#if TRACE_EVENTS + dlog_print("ps2 callback "); + dlog_printx(word1); + dlog_putc(' '); + dlog_printx(word2); + dlog_putc(' '); + dlog_printx(word3); + dlog_putc(' '); + dlog_printx(word4); + dlog_endline(); +#endif /* TRACE_EVENTS */ + + // Decode the PS2 event args + // In a normal IBM PS/2 BIOS (incl. VirtualBox/Bochs/qemu/SeaBIOS): + // word1 low byte = status (following PS2M_STATUS_*) + // word2 low byte = x + // word3 low byte = y + // word4 = always zero + // In a PS/2 BIOS with wheel support (incl. VMware/DOSBox-X): + // taken from CuteMouse/KoKo: + // word1 high byte = x + // word1 low byte = status + // word2 low byte = y + // word3 low byte = z + // word4 = always zero + // VirtualBox/Bochs/qemu/SeaBIOS behave like a normal one, + // but they also store the raw contents of all mouse packets in the EBDA (starting 0x28 = packet 0). + // Other BIOSes don't do that so it is not a reliable option either. + // So, how to detect which BIOS we have? + // For now we are always assuming "normal" PS/2 BIOS. + // But with VirtualBox integration on we'll get the wheel packet from the EBDA, + // and with VMWare integration on we'll get it from the VMware protocol. + status = (uint8_t) word1; + x = (uint8_t) word2; + y = (uint8_t) word3; + z = 0; + (void) word4; + + // Sign-extend X, Y as per the status byte + x = status & PS2M_STATUS_X_NEG ? 0xFF00 | x : x; + y = -(status & PS2M_STATUS_Y_NEG ? 0xFF00 | y : y); + +#if USE_VIRTUALBOX + if (data.vbavail) { + uint16_t vbx, vby; + if ((vbox_get_mouse(&data.vb, &abs, &vbx, &vby) == 0) && abs) { + // VirtualBox gives unsigned coordinates from 0...0xFFFFU, + // scale to 0..screen_size (in pixels). + refresh_video_info(); + // If the user is using a window larger than the screen, use it. + x = scaleu(vbx, 0xFFFFU, MAX(data.max.x, data.screen_max.x)); + y = scaleu(vby, 0xFFFFU, MAX(data.max.y, data.screen_max.y)); + data.vbhaveabs = true; + } else { + // VirtualBox does not support absolute coordinates, + // or user has disabled them. + data.vbhaveabs = false; + // Rely on PS/2 relative coordinates. + } + +#if USE_WHEEL + // VirtualBox/Bochs BIOS does not pass wheel data to the callback, + // so we will fetch it directly from the BIOS data segment. + if (data.haswheel) { + int8_t __far * mouse_packet = MK_FP(bda_get_ebda_segment(), 0x28); + z = mouse_packet[3]; + } +#endif + } +#endif /* USE_VIRTUALBOX */ + +#if USE_VMWARE + if (data.vmwavail) { + uint32_t vmwstatus = vmware_abspointer_status(); + uint16_t data_avail = vmwstatus & VMWARE_ABSPOINTER_STATUS_MASK_DATA; + +#if TRACE_EVENTS + dlog_print("vmware status=0x"); + dlog_printx(vmwstatus >> 16); + dlog_print(" "); + dlog_printx(vmwstatus & 0xFFFF); + dlog_endline(); +#endif + + if (data_avail >= VMWARE_ABSPOINTER_DATA_PACKET_SIZE) { + struct vmware_abspointer_data vmw; + vmware_abspointer_data(VMWARE_ABSPOINTER_DATA_PACKET_SIZE, &vmw); + +#if TRACE_EVENTS + dlog_print("vmware pstatus=0x"); + dlog_printx(status); + dlog_print(" x=0x"); + dlog_printx(vmw.x); + dlog_print(" z="); + dlog_printd((int8_t) (uint8_t) vmw.z); + dlog_endline(); +#endif + + if (vmw.status & VMWARE_ABSPOINTER_STATUS_RELATIVE) { + x = (int16_t) vmw.x; + y = (int16_t) vmw.y; + z = (int8_t) (uint8_t) vmw.z; + } else { + abs = true; + // Scale to screen coordinates + refresh_video_info(); + x = scaleu(vmw.x & 0xFFFFU, 0xFFFFU, + MAX(data.max.x, data.screen_max.x)); + y = scaleu(vmw.y & 0xFFFFU, 0xFFFFU, + MAX(data.max.y, data.screen_max.y)); + z = (int8_t) (uint8_t) vmw.z; + } + + if (vmw.status & VMWARE_ABSPOINTER_STATUS_BUTTON_LEFT) { + status |= PS2M_STATUS_BUTTON_1; + } + if (vmw.status & VMWARE_ABSPOINTER_STATUS_BUTTON_RIGHT) { + status |= PS2M_STATUS_BUTTON_2; + } + if (vmw.status & VMWARE_ABSPOINTER_STATUS_BUTTON_MIDDLE) { + status |= PS2M_STATUS_BUTTON_3; + } + } else { + return; // Ignore the PS/2 packet otherwise, it is likely garbage + } + } +#endif /* USE_VMWARE */ + + handle_mouse_event(status & (PS2M_STATUS_BUTTON_1 | PS2M_STATUS_BUTTON_2 | PS2M_STATUS_BUTTON_3), + abs, x, y, z); +} + +void __declspec(naked) __far ps2_mouse_callback() +{ + __asm { + pusha + push ds + push es + push fs + push gs + + ; 8 + 4 saved registers, 24 bytes + ; plus 4 bytes for retf address + ; = 28 bytes of stack before callback args + + mov bp, sp + push cs + pop ds + + mov ax,[bp+28+6] ; Status + mov bx,[bp+28+4] ; X + mov cx,[bp+28+2] ; Y + mov dx,[bp+28+0] ; Z + + call ps2_mouse_handler + + pop gs + pop fs + pop es + pop ds + popa + + retf + } +} + +#if USE_INTEGRATION +static void set_absolute(bool enable) +{ +#if USE_VIRTUALBOX + data.vbhaveabs = false; + if (data.vbavail) { + int err = vbox_set_mouse(&data.vb, enable, false); + if (enable && !err) { + dlog_puts("VBox absolute mouse enabled"); + data.vbhaveabs = true; + } else if (!enable) { + dlog_puts("VBox absolute mouse disabled"); + } + } +#endif /* USE_VIRTUALBOX */ +#if USE_VMWARE + if (data.vmwavail) { + if (enable) { + uint16_t data_avail; + // It looks like a reset of the PS/2 mouse completely disables + // the vmware interface, so we have to reenable it from scratch. + vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_ENABLE); + vmware_abspointer_data_clear(); + vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_REQUEST_ABSOLUTE); + + dlog_puts("VMware absolute mouse enabled"); + } else { + vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_REQUEST_RELATIVE); + vmware_abspointer_cmd(VMWARE_ABSPOINTER_CMD_DISABLE); + + dlog_puts("VMware absolute mouse disabled"); + } + } +#endif /* USE_VMWARE */ +} +#endif /* USE_INTEGRATION */ + +static void reset_mouse_hardware() +{ + ps2m_enable(false); + +#if USE_WHEEL + if (data.usewheel && ps2m_detect_wheel()) { + // Detect wheel also reinitializes the mouse to the proper packet size + data.haswheel = true; + } else { + // Otherwise do an extra reset to return back to initial state, just in case + data.haswheel = false; + ps2m_init(PS2M_PACKET_SIZE_PLAIN); + } +#else + ps2m_init(PS2M_PACKET_SIZE_PLAIN); +#endif + + ps2m_set_resolution(3); // 3 = 200 dpi, 8 counts per millimeter + ps2m_set_sample_rate(4); // 4 = 80 reports per second + ps2m_set_scaling_factor(1); // 1 = 1:1 scaling + + ps2m_set_callback(get_cs():>ps2_mouse_callback); + +#if USE_INTEGRATION + // By default, enable absolute mouse + set_absolute(true); + // Reload hardware cursor + load_cursor(); +#endif + + ps2m_enable(true); +} + +/** Reset "software" mouse settings, i.e. those configurable by the client program. */ +static void reset_mouse_settings() +{ + data.event_mask = 0; + data.event_handler = 0; + + data.mickeysPerLine.x = 8; + data.mickeysPerLine.y = 16; + data.doubleSpeedThreshold = 64; + data.min.x = 0; + data.max.x = data.screen_max.x; + data.min.y = 0; + data.max.y = data.screen_max.y; + data.visible_count = -1; + data.cursor_text_type = 0; + data.cursor_text_and_mask = 0xFFFFU; + data.cursor_text_xor_mask = 0x7700U; + data.cursor_hotspot.x = 0; + data.cursor_hotspot.y = 0; + memcpy(data.cursor_graphic, default_cursor_graphic, sizeof(data.cursor_graphic)); + +#if USE_WHEEL + data.usewheelapi = false; +#endif + + refresh_cursor(); // This will hide the cursor and update data.cursor_visible +} + +/** Reset the current mouse state and throw away past events. */ +static void reset_mouse_state() +{ + int i; + data.pos.x = data.min.x; + data.pos.y = data.min.y; + data.pos_frac.x = 0; + data.pos_frac.y = 0; + data.delta.x = 0; + data.delta.y = 0; + data.delta_frac.x = 0; + data.delta_frac.y = 0; + data.abs_pos.x = -1; + data.abs_pos.y = -1; + data.buttons = 0; + for (i = 0; i < NUM_BUTTONS; i++) { + data.button[i].pressed.count = 0; + data.button[i].pressed.last.x = 0; + data.button[i].pressed.last.y = 0; + data.button[i].released.count = 0; + data.button[i].released.last.x = 0; + data.button[i].released.last.y = 0; + } + data.wheel_delta = 0; + data.cursor_visible = false; + data.cursor_pos.x = 0; + data.cursor_pos.y = 0; + data.cursor_prev_char = 0; + memset(data.cursor_prev_graphic, 0, sizeof(data.cursor_prev_graphic)); +} + +static void return_clear_wheel_counter(union INTPACK __far *r) +{ + r->x.cx = snap_to_grid(data.wheel_last.x, data.screen_granularity.x); + r->x.dx = snap_to_grid(data.wheel_last.y, data.screen_granularity.y); + r->x.bx = data.wheel_delta; + data.wheel_last.x = 0; + data.wheel_last.y = 0; + data.wheel_delta = 0; +} + +static void return_clear_button_counter(union INTPACK __far *r, struct buttoncounter *c) +{ + r->x.cx = snap_to_grid(c->last.x, data.screen_granularity.x); + r->x.dx = snap_to_grid(c->last.y, data.screen_granularity.y); + r->x.bx = c->count; + c->last.x = 0; + c->last.y = 0; + c->count = 0; +} + +/** Entry point for our int33 API. */ +static void int33_handler(union INTPACK r) +#pragma aux int33_handler "*" parm caller [] modify [ax bx cx dx si di es fs gs] +{ + switch (r.x.ax) { + case INT33_RESET_MOUSE: + dlog_puts("Mouse reset"); + reload_video_info(); + reset_mouse_settings(); + reset_mouse_hardware(); + reset_mouse_state(); + r.x.ax = INT33_MOUSE_FOUND; + r.x.bx = NUM_BUTTONS; + break; + case INT33_SHOW_CURSOR: + data.visible_count++; + refresh_cursor(); + break; + case INT33_HIDE_CURSOR: + data.visible_count--; + refresh_cursor(); + break; + case INT33_GET_MOUSE_POSITION: +#if TRACE_EVENTS + dlog_puts("Mouse get position"); +#endif + r.x.cx = snap_to_grid(data.pos.x, data.screen_granularity.x); + r.x.dx = snap_to_grid(data.pos.y, data.screen_granularity.y); + r.x.bx = data.buttons; +#if USE_WHEEL + if (data.haswheel) { + r.h.bh = data.wheel_delta; + data.wheel_delta = 0; + } +#endif + break; + case INT33_SET_MOUSE_POSITION: +#if TRACE_EVENTS + dlog_puts("Mouse set position"); +#endif + data.pos.x = r.x.cx; + data.pos.y = r.x.dx; + data.pos_frac.x = 0; + data.pos_frac.y = 0; + data.delta.x = 0; + data.delta.y = 0; + data.delta_frac.x = 0; + data.delta_frac.y = 0; + bound_position_to_window(); + break; + case INT33_GET_BUTTON_PRESSED_COUNTER: + r.x.ax = data.buttons; +#if USE_WHEEL + if (data.haswheel) { + r.h.bh = data.wheel_delta; + if (r.x.bx == -1) { + // Asked for wheel information + return_clear_wheel_counter(&r); + break; + } + } +#endif + // Regular button information + return_clear_button_counter(&r, + &data.button[MIN(r.x.bx, NUM_BUTTONS - 1)].pressed); + break; + case INT33_GET_BUTTON_RELEASED_COUNTER: + r.x.ax = data.buttons; +#if USE_WHEEL + if (data.haswheel) { + r.h.bh = data.wheel_delta; + if (r.x.bx == -1) { + // Asked for wheel information + return_clear_wheel_counter(&r); + break; + } + } +#endif + return_clear_button_counter(&r, + &data.button[MIN(r.x.bx, NUM_BUTTONS - 1)].released); + break; + case INT33_SET_HORIZONTAL_WINDOW: + dlog_print("Mouse set horizontal window ["); + dlog_printd(r.x.cx); + dlog_putc(','); + dlog_printd(r.x.dx); + dlog_puts("]"); + // Recheck in case someone changed the video mode + refresh_video_info(); + data.min.x = r.x.cx; + data.max.x = r.x.dx; + bound_position_to_window(); + break; + case INT33_SET_VERTICAL_WINDOW: + dlog_print("Mouse set vertical window ["); + dlog_printd(r.x.cx); + dlog_putc(','); + dlog_printd(r.x.dx); + dlog_puts("]"); + refresh_video_info(); + data.min.y = r.x.cx; + data.max.y = r.x.dx; + bound_position_to_window(); + break; + case INT33_SET_GRAPHICS_CURSOR: + dlog_puts("Mouse set graphics cursor"); + hide_cursor(); + data.cursor_hotspot.x = r.x.bx; + data.cursor_hotspot.y = r.x.cx; + _fmemcpy(data.cursor_graphic, MK_FP(r.x.es, r.x.dx), sizeof(data.cursor_graphic)); + load_cursor(); + refresh_cursor(); + break; + case INT33_SET_TEXT_CURSOR: + dlog_print("Mouse set text cursor "); + dlog_printd(r.x.bx); + dlog_endline(); + hide_cursor(); + data.cursor_text_type = r.x.bx; + data.cursor_text_and_mask = r.x.cx; + data.cursor_text_xor_mask = r.x.dx; + refresh_cursor(); + break; + case INT33_GET_MOUSE_MOTION: +#if TRACE_EVENTS + dlog_puts("Mouse get motion"); +#endif + r.x.cx = data.delta.x; + r.x.dx = data.delta.y; + data.delta.x = 0; + data.delta.y = 0; + break; + case INT33_SET_EVENT_HANDLER: + dlog_puts("Mouse set event handler"); + data.event_mask = r.x.cx; + data.event_handler = MK_FP(r.x.es, r.x.dx); + break; + case INT33_SET_MOUSE_SPEED: + dlog_print("Mouse set speed x="); + dlog_printd(r.x.cx); + dlog_print(" y="); + dlog_printd(r.x.dx); + dlog_endline(); + data.mickeysPerLine.x = r.x.cx; + data.mickeysPerLine.y = r.x.dx; + break; + case INT33_SET_SPEED_DOUBLE_THRESHOLD: + dlog_print("Mouse set speed double threshold="); + dlog_printd(r.x.dx); + dlog_endline(); + data.doubleSpeedThreshold = r.x.dx; + break; + case INT33_EXCHANGE_EVENT_HANDLER: + dlog_puts("Mouse exchange event handler"); + data.event_mask = r.x.cx; + { + void (__far *prev_event_handler)() = data.event_handler; + data.event_handler = MK_FP(r.x.es, r.x.dx); + r.x.es = FP_SEG(prev_event_handler); + r.x.dx = FP_OFF(prev_event_handler); + } + break; + case INT33_GET_MOUSE_STATUS_SIZE: + dlog_puts("Mouse get status size"); + r.x.bx = sizeof(TSRDATA); + break; + case INT33_SAVE_MOUSE_STATUS: + dlog_puts("Mouse save status"); + _fmemcpy(MK_FP(r.x.es, r.x.dx), &data, sizeof(TSRDATA)); + break; + case INT33_LOAD_MOUSE_STATUS: + dlog_puts("Mouse load status"); + _fmemcpy(&data, MK_FP(r.x.es, r.x.dx), sizeof(TSRDATA)); + break; + case INT33_SET_MOUSE_SENSITIVITY: + dlog_print("Mouse set speed x="); + dlog_printd(r.x.bx); + dlog_print(" y="); + dlog_printd(r.x.cx); + dlog_print(" threshold="); + dlog_printd(r.x.dx); + dlog_endline(); + data.mickeysPerLine.x = r.x.bx; + data.mickeysPerLine.y = r.x.cx; + data.doubleSpeedThreshold = r.x.dx; + break; + case INT33_GET_MOUSE_SENSITIVITY: + r.x.bx = data.mickeysPerLine.x; + r.x.cx = data.mickeysPerLine.y; + r.x.dx = data.doubleSpeedThreshold; + break; + case INT33_RESET_SETTINGS: + dlog_puts("Mouse reset settings"); + reload_video_info(); + reset_mouse_settings(); + reset_mouse_state(); + r.x.ax = INT33_MOUSE_FOUND; + r.x.bx = NUM_BUTTONS; + break; + case INT33_GET_LANGUAGE: + r.x.bx = 0; + break; + case INT33_GET_DRIVER_INFO: + dlog_puts("Mouse get driver info"); + r.h.bh = REPORTED_VERSION_MAJOR; + r.h.bl = REPORTED_VERSION_MINOR; + r.h.ch = INT33_MOUSE_TYPE_PS2; + r.h.cl = 0; + break; + case INT33_GET_MAX_COORDINATES: + r.x.bx = 0; + r.x.cx = MAX(data.screen_max.x, data.max.x); + r.x.dx = MAX(data.screen_max.y, data.max.y); + break; + case INT33_GET_WINDOW: + r.x.ax = data.min.x; + r.x.bx = data.min.y; + r.x.cx = data.max.x; + r.x.dx = data.max.y; + break; +#if USE_WHEEL + // Wheel API extensions: + case INT33_GET_CAPABILITIES: + dlog_puts("Mouse get capabitilies"); + r.x.ax = INT33_WHEEL_API_MAGIC; // Driver supports wheel API + r.x.bx = 0; + r.x.cx = data.haswheel ? INT33_CAPABILITY_MOUSE_API : 0; + data.usewheelapi = true; // Someone calling this function likely wants to use wheel API + break; +#endif + // Our internal API extensions: + case INT33_GET_TSR_DATA: + dlog_puts("Get TSR data"); + r.x.es = FP_SEG(&data); + r.x.di = FP_OFF(&data); + break; + default: + dlog_print("Unknown mouse function ax="); + dlog_printx(r.x.ax); + dlog_endline(); + break; + } +} + +// Can't use __interrupt, it makes a call to GETDS on the runtime +void __declspec(naked) __far int33_isr(void) +{ + __asm { + pusha + push ds + push es + push fs + push gs + + mov bp, sp + push cs + pop ds + + call int33_handler + + pop gs + pop fs + pop es + pop ds + popa + iret + } +} + +#if USE_WIN386 +/** Windows will call this function to notify events when we are inside a DOS box. */ +static void windows_mouse_handler(int action, int x, int y, int buttons, int events) +#pragma aux windows_mouse_handler "*" parm [ax] [bx] [cx] [dx] [si] modify [ax bx cx dx es] +{ + switch (action) { + case VMD_ACTION_MOUSE_EVENT: + (void) events; + // Forward event to our internal system + handle_mouse_event(buttons, true, x, y, 0); + break; + case VMD_ACTION_HIDE_CURSOR: + dlog_puts("VMD_ACTION_HIDE_CURSOR"); + data.w386cursor = true; + refresh_cursor(); + break; + case VMD_ACTION_SHOW_CURSOR: + dlog_puts("VMD_ACTION_SHOW_CURSOR"); + data.w386cursor = false; + refresh_cursor(); + break; + } +} + +void __declspec(naked) __far windows_mouse_callback() +{ + __asm { + pusha + push ds + push es + push fs + push gs + + mov bp, sp + push cs + pop ds + + call windows_mouse_handler + + pop gs + pop fs + pop es + pop ds + popa + + retf + } +} + +static void int2f_handler(union INTPACK r) +#pragma aux int2f_handler "*" parm caller [] modify [ax bx cx dx es] +{ + switch (r.x.ax) { + case INT2F_NOTIFY_WIN386_STARTUP: + dlog_print("Windows is starting, version="); + dlog_printx(r.x.di); + dlog_endline(); + data.w386_startup.version = 3; + data.w386_startup.next = MK_FP(r.x.es, r.x.bx); + data.w386_startup.device_driver = 0; + data.w386_startup.device_driver_data = 0; + data.w386_startup.instance_data = &data.w386_instance; + data.w386_instance[0].ptr = &data; + data.w386_instance[0].size = sizeof(data); + data.w386_instance[1].ptr = 0; + data.w386_instance[1].size = 0; + r.x.es = FP_SEG(&data.w386_startup); + r.x.bx = FP_OFF(&data.w386_startup); + break; + case INT2F_NOTIFY_DEVICE_CALLOUT: + switch (r.x.bx) { + case VMD_DEVICE_ID: + switch (r.x.cx) { + case VMD_CALLOUT_TEST: + r.x.cx = 1; // Yes, we are here! + break; + case VMD_CALLOUT_GET_DOS_MOUSE_API: + // Windows is asking our mouse driver for the hook function address + r.x.ds = get_cs(); + r.x.si = FP_OFF(windows_mouse_callback); + r.x.ax = 0; // Yes, we are here! + break; + } + break; + } + break; + } +} + +void __declspec(naked) __far int2f_isr(void) +{ + __asm { + pusha + push ds + push es + push fs + push gs + + mov bp, sp + push cs + pop ds + + call int2f_handler + + pop gs + pop fs + pop es + pop ds + popa + + ; Jump to the next handler in the chain + jmp dword ptr cs:[data + 4] ; wasm doesn't support structs, this is data.prev_int2f_handler + } +} +#endif + +static LPTSRDATA int33_get_tsr_data(void); +#pragma aux int33_get_tsr_data = \ + "xor ax, ax" \ + "mov es, ax" \ + "mov di, ax" \ + "mov ax, 0x73" \ + "int 0x33" \ + __value [es di] \ + __modify [ax] + +static LPTSRDATA local_get_tsr_data(void); +#pragma aux local_get_tsr_data = \ + "mov ax, cs" \ + "mov es, ax" \ + "mov di, offset data" \ + __value [es di] \ + __modify [ax] + +LPTSRDATA __far get_tsr_data(bool installed) +{ + if (installed) { + return int33_get_tsr_data(); + } else { + return local_get_tsr_data(); + } +} + +int resident_end; diff --git a/mousetsr.h b/mousetsr.h new file mode 100644 index 0000000..a025c92 --- /dev/null +++ b/mousetsr.h @@ -0,0 +1,217 @@ +/* + * VBMouse - DOS mouse driver resident part + * 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. + */ + +#ifndef DOSTSR_H +#define DOSTSR_H + +#include +#include + +#include "int2fwin.h" +#include "int10vga.h" + +// User customizable defines + +/** Enable the VirtualBox integration */ +#define USE_VIRTUALBOX 1 +/** Enable the VMware integration */ +#define USE_VMWARE 1 +/** Enable the Windows 386/protected mode integration */ +#define USE_WIN386 1 +/** Enable the wheel. */ +#define USE_WHEEL 1 +/** Trace events verbosily */ +#define TRACE_EVENTS 0 + +// End of user customizable defines + +#define USE_INTEGRATION (USE_VIRTUALBOX || USE_VMWARE) + +#define NUM_BUTTONS 3 + +#define GRAPHIC_CURSOR_WIDTH 16 +#define GRAPHIC_CURSOR_HEIGHT 16 +#define GRAPHIC_CURSOR_SCANLINE_LEN 2 +#define GRAPHIC_CURSOR_MASK_LEN (GRAPHIC_CURSOR_HEIGHT * GRAPHIC_CURSOR_SCANLINE_LEN) +#define GRAPHIC_CURSOR_DATA_LEN (2 * GRAPHIC_CURSOR_MASK_LEN) + +#define VERSION_MAJOR 0 +#define VERSION_MINOR 4 +#define REPORTED_VERSION_MAJOR 6 +#define REPORTED_VERSION_MINOR 0x30 + +#if USE_VIRTUALBOX +#include "vbox.h" +#endif + +struct point { + int16_t x, y; +}; + +typedef struct tsrdata { + // TSR installation data + /** Previous int33 ISR, storing it for uninstall. */ + void (__interrupt __far *prev_int33_handler)(); +#if USE_WIN386 + void (__interrupt __far *prev_int2f_handler)(); +#endif +#if USE_WHEEL + /** Whether to enable & use wheel mouse. */ + bool usewheel; + /** Key (scancode) to generate on wheel scroll up/down, or 0 for none. */ + uint16_t wheel_up_key, wheel_down_key; +#endif + + // Video settings + /** Information of the current video mode. */ + struct modeinfo video_mode; + /** Max (virtual) coordinates of full screen in the current mode. + * Used for rendering graphic cursor, mapping absolute coordinates, + * and initializing the default min/max window. */ + struct point screen_max; + /** In some modes, the virtual coordinates are larger than the + * physical screen coordinates. + * real coordinates = virtual coordinates * screen_scale. */ + struct point screen_scale; + /** In text modes, we want to snap the cursor position to the cell grid. + * This stores the desired grid granularity. */ + struct point screen_granularity; + + // Detected mouse hardware +#if USE_WHEEL + /** Whether the current mouse has a wheel (and support is enabled). */ + bool haswheel; +#endif + + // Current mouse settings + /** Mouse sensitivity/speed. */ + struct point mickeysPerLine; // mickeys per 8 pixels + /** Mouse acceleration "double-speed threshold". */ + uint16_t doubleSpeedThreshold; // mickeys + /** Current window min coordinates. */ + struct point min; + /** Current window max coordinates. */ + struct point max; + /** Current cursor visible counter. If >= 0, cursor should be shown. */ + int16_t visible_count; + /** For text cursor, whether this is a software or hardware cursor. */ + uint8_t cursor_text_type; + /** Masks for the text cursor. */ + uint16_t cursor_text_and_mask, cursor_text_xor_mask; + /** Hotspot for the graphic cursor. */ + struct point cursor_hotspot; + /** Masks for the graphic cursor. */ + uint16_t cursor_graphic[GRAPHIC_CURSOR_DATA_LEN/sizeof(uint16_t)]; +#if USE_WHEEL + /** Whether someone asked for the int33 wheel API, in which case we + * should send them wheel movement rather than fake keypresses. */ + bool usewheelapi; +#endif + + // Current mouse status + /** Current cursor position (in pixels). */ + struct point pos; + /** Current remainder of movement that does not yet translate to an entire pixel + * (8ths of pixel). */ + struct point pos_frac; + /** Current delta movement (in mickeys) since the last report. */ + struct point delta; + /** Current remainder of delta movement that does not yet translate to an entire mickey + * Usually only when mickeysPerLine is not a multiple of 8. */ + struct point delta_frac; + /** Last absolute position (to compute a delta for relative motion emulation). Using -1 for "none". */ + struct point abs_pos; + /** Total mickeys moved in the last second. */ + uint16_t total_motion; + /** Ticks when the above value was last reset. */ + uint16_t last_ticks; + /** Current status of buttons (as bitfield). */ + uint16_t buttons; + struct { + struct buttoncounter { + struct point last; + uint16_t count; + } pressed, released; + } button[NUM_BUTTONS]; + /** Total delta movement of the wheel since the last wheel report. */ + int16_t wheel_delta; + /** Last position where the wheel was moved. */ + struct point wheel_last; + + // Cursor information + /** Whether the cursor is currently displayed or not. */ + bool cursor_visible; + /** The current position at which the cursor is displayed. */ + struct point cursor_pos; + /** For text mode cursor, the character data that was displayed below the cursor. */ + uint16_t cursor_prev_char; + /** For graphical mode cursor, contents of the screen that were displayed below + * the cursor before the cursor was drawn. */ + uint8_t cursor_prev_graphic[GRAPHIC_CURSOR_WIDTH * GRAPHIC_CURSOR_HEIGHT]; + + // Current handlers + /** Address of the event handler. */ + void (__far *event_handler)(); + /** Events for which we should call the event handler. */ + uint16_t event_mask; + +#if USE_WIN386 + /** Information that we pass to Windows 386 on startup. */ + win386_startup_info w386_startup; + win386_instance_item w386_instance[2]; + /** Whether Windows 386 is rendering the cursor for us, + * and therefore we should hide our own. */ + bool w386cursor : 1; +#endif + +#if USE_VIRTUALBOX + /** VirtualBox is available. */ + bool vbavail : 1; + /** Want to use the VirtualBox "host" cursor. */ + bool vbwantcursor : 1; + /** Have VirtualBox absolute coordinates. */ + bool vbhaveabs : 1; + struct vboxcomm vb; +#endif + +#if USE_VMWARE + /** VMware is available. */ + bool vmwavail : 1; +#endif +} TSRDATA; + +typedef TSRDATA * PTSRDATA; +typedef TSRDATA __far * LPTSRDATA; + +extern void __declspec(naked) __far int33_isr(void); + +extern void __declspec(naked) __far int2f_isr(void); + +extern LPTSRDATA __far get_tsr_data(bool installed); + +/** This symbol is always at the end of the TSR segment */ +extern int resident_end; + +/** This is not just data, but the entire segment. */ +static inline unsigned get_resident_size(void) +{ + return FP_OFF(&resident_end); +} + +#endif diff --git a/mousew16.c b/mousew16.c new file mode 100644 index 0000000..6e70371 --- /dev/null +++ b/mousew16.c @@ -0,0 +1,187 @@ +/* + * VBMouse - win16 mouse driver entry points + * 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 "utils.h" +#include "int33.h" +#include "int2fwin.h" +#include "mousew16.h" + +#define TRACE_EVENTS 0 + +#define MOUSE_NUM_BUTTONS 2 + +/** The routine Windows gave us which we should use to report events. */ +static LPFN_MOUSEEVENT eventproc; +/** Current status of the mouse driver. */ +static bool enabled; +/** Previous deltaX, deltaY from the int33 mouse callback (for relative motion) */ +static short prev_delta_x, prev_delta_y; + +/* This is how events are delivered to Windows */ + +static void send_event(unsigned short Status, short deltaX, short deltaY, short ButtonCount, short extra1, short extra2); +#pragma aux (MOUSEEVENTPROC) send_event = \ + "call dword ptr [eventproc]" + +/* Our "CALLBACKS" segment which is fixed and non-relocatable. */ + +#pragma code_seg ( "CALLBACKS" ) + +#include "dlog.h" + +static void FAR int33_mouse_callback(uint16_t events, uint16_t buttons, int16_t x, int16_t y, int16_t delta_x, int16_t delta_y) +#pragma aux (INT33_CB) int33_mouse_callback +{ + int status = 0; + +#if TRACE_EVENTS + dlog_print("w16mouse: events="); + dlog_printx(events); + dlog_print(" buttons="); + dlog_printx(buttons); + dlog_print(" x="); + dlog_printd(x); + dlog_print(" y="); + dlog_printd(y); + dlog_print(" dx="); + dlog_printd(delta_x); + dlog_print(" dy="); + dlog_printd(delta_y); + dlog_endline(); +#endif + + if (events & INT33_EVENT_MASK_LEFT_BUTTON_PRESSED) status |= SF_B1_DOWN; + if (events & INT33_EVENT_MASK_LEFT_BUTTON_RELEASED) status |= SF_B1_UP; + if (events & INT33_EVENT_MASK_RIGHT_BUTTON_PRESSED) status |= SF_B2_DOWN; + if (events & INT33_EVENT_MASK_RIGHT_BUTTON_RELEASED) status |= SF_B2_UP; + + if (events & INT33_EVENT_MASK_MOVEMENT) { + status |= SF_MOVEMENT; + } + + if (events & INT33_EVENT_MASK_ABSOLUTE) { + status |= SF_ABSOLUTE; + + // We set the window to be 0..0x7FFF, so just scale to 0xFFFF + x = (uint16_t)(x) * 2; + y = (uint16_t)(y) * 2; + } else { + // Prefer to use mickeys for relative motion if we don't have absolute data + x = delta_x - prev_delta_x; + y = delta_y - prev_delta_y; + + prev_delta_x = delta_x; + prev_delta_y = delta_y; + } + + // Unused + (void) buttons; + +#if TRACE_EVENTS + dlog_print("w16mouse: event status="); + dlog_printx(status); + dlog_print(" x="); + if (status & SF_ABSOLUTE) dlog_printu(x); + else dlog_printd(x); + dlog_print(" y="); + if (status & SF_ABSOLUTE) dlog_printu(y); + else dlog_printd(y); + dlog_endline(); +#endif + + send_event(status, x, y, MOUSE_NUM_BUTTONS, 0, 0); +} + +#pragma code_seg () + +/* Driver exported functions. */ + +/** DLL entry point (or driver initialization routine). + * The initialization routine should check whether a mouse exists. + * @return nonzero value indicates a mouse exists. + */ +#pragma off (unreferenced) +BOOL FAR PASCAL LibMain(HINSTANCE hInstance, WORD wDataSegment, + WORD wHeapSize, LPSTR lpszCmdLine) +#pragma pop (unreferenced) +{ + uint16_t version = int33_get_driver_version(); + + // For now we just check for the presence of any int33 driver version + if (version == 0) { + // No one responded to our request, we can assume no driver + // This will cause a "can't load .drv" message from Windows + return 0; + } + + return 1; +} + +/** Called by Windows to retrieve information about the mouse hardware. */ +WORD FAR PASCAL Inquire(LPMOUSEINFO lpMouseInfo) +{ + lpMouseInfo->msExist = 1; + lpMouseInfo->msRelative = 0; + lpMouseInfo->msNumButtons = MOUSE_NUM_BUTTONS; + lpMouseInfo->msRate = 80; + return sizeof(MOUSEINFO); +} + +/** Called by Windows to enable the mouse driver. + * @param lpEventProc Callback function to call when a mouse event happens. */ +VOID FAR PASCAL Enable(LPFN_MOUSEEVENT lpEventProc) +{ + // Store the windows-given callback + _disable(); // Write to far pointer may not be atomic, and we could be interrupted mid-write + eventproc = lpEventProc; + _enable(); + + if (!enabled) { + int33_reset(); + + // Since the mouse driver will likely not know the Windows resolution, + // let's manually set up a large window of coordinates so as to have + // as much precision as possible. + // We use 0x7FFF instead of 0xFFFF because this parameter is officially a signed value. + int33_set_horizontal_window(0, 0x7FFF); + int33_set_vertical_window(0, 0x7FFF); + + int33_set_event_handler(INT33_EVENT_MASK_ALL, int33_mouse_callback); + + enabled = true; + } +} + +/** Called by Windows to disable the mouse driver. */ +VOID FAR PASCAL Disable(VOID) +{ + if (enabled) { + int33_reset(); // This removes our handler and all other settings + enabled = false; + } +} + +/** Called by Window to retrieve the interrupt vector number used by this driver, or -1. */ +int FAR PASCAL MouseGetIntVect(VOID) +{ + return 0x33; +} diff --git a/mousew16.h b/mousew16.h new file mode 100644 index 0000000..54b262f --- /dev/null +++ b/mousew16.h @@ -0,0 +1,53 @@ +#ifndef MOUSEW16_H +#define MOUSEW16_H + +/* Win16's mouse driver interface. */ + +/** Contains information about the mouse, used by Inquire(). */ +typedef _Packed struct MOUSEINFO +{ + /** Whether a mouse exists. */ + char msExist; + /** Whether the mouse returns absolute or relative coordinates. */ + char msRelative; + /** Number of buttons. */ + short msNumButtons; + /** Maximum number of events per second. */ + short msRate; + // Reserved: + short msXThreshold; + short msYThreshold; + short msXRes; + short msYRes; + // The following are available in Windows >= 3.1 only: + #if 0 + /** Specifies the COM port used, or 0 for none. */ + short msMouseCommPort; + #endif +} MOUSEINFO; +typedef MOUSEINFO __far *LPMOUSEINFO; + +/** Movement occurred. */ +#define SF_MOVEMENT 0x0001 +/** Button 1 changed to down. */ +#define SF_B1_DOWN 0x0002 +/** Button 1 changed to up. */ +#define SF_B1_UP 0x0004 +/** Button 2 changed to down. */ +#define SF_B2_DOWN 0x0008 +/** Button 2 changed to up. */ +#define SF_B2_UP 0x0010 +/** Event coordinates are absolute instead of relative. */ +#define SF_ABSOLUTE 0x8000 + +/** Driver should call this callback when there are new mouse events to report. + * @param Status What happened. Combination of SF_MOVEMENT, SF_ABSOLUTE, etc. + * @param deltaX either number of mickeys moved or absolute coordinate if SB_ABSOLUTE. + * @param deltaY either number of mickeys moved or absolute coordinate if SB_ABSOLUTE. + * @param ButtonCount number of buttons + * @param extra1,extra2 leave as zero + */ +typedef void (__far *LPFN_MOUSEEVENT)(unsigned short Status, short deltaX, short deltaY, short ButtonCount, short extra1, short extra2); +#pragma aux MOUSEEVENTPROC parm [ax] [bx] [cx] [dx] [di] [si] + +#endif diff --git a/mousew16.lnk b/mousew16.lnk new file mode 100644 index 0000000..bb80eb2 --- /dev/null +++ b/mousew16.lnk @@ -0,0 +1,11 @@ +system windows_dll +option map=w16mouse.map +option modname=MOUSE # This is necessary; USER.EXE imports mouse functions using this module name +option description 'VirtualBox Mouse driver' + +segment CALLBACKS fixed shared # We need a non-moveable segment to store our callback routines + +export Inquire.1 +export Enable.2 +export Disable.3 +export MouseGetIntVect.4 diff --git a/mousmain.c b/mousmain.c new file mode 100644 index 0000000..5fe2246 --- /dev/null +++ b/mousmain.c @@ -0,0 +1,646 @@ +/* + * 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 + +#include "dlog.h" +#include "int33.h" +#include "int21dos.h" +#include "ps2.h" +#include "vbox.h" +#include "vmware.h" +#include "mousetsr.h" + +#if USE_WHEEL +static void detect_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 (data->haswheel = ps2m_detect_wheel()) { + printf("Wheel mouse found and enabled\n"); + } +} + +static int set_wheel(LPTSRDATA data, bool enable) +{ + printf("Setting wheel support to %s\n", enable ? "enabled" : "disabled"); + data->usewheel = enable; + + if (data->usewheel) { + detect_wheel(data); + } else { + data->haswheel = false; + } + + return 0; +} + +static int set_wheel_key(LPTSRDATA data, const char *keyname) +{ + if (!data->usewheel || !data->haswheel) { + fprintf(stderr, "Wheel not detected or support not enabled\n"); + return EXIT_FAILURE; + } + if (keyname) { + if (stricmp(keyname, "updn") == 0) { + data->wheel_up_key = 0x4800; + data->wheel_down_key = 0x5000; + printf("Generate Up Arrow / Down Arrow key presses on wheel movement\n"); + } else if (stricmp(keyname, "pageupdn") == 0) { + data->wheel_up_key = 0x4900; + data->wheel_down_key = 0x5100; + printf("Generate PageUp / PageDown key presses on wheel movement\n"); + } else { + fprintf(stderr, "Unknown key '%s'\n", keyname); + return EXIT_FAILURE; + } + } else { + printf("Disabling wheel keystroke generation\n"); + data->wheel_up_key = 0; + data->wheel_down_key = 0; + } + return EXIT_SUCCESS; +} +#endif /* USE_WHEEL */ + +#if USE_VIRTUALBOX +static int set_virtualbox_integration(LPTSRDATA data, bool enable) +{ + if (enable) { + int err; + + data->vbavail = false; // Reinitialize it even if already enabled + + err = vbox_init_device(&data->vb); + if (err) { + fprintf(stderr, "Cannot find VirtualBox PCI device, err=%d\n", err); + return err; + } + + printf("Found VirtualBox device at IO 0x%x\n", data->vb.iobase); + + err = vbox_init_buffer(&data->vb); + if (err) { + fprintf(stderr, "Cannot lock buffer used for VirtualBox communication, err=%d\n", err); + return err; + } + + err = vbox_report_guest_info(&data->vb, VBOXOSTYPE_DOS); + if (err) { + fprintf(stderr, "VirtualBox communication is not working, err=%d\n", err); + return err; + } + + printf("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("Disabled VirtualBox integration\n"); + data->vbavail = false; + data->vbhaveabs = false; + } else { + printf("VirtualBox integration already disabled or not available\n"); + } + } + + return 0; +} + +static int set_virtualbox_host_cursor(LPTSRDATA data, bool enable) +{ + printf("Setting host cursor to %s\n", enable ? "enabled" : "disabled"); + data->vbwantcursor = enable; + + return 0; +} +#endif + +#if USE_VMWARE +static int set_vmware_integration(LPTSRDATA data, bool enable) +{ + if (enable) { + int32_t version; + uint32_t status; + uint16_t data_avail; + + data->vmwavail = false; + + version = vmware_get_version(); + if (version < 0) { + fprintf(stderr, "Could not detect VMware, err=%ld\n", version); + return -1; + } + + printf("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, "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("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("Disabled VMware integration\n"); + } else { + printf("VMware integration already disabled or not available\n"); + } + } + + return 0; +} +#endif + +static int set_integration(LPTSRDATA data, bool enable) +{ + 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); + if (!err) return 0; +#endif + +#if USE_VMWARE + // Afterwards try VMWare integration + err = set_vmware_integration(data, true); + if (!err) return 0; +#endif + + printf("Neither VirtualBox nor VMware integration available\n"); + return err; + } else { +#if USE_VIRTUALBOX + if (data->vbavail) { + set_virtualbox_integration(data, false); + } +#endif +#if USE_VMWARE + if (data->vmwavail) { + set_vmware_integration(data, false); + } +#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("VirtualBox integration not available\n"); + return -1; +} + +static int configure_driver(LPTSRDATA data) +{ + int err; + + // Configure the debug logging port + dlog_init(); + + // Check for PS/2 mouse BIOS availability + if ((err = ps2m_init(PS2M_PACKET_SIZE_PLAIN))) { + fprintf(stderr, "Cannot init PS/2 mouse BIOS, err=%d\n", err); + // Can't do anything without PS/2 + return err; + } + +#if USE_WHEEL + // Let's utilize the wheel by default + data->usewheel = true; + data->wheel_up_key = 0; + data->wheel_down_key = 0; + detect_wheel(data); +#endif + +#if USE_INTEGRATION + // Enable integration by default + set_integration(data, true); +#endif + +#if USE_VIRTUALBOX + // Assume initially that we want host cursor + data->vbwantcursor = data->vbavail; +#endif + + return 0; +} + +/** Converts bytes to MS-DOS "paragraphs" (16 bytes), rounding up. */ +static inline unsigned get_paragraphs(unsigned bytes) +{ + return (bytes + 15) / 16; +} + +/** Gets the size of the resident part of this program, including the PSP. */ +static inline unsigned get_resident_program_size() +{ + return get_resident_size() + DOS_PSP_SIZE; +} + +/** Deallocates the environment block from the passed PSP segment. */ +static void deallocate_environment(__segment psp) +{ + // TODO : Too lazy to make PSP struct; + // 0x2C is offsetof the environment block field on the PSP + uint16_t __far *envblockP = (uint16_t __far *) MK_FP(psp, 0x2C); + dos_free(*envblockP); + *envblockP = 0; +} + +/** Copies a program to another location. + * @param new_seg PSP segment for the new location + * @param old_seg PSP segment for the old location + * @param size size of the program to copy including PSP size. */ +static void copy_program(__segment new_seg, __segment old_seg, unsigned size) +{ + // The MCB is always 1 segment before. + uint8_t __far *new_mcb = MK_FP(new_seg - 1, 0); + uint8_t __far *old_mcb = MK_FP(old_seg - 1, 0); + uint16_t __far *new_mcb_owner = (uint16_t __far *) &new_mcb[1]; + char __far *new_mcb_owner_name = &new_mcb[8]; + char __far *old_mcb_owner_name = &old_mcb[8]; + + // Copy entire resident segment including PSP + _fmemcpy(MK_FP(new_seg, 0), MK_FP(old_seg, 0), size); + + // Make the new MCB point to itself as owner + *new_mcb_owner = new_seg; + + // Copy the program name, too. + _fmemcpy(new_mcb_owner_name, old_mcb_owner_name, 8); +} + +/** Allocates a UMB of the given size. + * If no UMBs are available, this may still return a block in conventional memory. */ +static __segment allocate_umb(unsigned size) +{ + bool old_umb_link = dos_query_umb_link_state(); + unsigned int old_strategy = dos_query_allocation_strategy(); + __segment new_segment; + + dos_set_umb_link_state(true); + dos_set_allocation_strategy(DOS_FIT_BEST | DOS_FIT_HIGHONLY); + + new_segment = dos_alloc(get_paragraphs(size)); + + dos_set_umb_link_state(old_umb_link); + dos_set_allocation_strategy(old_strategy); + + return new_segment; +} + +static int reallocate_to_umb(LPTSRDATA __far * data) +{ + const unsigned int resident_size = get_resident_program_size(); + LPTSRDATA old_data = *data; + __segment old_psp_segment = FP_SEG(old_data) - (DOS_PSP_SIZE/16); + __segment new_psp_segment; + + deallocate_environment(_psp); + + // If we are already in UMA, don't bother + if (old_psp_segment >= 0xA000) { + return -1; + } + + new_psp_segment = allocate_umb(resident_size); + + if (new_psp_segment && new_psp_segment >= 0xA000) { + __segment new_segment = new_psp_segment + (DOS_PSP_SIZE/16); + printf("Moving to upper memory\n"); + + // Create a new program instance including PSP at the new_segment + copy_program(new_psp_segment, old_psp_segment, resident_size); + + // Tell DOS to "switch" to the new program + dos_set_psp(new_psp_segment); + + // Now update the data pointer to the new segment + *data = MK_FP(new_segment, FP_OFF(old_data)); + + return 0; + } else { + printf("No upper memory available\n"); + if (new_psp_segment) { + // In case we got another low-memory segment... + dos_free(new_psp_segment); + } + + return -1; + } +} + +static __declspec(aborts) int install_driver(LPTSRDATA data, bool high) +{ + const unsigned int resident_size = DOS_PSP_SIZE + get_resident_size(); + + // No more interruptions from now on and until we TSR. + // Inserting ourselves in the interrupt chain should be atomic. + _disable(); + + 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); +#endif + + printf("Driver installed\n"); + + // If we reallocated ourselves to UMB, + // it's time to free our initial conventional memory allocation + if (high) { + // We are about to free() our own code segment. + // Nothing should try to allocate memory between this and the TSR call + // below, since it could overwrite our code... + dos_free(_psp); + } + + _dos_keep(EXIT_SUCCESS, get_paragraphs(resident_size)); + + // Shouldn't reach this part + return EXIT_FAILURE; +} + +static bool check_if_driver_uninstallable(LPTSRDATA data) +{ + void (__interrupt __far *cur_int33_handler)() = _dos_getvect(0x33); + + // Compare the segment of the installed handler to see if its ours + // or someone else's + if (FP_SEG(cur_int33_handler) != FP_SEG(data)) { + fprintf(stderr, "INT33 has been hooked by someone else, cannot safely remove\n"); + return false; + } + +#if USE_WIN386 + { + void (__interrupt __far *cur_int2f_handler)() = _dos_getvect(0x2f); + + if (FP_SEG(cur_int2f_handler) != FP_SEG(data)) { + fprintf(stderr, "INT2F has been hooked by someone else, cannot safely remove\n"); + return false; + } + } +#endif + + return true; +} + +static int unconfigure_driver(LPTSRDATA data) +{ +#if USE_INTEGRATION + set_integration(data, false); +#endif + + ps2m_enable(false); + ps2m_set_callback(0); + + return 0; +} + +static int uninstall_driver(LPTSRDATA data) +{ + _dos_setvect(0x33, data->prev_int33_handler); + +#if USE_WIN386 + _dos_setvect(0x2f, data->prev_int2f_handler); +#endif + + // 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("Driver uninstalled\n"); + + return 0; +} + +static int driver_reset(void) +{ + printf("Reset mouse driver\n"); + return int33_reset() == 0xFFFF; +} + +static int driver_not_found(void) +{ + fprintf(stderr, "Driver data not found (driver not installed?)\n"); + return EXIT_FAILURE; +} + +static void print_help(void) +{ + printf("\n" + "Usage: \n" + " VBMOUSE \n\n" + "Supported actions:\n" + " install install the driver (default)\n" + " low install in conventional memory (otherwise UMB)\n" + " uninstall uninstall the driver from memory\n" +#if USE_WHEEL + " wheel enable/disable wheel API support\n" + " wheelkey emulate a specific keystroke on wheel scroll\n" + " supported keys: updn, pageupdn\n" +#endif +#if USE_INTEGRATION + " integ enable/disable virtualbox integration\n" + " hostcur enable/disable mouse cursor rendering in host\n" +#endif + " reset reset mouse driver settings\n" + ); +} + +static int invalid_arg(const char *s) +{ + fprintf(stderr, "Invalid argument '%s'\n", s); + print_help(); + return EXIT_FAILURE; +} + +static int arg_required(const char *s) +{ + fprintf(stderr, "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; +} + +int main(int argc, const char *argv[]) +{ + LPTSRDATA data = get_tsr_data(true); + int err, argi = 1; + + printf("\nVBMouse %x.%x (like 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; + + argi++; + for (; argi < argc; argi++) { + if (stricmp(argv[argi], "low") == 0) { + high = false; + } else if (stricmp(argv[argi], "high") == 0) { + high = true; + } else { + return invalid_arg(argv[argi]); + } + } + + if (data) { + printf("VBMouse already installed\n"); + print_help(); + return EXIT_SUCCESS; + } + + data = get_tsr_data(false); + if (high) { + err = reallocate_to_umb(&data); + if (err) high = false; // Not fatal + } else { + deallocate_environment(_psp); + } + err = configure_driver(data); + if (err) 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; + } + err = unconfigure_driver(data); + if (err) { + return EXIT_FAILURE; + } + return uninstall_driver(data); +#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); + } +#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); + } 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]); + } +} diff --git a/vbmouse.lnk b/vbmouse.lnk new file mode 100644 index 0000000..bc1596f --- /dev/null +++ b/vbmouse.lnk @@ -0,0 +1,9 @@ +system dos +option map=dosmouse.map +# Put the resident text & data first, then the rest of standard classses +order clname RES_CODE + clname FAR_DATA + clname CODE segment BEGTEXT segment _TEXT + clname BEGDATA + clname DATA + clname BSS diff --git a/w16mouse.c b/w16mouse.c deleted file mode 100644 index 910358f..0000000 --- a/w16mouse.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * VBMouse - win16 mouse driver entry points - * 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 "utils.h" -#include "int33.h" -#include "int2fwin.h" -#include "w16mouse.h" - -#define TRACE_EVENTS 0 - -#define MOUSE_NUM_BUTTONS 2 - -/** The routine Windows gave us which we should use to report events. */ -static LPFN_MOUSEEVENT eventproc; -/** Current status of the mouse driver. */ -static bool enabled; -/** Previous deltaX, deltaY from the int33 mouse callback (for relative motion) */ -static short prev_delta_x, prev_delta_y; - -/* This is how events are delivered to Windows */ - -static void send_event(unsigned short Status, short deltaX, short deltaY, short ButtonCount, short extra1, short extra2); -#pragma aux (MOUSEEVENTPROC) send_event = \ - "call dword ptr [eventproc]" - -/* Our "CALLBACKS" segment which is fixed and non-relocatable. */ - -#pragma code_seg ( "CALLBACKS" ) - -#include "dlog.h" - -static void FAR int33_mouse_callback(uint16_t events, uint16_t buttons, int16_t x, int16_t y, int16_t delta_x, int16_t delta_y) -#pragma aux (INT33_CB) int33_mouse_callback -{ - int status = 0; - -#if TRACE_EVENTS - dlog_print("w16mouse: events="); - dlog_printx(events); - dlog_print(" buttons="); - dlog_printx(buttons); - dlog_print(" x="); - dlog_printd(x); - dlog_print(" y="); - dlog_printd(y); - dlog_print(" dx="); - dlog_printd(delta_x); - dlog_print(" dy="); - dlog_printd(delta_y); - dlog_endline(); -#endif - - if (events & INT33_EVENT_MASK_LEFT_BUTTON_PRESSED) status |= SF_B1_DOWN; - if (events & INT33_EVENT_MASK_LEFT_BUTTON_RELEASED) status |= SF_B1_UP; - if (events & INT33_EVENT_MASK_RIGHT_BUTTON_PRESSED) status |= SF_B2_DOWN; - if (events & INT33_EVENT_MASK_RIGHT_BUTTON_RELEASED) status |= SF_B2_UP; - - if (events & INT33_EVENT_MASK_MOVEMENT) { - status |= SF_MOVEMENT; - } - - if (events & INT33_EVENT_MASK_ABSOLUTE) { - status |= SF_ABSOLUTE; - - // We set the window to be 0..0x7FFF, so just scale to 0xFFFF - x = (uint16_t)(x) * 2; - y = (uint16_t)(y) * 2; - } else { - // Prefer to use mickeys for relative motion if we don't have absolute data - x = delta_x - prev_delta_x; - y = delta_y - prev_delta_y; - - prev_delta_x = delta_x; - prev_delta_y = delta_y; - } - - // Unused - (void) buttons; - -#if TRACE_EVENTS - dlog_print("w16mouse: event status="); - dlog_printx(status); - dlog_print(" x="); - if (status & SF_ABSOLUTE) dlog_printu(x); - else dlog_printd(x); - dlog_print(" y="); - if (status & SF_ABSOLUTE) dlog_printu(y); - else dlog_printd(y); - dlog_endline(); -#endif - - send_event(status, x, y, MOUSE_NUM_BUTTONS, 0, 0); -} - -#pragma code_seg () - -/* Driver exported functions. */ - -/** DLL entry point (or driver initialization routine). - * The initialization routine should check whether a mouse exists. - * @return nonzero value indicates a mouse exists. - */ -#pragma off (unreferenced) -BOOL FAR PASCAL LibMain(HINSTANCE hInstance, WORD wDataSegment, - WORD wHeapSize, LPSTR lpszCmdLine) -#pragma pop (unreferenced) -{ - uint16_t version = int33_get_driver_version(); - - // For now we just check for the presence of any int33 driver version - if (version == 0) { - // No one responded to our request, we can assume no driver - // This will cause a "can't load .drv" message from Windows - return 0; - } - - return 1; -} - -/** Called by Windows to retrieve information about the mouse hardware. */ -WORD FAR PASCAL Inquire(LPMOUSEINFO lpMouseInfo) -{ - lpMouseInfo->msExist = 1; - lpMouseInfo->msRelative = 0; - lpMouseInfo->msNumButtons = MOUSE_NUM_BUTTONS; - lpMouseInfo->msRate = 80; - return sizeof(MOUSEINFO); -} - -/** Called by Windows to enable the mouse driver. - * @param lpEventProc Callback function to call when a mouse event happens. */ -VOID FAR PASCAL Enable(LPFN_MOUSEEVENT lpEventProc) -{ - // Store the windows-given callback - _disable(); // Write to far pointer may not be atomic, and we could be interrupted mid-write - eventproc = lpEventProc; - _enable(); - - if (!enabled) { - int33_reset(); - - // Since the mouse driver will likely not know the Windows resolution, - // let's manually set up a large window of coordinates so as to have - // as much precision as possible. - // We use 0x7FFF instead of 0xFFFF because this parameter is officially a signed value. - int33_set_horizontal_window(0, 0x7FFF); - int33_set_vertical_window(0, 0x7FFF); - - int33_set_event_handler(INT33_EVENT_MASK_ALL, int33_mouse_callback); - - enabled = true; - } -} - -/** Called by Windows to disable the mouse driver. */ -VOID FAR PASCAL Disable(VOID) -{ - if (enabled) { - int33_reset(); // This removes our handler and all other settings - enabled = false; - } -} - -/** Called by Window to retrieve the interrupt vector number used by this driver, or -1. */ -int FAR PASCAL MouseGetIntVect(VOID) -{ - return 0x33; -} diff --git a/w16mouse.h b/w16mouse.h deleted file mode 100644 index 54b262f..0000000 --- a/w16mouse.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef MOUSEW16_H -#define MOUSEW16_H - -/* Win16's mouse driver interface. */ - -/** Contains information about the mouse, used by Inquire(). */ -typedef _Packed struct MOUSEINFO -{ - /** Whether a mouse exists. */ - char msExist; - /** Whether the mouse returns absolute or relative coordinates. */ - char msRelative; - /** Number of buttons. */ - short msNumButtons; - /** Maximum number of events per second. */ - short msRate; - // Reserved: - short msXThreshold; - short msYThreshold; - short msXRes; - short msYRes; - // The following are available in Windows >= 3.1 only: - #if 0 - /** Specifies the COM port used, or 0 for none. */ - short msMouseCommPort; - #endif -} MOUSEINFO; -typedef MOUSEINFO __far *LPMOUSEINFO; - -/** Movement occurred. */ -#define SF_MOVEMENT 0x0001 -/** Button 1 changed to down. */ -#define SF_B1_DOWN 0x0002 -/** Button 1 changed to up. */ -#define SF_B1_UP 0x0004 -/** Button 2 changed to down. */ -#define SF_B2_DOWN 0x0008 -/** Button 2 changed to up. */ -#define SF_B2_UP 0x0010 -/** Event coordinates are absolute instead of relative. */ -#define SF_ABSOLUTE 0x8000 - -/** Driver should call this callback when there are new mouse events to report. - * @param Status What happened. Combination of SF_MOVEMENT, SF_ABSOLUTE, etc. - * @param deltaX either number of mickeys moved or absolute coordinate if SB_ABSOLUTE. - * @param deltaY either number of mickeys moved or absolute coordinate if SB_ABSOLUTE. - * @param ButtonCount number of buttons - * @param extra1,extra2 leave as zero - */ -typedef void (__far *LPFN_MOUSEEVENT)(unsigned short Status, short deltaX, short deltaY, short ButtonCount, short extra1, short extra2); -#pragma aux MOUSEEVENTPROC parm [ax] [bx] [cx] [dx] [di] [si] - -#endif diff --git a/w16mouse.lnk b/w16mouse.lnk deleted file mode 100644 index bb80eb2..0000000 --- a/w16mouse.lnk +++ /dev/null @@ -1,11 +0,0 @@ -system windows_dll -option map=w16mouse.map -option modname=MOUSE # This is necessary; USER.EXE imports mouse functions using this module name -option description 'VirtualBox Mouse driver' - -segment CALLBACKS fixed shared # We need a non-moveable segment to store our callback routines - -export Inquire.1 -export Enable.2 -export Disable.3 -export MouseGetIntVect.4 -- cgit v1.2.3