diff options
author | FeralChild64 <unknown> | 2022-10-07 08:29:11 +0300 |
---|---|---|
committer | kcgen <1557255+kcgen@users.noreply.github.com> | 2022-10-22 21:15:34 +0300 |
commit | b2e3cef0eb5b297eed194c92e6fd16254bd50910 (patch) | |
tree | e9298418e2a39c4c70820323f3b1fadda64a31ea /src | |
parent | 1d8a5cfa903398d82513f86a155b22abec6901eb (diff) |
Add mouse mapper, config tool and config section
Diffstat (limited to 'src')
31 files changed, 4845 insertions, 1808 deletions
diff --git a/src/config.h.in b/src/config.h.in index a9185f269..df120abc4 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -124,6 +124,9 @@ // Define to 1 to enable MT-32 emulator #mesondefine C_MT32EMU +// Define to 1 to enable mouse mapping support +#mesondefine C_MANYMOUSE + // Compiler supports Core Audio headers #mesondefine C_COREAUDIO diff --git a/src/dos/dos_programs.cpp b/src/dos/dos_programs.cpp index 48ed523d6..3e587a2e3 100644 --- a/src/dos/dos_programs.cpp +++ b/src/dos/dos_programs.cpp @@ -33,6 +33,7 @@ #include "program_ls.h" #include "program_mem.h" #include "program_mount.h" +#include "program_mousectl.h" #include "program_placeholder.h" #include "program_rescan.h" #include "program_serial.h" @@ -75,6 +76,7 @@ void Add_VFiles(const bool add_autoexec) PROGRAMS_MakeFile("LS.COM", ProgramCreate<LS>); PROGRAMS_MakeFile("MEM.COM", ProgramCreate<MEM>); PROGRAMS_MakeFile("MOUNT.COM", ProgramCreate<MOUNT>); + PROGRAMS_MakeFile("MOUSECTL.COM", ProgramCreate<MOUSECTL>); PROGRAMS_MakeFile("RESCAN.COM", ProgramCreate<RESCAN>); PROGRAMS_MakeFile("MIXER.COM", MIXER_ProgramCreate); PROGRAMS_MakeFile("CONFIG.COM", CONFIG_ProgramCreate); diff --git a/src/dos/meson.build b/src/dos/meson.build index 4a31a1bf1..c7521b460 100644 --- a/src/dos/meson.build +++ b/src/dos/meson.build @@ -36,6 +36,7 @@ libdos_sources = files( 'program_mem.cpp', 'program_mount.cpp', 'program_mount_common.cpp', + 'program_mousectl.cpp', 'program_placeholder.cpp', 'program_rescan.cpp', 'program_serial.cpp', diff --git a/src/dos/program_boot.cpp b/src/dos/program_boot.cpp index ba6092e4d..608349396 100644 --- a/src/dos/program_boot.cpp +++ b/src/dos/program_boot.cpp @@ -30,6 +30,7 @@ #include "dma.h" #include "drives.h" #include "mapper.h" +#include "mouse.h" #include "regs.h" #include "string_utils.h" @@ -442,6 +443,8 @@ void BOOT::Run(void) for (auto &disk : diskSwap) disk.reset(); + MOUSE_NotifyBooting(); + if (cart_cmd.empty()) { uint32_t old_int18 = mem_readd(0x60); /* run cartridge setup */ @@ -469,6 +472,7 @@ void BOOT::Run(void) } else { disable_umb_ems_xms(); MEM_RemoveEMSPageFrame(); + MOUSE_NotifyBooting(); WriteOut(MSG_Get("PROGRAM_BOOT_BOOT"), drive); for (i = 0; i < 512; i++) real_writeb(0, 0x7c00 + i, bootarea.rawdata[i]); diff --git a/src/dos/program_mousectl.cpp b/src/dos/program_mousectl.cpp new file mode 100644 index 000000000..073718edf --- /dev/null +++ b/src/dos/program_mousectl.cpp @@ -0,0 +1,522 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2021-2022 The DOSBox Staging Team + * + * 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 "program_mousectl.h" + +#include "ansi_code_markup.h" +#include "checks.h" + +#include <set> + +CHECK_NARROWING(); + + +void MOUSECTL::Run() +{ + if (HelpRequested()) { + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_HELP_LONG")); + return; + } + + ParseAndRun(); + // TODO: once exit codes are supported, set it according to result +} + +bool MOUSECTL::ParseAndRun() +{ + // Put all the parameters into vector + std::vector<std::string> params; + cmd->FillVector(params); + + // Extract the list of interfaces from teh vector + std::vector<MouseInterfaceId> list_ids; + if (!ParseInterfaces(params, list_ids) || !CheckInterfaces(list_ids)) + return false; + + auto param_equal = [¶ms](const size_t idx, const char *string) + { + if (idx >= params.size()) + return false; + return (0 == strcasecmp(params[idx].c_str(), string)); + }; + + // CmdShow + if (list_ids.size() == 0 && params.size() == 0) + return CmdShow(false); + if (list_ids.size() == 0 && params.size() == 1) + if (param_equal(0, "-all")) + return CmdShow(true); + + // CmdMap - by supplied host mouse name + if (list_ids.size() == 1 && params.size() == 2) + if (param_equal(0, "-map")) + return CmdMap(list_ids[0], params[1]); + + // CmdMap - interactive + if (list_ids.size() >= 1 && params.size() == 1) + if (param_equal(0, "-map")) + return CmdMap(list_ids); + + // CmdUnmap / CmdOnOff / CmdReset / CmdSensitivity / CmdMinRate + if (params.size() == 1) { + if (param_equal(0, "-unmap")) + return CmdUnMap(list_ids); + if (param_equal(0, "-on")) + return CmdOnOff(list_ids, true); + if (param_equal(0, "-off")) + return CmdOnOff(list_ids, false); + if (param_equal(0, "-reset")) + return CmdReset(list_ids); + if (param_equal(0, "-s")) + return CmdSensitivity(list_ids); + if (param_equal(0, "-sx")) + return CmdSensitivityX(list_ids); + if (param_equal(0, "-sy")) + return CmdSensitivityY(list_ids); + if (param_equal(0, "-r")) + return CmdMinRate(list_ids); + } + + // CmdSensitivity / CmdMinRate with a non-default value + if (params.size() == 2) { + if (param_equal(0, "-r")) + return CmdMinRate(list_ids, params[1]); + + const auto value = std::atoi(params[1].c_str()); + if (value < 1 || value > 99) { + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_SYNTAX_SENSITIVITY")); + return false; + } + if (param_equal(0, "-s")) + return CmdSensitivity(list_ids, static_cast<uint8_t>(value)); + if (param_equal(0, "-sx")) + return CmdSensitivityX(list_ids, static_cast<uint8_t>(value)); + if (param_equal(0, "-sy")) + return CmdSensitivityY(list_ids, static_cast<uint8_t>(value)); + } + + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_SYNTAX")); + return false; +} + +bool MOUSECTL::ParseInterfaces(std::vector<std::string> ¶ms, + std::vector<MouseInterfaceId> &list_ids) +{ + while (params.size()) { + auto compare = [¶ms](const char *string) + { + return (0 == strcasecmp(params[0].c_str(), string)); + }; + + // Check if first parameter is one of supported mouse interfaces + if (compare("DOS")) + list_ids.push_back(MouseInterfaceId::DOS); + else if (compare("PS/2")) + list_ids.push_back(MouseInterfaceId::PS2); + else if (compare("PS2")) + list_ids.push_back(MouseInterfaceId::PS2); + else if (compare("COM1")) + list_ids.push_back(MouseInterfaceId::COM1); + else if (compare("COM2")) + list_ids.push_back(MouseInterfaceId::COM2); + else if (compare("COM3")) + list_ids.push_back(MouseInterfaceId::COM3); + else if (compare("COM4")) + list_ids.push_back(MouseInterfaceId::COM4); + else + break; // It isn't - end of interface list + + // First parameter is consummed, remove it + params.erase(params.begin()); + } + + // Check that all interfaces are unique + std::set<MouseInterfaceId> tmp(list_ids.begin(), list_ids.end()); + if (list_ids.size() != tmp.size()) { + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_SYNTAX_DUPLICATED")); + return false; + } + + return true; +} + +bool MOUSECTL::CheckInterfaces(const std::vector<MouseInterfaceId> &list_ids) +{ + if (!MouseConfigAPI::CheckInterfaces(list_ids)) + { + if (list_ids.empty()) + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_NO_INTERFACES")); + else + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_MISSING_INTERFACES")); + return false; + } + + return true; +} + +const char *MOUSECTL::GetInterfaceStr(const MouseInterfaceId interface_id) const +{ + switch (interface_id) { + case MouseInterfaceId::DOS: return "DOS"; + case MouseInterfaceId::PS2: return "PS/2"; + case MouseInterfaceId::COM1: return "COM1"; + case MouseInterfaceId::COM2: return "COM2"; + case MouseInterfaceId::COM3: return "COM3"; + case MouseInterfaceId::COM4: return "COM4"; + case MouseInterfaceId::None: return ""; + default: + assert(false); // missing implementation + return nullptr; + } +} + +const char *MOUSECTL::GetMapStatusStr(const MouseMapStatus map_status) const +{ + switch (map_status) { + case MouseMapStatus::HostPointer: + return MSG_Get("SHELL_CMD_MOUSECTL_TABLE_STATUS_HOST"); + case MouseMapStatus::Mapped: + return MSG_Get("SHELL_CMD_MOUSECTL_TABLE_STATUS_MAPPED"); + case MouseMapStatus::Disconnected: + return MSG_Get("SHELL_CMD_MOUSECTL_TABLE_STATUS_DISCONNECTED"); + case MouseMapStatus::Disabled: + return MSG_Get("SHELL_CMD_MOUSECTL_TABLE_STATUS_DISABLED"); + default: + assert(false); // missing implementation + return nullptr; + } +} + +bool MOUSECTL::CmdShow(const bool show_all) +{ + MouseConfigAPI mouse_config_api; + const auto info_interfaces = mouse_config_api.GetInfoInterfaces(); + + bool show_mapped = false; + bool hint_rate_com = false; + bool hint_rate_min = false; + + // Display emulated interface list + WriteOut("\n"); + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_TABLE_HEADER1")); + for (const auto &entry : info_interfaces) { + if (!entry.IsEmulated()) + continue; + const auto interface_id = entry.GetInterfaceId(); + const auto rate_hz = entry.GetRate(); + + if (entry.GetMinRate()) + hint_rate_min = true; + + if (interface_id == MouseInterfaceId::COM1 || + interface_id == MouseInterfaceId::COM2 || + interface_id == MouseInterfaceId::COM3 || + interface_id == MouseInterfaceId::COM4) + hint_rate_com = true; + + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_TABLE_LAYOUT1"), + GetInterfaceStr(interface_id), + entry.GetSensitivityX(), + entry.GetSensitivityY(), + hint_rate_min ? "*" : "", + rate_hz ? std::to_string(rate_hz).c_str() : "-", + convert_ansi_markup(GetMapStatusStr(entry.GetMapStatus())).c_str()); + WriteOut("\n"); + + if (entry.GetMapStatus() == MouseMapStatus::Mapped) + show_mapped = true; + } + WriteOut("\n"); + + const bool hint = hint_rate_com || hint_rate_min; + if (hint) + { + if (hint_rate_com) { + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_TABLE_HINT_RATE_COM")); + WriteOut("\n"); + } + if (hint_rate_min) { + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_TABLE_HINT_RATE_MIN")); + WriteOut("\n"); + } + WriteOut("\n"); + } + + if (!show_all && !show_mapped) + return true; + + const auto info_physical = mouse_config_api.GetInfoPhysical(); + if (info_physical.empty()) { + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_NO_PHYSICAL_MICE")); + WriteOut("\n\n"); + return true; + } + + if (hint) + WriteOut("\n"); + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_TABLE_HEADER2")); + + // Display physical mice mapped to some interface + for (const auto &entry : info_interfaces) { + if (!entry.IsMapped() || entry.IsMappedDeviceDisconnected()) + continue; + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_TABLE_LAYOUT2"), + GetInterfaceStr(entry.GetInterfaceId()), + entry.GetMappedDeviceName().c_str()); + WriteOut("\n"); + } + + if (!show_all) + return true; + + // Display physical mice not mapped to any interface + for (const auto &entry : info_physical) { + if (entry.IsMapped() || entry.IsDeviceDisconnected()) + continue; + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_TABLE_LAYOUT2_UNMAPPED"), + entry.GetDeviceName().c_str()); + WriteOut("\n"); + } + WriteOut("\n"); + + return true; +} + +bool MOUSECTL::CmdMap(const MouseInterfaceId interface_id, + const std::string &pattern) +{ + std::regex regex; + if (!MouseConfigAPI::PatternToRegex(pattern, regex)) { + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_SYNTAX_PATTERN")); + return false; + } + + MouseConfigAPI mouse_config_api; + if (!mouse_config_api.Map(interface_id, regex)) { + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_NO_MATCH")); + return false; + } + + return true; +} + +bool MOUSECTL::CmdMap(const std::vector<MouseInterfaceId> &list_ids) +{ + assert(list_ids.size() >= 1); + + MouseConfigAPI mouse_config_api; + const auto info_physical = mouse_config_api.GetInfoPhysical(); + + if (info_physical.empty()) { + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_NO_PHYSICAL_MICE")); + WriteOut("\n\n"); + return false; + } + + // Clear the current mapping before starting interactive mapper + std::vector<MouseInterfaceId> empty; + mouse_config_api.UnMap(empty); + + WriteOut("\n"); + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_MAP_ADVICE")); + WriteOut("\n\n"); + + for (const auto &interface_id : list_ids) { + WriteOut(convert_ansi_markup("[color=cyan]%-4s[reset] ?").c_str(), + GetInterfaceStr(interface_id)); + + uint8_t device_id = 0; + if (!mouse_config_api.ProbeForMapping(device_id)) { + mouse_config_api.UnMap(empty); + WriteOut("\b"); + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_MAP_CANCEL")); + WriteOut("\n\n"); + return false; + } + + WriteOut("\b"); + WriteOut(info_physical[device_id].GetDeviceName().c_str()); + WriteOut("\n"); + mouse_config_api.Map(interface_id, device_id); + } + WriteOut("\n"); + + return true; +} + +bool MOUSECTL::CmdUnMap(const std::vector<MouseInterfaceId> &list_ids) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.UnMap(list_ids); + return true; +} + +bool MOUSECTL::CmdOnOff(const std::vector<MouseInterfaceId> &list_ids, + const bool enable) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.OnOff(list_ids, enable); + return true; +} + +bool MOUSECTL::CmdReset(const std::vector<MouseInterfaceId> &list_ids) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.Reset(list_ids); + return true; +} + +bool MOUSECTL::CmdSensitivity(const std::vector<MouseInterfaceId> &list_ids, + const uint8_t value) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.SetSensitivity(list_ids, value, value); + return true; +} + +bool MOUSECTL::CmdSensitivityX(const std::vector<MouseInterfaceId> &list_ids, + const uint8_t value) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.SetSensitivityX(list_ids, value); + return true; +} + +bool MOUSECTL::CmdSensitivityY(const std::vector<MouseInterfaceId> &list_ids, + const uint8_t value) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.SetSensitivityY(list_ids, value); + return true; +} + +bool MOUSECTL::CmdSensitivity(const std::vector<MouseInterfaceId> &list_ids) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.ResetSensitivity(list_ids); + return true; +} + +bool MOUSECTL::CmdSensitivityX(const std::vector<MouseInterfaceId> &list_ids) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.ResetSensitivityX(list_ids); + return true; +} + +bool MOUSECTL::CmdSensitivityY(const std::vector<MouseInterfaceId> &list_ids) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.ResetSensitivityY(list_ids); + return true; +} + +bool MOUSECTL::CmdMinRate(const std::vector<MouseInterfaceId> &list_ids, + const std::string ¶m) +{ + const auto &valid_list = MouseConfigAPI::GetValidMinRateList(); + const auto &valid_str = MouseConfigAPI::GetValidMinRateStr(); + + const auto tmp = std::atoi(param.c_str()); + if (tmp < 0 || tmp > UINT16_MAX) { + // Parameter way out of range + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_SYNTAX_MIN_RATE"), valid_str.c_str()); + return false; + } + + uint16_t value_hz = static_cast<uint16_t>(tmp); + if (std::find(valid_list.begin(), valid_list.end(), value_hz) == valid_list.end()) { + // Parameter not in the list of allowed values + WriteOut(MSG_Get("SHELL_CMD_MOUSECTL_SYNTAX_MIN_RATE"), valid_str.c_str()); + return false; + } + + MouseConfigAPI mouse_config_api; + mouse_config_api.SetMinRate(list_ids, value_hz); + return true; +} + +bool MOUSECTL::CmdMinRate(const std::vector<MouseInterfaceId> &list_ids) +{ + MouseConfigAPI mouse_config_api; + mouse_config_api.ResetMinRate(list_ids); + return true; +} + +void MOUSECTL::AddMessages() +{ + MSG_Add("SHELL_CMD_MOUSECTL_HELP_LONG", + "Manages physical and logical mice.\n" + "\n" + "Usage:\n" + " [color=green]mousectl[reset] [-all]\n" + " [color=green]mousectl[reset] [color=white]INTERFACE[reset] -map name\n" + " [color=green]mousectl[reset] [color=white]INTERFACE[reset] [[color=white]INTERFACE[reset] ...] -map\n" + " [color=green]mousectl[reset] [[color=white]INTERFACE[reset] ...] -unmap | -on | -off | -reset\n" + " [color=green]mousectl[reset] [[color=white]INTERFACE[reset] ...] -s | -sx | -sy [sensitivity]\n" + " [color=green]mousectl[reset] [[color=white]INTERFACE[reset] ...] -r [min_rate]\n" + "\n" + "Where:\n" + " [color=white]INTERFACE[reset] one of [color=white]DOS[reset], [color=white]PS/2[reset], [color=white]COM1[reset], [color=white]COM2[reset], [color=white]COM3[reset], [color=white]COM4[reset]\n" + " -map -unmap maps/unmaps physical mouse, honors DOS wildcards in name\n" + " -s -sx -sy sets sensitivity / for x axis / for y axis, value is 1-99\n" + " -r sets minimum mouse sampling rate\n" + " -on -off enables or disables mouse on the given interface\n" + " -reset restores all mouse settings from the configuration file\n" + "\n" + "Notes:\n" + " - if host mouse name is omitted, it is mapped in interactive mode\n" + " - if sensitivity or rate is omitted, it is reset to default value\n" + "\n" + "Examples:\n" + " [color=green]mousectl[reset] [color=white]DOS[reset] [color=white]COM1[reset] -map ; asks user to select mice for a two player game"); + + MSG_Add("SHELL_CMD_MOUSECTL_SYNTAX", "Wrong command syntax"); + MSG_Add("SHELL_CMD_MOUSECTL_SYNTAX_PATTERN", "Wrong syntax, only ASCII characters allowed in pattern"); + MSG_Add("SHELL_CMD_MOUSECTL_SYNTAX_SENSITIVITY", "Wrong syntax, sensitivity needs to be in 1-99 range"); + MSG_Add("SHELL_CMD_MOUSECTL_SYNTAX_DUPLICATED", "Wrong syntax, duplicated mouse interfaces"); + MSG_Add("SHELL_CMD_MOUSECTL_SYNTAX_MIN_RATE", "Wrong syntax, sampling rate has to be one of:\n%s"); + + MSG_Add("SHELL_CMD_MOUSECTL_NO_INTERFACES", "No mouse interfaces available"); + MSG_Add("SHELL_CMD_MOUSECTL_MISSING_INTERFACES", "Mouse interface not available"); + MSG_Add("SHELL_CMD_MOUSECTL_NO_PHYSICAL_MICE", "No physical mice detected"); + MSG_Add("SHELL_CMD_MOUSECTL_NO_MATCH", "No available mouse matching the pattern found"); + + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_HEADER1", "[color=white]Interface Sensitivity Rate (Hz) Status[reset]\n"); + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_LAYOUT1", "[color=cyan]%-4s[reset] X:%02d Y:%02d %1s %3s %s"); + + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_HEADER2", "[color=white]Interface Mouse Name[reset]\n"); + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_LAYOUT2", "[color=cyan]%-4s[reset] %s"); + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_LAYOUT2_UNMAPPED", "not mapped %s"); + + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_STATUS_HOST", "uses system pointer"); + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_STATUS_MAPPED", "mapped physical mouse"); + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_STATUS_DISCONNECTED", "[color=red]mapped mouse disconnected[reset]"); + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_STATUS_DISABLED", "disabled"); + + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_HINT_RATE_COM", "Sampling rates for mice on [color=cyan]COM[reset] interfaces are estimations only."); + MSG_Add("SHELL_CMD_MOUSECTL_TABLE_HINT_RATE_MIN", "Sampling rates with minimum value set are marked with '*'."); + + MSG_Add("SHELL_CMD_MOUSECTL_MAP_ADVICE", + "Click [color=white]left[reset] mouse button to map the physical mouse to the interface. Clicking\n" + "any other button cancels the mapping and assigns system pointer to all the\n" + "mouse interfaces."); + MSG_Add("SHELL_CMD_MOUSECTL_MAP_CANCEL", "(mapping cancelled)"); +} diff --git a/src/dos/program_mousectl.h b/src/dos/program_mousectl.h new file mode 100644 index 000000000..f744de9cb --- /dev/null +++ b/src/dos/program_mousectl.h @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2020-2022 The DOSBox Staging Team + * + * 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 DOSBOX_PROGRAM_MOUSECTL_H +#define DOSBOX_PROGRAM_MOUSECTL_H + +#include "programs.h" + +#include "mouse.h" + + +class MOUSECTL final : public Program { +public: + MOUSECTL() + { + AddMessages(); + help_detail = {HELP_Filter::All, + HELP_Category::Dosbox, + HELP_CmdType::Program, + "MOUSECTL"}; + } + void Run(); + +private: + + bool ParseAndRun(); + bool ParseInterfaces(std::vector<std::string> ¶ms, + std::vector<MouseInterfaceId> &list_ids); + bool CheckInterfaces(const std::vector<MouseInterfaceId> &list_ids); + + const char *GetInterfaceStr(const MouseInterfaceId interface_id) const; + const char *GetMapStatusStr(const MouseMapStatus map_status) const; + + bool CmdShow(const bool show_all); + bool CmdMap(const MouseInterfaceId interface_id, + const std::string &pattern); + bool CmdMap(const std::vector<MouseInterfaceId> &list_ids); + bool CmdUnMap(const std::vector<MouseInterfaceId> &list_ids); + bool CmdOnOff(const std::vector<MouseInterfaceId> &list_ids, + const bool enable); + bool CmdReset(const std::vector<MouseInterfaceId> &list_ids); + bool CmdSensitivity(const std::vector<MouseInterfaceId> &list_ids, + const uint8_t value); + bool CmdSensitivityX(const std::vector<MouseInterfaceId> &list_ids, + const uint8_t value); + bool CmdSensitivityY(const std::vector<MouseInterfaceId> &list_ids, + const uint8_t value); + bool CmdSensitivity(const std::vector<MouseInterfaceId> &list_ids); + bool CmdSensitivityX(const std::vector<MouseInterfaceId> &list_ids); + bool CmdSensitivityY(const std::vector<MouseInterfaceId> &list_ids); + bool CmdMinRate(const std::vector<MouseInterfaceId> &list_ids, + const std::string ¶m); + bool CmdMinRate(const std::vector<MouseInterfaceId> &list_ids); + + void AddMessages(); +}; + +#endif diff --git a/src/dosbox.cpp b/src/dosbox.cpp index addca086d..318124188 100644 --- a/src/dosbox.cpp +++ b/src/dosbox.cpp @@ -42,6 +42,7 @@ #include "mapper.h" #include "midi.h" #include "mixer.h" +#include "mouse.h" #include "ne2000.h" #include "pci_bus.h" #include "pic.h" @@ -88,7 +89,6 @@ void PCI_Init(Section*); void KEYBOARD_Init(Section*); //TODO This should setup INT 16 too but ok ;) void JOYSTICK_Init(Section*); -void MOUSE_Init(Section*); void SBLASTER_Init(Section*); void MPU401_Init(Section*); void PCSPEAKER_Init(Section*); @@ -733,6 +733,8 @@ void DOSBOX_Init() secprop=control->AddSection_prop("pci",&PCI_Init,false); //PCI bus #endif + // Configure mouse + MOUSE_AddConfigSection(control); // Configure mixer MIXER_AddConfigSection(control); @@ -1094,14 +1096,7 @@ void DOSBOX_Init() "Additional parameters must be on the same line in the form of\n" "parameter:value. Parameter for all types is irq (optional).\n" "for mouse:\n" - " type, can be one of:\n" - " 2btn: 2 buttons, Microsoft serial mouse\n" - " 3btn: 3 buttons, Logitech serial mouse\n" - " wheel: 3 buttons + wheel serial mouse\n" - " msm: 3 buttons, Mouse Systems Mouse\n" - " 2btn+msm, 3btn+msm, wheel+msm : autoselection\n" - " rate, can be normal or smooth (more frequent updates than on real PC)\n" - " Default is type:wheel+msm rate:smooth\n" + " model, overrides setting from [mouse] section\n" "for direct: realport (required), rxdelay (optional).\n" " (realport:COM1 realport:ttyS0).\n" "for modem: listenport, sock, baudrate (all optional).\n" diff --git a/src/gui/sdlmain.cpp b/src/gui/sdlmain.cpp index 15f4caccd..25a7def26 100644 --- a/src/gui/sdlmain.cpp +++ b/src/gui/sdlmain.cpp @@ -3552,6 +3552,7 @@ static void GUI_StartUp(Section *sec) } else if (control_choice == "nomouse") { sdl.mouse.control_choice = NoMouse; mouse_control_msg = "is disabled"; + MOUSE_SetNoMouse(); } else { assert(sdl.mouse.control_choice == CaptureOnClick); } @@ -3575,8 +3576,8 @@ static void GUI_StartUp(Section *sec) // Apply the user's mouse sensitivity settings PropMultiVal *p3 = section->GetMultiVal("sensitivity"); - sdl.mouse.xsensitivity = static_cast<float>(p3->GetSection()->Get_int("xsens")) / 100.0f; - sdl.mouse.ysensitivity = static_cast<float>(p3->GetSection()->Get_int("ysens")) / 100.0f; + const auto sensitivity_x = static_cast<float>(p3->GetSection()->Get_int("xsens")) / 100.0f; + const auto sensitivity_y = static_cast<float>(p3->GetSection()->Get_int("ysens")) / 100.0f; // Apply raw mouse input setting const auto raw_mouse_input = section->Get_bool("raw_mouse_input"); @@ -3585,7 +3586,7 @@ static void GUI_StartUp(Section *sec) SDL_HINT_OVERRIDE); // Notify mouse emulation routines about the configuration - MOUSE_SetConfig(raw_mouse_input); + MOUSE_SetConfig(raw_mouse_input, sensitivity_x, sensitivity_y); } /* Get some Event handlers */ @@ -3616,8 +3617,8 @@ static void HandleMouseMotion(SDL_MouseMotionEvent *motion) { if (mouse_seamless_driver || mouse_is_captured || sdl.mouse.control_choice == Seamless) - MOUSE_EventMoved(static_cast<float>(motion->xrel) * sdl.mouse.xsensitivity, - static_cast<float>(motion->yrel) * sdl.mouse.ysensitivity, + MOUSE_EventMoved(static_cast<float>(motion->xrel), + static_cast<float>(motion->yrel), std::clamp(motion->x, 0, static_cast<int>(UINT16_MAX)), std::clamp(motion->y, 0, static_cast<int>(UINT16_MAX))); } @@ -3630,35 +3631,25 @@ static void HandleMouseWheel(SDL_MouseWheelEvent *wheel) static void HandleMouseButton(SDL_MouseButtonEvent * button) { - auto notify_pressed = [](uint8_t button) { + auto notify_button = [](const uint8_t button, const bool pressed) { switch (button) { - case SDL_BUTTON_LEFT: MOUSE_EventPressed(0); break; - case SDL_BUTTON_RIGHT: MOUSE_EventPressed(1); break; - case SDL_BUTTON_MIDDLE: MOUSE_EventPressed(2); break; - case SDL_BUTTON_X1: MOUSE_EventPressed(3); break; - case SDL_BUTTON_X2: MOUSE_EventPressed(4); break; - } - }; - - auto notify_released = [](uint8_t button) { - switch (button) { - case SDL_BUTTON_LEFT: MOUSE_EventReleased(0); break; - case SDL_BUTTON_RIGHT: MOUSE_EventReleased(1); break; - case SDL_BUTTON_MIDDLE: MOUSE_EventReleased(2); break; - case SDL_BUTTON_X1: MOUSE_EventReleased(3); break; - case SDL_BUTTON_X2: MOUSE_EventReleased(4); break; + case SDL_BUTTON_LEFT: MOUSE_EventButton(0, pressed); break; + case SDL_BUTTON_RIGHT: MOUSE_EventButton(1, pressed); break; + case SDL_BUTTON_MIDDLE: MOUSE_EventButton(2, pressed); break; + case SDL_BUTTON_X1: MOUSE_EventButton(3, pressed); break; + case SDL_BUTTON_X2: MOUSE_EventButton(4, pressed); break; } }; if (button->state == SDL_RELEASED) { - notify_released(button->button); + notify_button(button->button, false); return; } assert(button->state == SDL_PRESSED); if (sdl.desktop.fullscreen || mouse_seamless_driver) { - notify_pressed(button->button); + notify_button(button->button, true); return; } @@ -3672,7 +3663,7 @@ static void HandleMouseButton(SDL_MouseButtonEvent * button) return; // Don't pass click to mouse handler } - notify_pressed(button->button); + notify_button(button->button, true); } void GFX_LosingFocus() diff --git a/src/hardware/meson.build b/src/hardware/meson.build index de8a2db8e..4843b8e0e 100644 --- a/src/hardware/meson.build +++ b/src/hardware/meson.build @@ -1,6 +1,15 @@ libhardware_sources = files( 'mame/saa1099.cpp', 'mame/sn76496.cpp', + 'mouse/mouse.cpp', + 'mouse/mouse_common.cpp', + 'mouse/mouse_config.cpp', + 'mouse/mouse_interfaces.cpp', + 'mouse/mouse_manymouse.cpp', + 'mouse/mouse_queue.cpp', + 'mouse/mouseif_dos_driver.cpp', + 'mouse/mouseif_ps2_bios.cpp', + 'mouse/mouseif_virtual_machines.cpp', 'serialport/directserial.cpp', 'serialport/libserial.cpp', 'serialport/misc_util.cpp', diff --git a/src/hardware/mouse/mouse.cpp b/src/hardware/mouse/mouse.cpp new file mode 100644 index 000000000..765102d69 --- /dev/null +++ b/src/hardware/mouse/mouse.cpp @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * Copyright (C) 2002-2021 The DOSBox Team + * + * 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 "mouse.h" +#include "mouse_config.h" +#include "mouse_interfaces.h" +#include "mouse_manymouse.h" +#include "mouse_queue.h" + +#include <algorithm> +#include <string> +#include <sstream> +#include <vector> + +#include "callback.h" +#include "checks.h" +#include "cpu.h" +#include "pic.h" +#include "video.h" + +CHECK_NARROWING(); + + +bool mouse_seamless_driver = false; + +static Bitu int74_ret_callback = 0; + +static MouseQueue &queue = MouseQueue::GetInstance(); +static ManyMouseGlue &manymouse = ManyMouseGlue::GetInstance(); + +// *************************************************************************** +// Interrupt 74 implementation +// *************************************************************************** + +static Bitu int74_exit() +{ + SegSet16(cs, RealSeg(CALLBACK_RealPointer(int74_ret_callback))); + reg_ip = RealOff(CALLBACK_RealPointer(int74_ret_callback)); + + return CBRET_NONE; +} + +static Bitu int74_handler() +{ + MouseEvent ev; + queue.FetchEvent(ev); + + // Handle DOS events + if (ev.request_dos) { + + uint8_t mask = 0; + if (ev.dos_moved) { + mask = MOUSEDOS_UpdateMoved(); + + // Taken from DOSBox X: HERE within the IRQ 12 handler is the + // appropriate place to redraw the cursor. OSes like Windows 3.1 + // expect real-mode code to do it in response to IRQ 12, not + // "out of the blue" from the SDL event handler like the + // original DOSBox code did it. Doing this allows the INT 33h + // emulation to draw the cursor while not causing Windows 3.1 to + // crash or behave erratically. + if (mask) + MOUSEDOS_DrawCursor(); + } + if (ev.dos_button) + mask = static_cast<uint8_t>(mask | MOUSEDOS_UpdateButtons(ev.dos_buttons)); + if (ev.dos_wheel) + mask = static_cast<uint8_t>(mask | MOUSEDOS_UpdateWheel()); + + // If DOS driver's client is not interested in this particular + // type of event - skip it + if (!MOUSEDOS_HasCallback(mask)) + return int74_exit(); + + CPU_Push16(RealSeg(CALLBACK_RealPointer(int74_ret_callback))); + CPU_Push16(RealOff(static_cast<RealPt>(CALLBACK_RealPointer(int74_ret_callback)) + 7)); + + return MOUSEDOS_DoCallback(mask, ev.dos_buttons); + } + + // Handle PS/2 and BIOS mouse events + if (ev.request_ps2 && mouse_shared.active_bios) { + CPU_Push16(RealSeg(CALLBACK_RealPointer(int74_ret_callback))); + CPU_Push16(RealOff(CALLBACK_RealPointer(int74_ret_callback))); + + MOUSEPS2_UpdatePacket(); + return MOUSEBIOS_DoCallback(); + } + + // No mouse emulation module is interested in event + return int74_exit(); +} + +Bitu int74_ret_handler() +{ + queue.StartTimerIfNeeded(); + return CBRET_NONE; +} + +// *************************************************************************** +// External notifications +// *************************************************************************** + +void MOUSE_NewScreenParams(const uint16_t clip_x, const uint16_t clip_y, + const uint16_t res_x, const uint16_t res_y, + const bool fullscreen, const uint16_t x_abs, + const uint16_t y_abs) +{ + mouse_video.clip_x = clip_x; + mouse_video.clip_y = clip_y; + + // Protection against strange window sizes, + // to prevent division by 0 in some places + mouse_video.res_x = std::max(res_x, static_cast<uint16_t>(2)); + mouse_video.res_y = std::max(res_y, static_cast<uint16_t>(2)); + + mouse_video.fullscreen = fullscreen; + + MOUSEVMM_NewScreenParams(x_abs, y_abs); + MOUSE_NotifyStateChanged(); +} + +void MOUSE_NotifyResetDOS() +{ + queue.ClearEventsDOS(); +} + +void MOUSE_NotifyStateChanged() +{ + const auto old_seamless_driver = mouse_seamless_driver; + + // Prepare suggestions to the GUI + mouse_seamless_driver = mouse_shared.active_vmm && !mouse_video.fullscreen; + + // If state has really changed, update the GUI + if (mouse_seamless_driver != old_seamless_driver) + GFX_UpdateMouseState(); +} + +void MOUSE_NotifyDisconnect(const MouseInterfaceId interface_id) +{ + auto interface = MouseInterface::Get(interface_id); + if (interface) + interface->NotifyDisconnect(); +} + +void MOUSE_NotifyFakePS2() +{ + auto interface = MouseInterface::GetPS2(); + + if (interface && interface->IsUsingEvents()) { + MouseEvent ev; + ev.request_ps2 = true; + queue.AddEvent(ev); + } +} + +void MOUSE_NotifyBooting() +{ + for (auto &interface : mouse_interfaces) + interface->NotifyBooting(); +} + + +void MOUSE_EventMoved(const float x_rel, + const float y_rel, + const uint16_t x_abs, + const uint16_t y_abs) +{ + // From the GUI we are getting mouse movement data in two + // distinct formats: + // + // - relative; this one has a chance to be raw movements, + // it has to be fed to PS/2 mouse emulation, serial port + // mouse emulation, etc.; any guest side software accessing + // these mouse interfaces will most likely implement it's + // own mouse acceleration/smoothing/etc. + // - absolute; this follows host OS mouse behavior and should + // be fed to VMware seamless mouse emulation and similar + // interfaces + // + // Our DOS mouse driver (INT 33h) is a bit special, as it can + // act both ways (seamless and non-seamless mouse pointer), + // so it needs data in both formats. + + // Notify mouse interfaces + + MouseEvent ev; + for (auto &interface : mouse_interfaces) + if (interface->IsUsingHostPointer()) + interface->NotifyMoved(ev, x_rel, y_rel, x_abs, y_abs); + queue.AddEvent(ev); +} + +void MOUSE_EventMoved(const float x_rel, + const float y_rel, + const MouseInterfaceId interface_id) +{ + auto interface = MouseInterface::Get(interface_id); + if (interface && interface->IsUsingEvents()) { + MouseEvent ev; + interface->NotifyMoved(ev, x_rel, y_rel, 0, 0); + queue.AddEvent(ev); + } +} + +void MOUSE_EventButton(const uint8_t idx, + const bool pressed) +{ + MouseEvent ev; + for (auto &interface : mouse_interfaces) + if (interface->IsUsingHostPointer()) + interface->NotifyButton(ev, idx, pressed); + queue.AddEvent(ev); +} + +void MOUSE_EventButton(const uint8_t idx, + const bool pressed, + const MouseInterfaceId interface_id) +{ + auto interface = MouseInterface::Get(interface_id); + if (interface && interface->IsUsingEvents()) { + MouseEvent ev; + interface->NotifyButton(ev, idx, pressed); + queue.AddEvent(ev); + } +} + +void MOUSE_EventWheel(const int16_t w_rel) +{ + MouseEvent ev; + for (auto &interface : mouse_interfaces) + if (interface->IsUsingHostPointer()) + interface->NotifyWheel(ev, w_rel); + queue.AddEvent(ev); +} + +void MOUSE_EventWheel(const int16_t w_rel, + const MouseInterfaceId interface_id) +{ + auto interface = MouseInterface::Get(interface_id); + if (interface && interface->IsUsingEvents()) { + MouseEvent ev; + interface->NotifyWheel(ev, w_rel); + queue.AddEvent(ev); + } +} + +// *************************************************************************** +// MOUSECTL.COM / GUI configurator interface +// *************************************************************************** + +void get_relevant_interfaces(std::vector<MouseInterface *> &list_out, + const std::vector<MouseInterfaceId> &list_ids) +{ + list_out.clear(); + auto list_tmp = list_out; + + if (list_ids.empty()) + // If command does not specify interfaces, + // assume we are interested in all of them + list_tmp = mouse_interfaces; + else + for (const auto &interface_id : list_ids) { + auto interface = MouseInterface::Get(interface_id); + if (interface) + list_tmp.push_back(interface); + } + + // Filter out not emulated ones + for (const auto &interface : list_tmp) + if (interface->IsEmulated()) + list_out.push_back(interface); +} + +MouseConfigAPI::MouseConfigAPI() +{ + manymouse.StartConfigAPI(); +} + +MouseConfigAPI::~MouseConfigAPI() +{ + manymouse.StopConfigAPI(); +} + +const std::vector<MouseInterfaceInfoEntry> &MouseConfigAPI::GetInfoInterfaces() const +{ + return mouse_info.interfaces; +} + +const std::vector<MousePhysicalInfoEntry> &MouseConfigAPI::GetInfoPhysical() +{ + manymouse.RescanIfSafe(); + return mouse_info.physical; +} + +bool MouseConfigAPI::CheckInterfaces(const MouseConfigAPI::ListIDs &list_ids) +{ + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + if (list_ids.empty() && list.empty()) + return false; // no emulated mouse interfaces + if (list_ids.empty()) + return true; // OK, requested all emulated interfaces + if (list_ids.size() != list.size()) + return false; // at least one requested interface is not emulated + + return true; +} + +bool MouseConfigAPI::PatternToRegex(const std::string &pattern, + std::regex ®ex) +{ + // Convert DOS wildcard pattern to a regular expression + std::stringstream pattern_regex; + pattern_regex << std::hex; + for (const auto character : pattern) { + if (character < 0x20 || character > 0x7E) { + return false; + } + if (character == '?') + pattern_regex << "."; + else if (character == '*') + pattern_regex << ".*"; + else if ((character >= '0' && character <= '9') || + (character >= 'a' && character <= 'z') || + (character >= 'A' && character <= 'Z')) + pattern_regex << character; + else + pattern_regex << "\\x" << static_cast<int>(character); + } + + // Return a case-insensitive regular expression + regex = std::regex(pattern_regex.str(), std::regex_constants::icase); + return true; +} + +bool MouseConfigAPI::ProbeForMapping(uint8_t &device_id) +{ + manymouse.RescanIfSafe(); + return manymouse.ProbeForMapping(device_id); +} + +bool MouseConfigAPI::Map(const MouseInterfaceId interface_id, + const uint8_t device_idx) +{ + auto mouse_interface = MouseInterface::Get(interface_id); + if (!mouse_interface) + return false; + + return mouse_interface->ConfigMap(device_idx); +} + +bool MouseConfigAPI::Map(const MouseInterfaceId interface_id, + const std::regex ®ex) +{ + manymouse.RescanIfSafe(); + const auto idx = manymouse.GetIdx(regex); + if (idx >= mouse_info.physical.size()) + return false; + + return Map(interface_id, idx); +} + +bool MouseConfigAPI::UnMap(const MouseConfigAPI::ListIDs &list_ids) +{ + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigUnMap(); + + return !list.empty(); +} + +bool MouseConfigAPI::OnOff(const MouseConfigAPI::ListIDs &list_ids, + const bool enable) +{ + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigOnOff(enable); + + return !list.empty(); +} + +bool MouseConfigAPI::Reset(const MouseConfigAPI::ListIDs &list_ids) +{ + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigReset(); + + return !list.empty(); +} + +bool MouseConfigAPI::SetSensitivity(const MouseConfigAPI::ListIDs &list_ids, + const uint8_t sensitivity_x, + const uint8_t sensitivity_y) +{ + if (!sensitivity_x || sensitivity_x > mouse_predefined.sensitivity_user_max || + !sensitivity_y || sensitivity_y > mouse_predefined.sensitivity_user_max) + return false; + + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigSetSensitivity(sensitivity_x, sensitivity_y); + + return !list.empty(); +} + +bool MouseConfigAPI::SetSensitivityX(const MouseConfigAPI::ListIDs &list_ids, + const uint8_t sensitivity_x) +{ + if (!sensitivity_x || sensitivity_x > mouse_predefined.sensitivity_user_max) + return false; + + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigSetSensitivityX(sensitivity_x); + + return !list.empty(); +} + +bool MouseConfigAPI::SetSensitivityY(const MouseConfigAPI::ListIDs &list_ids, + const uint8_t sensitivity_y) +{ + if (!sensitivity_y || sensitivity_y > mouse_predefined.sensitivity_user_max) + return false; + + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigSetSensitivityY(sensitivity_y); + + return !list.empty(); +} + +bool MouseConfigAPI::ResetSensitivity(const MouseConfigAPI::ListIDs &list_ids) +{ + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigResetSensitivity(); + + return !list.empty(); +} + +bool MouseConfigAPI::ResetSensitivityX(const MouseConfigAPI::ListIDs &list_ids) +{ + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigResetSensitivityX(); + + return !list.empty(); +} + +bool MouseConfigAPI::ResetSensitivityY(const MouseConfigAPI::ListIDs &list_ids) +{ + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigResetSensitivityY(); + + return !list.empty(); +} + +const std::vector<uint16_t> &MouseConfigAPI::GetValidMinRateList() +{ + return MouseConfig::GetValidMinRateList(); +} + +const std::string &MouseConfigAPI::GetValidMinRateStr() +{ + static std::string out_str = ""; + + if (out_str.empty()) { + const auto &valid_list = GetValidMinRateList(); + + bool first = true; + for (const auto &rate : valid_list) { + if (first) + first = false; + else + out_str += std::string(", "); + out_str += std::to_string(rate); + } + } + + return out_str; +} + +bool MouseConfigAPI::SetMinRate(const MouseConfigAPI::ListIDs &list_ids, + const uint16_t value_hz) +{ + const auto &valid_list = GetValidMinRateList(); + if (std::find(valid_list.begin(), valid_list.end(), value_hz) == valid_list.end()) + return false; // invalid value + + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigSetMinRate(value_hz); + + return !list.empty(); +} + +bool MouseConfigAPI::ResetMinRate(const MouseConfigAPI::ListIDs &list_ids) +{ + std::vector<MouseInterface *> list; + get_relevant_interfaces(list, list_ids); + + for (auto &interface : list) + interface->ConfigResetMinRate(); + + return !list.empty(); +} + +// *************************************************************************** +// Initialization +// *************************************************************************** + +void MOUSE_SetConfig(const bool raw_input, + const float sensitivity_x, + const float sensitivity_y) +{ + // Called during SDL initialization + mouse_config.raw_input = raw_input; + mouse_config.sensitivity_gui_x = sensitivity_x; + mouse_config.sensitivity_gui_y = sensitivity_y; + + // Just in case it is also called later + for (auto &interface : mouse_interfaces) + interface->UpdateConfig(); + + // Start mouse emulation if ready + mouse_shared.ready_config_sdl = true; + MOUSE_Startup(); +} + +void MOUSE_SetNoMouse() +{ + // NOTE: if it is decided to not allow enabling/disabling + // this during runtime, add button click releases for all + // the mouse buttons + mouse_config.no_mouse = true; +} + +void MOUSE_Startup() +{ + if (mouse_shared.started || + !mouse_shared.ready_startup_sequence || + !mouse_shared.ready_config_mouse || + !mouse_shared.ready_config_sdl) + return; + + // Callback for ps2 irq + auto call_int74 = CALLBACK_Allocate(); + CALLBACK_Setup(call_int74, &int74_handler, CB_IRQ12, "int 74"); + // pseudocode for CB_IRQ12: + // sti + // push ds + // push es + // pushad + // callback int74_handler + // ps2 or user callback if requested + // otherwise jumps to CB_IRQ12_RET + // push ax + // mov al, 0x20 + // out 0xa0, al + // out 0x20, al + // pop ax + // cld + // retf + + int74_ret_callback = CALLBACK_Allocate(); + CALLBACK_Setup(int74_ret_callback, &int74_ret_handler, CB_IRQ12_RET, "int 74 ret"); + // pseudocode for CB_IRQ12_RET: + // cli + // mov al, 0x20 + // out 0xa0, al + // out 0x20, al + // callback int74_ret_handler + // popad + // pop es + // pop ds + // iret + + // (MOUSE_IRQ > 7) ? (0x70 + MOUSE_IRQ - 8) : (0x8 + MOUSE_IRQ); + RealSetVec(0x74, CALLBACK_RealPointer(call_int74)); + + MouseInterface::InitAllInstances(); + mouse_shared.started = true; +} + +void MOUSE_Init(Section * /*sec*/) +{ + // Start mouse emulation if ready + mouse_shared.ready_startup_sequence = true; + MOUSE_Startup(); +} diff --git a/src/hardware/mouse/mouse_common.cpp b/src/hardware/mouse/mouse_common.cpp new file mode 100644 index 000000000..75e2f5d08 --- /dev/null +++ b/src/hardware/mouse/mouse_common.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 "mouse_common.h" + +#include <algorithm> + +#include "checks.h" +#include "pic.h" + +CHECK_NARROWING(); + +// *************************************************************************** +// Common variables +// *************************************************************************** + +MouseInfo mouse_info; +MouseShared mouse_shared; +MouseVideo mouse_video; + +// *************************************************************************** +// Common helper calculations +// *************************************************************************** + +float MOUSE_GetBallisticsCoeff(const float speed) +{ + // This routine provides a function for mouse ballistics + // (cursor acceleration), to be reused by various mouse interfaces. + // Since this is a DOS emulator, the acceleration model is + // based on a historic PS/2 mouse specification. + + // Input: mouse speed + // Output: acceleration coefficient (1.0f for speed >= 6.0f) + + // NOTE: If we don't have raw mouse input, stay with flat profile; + // in such case the acceleration is already handled by the host OS, + // adding our own could lead to hard to predict (most likely + // undesirable) effects + + constexpr float a = 0.017153417f; + constexpr float b = 0.382477002f; + constexpr float lowest = 0.5f; + + // Normal PS/2 mouse 2:1 scaling algorithm is just a substitution: + // 0 => 0, 1 => 1, 2 => 1, 3 => 3, 4 => 6, 5 => 9, other x => x * 2 + // and the same for negatives. But we want smooth cursor movement, + // therefore we use approximation model (least square regression, + // 3rd degree polynomial, on points -6, -5, ..., 0, ... , 5, 6, + // here scaled to give f(6.0) = 6.0). Polynomial would be: + // + // f(x) = a*(x^3) + b*(x^1) = x*(a*(x^2) + b) + // + // This C++ function provides not the full polynomial, but rather + // a coefficient (0.0f ... 1.0f) calculated from supplied speed, + // by which the relative mouse measurement should be multiplied + + if (speed > -6.0f && speed < 6.0f) + return std::max((a * speed * speed + b), lowest); + else + return 1.0f; + + // Please consider this algorithm as yet another nod to the past, + // one more small touch of 20th century PC computing history :) +} + +uint8_t MOUSE_GetDelayFromRateHz(const uint16_t rate_hz) +{ + assert(rate_hz); + const auto tmp = std::lround(1000.0f / MOUSE_ClampRateHz(rate_hz)); + return static_cast<uint8_t>(tmp); +} + +float MOUSE_ClampRelativeMovement(const float rel) +{ + // Enforce sane upper limit of relative mouse movement + return std::clamp(rel, -2048.0f, 2048.0f); +} + +uint16_t MOUSE_ClampRateHz(const uint16_t rate_hz) +{ + constexpr auto rate_min = static_cast<uint16_t>(10); + constexpr auto rate_max = static_cast<uint16_t>(500); + + return std::clamp(rate_hz, rate_min, rate_max); +} + +int8_t MOUSE_ClampToInt8(const int32_t val) +{ + const auto tmp = std::clamp(val, + static_cast<int32_t>(INT8_MIN), + static_cast<int32_t>(INT8_MAX)); + + return static_cast<int8_t>(tmp); +} + +int16_t MOUSE_ClampToInt16(const int32_t val) +{ + const auto tmp = std::clamp(val, + static_cast<int32_t>(INT16_MIN), + static_cast<int32_t>(INT16_MAX)); + + return static_cast<int16_t>(tmp); +} + +// *************************************************************************** +// Mouse speed calculation +// *************************************************************************** + +MouseSpeedCalculator::MouseSpeedCalculator(const float scaling) : + scaling(scaling * 1000.0f) // to convert from units/ms to units/s +{ + clock_time_start = std::chrono::steady_clock::now(); + pic_ticks_start = PIC_Ticks; +} + +float MouseSpeedCalculator::Get() const +{ + return speed; +} + +void MouseSpeedCalculator::Update(const float delta) +{ + constexpr auto n = static_cast<float>(std::chrono::steady_clock::period::num); + constexpr auto d = static_cast<float>(std::chrono::steady_clock::period::den); + constexpr auto period_ms = 1000.0f * n / d; + // For the measurement duration require no more than 400 milliseconds + // and at least 10 times the clock granularity + constexpr uint32_t max_diff_ms = 400; + constexpr uint32_t min_diff_ms = std::max(static_cast<uint32_t>(1), + static_cast<uint32_t>(period_ms * 10)); + // Require at least 40 ticks of PIC emulator to pass + constexpr uint32_t min_diff_ticks = 40; + + // Get current time, calculate difference + const auto time_now = std::chrono::steady_clock::now(); + const auto diff_time = time_now - clock_time_start; + const auto diff_ticks = PIC_Ticks - pic_ticks_start; + const auto diff_ms = + std::chrono::duration_cast<std::chrono::milliseconds>(diff_time).count(); + + // Try to calculate cursor speed + if (diff_ms > std::max(max_diff_ms, static_cast<uint32_t>(10 * period_ms))) { + // Do not wait any more for movement, consider speed to be 0 + speed = 0.0f; + } else if (diff_ms >= 0) { + // Update distance travelled by the cursor + distance += delta; + + // Make sure enough time passed for accurate speed calculation + if (diff_ms < min_diff_ms || (diff_ticks < min_diff_ticks && diff_ticks > 0)) + return; + + // Update cursor speed + speed = scaling * distance / static_cast<float>(diff_ms); + } + + // Start new measurement + distance = 0.0f; + clock_time_start = time_now; + pic_ticks_start = PIC_Ticks; +} + +// *************************************************************************** +// Types for storing mouse buttons +// *************************************************************************** + +MouseButtons12 &MouseButtons12::operator=(const MouseButtons12 &other) +{ + data = other.data; + return *this; +} + +MouseButtons345 &MouseButtons345::operator=(const MouseButtons345 &other) +{ + data = other.data; + return *this; +} + +MouseButtonsAll &MouseButtonsAll::operator=(const MouseButtonsAll &other) +{ + data = other.data; + return *this; +} + +MouseButtons12S &MouseButtons12S::operator=(const MouseButtons12S &other) +{ + data = other.data; + return *this; +} diff --git a/src/hardware/mouse/mouse_common.h b/src/hardware/mouse/mouse_common.h new file mode 100644 index 000000000..5c54b26d1 --- /dev/null +++ b/src/hardware/mouse/mouse_common.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 DOSBOX_MOUSE_COMMON_H +#define DOSBOX_MOUSE_COMMON_H + +#include "mouse.h" + +#include <chrono> + +#include "bit_view.h" + +// *************************************************************************** +// Common variables +// *************************************************************************** + +class MouseShared { +public: + bool active_bios = false; // true = BIOS has a registered callback + bool active_dos = false; // true = DOS driver has a functioning callback + bool active_vmm = false; // true = VMware-compatible driver is active + + bool dos_cb_running = false; // true = DOS callback is running + + // Readiness for initialization + bool ready_startup_sequence = false; + bool ready_config_mouse = false; + bool ready_config_sdl = false; + + bool started = false; +}; + +class MouseVideo { +public: + bool fullscreen = true; + + uint16_t res_x = 640; // resolution to which guest image is scaled, + uint16_t res_y = 400; // excluding black borders + + uint16_t clip_x = 0; // clipping = size of black border (one side) + uint16_t clip_y = 0; +}; + +class MouseInfo { +public: + std::vector<MouseInterfaceInfoEntry> interfaces = {}; + std::vector<MousePhysicalInfoEntry> physical = {}; +}; + +extern MouseInfo mouse_info; // information which can be shared externally +extern MouseShared mouse_shared; // shared internal information +extern MouseVideo mouse_video; // video information - resolution, clipping, etc. + +extern bool mouse_is_captured; + + +// *************************************************************************** +// Common helper calculations +// *************************************************************************** + +float MOUSE_GetBallisticsCoeff(const float speed); +uint8_t MOUSE_GetDelayFromRateHz(const uint16_t rate_hz); + +float MOUSE_ClampRelativeMovement(const float rel); +uint16_t MOUSE_ClampRateHz(const uint16_t rate_hz); + +int8_t MOUSE_ClampToInt8(const int32_t val); +int16_t MOUSE_ClampToInt16(const int32_t val); + +// *************************************************************************** +// Mouse speed calculation +// *************************************************************************** + +class MouseSpeedCalculator final { +public: + + MouseSpeedCalculator(const float scaling); + + float Get() const; + void Update(const float delta); + +private: + + MouseSpeedCalculator() = delete; + MouseSpeedCalculator(const MouseSpeedCalculator &) = delete; + MouseSpeedCalculator &operator=(const MouseSpeedCalculator &) = delete; + + std::chrono::time_point<std::chrono::steady_clock> clock_time_start = std::chrono::steady_clock::now(); + uint32_t pic_ticks_start = 0; + + const float scaling; + + float distance = 0.0f; + float speed = 0.0f; +}; + +// *************************************************************************** +// Types for storing mouse buttons +// *************************************************************************** + +// NOTE: bit layouts has to be compatible with each other and with INT 33 +// (DOS driver) functions 0x03 / 0x05 / 0x06 and it's callback interface + +union MouseButtons12 { + // For storing left and right buttons only + uint8_t data = 0; + + bit_view<0, 1> left; + bit_view<1, 1> right; + + MouseButtons12() : data(0) {} + MouseButtons12(const uint8_t data) : data(data) {} + MouseButtons12(const MouseButtons12 &other) : data(other.data) {} + MouseButtons12 &operator=(const MouseButtons12 &other); +}; + +union MouseButtons345 { + // For storing middle and extra buttons + uint8_t data = 0; + + bit_view<2, 1> middle; + bit_view<3, 1> extra_1; + bit_view<4, 1> extra_2; + + MouseButtons345() : data(0) {} + MouseButtons345(const uint8_t data) : data(data) {} + MouseButtons345(const MouseButtons345 &other) : data(other.data) {} + MouseButtons345 &operator=(const MouseButtons345 &other); +}; + +union MouseButtonsAll { + // For storing all 5 mouse buttons + uint8_t data = 0; + + bit_view<0, 1> left; + bit_view<1, 1> right; + bit_view<2, 1> middle; + bit_view<3, 1> extra_1; + bit_view<4, 1> extra_2; + + MouseButtonsAll() : data(0) {} + MouseButtonsAll(const uint8_t data) : data(data) {} + MouseButtonsAll(const MouseButtonsAll &other) : data(other.data) {} + MouseButtonsAll &operator=(const MouseButtonsAll &other); +}; + +union MouseButtons12S { + // To be used where buttons 3/4/5 are squished + // into a virtual middle button + uint8_t data = 0; + + bit_view<0, 1> left; + bit_view<1, 1> right; + bit_view<2, 1> middle; + + MouseButtons12S() : data(0) {} + MouseButtons12S(const uint8_t data) : data(data) {} + MouseButtons12S(const MouseButtons12S &other) : data(other.data) {} + MouseButtons12S &operator=(const MouseButtons12S &other); +}; + +// *************************************************************************** +// Internal mouse event types +// *************************************************************************** + +struct MouseEvent { + bool request_dos = false; // if DOS mouse driver needs an event + bool request_ps2 = false; // if PS/2 mouse emulation needs an event + + bool dos_moved = false; + bool dos_button = false; + bool dos_wheel = false; + + MouseButtons12S dos_buttons = 0; +}; + + +#endif // DOSBOX_MOUSE_COMMON_H diff --git a/src/hardware/mouse/mouse_config.cpp b/src/hardware/mouse/mouse_config.cpp new file mode 100644 index 000000000..893f91d78 --- /dev/null +++ b/src/hardware/mouse/mouse_config.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 "mouse_common.h" +#include "mouse_config.h" +#include "mouse_interfaces.h" + +#include "checks.h" +#include "control.h" +#include "setup.h" +#include "support.h" + +CHECK_NARROWING(); + + +// TODO - IntelliMouse Explorer emulation is currently deactivated - there is +// probably no way to test it. The IntelliMouse 3.0 software can use it, but +// it seems to require physical PS/2 mouse registers to work correctly, +// and these are not emulated yet. + +// #define ENABLE_EXPLORER_MOUSE + + +MouseConfig mouse_config; +MousePredefined mouse_predefined; + +static std::vector<std::string> list_models_ps2 = { + "standard", + "intellimouse", +#ifdef ENABLE_EXPLORER_MOUSE + "explorer", +#endif +}; + +static std::vector<std::string> list_models_com = { + "2button", + "3button", + "wheel", + "msm", + "2button+msm", + "3button+msm", + "wheel+msm", +}; + +static std::vector<uint16_t> list_rates = { +// Commented out values are probably not interesting +// for the end user as "boosted" sampling rate +// 10", // PS/2 mouse +// 20", // PS/2 mouse +// 30", // bus/InPort mouse + 40, // PS/2 mouse, approx. limit for 1200 baud serial mouse +// 50, // bus/InPort mouse + 60, // PS/2 mouse, used by Microsoft Mouse Driver 8.20 + 80, // PS/2 mouse, approx. limit for 2400 baud serial mouse + 100, // PS/2 mouse, bus/InPort mouse, used by CuteMouse 2.1b4 + 125, // USB mouse (basic, non-gaming), Bluetooth mouse + 160, // approx. limit for 4800 baud serial mouse + 200, // PS/2 mouse, bus/InPort mouse + 250, // USB mouse (gaming) + 330, // approx. limit for 9600 baud serial mouse + 500, // USB mouse (gaming) + +// Todays gaming USB mice are capable of even higher sampling +// rates (like 1000 Hz), but such values are way higher than +// anything DOS games were designed for; most likely such rates +// would only result in emulation slowdowns and compatibility +// issues. +}; + +bool MouseConfig::ParseSerialModel(const std::string &model_str, + MouseModelCOM &model, + bool &auto_msm) +{ + if (model_str == list_models_com[0]) { + model = MouseModelCOM::Microsoft; + auto_msm = false; + return true; + } else if (model_str == list_models_com[1]) { + model = MouseModelCOM::Logitech; + auto_msm = false; + return true; + } else if (model_str == list_models_com[2]) { + model = MouseModelCOM::Wheel; + auto_msm = false; + return true; + } else if (model_str == list_models_com[3]) { + model = MouseModelCOM::MouseSystems; + auto_msm = false; + return true; + } else if (model_str == list_models_com[4]) { + model = MouseModelCOM::Microsoft; + auto_msm = true; + return true; + } else if (model_str == list_models_com[5]) { + model = MouseModelCOM::Logitech; + auto_msm = true; + return true; + } else if (model_str == list_models_com[6]) { + model = MouseModelCOM::Wheel; + auto_msm = true; + return true; + } + + return false; +} + +const std::vector<uint16_t> &MouseConfig::GetValidMinRateList() +{ + return list_rates; +} + +static void config_read(Section *section) +{ + assert(section); + const Section_prop *conf = dynamic_cast<Section_prop *>(section); + assert(conf); + if (!conf) + return; + + // Config - settings changeable during runtime + + mouse_config.mouse_dos_immediate = conf->Get_bool("mouse_dos_immediate"); + if (mouse_shared.ready_config_mouse) + return; + + // Config - DOS driver + + mouse_config.mouse_dos_enable = conf->Get_bool("mouse_dos"); + + // Config - PS/2 AUX port + + std::string prop_str = conf->Get_string("model_ps2"); + if (prop_str == list_models_ps2[0]) + mouse_config.model_ps2 = MouseModelPS2::Standard; + if (prop_str == list_models_ps2[1]) + mouse_config.model_ps2 = MouseModelPS2::IntelliMouse; +#ifdef ENABLE_EXPLORER_MOUSE + if (prop_str == list_models_ps2[2]) + mouse_config.model_ps2 = MouseModelPS2::Explorer; +#endif + + // Config - serial (COM port) mice + + auto set_model_com = [](const std::string &model_str, + MouseModelCOM& model_var, + bool &model_auto) { + if (model_str == list_models_com[0] || model_str == list_models_com[4]) + model_var = MouseModelCOM::Microsoft; + if (model_str == list_models_com[1] || model_str == list_models_com[5]) + model_var = MouseModelCOM::Logitech; + if (model_str == list_models_com[2] || model_str == list_models_com[6]) + model_var = MouseModelCOM::Wheel; + if (model_str == list_models_com[3]) + model_var = MouseModelCOM::MouseSystems; + + if (model_str == list_models_com[4] || + model_str == list_models_com[5] || + model_str == list_models_com[6]) + model_auto = true; + else + model_auto = false; + }; + + prop_str = conf->Get_string("model_com"); + set_model_com(prop_str, + mouse_config.model_com, + mouse_config.model_com_auto_msm); + + // Start mouse emulation if ready + mouse_shared.ready_config_mouse = true; + MOUSE_Startup(); +} + +static void config_init(Section_prop &secprop) +{ + constexpr auto always = Property::Changeable::Always; + constexpr auto only_at_start = Property::Changeable::OnlyAtStart; + + Prop_bool *prop_bool = nullptr; + Prop_string *prop_str = nullptr; + + // Mouse enable/disable settings + + prop_bool = secprop.Add_bool("mouse_dos", only_at_start, true); + assert(prop_bool); + prop_bool->Set_help("Enable built-in DOS mouse driver.\n" + "Notes:\n" + " Disable if you intend to use original MOUSE.COM driver in emulated DOS.\n" + " When guest OS is booted, built-in driver gets disabled automatically."); + + prop_bool = secprop.Add_bool("mouse_dos_immediate", always, false); + assert(prop_bool); + prop_bool->Set_help("Updates mouse movement counters immediately, without waiting for interrupt.\n" + "May improve gameplay, especially in fast paced games (arcade, FPS, etc.) - as\n" + "for some games it effectively boosts the mouse sampling rate to 1000 Hz, without\n" + "increasing interrupt overhead.\n" + "Might cause compatibility issues. List of known incompatible games:\n" + " - Ultima Underworld: The Stygian Abyss\n" + " - Ultima Underworld II: Labyrinth of Worlds\n" + "Please file a bug with the project if you find another game that fails when\n" + "this is enabled, we will update this list."); + + // Mouse models + + prop_str = secprop.Add_string("model_ps2", only_at_start, "intellimouse"); + assert(prop_str); + prop_str->Set_values(list_models_ps2); + prop_str->Set_help("PS/2 AUX port mouse model:\n" + // TODO - Add option "none" + " standard: 3 buttons (standard PS/2 mouse).\n" + " intellimouse: 3 buttons + wheel (Microsoft IntelliMouse).\n" +#ifdef ENABLE_EXPLORER_MOUSE + " explorer: 5 buttons + wheel (Microsoft IntelliMouse Explorer).\n" +#endif + "Default: intellimouse"); + + prop_str = secprop.Add_string("model_com", only_at_start, "wheel+msm"); + assert(prop_str); + prop_str->Set_values(list_models_com); + prop_str->Set_help("COM (serial) port default mouse model:\n" + " 2button: 2 buttons, Microsoft mouse.\n" + " 3button: 3 buttons, Logitech mouse, mostly compatible with Microsoft mouse.\n" + " wheel: 3 buttons + wheel, mostly compatible with Microsoft mouse.\n" + " msm: 3 buttons, Mouse Systems mouse, NOT COMPATIBLE with Microsoft mouse.\n" + " 2button+msm: Automatic choice between 2button and msm.\n" + " 3button+msm: Automatic choice between 3button and msm.\n" + " wheel+msm: Automatic choice between wheel and msm.\n" + "Default: wheel+msm\n" + "Notes:\n" + " Go to [serial] section to enable/disable COM port mice."); +} + +void MOUSE_AddConfigSection(const config_ptr_t &conf) +{ + assert(conf); + Section_prop *sec = conf->AddSection_prop("mouse", &config_read, true); + assert(sec); + config_init(*sec); +} diff --git a/src/hardware/mouse/mouse_config.h b/src/hardware/mouse/mouse_config.h new file mode 100644 index 000000000..2edd19058 --- /dev/null +++ b/src/hardware/mouse/mouse_config.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 DOSBOX_MOUSE_CONFIG_H +#define DOSBOX_MOUSE_CONFIG_H + +#include "dosbox.h" + +#include "mouse.h" + + +// *************************************************************************** +// Predefined calibration +// *************************************************************************** + +struct MousePredefined { + // Mouse equalization for consistent user experience - please adjust + // values so that on full screen, with RAW mouse input, the mouse feel + // is similar to Windows 3.11 for Workgroups with PS/2 mouse driver + // and default settings + const float sensitivity_dos = 1.0f; + const float sensitivity_ps2 = 1.0f; + const float sensitivity_vmm = 3.0f; + const float sensitivity_com = 1.0f; + // Constants to move 'intersection point' for the acceleration curve + // Requires raw mouse input, otherwise there is no effect + // Larger values = higher mouse acceleration + const float acceleration_dos = 1.0f; + const float acceleration_vmm = 1.0f; + + const uint8_t sensitivity_user_default = 50; + const uint8_t sensitivity_user_max = 99; +}; + +extern MousePredefined mouse_predefined; + +// *************************************************************************** +// Configuration file content +// *************************************************************************** + +enum class MouseModelPS2 : uint8_t { + // Values must match PS/2 protocol IDs + Standard = 0x00, + IntelliMouse = 0x03, + Explorer = 0x04, +}; + +enum class MouseModelCOM : uint8_t { + NoMouse, // dummy value or no mouse + Microsoft, + Logitech, + Wheel, + MouseSystems +}; + +enum class MouseModelBus : uint8_t { + NoMouse, + Bus, + InPort, +}; + +struct MouseConfig { + + // From [sdl] section + + float sensitivity_gui_x = 0.0f; + float sensitivity_gui_y = 0.0f; + bool no_mouse = false; // true = NoMouse selected in GUI + bool raw_input = false; // true = relative input is raw data, without + // host OS mouse acceleration applied + + // From [mouse] section + + bool mouse_dos_enable = false; + bool mouse_dos_immediate = false; + + MouseModelPS2 model_ps2 = MouseModelPS2::Standard; + + MouseModelCOM model_com = MouseModelCOM::Wheel; + bool model_com_auto_msm = true; + + // Helper functions for external modules + + static const std::vector<uint16_t> &GetValidMinRateList(); + static bool ParseSerialModel(const std::string &model_str, MouseModelCOM &model, bool &auto_msm); +}; + +extern MouseConfig mouse_config; + +#endif // DOSBOX_MOUSE_CONFIG_H diff --git a/src/hardware/mouse/mouse_interfaces.cpp b/src/hardware/mouse/mouse_interfaces.cpp new file mode 100644 index 000000000..0da6f3eb1 --- /dev/null +++ b/src/hardware/mouse/mouse_interfaces.cpp @@ -0,0 +1,878 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 "mouse.h" +#include "mouse_common.h" +#include "mouse_config.h" +#include "mouse_interfaces.h" +#include "mouse_manymouse.h" +#include "mouse_queue.h" + +#include "checks.h" + +CHECK_NARROWING(); + + +std::vector<MouseInterface *> mouse_interfaces = {}; + + +// *************************************************************************** +// Mouse interface information facade +// *************************************************************************** + +MouseInterfaceInfoEntry::MouseInterfaceInfoEntry(const MouseInterfaceId interface_id) : + idx(static_cast<uint8_t>(interface_id)) +{ +} + +const MouseInterface &MouseInterfaceInfoEntry::Interface() const +{ + return *mouse_interfaces[idx]; +} + +const MousePhysical &MouseInterfaceInfoEntry::MappedPhysical() const +{ + const auto idx = Interface().GetMappedDeviceIdx(); + return ManyMouseGlue::GetInstance().physical_devices[idx]; +} + +bool MouseInterfaceInfoEntry::IsEmulated() const +{ + return Interface().IsEmulated(); +} + +bool MouseInterfaceInfoEntry::IsMapped() const +{ + return Interface().IsMapped(); +} + +bool MouseInterfaceInfoEntry::IsMapped(const uint8_t device_idx) const +{ + return Interface().IsMapped(device_idx); +} + +bool MouseInterfaceInfoEntry::IsMappedDeviceDisconnected() const +{ + if (!IsMapped()) + return false; + + return MappedPhysical().IsDisconnected(); +} + +MouseInterfaceId MouseInterfaceInfoEntry::GetInterfaceId() const +{ + return Interface().GetInterfaceId(); +} + +MouseMapStatus MouseInterfaceInfoEntry::GetMapStatus() const +{ + return Interface().GetMapStatus(); +} + +const std::string &MouseInterfaceInfoEntry::GetMappedDeviceName() const +{ + static const std::string empty = ""; + if (!IsMapped()) + return empty; + + return MappedPhysical().GetName(); +} + +uint8_t MouseInterfaceInfoEntry::GetSensitivityX() const +{ + return Interface().GetSensitivityX(); +} + +uint8_t MouseInterfaceInfoEntry::GetSensitivityY() const +{ + return Interface().GetSensitivityY(); +} + +uint16_t MouseInterfaceInfoEntry::GetMinRate() const +{ + return Interface().GetMinRate(); +} + +uint16_t MouseInterfaceInfoEntry::GetRate() const +{ + return Interface().GetRate(); +} + +// *************************************************************************** +// Physical mouse information facade +// *************************************************************************** + +MousePhysicalInfoEntry::MousePhysicalInfoEntry(const uint8_t idx) : + idx(idx) +{ +} + +const MousePhysical &MousePhysicalInfoEntry::Physical() const +{ + return ManyMouseGlue::GetInstance().physical_devices[idx]; +} + +bool MousePhysicalInfoEntry::IsMapped() const +{ + return Physical().IsMapped(); +} + +bool MousePhysicalInfoEntry::IsDeviceDisconnected() const +{ + return Physical().IsDisconnected(); +} + +const std::string &MousePhysicalInfoEntry::GetDeviceName() const +{ + return Physical().GetName(); +} + +// *************************************************************************** +// Concrete interfaces - declarations +// *************************************************************************** + +class InterfaceDos final : public MouseInterface +{ +public: + + void NotifyMoved(MouseEvent &ev, + const float x_rel, + const float y_rel, + const uint16_t x_abs, + const uint16_t y_abs) override; + void NotifyButton(MouseEvent &ev, + const uint8_t idx, + const bool pressed) override; + void NotifyWheel(MouseEvent &ev, + const int16_t w_rel) override; + + void NotifyBooting() override; + +private: + + friend class MouseInterface; + + InterfaceDos(); + ~InterfaceDos() = default; + InterfaceDos(const InterfaceDos &) = delete; + InterfaceDos &operator=(const InterfaceDos &) = delete; + + void Init() override; + + void UpdateRawMapped() override; + void UpdateMinRate() override; + void UpdateRate() override; +}; + +class InterfacePS2 final : public MouseInterface +{ +public: + + void NotifyMoved(MouseEvent &ev, + const float x_rel, + const float y_rel, + const uint16_t x_abs, + const uint16_t y_abs) override; + void NotifyButton(MouseEvent &ev, + const uint8_t idx, + const bool pressed) override; + void NotifyWheel(MouseEvent &ev, + const int16_t w_rel) override; + +private: + + friend class MouseInterface; + + InterfacePS2(); + ~InterfacePS2() = default; + InterfacePS2(const InterfacePS2 &other) = delete; + InterfacePS2 &operator=(const InterfacePS2 &other) = delete; + + void Init() override; + + void UpdateRawMapped() override; + void UpdateSensitivity() override; + void UpdateRate() override; + + float sensitivity_coeff_vmm_x = 1.0f; // cached sensitivity coefficient + float sensitivity_coeff_vmm_y = 1.0f; +}; + +class InterfaceCOM final : public MouseInterface +{ +public: + + void NotifyMoved(MouseEvent &ev, + const float x_rel, + const float y_rel, + const uint16_t x_abs, + const uint16_t y_abs) override; + void NotifyButton(MouseEvent &ev, + const uint8_t idx, + const bool pressed) override; + void NotifyWheel(MouseEvent &ev, + const int16_t w_rel) override; + + void UpdateRate() override; + + void RegisterListener(CSerialMouse &listener_object) override; + void UnRegisterListener() override; + +private: + + friend class MouseInterface; + + InterfaceCOM(const uint8_t port_id); + InterfaceCOM() = delete; + ~InterfaceCOM() = default; + InterfaceCOM(const InterfaceCOM &) = delete; + InterfaceCOM &operator=(const InterfaceCOM &) = delete; + + CSerialMouse *listener = nullptr; +}; + +// *************************************************************************** +// Base mouse interface +// *************************************************************************** + +void MouseInterface::InitAllInstances() +{ + if (!mouse_interfaces.empty()) + return; // already initialized + + const auto i_first = static_cast<uint8_t>(MouseInterfaceId::First); + const auto i_last = static_cast<uint8_t>(MouseInterfaceId::Last); + const auto i_com1 = static_cast<uint8_t>(MouseInterfaceId::COM1); + + for (uint8_t i = i_first; i <= i_last; i++) + switch (static_cast<MouseInterfaceId>(i)) { + case MouseInterfaceId::DOS: + mouse_interfaces.emplace_back(new InterfaceDos()); + break; + case MouseInterfaceId::PS2: + mouse_interfaces.emplace_back(new InterfacePS2()); + break; + case MouseInterfaceId::COM1: + case MouseInterfaceId::COM2: + case MouseInterfaceId::COM3: + case MouseInterfaceId::COM4: + mouse_interfaces.emplace_back(new InterfaceCOM(static_cast<uint8_t>(i - i_com1))); + break; + default: + assert(false); + break; + } + + for (auto interface : mouse_interfaces) + interface->Init(); +} + +MouseInterface *MouseInterface::Get(const MouseInterfaceId interface_id) +{ + const auto idx = static_cast<size_t>(interface_id); + if (idx < mouse_interfaces.size()) + return mouse_interfaces[idx]; + + assert(interface_id == MouseInterfaceId::None); + return nullptr; +} + +MouseInterface *MouseInterface::GetDOS() +{ + const auto idx = static_cast<uint8_t>(MouseInterfaceId::DOS); + return MouseInterface::Get(static_cast<MouseInterfaceId>(idx)); +} + +MouseInterface *MouseInterface::GetPS2() +{ + const auto idx = static_cast<uint8_t>(MouseInterfaceId::PS2); + return MouseInterface::Get(static_cast<MouseInterfaceId>(idx)); +} + +MouseInterface *MouseInterface::GetSerial(const uint8_t port_id) +{ + if (port_id < num_mouse_interfaces_com) { + const auto idx = static_cast<uint8_t>(MouseInterfaceId::COM1) + port_id; + return MouseInterface::Get(static_cast<MouseInterfaceId>(idx)); + } + + LOG_ERR("MOUSE: Ports above COM4 not supported"); + assert(false); + return nullptr; +} + +MouseInterface::MouseInterface(const MouseInterfaceId interface_id, + const float sensitivity_predefined) : + interface_id(interface_id), + sensitivity_predefined(sensitivity_predefined) +{ + ConfigResetSensitivity(); + mouse_info.interfaces.emplace_back(MouseInterfaceInfoEntry(interface_id)); +} + +void MouseInterface::Init() +{ +} + +uint8_t MouseInterface::GetInterfaceIdx() const +{ + return static_cast<uint8_t>(interface_id); +} + +bool MouseInterface::IsMapped() const +{ + return mapped_idx < mouse_info.physical.size(); +} + +bool MouseInterface::IsMapped(const uint8_t device_idx) const +{ + return mapped_idx == device_idx; +} + +bool MouseInterface::IsEmulated() const +{ + return emulated; +} + +bool MouseInterface::IsUsingEvents() const +{ + return IsEmulated() && + (map_status == MouseMapStatus::HostPointer || + map_status == MouseMapStatus::Mapped); +} + +bool MouseInterface::IsUsingHostPointer() const +{ + return IsEmulated() && (map_status == MouseMapStatus::HostPointer); +} + +uint16_t MouseInterface::GetMinRate() const +{ + return min_rate_hz; +} + +MouseInterfaceId MouseInterface::GetInterfaceId() const +{ + return interface_id; +} + +MouseMapStatus MouseInterface::GetMapStatus() const +{ + return map_status; +} + +uint8_t MouseInterface::GetMappedDeviceIdx() const +{ + return mapped_idx; +} + +uint8_t MouseInterface::GetSensitivityX() const +{ + return sensitivity_user_x; +} + +uint8_t MouseInterface::GetSensitivityY() const +{ + return sensitivity_user_y; +} + +uint16_t MouseInterface::GetRate() const +{ + return rate_hz; +} + +void MouseInterface::NotifyInterfaceRate(const uint16_t value_hz) +{ + interface_rate_hz = value_hz; + UpdateRate(); +} + +void MouseInterface::NotifyBooting() +{ +} + +void MouseInterface::NotifyDisconnect() +{ + SetMapStatus(MouseMapStatus::Disconnected, mapped_idx); +} + +void MouseInterface::SetMapStatus(const MouseMapStatus status, + const uint8_t device_idx) +{ + MouseMapStatus new_map_status = status; + uint8_t new_mapped_idx = device_idx; + + // Change 'mapped to host pointer' to just 'host pointer' + if (new_map_status == MouseMapStatus::Mapped && + new_mapped_idx >= mouse_info.physical.size()) + new_map_status = MouseMapStatus::HostPointer; + + // if physical device is disconnected, change state from + // 'mapped' to 'disconnected' + if (new_map_status == MouseMapStatus::Mapped && + mouse_info.physical[new_mapped_idx].IsDeviceDisconnected()) + new_map_status = MouseMapStatus::Disconnected; + + // Perform necessary updates for mapping change + if (map_status != new_map_status || mapped_idx != new_mapped_idx) + ResetButtons(); + if (map_status != new_map_status) + UpdateRawMapped(); + if (mapped_idx != new_mapped_idx) + ManyMouseGlue::GetInstance().Map(new_mapped_idx, interface_id); + + // Apply new mapping + mapped_idx = new_mapped_idx; + map_status = new_map_status; +} + +bool MouseInterface::ConfigMap(const uint8_t device_idx) +{ + if (!IsEmulated()) + return false; + + SetMapStatus(MouseMapStatus::Mapped, device_idx); + return true; +} + +void MouseInterface::ConfigUnMap() +{ + SetMapStatus(MouseMapStatus::Mapped, idx_host_pointer); +} + +void MouseInterface::ConfigOnOff(const bool enable) +{ + if (!IsEmulated()) + return; + + if (!enable) + SetMapStatus(MouseMapStatus::Disabled); + else if (map_status == MouseMapStatus::Disabled) + SetMapStatus(MouseMapStatus::HostPointer); +} + +void MouseInterface::ConfigReset() +{ + ConfigUnMap(); + ConfigOnOff(true); + ConfigResetSensitivity(); + ConfigResetMinRate(); +} + +void MouseInterface::ConfigSetSensitivity(const uint8_t value_x, + const uint8_t value_y) +{ + if (!IsEmulated()) + return; + + sensitivity_user_x = value_x; + sensitivity_user_y = value_y; + UpdateSensitivity(); +} + +void MouseInterface::ConfigSetSensitivityX(const uint8_t value) +{ + if (!IsEmulated()) + return; + + sensitivity_user_x = value; + UpdateSensitivity(); +} + +void MouseInterface::ConfigSetSensitivityY(const uint8_t value) +{ + if (!IsEmulated()) + return; + + sensitivity_user_y = value; + UpdateSensitivity(); +} + +void MouseInterface::ConfigResetSensitivity() +{ + ConfigSetSensitivity(mouse_predefined.sensitivity_user_default, + mouse_predefined.sensitivity_user_default); +} + +void MouseInterface::ConfigResetSensitivityX() +{ + ConfigSetSensitivityX(mouse_predefined.sensitivity_user_default); +} + +void MouseInterface::ConfigResetSensitivityY() +{ + ConfigSetSensitivityY(mouse_predefined.sensitivity_user_default); +} + +void MouseInterface::ConfigSetMinRate(const uint16_t value_hz) +{ + min_rate_hz = value_hz; + UpdateMinRate(); +} + +void MouseInterface::ConfigResetMinRate() +{ + ConfigSetMinRate(0); +} + +void MouseInterface::RegisterListener(CSerialMouse &) +{ + assert(false); // should never be called for unsupported interface +} + +void MouseInterface::UnRegisterListener() +{ + assert(false); // should never be called for unsupported interface +} + +void MouseInterface::UpdateConfig() +{ + UpdateRawMapped(); + UpdateSensitivity(); +} + +void MouseInterface::UpdateRawMapped() +{ +} + +void MouseInterface::UpdateSensitivity() +{ + auto calculate = [this](const uint8_t user_val) + { + // Mouse sensitivity formula is exponential - as it is probably + // reasonable to expect user wanting to increase sensitivity + // 1.5 times, but not 1.9 times - while the difference between + // 5.0 and 5.4 times sensitivity increase is rather hard to + // notice in a real life + + // Increase user_val by 20 steps = double the sensitivity + constexpr float double_steps = 20.0f; + + return sensitivity_predefined * + std::pow(2.0f, static_cast<float>(user_val - 50) / double_steps); + }; + + sensitivity_coeff_x = calculate(sensitivity_user_x) * mouse_config.sensitivity_gui_x; + sensitivity_coeff_y = calculate(sensitivity_user_y) * mouse_config.sensitivity_gui_y; +} + +void MouseInterface::UpdateMinRate() +{ + UpdateRate(); +} + +void MouseInterface::UpdateRate() +{ + rate_hz = MOUSE_ClampRateHz(std::max(interface_rate_hz, min_rate_hz)); +} + +void MouseInterface::UpdateButtons(const uint8_t idx, + const bool pressed) +{ + old_buttons_12 = buttons_12; + old_buttons_345 = buttons_345; + + switch (idx) { + case 0: // left button + buttons_12.left = pressed ? 1 : 0; + break; + case 1: // right button + buttons_12.right = pressed ? 1 : 0; + break; + case 2: // middle button + buttons_345.middle = pressed ? 1 : 0; + break; + case 3: // extra button #1 + buttons_345.extra_1 = pressed ? 1 : 0; + break; + case 4: // extra button #2 + buttons_345.extra_2 = pressed ? 1 : 0; + break; + default: // button not supported + return; + } +} + +void MouseInterface::ResetButtons() +{ + buttons_12 = 0; + buttons_345 = 0; +} + +bool MouseInterface::ChangedButtonsJoined() const +{ + return (old_buttons_12.data != buttons_12.data) || + (old_buttons_345.data != buttons_345.data); +} + +bool MouseInterface::ChangedButtonsSquished() const +{ + if (GCC_LIKELY(old_buttons_12.data != buttons_12.data)) + return true; + + return (old_buttons_345.data == 0 && buttons_345.data != 0) || + (old_buttons_345.data != 0 && buttons_345.data == 0); +} + +MouseButtonsAll MouseInterface::GetButtonsJoined() const +{ + MouseButtonsAll buttons_all; + buttons_all.data = buttons_12.data | buttons_345.data; + + return buttons_all; +} + +MouseButtons12S MouseInterface::GetButtonsSquished() const +{ + MouseButtons12S buttons_12S; + + // Squish buttons 3/4/5 into single virtual middle button + buttons_12S.data = buttons_12.data; + if (buttons_345.data) + buttons_12S.middle = 1; + + return buttons_12S; +} + +// *************************************************************************** +// Concrete interfaces - implementation +// *************************************************************************** + +InterfaceDos::InterfaceDos() : + MouseInterface(MouseInterfaceId::DOS, mouse_predefined.sensitivity_dos) +{ + UpdateSensitivity(); +} + +void InterfaceDos::Init() +{ + if (mouse_config.mouse_dos_enable) + MOUSEDOS_Init(); + else + emulated = false; + MOUSEDOS_NotifyMinRate(min_rate_hz); +} + +void InterfaceDos::NotifyMoved(MouseEvent &ev, + const float x_rel, + const float y_rel, + const uint16_t x_abs, + const uint16_t y_abs) +{ + ev.dos_moved = MOUSEDOS_NotifyMoved( + x_rel * sensitivity_coeff_x, + y_rel * sensitivity_coeff_y, + x_abs, + y_abs); + ev.request_dos = ev.dos_moved; +} + +void InterfaceDos::NotifyButton(MouseEvent &ev, + const uint8_t idx, + const bool pressed) +{ + UpdateButtons(idx, pressed); + if (GCC_UNLIKELY(!ChangedButtonsSquished())) + return; + + ev.dos_button = true; + ev.dos_buttons = GetButtonsSquished(); + ev.request_dos = true; +} + +void InterfaceDos::NotifyWheel(MouseEvent &ev, + const int16_t w_rel) +{ + ev.dos_wheel = MOUSEDOS_NotifyWheel(w_rel); + ev.request_dos = ev.dos_wheel; +} + +void InterfaceDos::NotifyBooting() +{ + // DOS virtual mouse driver gets unavailable + // if guest OS is booted so do not waste time + // emulating this interface + + ConfigReset(); + emulated = false; + ManyMouseGlue::GetInstance().ShutdownIfSafe(); +} + +void InterfaceDos::UpdateRawMapped() +{ + MOUSEDOS_NotifyMapped(IsMapped()); + MOUSEDOS_NotifyRawInput(mouse_config.raw_input || IsMapped()); +} + +void InterfaceDos::UpdateMinRate() +{ + MOUSEDOS_NotifyMinRate(min_rate_hz); +} + +void InterfaceDos::UpdateRate() +{ + MouseInterface::UpdateRate(); + MouseQueue::GetInstance().SetRateDOS(rate_hz); +} + +InterfacePS2::InterfacePS2() : + MouseInterface(MouseInterfaceId::PS2, mouse_predefined.sensitivity_ps2) +{ + UpdateSensitivity(); +} + +void InterfacePS2::Init() +{ + MOUSEPS2_Init(); + MOUSEVMM_Init(); +} + +void InterfacePS2::NotifyMoved(MouseEvent &ev, + const float x_rel, + const float y_rel, + const uint16_t x_abs, + const uint16_t y_abs) +{ + const bool request_ps2 = MOUSEPS2_NotifyMoved(x_rel * sensitivity_coeff_x, + y_rel * sensitivity_coeff_y); + const bool request_vmm = MOUSEVMM_NotifyMoved(x_rel * sensitivity_coeff_vmm_x, + y_rel * sensitivity_coeff_vmm_y, + x_abs, + y_abs); + + ev.request_ps2 = request_ps2 || request_vmm; +} + +void InterfacePS2::NotifyButton(MouseEvent &ev, + const uint8_t idx, + const bool pressed) +{ + UpdateButtons(idx, pressed); + if (GCC_UNLIKELY(!ChangedButtonsJoined())) + return; + + const bool request_ps2 = MOUSEPS2_NotifyButton(GetButtonsSquished(), + GetButtonsJoined()); + const bool request_vmm = MOUSEVMM_NotifyButton(GetButtonsSquished()); + + ev.request_ps2 = request_ps2 || request_vmm; +} + +void InterfacePS2::NotifyWheel(MouseEvent &ev, + const int16_t w_rel) +{ + const bool request_ps2 = MOUSEPS2_NotifyWheel(w_rel); + const bool request_vmm = MOUSEVMM_NotifyWheel(w_rel); + + ev.request_ps2 = request_ps2 || request_vmm; +} + +void InterfacePS2::UpdateRawMapped() +{ + MOUSEVMM_NotifyMapped(IsMapped()); + MOUSEVMM_NotifyRawInput(mouse_config.raw_input || IsMapped()); +} + +void InterfacePS2::UpdateSensitivity() +{ + MouseInterface::UpdateSensitivity(); + + const float tmp = mouse_predefined.sensitivity_vmm / + mouse_predefined.sensitivity_ps2; + sensitivity_coeff_vmm_x = sensitivity_coeff_x * tmp; + sensitivity_coeff_vmm_y = sensitivity_coeff_y * tmp; +} + +void InterfacePS2::UpdateRate() +{ + MouseInterface::UpdateRate(); + MouseQueue::GetInstance().SetRatePS2(rate_hz); +} + +InterfaceCOM::InterfaceCOM(const uint8_t port_id) : + MouseInterface(static_cast<MouseInterfaceId>(static_cast<uint8_t>(MouseInterfaceId::COM1) + port_id), + mouse_predefined.sensitivity_com) +{ + UpdateSensitivity(); + // Wait for CSerialMouse to register itself + emulated = false; +} + +void InterfaceCOM::NotifyMoved(MouseEvent &, + const float x_rel, + const float y_rel, + const uint16_t, + const uint16_t) +{ + assert(listener); + + listener->NotifyMoved(x_rel * sensitivity_coeff_x, + y_rel * sensitivity_coeff_y); +} + +void InterfaceCOM::NotifyButton(MouseEvent &, + const uint8_t idx, + const bool pressed) +{ + assert(listener); + + UpdateButtons(idx, pressed); + if (GCC_UNLIKELY(!ChangedButtonsSquished())) + return; + + listener->NotifyButton(GetButtonsSquished().data, idx); +} + +void InterfaceCOM::NotifyWheel(MouseEvent &, + const int16_t w_rel) +{ + assert(listener); + + listener->NotifyWheel(w_rel); +} + +void InterfaceCOM::UpdateRate() +{ + MouseInterface::UpdateRate(); + + if (!listener) + return; + + if (interface_rate_hz >= rate_hz || !interface_rate_hz) + listener->BoostRate(0); + else + // Ask serial mouse emulation code to cheat on transmission + // speed to simulate higher sampling rate + listener->BoostRate(rate_hz); +} + +void InterfaceCOM::RegisterListener(CSerialMouse &listener_object) +{ + listener = &listener_object; + emulated = true; +} + +void InterfaceCOM::UnRegisterListener() +{ + // Serial mouse gets unavailable when listener object disconnects + + ConfigReset(); + listener = nullptr; + emulated = false; + ManyMouseGlue::GetInstance().ShutdownIfSafe(); +} diff --git a/src/hardware/mouse/mouse_interfaces.h b/src/hardware/mouse/mouse_interfaces.h new file mode 100644 index 000000000..6ddcb113e --- /dev/null +++ b/src/hardware/mouse/mouse_interfaces.h @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 DOSBOX_MOUSE_INTERFACES_H +#define DOSBOX_MOUSE_INTERFACES_H + +#include "mouse_common.h" + +#include "../serialport/serialmouse.h" + +// *************************************************************************** +// Main mouse module +// *************************************************************************** + +void MOUSE_Startup(); + +void MOUSE_NotifyDisconnect(const MouseInterfaceId interface_id); +void MOUSE_NotifyFakePS2(); // fake PS/2 event, for VMware protocol support +void MOUSE_NotifyResetDOS(); +void MOUSE_NotifyStateChanged(); + +// *************************************************************************** +// DOS mouse driver +// *************************************************************************** + +void MOUSEDOS_Init(); +void MOUSEDOS_NotifyMapped(const bool enabled); +void MOUSEDOS_NotifyRawInput(const bool enabled); +void MOUSEDOS_NotifyMinRate(const uint16_t value_hz); +void MOUSEDOS_DrawCursor(); + +bool MOUSEDOS_HasCallback(const uint8_t mask); +Bitu MOUSEDOS_DoCallback(const uint8_t mask, + const MouseButtons12S buttons_12S); + +// - needs relative movements +// - understands up to 3 buttons + +bool MOUSEDOS_NotifyMoved(const float x_rel, + const float y_rel, + const uint16_t x_abs, + const uint16_t y_abs); +bool MOUSEDOS_NotifyWheel(const int16_t w_rel); + +uint8_t MOUSEDOS_UpdateMoved(); +uint8_t MOUSEDOS_UpdateButtons(const MouseButtons12S buttons_12S); +uint8_t MOUSEDOS_UpdateWheel(); + +// *************************************************************************** +// PS/2 mouse +// *************************************************************************** + +void MOUSEPS2_Init(); +void MOUSEPS2_UpdateButtonSquish(); +void MOUSEPS2_PortWrite(const uint8_t byte); +void MOUSEPS2_UpdatePacket(); +bool MOUSEPS2_SendPacket(); + +// - needs relative movements +// - understands up to 5 buttons in Intellimouse Explorer mode +// - understands up to 3 buttons in other modes + +bool MOUSEPS2_NotifyMoved(const float x_rel, const float y_rel); +bool MOUSEPS2_NotifyButton(const MouseButtons12S buttons_12S, + const MouseButtonsAll buttons_all); +bool MOUSEPS2_NotifyWheel(const int16_t w_rel); + +// *************************************************************************** +// BIOS mouse interface for PS/2 mouse +// *************************************************************************** + +Bitu MOUSEBIOS_DoCallback(); + +// *************************************************************************** +// VMware protocol extension for PS/2 mouse +// *************************************************************************** + +void MOUSEVMM_Init(); +void MOUSEVMM_NotifyMapped(const bool enabled); +void MOUSEVMM_NotifyRawInput(const bool enabled); +void MOUSEVMM_NewScreenParams(const uint16_t x_abs, const uint16_t y_abs); +void MOUSEVMM_Deactivate(); + +// - needs absolute mouse position +// - understands up to 3 buttons + +bool MOUSEVMM_NotifyMoved(const float x_rel, + const float y_rel, + const uint16_t x_abs, + const uint16_t y_abs); +bool MOUSEVMM_NotifyButton(const MouseButtons12S buttons_12S); +bool MOUSEVMM_NotifyWheel(const int16_t w_rel); + +// *************************************************************************** +// Serial mouse +// *************************************************************************** + +// - needs relative movements +// - understands up to 3 buttons +// - needs index of button which changed state + +// Nothing to declare + +// *************************************************************************** +// Base mouse interface +// *************************************************************************** + +class MouseInterface +{ +public: + + static void InitAllInstances(); + static MouseInterface *Get(const MouseInterfaceId interface_id); + static MouseInterface *GetDOS(); + static MouseInterface *GetPS2(); + static MouseInterface *GetSerial(const uint8_t port_id); + + virtual void NotifyMoved(MouseEvent &ev, + const float x_rel, + const float y_rel, + const uint16_t x_abs, + const uint16_t y_abs) = 0; + virtual void NotifyButton(MouseEvent &ev, + const uint8_t idx, + const bool pressed) = 0; + virtual void NotifyWheel(MouseEvent &ev, + const int16_t w_rel) = 0; + + void NotifyInterfaceRate(const uint16_t rate_hz); + virtual void NotifyBooting(); + void NotifyDisconnect(); + + bool IsMapped() const; + bool IsMapped(const uint8_t device_idx) const; + bool IsEmulated() const; + bool IsUsingEvents() const; + bool IsUsingHostPointer() const; + + MouseInterfaceId GetInterfaceId() const; + MouseMapStatus GetMapStatus() const; + uint8_t GetMappedDeviceIdx() const; + uint8_t GetSensitivityX() const; + uint8_t GetSensitivityY() const; + uint16_t GetMinRate() const; + uint16_t GetRate() const; + + bool ConfigMap(const uint8_t device_idx); + void ConfigUnMap(); + + void ConfigOnOff(const bool enable); + void ConfigReset(); + void ConfigSetSensitivity(const uint8_t value_x, const uint8_t value_y); + void ConfigSetSensitivityX(const uint8_t value); + void ConfigSetSensitivityY(const uint8_t value); + void ConfigResetSensitivity(); + void ConfigResetSensitivityX(); + void ConfigResetSensitivityY(); + void ConfigSetMinRate(const uint16_t value_hz); + void ConfigResetMinRate(); + + virtual void UpdateConfig(); + virtual void RegisterListener(CSerialMouse &listener_object); + virtual void UnRegisterListener(); + +protected: + + static constexpr uint8_t idx_host_pointer = UINT8_MAX; + + MouseInterface(const MouseInterfaceId interface_id, + const float sensitivity_predefined); + virtual ~MouseInterface() = default; + virtual void Init(); + + uint8_t GetInterfaceIdx() const; + + void SetMapStatus(const MouseMapStatus status, + const uint8_t device_idx = idx_host_pointer); + + virtual void UpdateRawMapped(); + virtual void UpdateSensitivity(); + virtual void UpdateMinRate(); + virtual void UpdateRate(); + void UpdateButtons(const uint8_t idx, const bool pressed); + void ResetButtons(); + + bool ChangedButtonsJoined() const; + bool ChangedButtonsSquished() const; + + MouseButtonsAll GetButtonsJoined() const; + MouseButtons12S GetButtonsSquished() const; + + bool emulated = true; + + float sensitivity_coeff_x = 1.0f; // cached combined sensitivity coefficients + float sensitivity_coeff_y = 1.0f; // to reduce amount of multiplications + + uint8_t sensitivity_user_x = 0; + uint8_t sensitivity_user_y = 0; + + uint16_t rate_hz = 0; + uint16_t min_rate_hz = 0; + uint16_t interface_rate_hz = 0; + +private: + + MouseInterface() = delete; + MouseInterface(const MouseInterface &) = delete; + MouseInterface &operator=(const MouseInterface &) = delete; + + const MouseInterfaceId interface_id = MouseInterfaceId::None; + + MouseMapStatus map_status = MouseMapStatus::HostPointer; + uint8_t mapped_idx = idx_host_pointer; // index of mapped physical mouse + + MouseButtons12 buttons_12 = 0; // host side buttons 1 (left), 2 (right) + MouseButtons345 buttons_345 = 0; // host side buttons 3 (middle), 4, and 5 + + MouseButtons12 old_buttons_12 = 0; // pre-update values + MouseButtons345 old_buttons_345 = 0; + + float sensitivity_predefined = 1.0f; // hardcoded for the given interface +}; + +extern std::vector<MouseInterface *> mouse_interfaces; + +#endif // DOSBOX_MOUSE_INTERFACES_H diff --git a/src/hardware/mouse/mouse_manymouse.cpp b/src/hardware/mouse/mouse_manymouse.cpp new file mode 100644 index 000000000..f8e798d85 --- /dev/null +++ b/src/hardware/mouse/mouse_manymouse.cpp @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 "mouse_manymouse.h" +#include "mouse_common.h" +#include "mouse_config.h" + +#include "callback.h" +#include "checks.h" +#include "pic.h" +#include "string_utils.h" + +CHECK_NARROWING(); + + +void manymouse_tick(uint32_t) +{ +#if C_MANYMOUSE + ManyMouseGlue::GetInstance().Tick(); +#endif // C_MANYMOUSE +} + +MousePhysical::MousePhysical(const std::string &name) : + name(name) +{ +} + +bool MousePhysical::IsMapped() const +{ + return mapped_id != MouseInterfaceId::None; +} + +bool MousePhysical::IsDisconnected() const +{ + return disconnected; +} + +MouseInterfaceId MousePhysical::GetMappedInterfaceId() const +{ + return mapped_id; +} + +const std::string &MousePhysical::GetName() const +{ + return name; +} + +ManyMouseGlue &ManyMouseGlue::GetInstance() +{ + static ManyMouseGlue *instance = nullptr; + if (!instance) + instance = new ManyMouseGlue(); + return *instance; +} + +#if C_MANYMOUSE + +void ManyMouseGlue::InitIfNeeded() +{ + if (initialized || malfunction) + return; + + num_mice = ManyMouse_Init(); + if (num_mice < 0) { + LOG_ERR("MOUSE: ManyMouse initialization failed"); + ManyMouse_Quit(); + malfunction = true; + return; + } + initialized = true; + + if (num_mice >= max_mice) { + num_mice = max_mice - 1; + static bool logged = false; + if (!logged) { + logged = true; + LOG_ERR("MOUSE: Up to %d simultaneously connected mice supported", + max_mice); + } + } + + const auto new_driver_name = std::string(ManyMouse_DriverName()); + if (new_driver_name != driver_name) { + driver_name = new_driver_name; + LOG_INFO("MOUSE: ManyMouse driver '%s'", driver_name.c_str()); + } + + Rescan(); +} + +void ManyMouseGlue::ShutdownIfSafe() +{ + if (mapping_in_effect || config_api_counter) + return; + + ShutdownForced(); +} + +void ManyMouseGlue::ShutdownForced() +{ + if (!initialized) + return; + + PIC_RemoveEvents(manymouse_tick); + ManyMouse_Quit(); + ClearPhysicalMice(); + num_mice = 0; + initialized = false; +} + +void ManyMouseGlue::StartConfigAPI() +{ + // Config API object is being created + ++config_api_counter; + assert(config_api_counter > 0); +} + +void ManyMouseGlue::StopConfigAPI() +{ + // Config API object is being destroyed + assert(config_api_counter > 0); + config_api_counter--; + ShutdownIfSafe(); + if (!config_api_counter) + rescan_blocked_config = false; +} + +void ManyMouseGlue::ClearPhysicalMice() +{ + mouse_info.physical.clear(); + physical_devices.clear(); + rel_x.clear(); + rel_y.clear(); +} + +void ManyMouseGlue::Rescan() +{ + if (config_api_counter) + // Do not allow another rescan until MouseConfigAPI + // stops being used, it would be unsafe due to possible + // changes of physical device list size/indices + rescan_blocked_config = true; + + ClearPhysicalMice(); + + for (uint8_t idx = 0; idx < num_mice; idx++) { + const auto name_utf8 = ManyMouse_DeviceName(idx); + std::string name; + UTF8_RenderForDos(name_utf8, name); + + for (auto pos = name.size(); pos > 0; pos--) { + // Replace non-breaking space with a regular space + if (name[pos - 1] == 0x7f) + name[pos - 1] = 0x20; + // Remove non-ASCII and control characters + if (name[pos - 1] < 0x20 || name[pos - 1] > 0x7e) + name.erase(pos - 1, 1); + } + + // Try to rework into something useful names in the forms + // 'FooBar Corp FooBar Corp Incredible Mouse' + size_t pos = name.size() / 2 + 1; + while (--pos > 2) { + if (name[pos - 1] != ' ') + continue; + const std::string tmp = name.substr(0, pos); + if (name.rfind(tmp + tmp, 0) == std::string::npos) + continue; + name = name.substr(pos); + break; + } + + // ManyMouse should limit device names to 64 characters, + // but make sure name is indeed limited in length, and + // strip trailing spaces + name.resize(std::min(static_cast<size_t>(64), name.size())); + while (!name.empty() && name.back() == ' ') + name.pop_back(); + + physical_devices.emplace_back(MousePhysical(name)); + mouse_info.physical.emplace_back( + MousePhysicalInfoEntry(static_cast<uint8_t>(physical_devices.size() - 1))); + } +} + +void ManyMouseGlue::RescanIfSafe() +{ + if (rescan_blocked_config) + return; + + ShutdownIfSafe(); + InitIfNeeded(); +} + +bool ManyMouseGlue::ProbeForMapping(uint8_t &device_id) +{ + // Wait a little to speedup screen update + const auto pic_ticks_start = PIC_Ticks; + while (PIC_Ticks >= pic_ticks_start && + PIC_Ticks - pic_ticks_start < 50) + CALLBACK_Idle(); + + // Make sure the module is initialized, + // but suppress default event handling + InitIfNeeded(); + if (!initialized) + return false; + PIC_RemoveEvents(manymouse_tick); + + // Flush events, handle critical ones + ManyMouseEvent event; + while (ManyMouse_PollEvent(&event)) + HandleEvent(event, true); // handle critical events + + bool success = false; + while (true) { + // Poll mouse events, handle critical ones + if (!ManyMouse_PollEvent(&event)) { + CALLBACK_Idle(); + continue; + } + if (event.device >= max_mice) + continue; + HandleEvent(event, true); + + // Wait for mouse button press + if (event.type != MANYMOUSE_EVENT_BUTTON || !event.value) + continue; + device_id = static_cast<uint8_t>(event.device); + + if (event.item >= 1) + break; // user cancelled the interactive mouse mapping + + // Do not accept already mapped devices + bool already_mapped = false; + for (auto &interface : mouse_interfaces) + if (interface->IsMapped(device_id)) + already_mapped = true; + if (already_mapped) + continue; + + // Mouse probed successfully + device_id = static_cast<uint8_t>(event.device); + success = true; + break; + } + + if (mapping_in_effect && !mouse_config.no_mouse) + PIC_AddEvent(manymouse_tick, tick_interval); + return success; +} + +uint8_t ManyMouseGlue::GetIdx(const std::regex ®ex) +{ + // Try to match the mouse name which is not mapped yet + + for (size_t i = 0; i < physical_devices.size(); i++) { + const auto &physical_device = physical_devices[i]; + + if (physical_device.IsDisconnected() || + !std::regex_match(physical_device.GetName(), regex)) + // mouse disconnected or name does not match + continue; + + if (physical_device.GetMappedInterfaceId() == MouseInterfaceId::None) + // name matches, mouse not mapped yet - use it! + return static_cast<uint8_t>(i); + } + + return max_mice; +} + +void ManyMouseGlue::Map(const uint8_t physical_idx, + const MouseInterfaceId interface_id) +{ + assert(interface_id != MouseInterfaceId::None); + + if (physical_idx >= physical_devices.size()) { + UnMap(interface_id); + return; + } + + auto &physical_device = physical_devices[physical_idx]; + if (interface_id == physical_device.GetMappedInterfaceId()) + return; // nothing to update + physical_device.mapped_id = interface_id; + + MapFinalize(); +} + +void ManyMouseGlue::UnMap(const MouseInterfaceId interface_id) +{ + for (auto &physical_device : physical_devices) { + if (interface_id != physical_device.GetMappedInterfaceId()) + continue; // not a device to unmap + physical_device.mapped_id = MouseInterfaceId::None; + break; + } + + MapFinalize(); +} + +void ManyMouseGlue::MapFinalize() +{ + PIC_RemoveEvents(manymouse_tick); + mapping_in_effect = false; + for (const auto &entry : mouse_info.physical) { + if (entry.IsMapped()) + continue; + + mapping_in_effect = true; + if (!mouse_config.no_mouse) + PIC_AddEvent(manymouse_tick, tick_interval); + break; + } +} + +void ManyMouseGlue::HandleEvent(const ManyMouseEvent &event, + const bool critical_only) +{ + if (GCC_UNLIKELY(event.device >= mouse_info.physical.size())) + return; // device ID out of supported range + if (GCC_UNLIKELY(mouse_config.no_mouse && + event.type != MANYMOUSE_EVENT_DISCONNECT)) + return; // mouse control disabled in GUI + + const auto device_idx = static_cast<uint8_t>(event.device); + const auto interface_id = physical_devices[device_idx].GetMappedInterfaceId(); + const bool no_interface = (interface_id == MouseInterfaceId::None); + + switch (event.type) { + + case MANYMOUSE_EVENT_ABSMOTION: + // LOG_INFO("MANYMOUSE #%u ABSMOTION axis %d, %d", event.device, event.item, event.value); + break; + + case MANYMOUSE_EVENT_RELMOTION: + // LOG_INFO("MANYMOUSE #%u RELMOTION axis %d, %d", event.device, event.item, event.value); + if (no_interface || critical_only || (event.item != 0 && event.item != 1)) + break; + + if (rel_x.size() <= device_idx) { + rel_x.resize(static_cast<size_t>(device_idx + 1), 0); + rel_y.resize(static_cast<size_t>(device_idx + 1), 0); + } + + if (event.item) + rel_y[event.device] += event.value; + else + rel_x[event.device] += event.value; + break; + + case MANYMOUSE_EVENT_BUTTON: + // LOG_INFO("MANYMOUSE #%u BUTTON %u %s", event.device, event.item, event.value ? "press" : "release"); + if (no_interface || (critical_only && !event.value) || (event.item >= max_buttons)) + // TODO: Consider supporting extra mouse buttons + // in the future. On Linux event items 3-7 are for + // scroll wheel(s), 8 is for SDL button X1, 9 is + // for X2, etc. - but I don't know yet if this + // is consistent across various platforms + break; + MOUSE_EventButton(static_cast<uint8_t>(event.item), event.value, interface_id); + break; + + case MANYMOUSE_EVENT_SCROLL: + // LOG_INFO("MANYMOUSE #%u WHEEL #%u %d", event.device, event.item, event.value); + if (no_interface || critical_only || (event.item != 0)) + break; // only the 1st wheel is supported + MOUSE_EventWheel(MOUSE_ClampToInt16(-event.value), interface_id); + break; + + case MANYMOUSE_EVENT_DISCONNECT: + // LOG_INFO("MANYMOUSE #%u DISCONNECT", event.device); + physical_devices[event.device].disconnected = true; + for (uint8_t button = 0; button < max_buttons; button++) + MOUSE_EventButton(button, false, interface_id); + MOUSE_NotifyDisconnect(interface_id); + break; + + default: + // LOG_INFO("MANYMOUSE #%u (other event)", event.device); + break; + } +} + +void ManyMouseGlue::Tick() +{ + assert(!mouse_config.no_mouse); + + // Handle all the events from the queue + ManyMouseEvent event; + while (ManyMouse_PollEvent(&event)) + HandleEvent(event); + + // Report accumulated mouse movements + for (uint8_t idx = 0; idx < rel_x.size(); idx++) { + if (rel_x[idx] == 0 && rel_y[idx] == 0) + continue; + + const auto interface_id = physical_devices[idx].GetMappedInterfaceId(); + MOUSE_EventMoved(static_cast<float>(rel_x[idx]), + static_cast<float>(rel_y[idx]), + interface_id); + rel_x[idx] = 0; + rel_y[idx] = 0; + } + + if (mapping_in_effect) + PIC_AddEvent(manymouse_tick, tick_interval); +} + +#else + +// ManyMouse is not available + +void ManyMouseGlue::RescanIfSafe() +{ + static bool already_warned = false; + if (!already_warned) { + LOG_ERR("MOUSE: This build has no ManyMouse support"); + already_warned = true; + } +} + +void ManyMouseGlue::ShutdownIfSafe() +{ +} + +void ManyMouseGlue::StartConfigAPI() +{ +} + +void ManyMouseGlue::StopConfigAPI() +{ +} + +bool ManyMouseGlue::ProbeForMapping(uint8_t &) +{ + return false; +} + +uint8_t ManyMouseGlue::GetIdx(const std::regex &) +{ + return UINT8_MAX; +} + +void ManyMouseGlue::Map(const uint8_t, const MouseInterfaceId) +{ +} + +#endif // C_MANYMOUSE diff --git a/src/hardware/mouse/mouse_manymouse.h b/src/hardware/mouse/mouse_manymouse.h new file mode 100644 index 000000000..94d3088b5 --- /dev/null +++ b/src/hardware/mouse/mouse_manymouse.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 DOSBOX_MOUSE_MANYMOUSE_H +#define DOSBOX_MOUSE_MANYMOUSE_H + +#include "mouse.h" +#include "mouse_interfaces.h" + +#include <string> +#include <vector> + +#if C_MANYMOUSE +#include "../../libs/manymouse/manymouse.h" +#endif // C_MANYMOUSE + +class MousePhysical +{ +public: + MousePhysical(const std::string &name); + + bool IsMapped() const; + bool IsDisconnected() const; + + MouseInterfaceId GetMappedInterfaceId() const; + const std::string &GetName() const; + +private: + + friend class ManyMouseGlue; + + const std::string name = ""; + MouseInterfaceId mapped_id = MouseInterfaceId::None; + bool disconnected = false; +}; + +class ManyMouseGlue final { +public: + + static ManyMouseGlue &GetInstance(); + + void RescanIfSafe(); + void ShutdownIfSafe(); + void StartConfigAPI(); + void StopConfigAPI(); + + bool ProbeForMapping(uint8_t &device_id); + uint8_t GetIdx(const std::regex ®ex); + + void Map(const uint8_t physical_idx, + const MouseInterfaceId interface_id); + +private: + + friend class MouseInterfaceInfoEntry; + friend class MousePhysicalInfoEntry; + + ManyMouseGlue() = default; + ~ManyMouseGlue() = delete; + ManyMouseGlue(const ManyMouseGlue &) = delete; + ManyMouseGlue &operator=(const ManyMouseGlue &) = delete; + + void Tick(); + friend void manymouse_tick(uint32_t); + +#if C_MANYMOUSE + + void InitIfNeeded(); + void ShutdownForced(); + void ClearPhysicalMice(); + void Rescan(); + + void UnMap(const MouseInterfaceId interface_id); + void MapFinalize(); + + void HandleEvent(const ManyMouseEvent &event, + const bool critical_only = false); + + bool initialized = false; + bool malfunction = false; // once set to false, will stay false forever + bool mapping_in_effect = false; + bool rescan_blocked_config = false; // true = rescan blocked due to config API usage + uint32_t config_api_counter = 0; + + int num_mice = 0; + + std::string driver_name = ""; + + std::vector<int> rel_x = {}; // not yet reported accumulated movements + std::vector<int> rel_y = {}; + + static constexpr uint8_t max_buttons = 3; + static constexpr uint8_t max_mice = UINT8_MAX; + static constexpr double tick_interval = 5.0; + +#endif // C_MANYMOUSE + + std::vector<MousePhysical> physical_devices = {}; +}; + +#endif // DOSBOX_MOUSE_MANYMOUSE_H diff --git a/src/hardware/mouse/mouse_queue.cpp b/src/hardware/mouse/mouse_queue.cpp new file mode 100644 index 000000000..e793b41e0 --- /dev/null +++ b/src/hardware/mouse/mouse_queue.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 "mouse_queue.h" +#include "mouse_config.h" +#include "mouse_interfaces.h" + +#include <array> + +#include "checks.h" +#include "pic.h" + +CHECK_NARROWING(); + + +// *************************************************************************** +// Debug code, normally not enabled +// *************************************************************************** + +// #define DEBUG_QUEUE_ENABLE + +#ifndef DEBUG_QUEUE_ENABLE +# define DEBUG_QUEUE(...) ; +#else +// TODO: after migrating to C++20, allow to skip the 2nd argument by using +// '__VA_OPT__(,) __VA_ARGS__' instead of ', __VA_ARGS__' +# define DEBUG_QUEUE(fmt, ...) \ + LOG_INFO("(queue) %04d: " fmt, DEBUG_GetDiffTicks(), __VA_ARGS__); + +static uint32_t DEBUG_GetDiffTicks() +{ + static uint32_t previous_ticks = 0; + uint32_t diff_ticks = 0; + + if (previous_ticks) + diff_ticks = PIC_Ticks - previous_ticks; + + previous_ticks = PIC_Ticks; + return diff_ticks; +} + +#endif + +// *************************************************************************** +// Mouse event queue implementation +// *************************************************************************** + +void mouse_queue_tick(uint32_t) +{ + MouseQueue::GetInstance().Tick(); +} + +MouseQueue &MouseQueue::GetInstance() +{ + static MouseQueue *instance = nullptr; + if (!instance) + instance = new MouseQueue(); + return *instance; +} + +void MouseQueue::SetRateDOS(const uint16_t rate_hz) +{ + // Convert rate in Hz to delay in milliseconds + start_delay.dos_ms = MOUSE_GetDelayFromRateHz(rate_hz); +} + +void MouseQueue::SetRatePS2(const uint16_t rate_hz) +{ + // Convert rate in Hz to delay in milliseconds + start_delay.ps2_ms = MOUSE_GetDelayFromRateHz(rate_hz); +} + +void MouseQueue::AddEvent(MouseEvent &ev) +{ + DEBUG_QUEUE("AddEvent: %s %s", + ev.request_dos ? "DOS" : "---" + ev.request_ps2 ? "PS2" : "---"); + + // Prevent unnecessary processing + AggregateDosEvents(ev); + if (!ev.request_dos && !ev.request_ps2) + return; // event not relevant any more + + bool restart_timer = false; + if (ev.request_dos) { + if (!HasEventDos() && timer_in_progress && !delay.dos_ms) { + DEBUG_QUEUE("AddEvent: restart timer for %s", "DOS"); + // We do not want the timer to start only then PS/2 + // event gets processed - for minimum latency it is + // better to restart the timer + restart_timer = true; + } + + if (ev.dos_moved) { + // Mouse has moved + pending_dos_moved = true; + } else if (ev.dos_wheel) { + // Wheel has moved + pending_dos_wheel = true; + } else { + // Button press/release + pending_dos_button = true; + pending_dos_buttons_state = ev.dos_buttons; + } + } + + if (ev.request_ps2) { + if (!HasEventPS2() && timer_in_progress && !delay.ps2_ms) { + DEBUG_QUEUE("AddEvent: restart timer for %s", "PS2"); + // We do not want the timer to start only when other event + // gets processed - for minimum latency it is better to + // restart the timer + restart_timer = true; + } + + // Events for PS/2 interface (or virtual machine compatible + // drivers) do not carry any information - they are only + // notifications that new data is available + pending_ps2 |= ev.request_ps2; + } + + if (restart_timer) { + timer_in_progress = false; + PIC_RemoveEvents(mouse_queue_tick); + UpdateDelayCounters(); + StartTimerIfNeeded(); + } else if (!timer_in_progress) { + DEBUG_QUEUE("ActivateIRQ, in %s", __FUNCTION__); + // If no timer in progress, handle the event now + PIC_ActivateIRQ(12); + } +} + +void MouseQueue::AggregateDosEvents(MouseEvent &ev) +{ + // We do not need duplicate move / wheel events + if (pending_dos_moved) + ev.dos_moved = false; + if (pending_dos_wheel) + ev.dos_wheel = false; + + // Same for mouse buttons - but in such case always update button data + if (pending_dos_button && ev.dos_button) { + ev.dos_button = false; + pending_dos_buttons_state = ev.dos_buttons; + } + + // Check if we still need this event + if (!ev.dos_moved && !ev.dos_wheel && !ev.dos_button) + ev.request_dos = false; +} + +void MouseQueue::FetchEvent(MouseEvent &ev) +{ + // First try (prioritized) DOS events + if (HasReadyEventDos()) { + DEBUG_QUEUE("FetchEvent %s", "DOS"); + // Mark event as DOS one + ev.request_dos = true; + ev.dos_moved = pending_dos_moved; + ev.dos_button = pending_dos_button; + ev.dos_wheel = pending_dos_wheel; + ev.dos_buttons = pending_dos_buttons_state; + // Set delay before next DOS events + delay.dos_ms = start_delay.dos_ms; + // Clear event information + pending_dos_moved = false; + pending_dos_button = false; + pending_dos_wheel = false; + return; + } + + // Now try PS/2 event + if (HasReadyEventPS2()) { + DEBUG_QUEUE("FetchEvent %s", "PS2"); + // Set delay before next PS/2 events + delay.ps2_ms = start_delay.ps2_ms; + // PS/2 events are really dummy - merely a notification + // that something has happened and driver has to react + ev.request_ps2 = true; + pending_ps2 = false; + return; + } + + // Nothing to provide to interrupt handler, + // event will stay empty +} + +void MouseQueue::ClearEventsDOS() +{ + // Clear DOS relevant part of the queue + pending_dos_moved = false; + pending_dos_button = false; + pending_dos_wheel = false; + delay.dos_ms = 0; + + // If timer is not needed, stop it + if (!HasEventAny()) { + timer_in_progress = false; + PIC_RemoveEvents(mouse_queue_tick); + } +} + +void MouseQueue::StartTimerIfNeeded() +{ + // Do nothing if timer is already in progress + if (timer_in_progress) + return; + + bool timer_needed = false; + uint8_t delay_ms = UINT8_MAX; // dummy delay, will never be used + + if (HasEventPS2() || delay.ps2_ms) { + timer_needed = true; + delay_ms = std::min(delay_ms, delay.ps2_ms); + } + if (HasEventDos() || delay.dos_ms) { + timer_needed = true; + delay_ms = std::min(delay_ms, delay.dos_ms); + } + + // If queue is empty and all expired, we need no timer + if (!timer_needed) + return; + + // Enforce some non-zero delay between events; needed + // for example if DOS interrupt handler is busy + delay_ms = std::max(delay_ms, static_cast<uint8_t>(1)); + + // Start the timer + DEBUG_QUEUE("StartTimer, %d", delay_ms); + pic_ticks_start = PIC_Ticks; + timer_in_progress = true; + PIC_AddEvent(mouse_queue_tick, static_cast<double>(delay_ms)); +} + +void MouseQueue::UpdateDelayCounters() +{ + const uint32_t tmp = (PIC_Ticks > pic_ticks_start) ? (PIC_Ticks - pic_ticks_start) : 1; + uint8_t elapsed = static_cast<uint8_t>( + std::min(tmp, static_cast<uint32_t>(UINT8_MAX))); + if (!pic_ticks_start) + elapsed = 1; + + auto calc_new_delay = [](const uint8_t delay, const uint8_t elapsed) { + return static_cast<uint8_t>((delay > elapsed) ? (delay - elapsed) + : 0); + }; + + delay.dos_ms = calc_new_delay(delay.dos_ms, elapsed); + delay.ps2_ms = calc_new_delay(delay.ps2_ms, elapsed); + + pic_ticks_start = 0; +} + +void MouseQueue::Tick() +{ + DEBUG_QUEUE("%s", "Tick"); + + timer_in_progress = false; + UpdateDelayCounters(); + + // If we have anything to pass to guest side via INT74, activate + // interrupt; otherwise start the timer again + if (HasReadyEventDos() || HasReadyEventPS2()) { + DEBUG_QUEUE("ActivateIRQ, in %s", __FUNCTION__); + PIC_ActivateIRQ(12); + } else + StartTimerIfNeeded(); +} + +bool MouseQueue::HasEventDos() const +{ + return pending_dos_moved || pending_dos_button || pending_dos_wheel; +} + +bool MouseQueue::HasEventPS2() const +{ + return pending_ps2; +} + +bool MouseQueue::HasEventAny() const +{ + return HasEventDos() || HasEventPS2(); +} + +bool MouseQueue::HasReadyEventDos() const +{ + return HasEventDos() && !delay.dos_ms && + // do not launch DOS callback if it's busy + !mouse_shared.dos_cb_running; +} + +bool MouseQueue::HasReadyEventPS2() const +{ + return HasEventPS2() && !delay.ps2_ms; +} diff --git a/src/hardware/mouse/mouse_queue.h b/src/hardware/mouse/mouse_queue.h new file mode 100644 index 000000000..f2ffdefd4 --- /dev/null +++ b/src/hardware/mouse/mouse_queue.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The DOSBox Staging Team + * + * 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 DOSBOX_MOUSE_QUEUE_H +#define DOSBOX_MOUSE_QUEUE_H + +#include "mouse_common.h" + + +class MouseQueue final { +public: + + static MouseQueue &GetInstance(); + + void SetRateDOS(const uint16_t rate_hz); // for DOS mouse driver + void SetRatePS2(const uint16_t rate_hz); // for PS/2 AUX port mice + + void AddEvent(MouseEvent &ev); + void FetchEvent(MouseEvent &ev); + void ClearEventsDOS(); + void StartTimerIfNeeded(); + +private: + + MouseQueue() = default; + ~MouseQueue() = delete; + MouseQueue(const MouseQueue &) = delete; + MouseQueue &operator=(const MouseQueue &) = delete; + + void Tick(); + friend void mouse_queue_tick(uint32_t); + + void AggregateDosEvents(MouseEvent &ev); + void UpdateDelayCounters(); + uint8_t ClampStartDelay(float value_ms) const; + + struct { // intial value of delay counters, in milliseconds + uint8_t dos_ms = 5; + uint8_t ps2_ms = 5; + } start_delay = {}; + + // Time in milliseconds which has to elapse before event can take place + struct { + uint8_t dos_ms = 0; + uint8_t ps2_ms = 0; + } delay = {}; + + // Pending events, waiting to be passed to guest system + bool pending_dos_moved = false; + bool pending_dos_button = false; + bool pending_dos_wheel = false; + bool pending_ps2 = false; + + MouseButtons12S pending_dos_buttons_state = 0; + + bool timer_in_progress = false; + uint32_t pic_ticks_start = 0; // PIC_Ticks value when timer starts + + // Helpers to check if there are events in the queue + bool HasEventDos() const; + bool HasEventPS2() const; + bool HasEventAny() const; + + // Helpers to check if there are events ready to be handled + bool HasReadyEventDos() const; + bool HasReadyEventPS2() const; +}; + +#endif // DOSBOX_MOUSE_QUEUE_H diff --git a/src/ints/mouse_dos_driver.cpp b/src/hardware/mouse/mouseif_dos_driver.cpp index 25c1a0cb9..277d1d0c3 100644 --- a/src/ints/mouse_dos_driver.cpp +++ b/src/hardware/mouse/mouseif_dos_driver.cpp @@ -18,7 +18,8 @@ */ #include "mouse.h" -#include "mouse_core.h" +#include "mouse_config.h" +#include "mouse_interfaces.h" #include <algorithm> @@ -29,10 +30,11 @@ #include "checks.h" #include "cpu.h" #include "dos_inc.h" -#include "int10.h" #include "pic.h" #include "regs.h" +#include "../../ints/int10.h" + using namespace bit::literals; CHECK_NARROWING(); @@ -42,7 +44,7 @@ CHECK_NARROWING(); // Reference: // - Ralf Brown's Interrupt List -// - WHEELAPI.TXT from CuteMouse package +// - WHEELAPI.TXT, INT10.LST, and INT33.LST from CuteMouse driver // - https://www.stanislavs.org/helppc/int_33.html // - http://www2.ift.ulaval.ca/~marchand/ift17583/dosints.pdf @@ -54,18 +56,36 @@ static constexpr uint8_t num_buttons = 3; enum class MouseCursor : uint8_t { Software = 0, Hardware = 1, Text = 2 }; +// This enum has to be compatible with mask in DOS driver function 0x0c +enum class MouseEventId : uint8_t { + NotDosEvent = 0, + MouseHasMoved = 1 << 0, + PressedLeft = 1 << 1, + ReleasedLeft = 1 << 2, + PressedRight = 1 << 3, + ReleasedRight = 1 << 4, + PressedMiddle = 1 << 5, + ReleasedMiddle = 1 << 6, + WheelHasMoved = 1 << 0, +}; + // These values represent 'hardware' state, not driver state static MouseButtons12S buttons = 0; static float pos_x = 0.0f; static float pos_y = 0.0f; -static int8_t counter_w = 0; -static uint8_t rate_hz = 0; // TODO: add proper reaction for 0 (disable driver) +static int8_t counter_w = 0; // wheel counter +static bool is_mapped = false; // true = physical mouse is mapped to this interface +static bool raw_input = true; // true = no host mouse acceleration pre-applied -static struct { - // Data from mouse events which were already received, - // but not necessary visible to the application +static bool rate_is_set = false; // true = rate was set by DOS application +static uint16_t rate_hz = 0; +static uint16_t min_rate_hz = 0; + +// Data from mouse events which were already received, +// but not necessary visible to the application +static struct { // Mouse movement float x_rel = 0.0f; float y_rel = 0.0f; @@ -82,21 +102,25 @@ static struct { } } pending; +// Multiply by 6.0f to compensate for 'MOUSE_GetBallisticsCoeff', which uses +// 6 as intersection point (just like 2:1 scaling model from PS/2 specification) +static MouseSpeedCalculator speed_mickeys(mouse_predefined.acceleration_dos * 6.0f); + static struct { // DOS driver state // Structure containing (only!) data which should be // saved/restored during task switching // DANGER, WILL ROBINSON! - /// + // // This whole structure can be read or written from the guest side // via virtual DOS driver, functions 0x15 / 0x16 / 0x17. // Do not put here any array indices, pointers, or anything that // can crash the emulator if filled-in incorrectly, or that can // be used by malicious code to escape from emulation! - bool enabled = false; // TODO: make use of this - bool cute_mouse = false; + bool enabled = false; // TODO: make use of this + bool wheel_api = false; // CuteMouse compatible wheel extension uint16_t times_pressed[num_buttons] = {0}; uint16_t times_released[num_buttons] = {0}; @@ -107,13 +131,16 @@ static struct { // DOS driver state uint16_t last_wheel_moved_x = 0; uint16_t last_wheel_moved_y = 0; - float mickey_x = 0.0f; - float mickey_y = 0.0f; + int16_t mickey_counter_x = 0; + int16_t mickey_counter_y = 0; + + float mickey_delta_x = 0.0f; + float mickey_delta_y = 0.0f; float mickeys_per_pixel_x = 0.0f; float mickeys_per_pixel_y = 0.0f; - float pixels_per_mickey_x = 0.0f; - float pixels_per_mickey_y = 0.0f; + + uint16_t double_speed_threshold = 0; // in mickeys/s uint16_t granularity_x = 0; // mask uint16_t granularity_y = 0; @@ -122,14 +149,17 @@ static struct { // DOS driver state int16_t update_region_y[2] = {0}; uint16_t language = 0; // language for driver messages, unused - uint8_t mode = 0; + uint8_t mode = 0; // sensitivity - uint16_t senv_x_val = 0; - uint16_t senv_y_val = 0; - uint16_t double_speed_threshold = 0; // in mickeys/s TODO: should affect movement - float senv_x = 0.0f; - float senv_y = 0.0f; + uint8_t sensitivity_x = 0; + uint8_t sensitivity_y = 0; + // TODO: find out what it is for (acceleration?), for now + // just set it to default value on startup + uint8_t unknown_01 = 50; + + float sensitivity_coeff_x = 0; + float sensitivity_coeff_y = 0; // mouse position allowed range int16_t minpos_x = 0; @@ -196,11 +226,6 @@ static uint16_t signed_to_reg16(const int16_t x) return static_cast<uint16_t>(0x10000 + x); } -static uint16_t signed_to_reg16(const float x) -{ - return signed_to_reg16(static_cast<int16_t>(x)); -} - static int16_t reg_to_signed16(const uint16_t x) { if (bit::is(x, b15)) @@ -210,15 +235,6 @@ static int16_t reg_to_signed16(const uint16_t x) return static_cast<int16_t>(x); } -int8_t clamp_to_int8(const int32_t val) -{ - const auto tmp = std::clamp(val, - static_cast<int32_t>(INT8_MIN), - static_cast<int32_t>(INT8_MAX)); - - return static_cast<int8_t>(tmp); -} - static uint16_t get_pos_x() { return static_cast<uint16_t>(std::lround(pos_x)) & state.granularity_x; @@ -561,7 +577,7 @@ static void update_driver_active() static uint8_t get_reset_wheel_8bit() { - if (!state.cute_mouse) // wheel requires CuteMouse extensions + if (!state.wheel_api) return 0; const auto tmp = counter_w; @@ -573,7 +589,7 @@ static uint8_t get_reset_wheel_8bit() static uint16_t get_reset_wheel_16bit() { - if (!state.cute_mouse) // wheel requires CuteMouse extensions + if (!state.wheel_api) return 0; const int16_t tmp = counter_w; @@ -588,13 +604,10 @@ static void set_mickey_pixel_rate(const int16_t ratio_x, const int16_t ratio_y) // the values should be non-negative (highest bit not set) if ((ratio_x > 0) && (ratio_y > 0)) { - constexpr auto x_mickey = 8.0f; - constexpr auto y_mickey = 8.0f; - - state.mickeys_per_pixel_x = static_cast<float>(ratio_x) / x_mickey; - state.mickeys_per_pixel_y = static_cast<float>(ratio_y) / y_mickey; - state.pixels_per_mickey_x = x_mickey / static_cast<float>(ratio_x); - state.pixels_per_mickey_y = y_mickey / static_cast<float>(ratio_y); + // ratio = number of mickeys per 8 pixels + constexpr auto pixels = 8.0f; + state.mickeys_per_pixel_x = static_cast<float>(ratio_x) / pixels; + state.mickeys_per_pixel_y = static_cast<float>(ratio_y) / pixels; } } @@ -606,40 +619,78 @@ static void set_double_speed_threshold(const uint16_t threshold) state.double_speed_threshold = 64; // default value } -static void set_sensitivity(const uint16_t px, const uint16_t py, - const uint16_t double_speed_threshold) +static void set_sensitivity(const uint16_t sensitivity_x, + const uint16_t sensitivity_y, + const uint16_t unknown) { - uint16_t tmp_x = std::min(static_cast<uint16_t>(100), px); - uint16_t tmp_y = std::min(static_cast<uint16_t>(100), py); - - // save values - state.senv_x_val = tmp_x; - state.senv_y_val = tmp_y; - state.double_speed_threshold = std::min(static_cast<uint16_t>(100), - double_speed_threshold); - if ((tmp_x != 0) && (tmp_y != 0)) { - --tmp_x; // Inspired by CuteMouse - --tmp_y; // Although their cursor update routine is far more - // complex then ours - state.senv_x = (static_cast<float>(tmp_x) * tmp_x) / 3600.0f + 1.0f / 3.0f; - state.senv_y = (static_cast<float>(tmp_y) * tmp_y) / 3600.0f + 1.0f / 3.0f; - } + const auto tmp_x = std::min(static_cast<uint16_t>(100), sensitivity_x); + const auto tmp_y = std::min(static_cast<uint16_t>(100), sensitivity_y); + const auto tmp_u = std::min(static_cast<uint16_t>(100), unknown); + + state.sensitivity_x = static_cast<uint8_t>(tmp_x); + state.sensitivity_y = static_cast<uint8_t>(tmp_y); + state.unknown_01 = static_cast<uint8_t>(tmp_u); + + // It is unclear how the original mouse driver handles sensitivity, + // but one can observe that setting value 0 stops the mouse movement + // completely, 50 is the default, and 100 seems to more or less + // dobule it. Linear sensitivity should be good enough. + + state.sensitivity_coeff_x = state.sensitivity_x / 50.0f; + state.sensitivity_coeff_y = state.sensitivity_y / 50.0f; +} + +static void notify_interface_rate() +{ + // Real mouse drivers set the PS/2 mouse sampling rate + // to the following rates: + // - A4 Pointing Device 8.04A 100 Hz + // - CuteMouse 2.1b4 100 Hz + // - Genius Dynamic Mouse 9.20 60 Hz + // - Microsoft Mouse 8.20 60 Hz + // - Mouse Systems 8.00 100 Hz + // and the most common serial mice were 1200 bauds, which gives + // approx. 40 Hz sampling rate limit due to COM port bandwidth. + + // Original DOSBox uses 200 Hz for callbacks, but the internal + // states (buttons, mickey counters) are updated in realtime. + // This is too much (at least Ultima Underworld I and II do not + // like this). + + // Set default value to 200 Hz (which is the maximum setting for + // PS/2 mice - and hopefully this is safe (if it's not, user can + // always adjust it in configuration file or with MOUSECTL.COM). + + constexpr uint16_t rate_default_hz = 200; + + if (rate_is_set) + // Rate was set by guest application - use this value. The minimum + // will be enforced by MouseInterface nevertheless + MouseInterface::GetDOS()->NotifyInterfaceRate(rate_hz); + else if (min_rate_hz) + // If user set the minimum mouse rate - follow it + MouseInterface::GetDOS()->NotifyInterfaceRate(min_rate_hz); + else + // No user setting in effect - use default value + MouseInterface::GetDOS()->NotifyInterfaceRate(rate_default_hz); } static void set_interrupt_rate(const uint16_t rate_id) { + uint16_t val_hz; + switch (rate_id) { - case 0: rate_hz = 0; break; // no events, TODO: this should be simulated - case 1: rate_hz = 30; break; - case 2: rate_hz = 50; break; - case 3: rate_hz = 100; break; - default: rate_hz = 200; break; // above 4 is not suported, set max + case 0: val_hz = 0; break; // no events, TODO: this should be simulated + case 1: val_hz = 30; break; + case 2: val_hz = 50; break; + case 3: val_hz = 100; break; + default: val_hz = 200; break; // above 4 is not suported, set max } - // Update event queue settings - if (rate_hz) { - // Update event queue settings - MOUSE_NotifyRateDOS(rate_hz); + if (val_hz) { + rate_is_set = true; + rate_hz = val_hz; + notify_interface_rate(); } } @@ -648,12 +699,25 @@ static void reset_hardware() // Resetting the wheel API status in reset() might seem to be a more // logical approach, but this is clearly not what CuteMouse does; // if this is done in reset(), the DN2 is unable to use mouse wheel - state.cute_mouse = false; + state.wheel_api = false; counter_w = 0; - set_interrupt_rate(4); PIC_SetIRQMask(12, false); // lower IRQ line - MOUSE_NotifyRateDOS(60); // same rate Microsoft Mouse 8.20 sets + + // Reset mouse refresh rate + rate_is_set = false; + notify_interface_rate(); +} + +void MOUSEDOS_NotifyMinRate(const uint16_t value_hz) +{ + min_rate_hz = value_hz; + + // If rate was set by a DOS application, don't change it + if (rate_is_set) + return; + + notify_interface_rate(); } void MOUSEDOS_BeforeNewVideoMode() @@ -751,8 +815,10 @@ static void reset() pos_x = static_cast<float>((state.maxpos_x + 1) / 2); pos_y = static_cast<float>((state.maxpos_y + 1) / 2); - state.mickey_x = 0; - state.mickey_y = 0; + state.mickey_counter_x = 0; + state.mickey_counter_y = 0; + state.mickey_delta_x = 0.0f; + state.mickey_delta_y = 0.0f; state.last_wheel_moved_x = 0; state.last_wheel_moved_y = 0; @@ -782,65 +848,57 @@ static void limit_coordinates() pos = std::clamp(pos, min, max); }; - // TODO: If the pointer go out of limited coordinates, - // trigger showing mouse_suggest_show - limit(pos_x, state.minpos_x, state.maxpos_x); limit(pos_y, state.minpos_y, state.maxpos_y); } -static void update_mickeys_on_move(float &dx, float &dy, const float x_rel, - const float y_rel) +static void update_mickeys_on_move(const float x_rel, const float y_rel) { - auto calculate_d = [](const float rel, - const float pixel_per_mickey, - const float senv) { - float d = rel * pixel_per_mickey; - if ((fabs(rel) > 1.0f) || (senv < 1.0f)) - d *= senv; - return d; - }; + auto update = [](int16_t &counter, float &delta, const float rel) { + delta += rel; - auto update_mickey = - [](float &mickey, const float d, const float mickeys_per_pixel) { - mickey += d * mickeys_per_pixel; - if (mickey >= 32768.0f) - mickey -= 65536.0f; - else if (mickey <= -32769.0f) - mickey += 65536.0f; - }; + // Check if movement is significant enough + const auto d = static_cast<int16_t>(std::lround(delta)); + if (d == 0) + return; - // Calculate cursor displacement - dx = calculate_d(x_rel, state.pixels_per_mickey_x, state.senv_x); - dy = calculate_d(y_rel, state.pixels_per_mickey_y, state.senv_y); + // Consume part of delta to increase/decrease the counter + delta -= d; + int32_t counter_big = counter + d; - // Update mickey counters - update_mickey(state.mickey_x, dx, state.mickeys_per_pixel_x); - update_mickey(state.mickey_y, dy, state.mickeys_per_pixel_y); + // Handle counter wrap around int16_t limits + if (counter_big > INT16_MAX) + counter_big -= UINT16_MAX + 1; + else if (counter_big < INT16_MIN) + counter_big += UINT16_MAX + 1; + + counter = static_cast<int16_t>(counter_big); + }; + + const float x_mov = x_rel * state.mickeys_per_pixel_x; + const float y_mov = y_rel * state.mickeys_per_pixel_y; + + // Update mickey counters and mickey speed measurement + update(state.mickey_counter_x, state.mickey_delta_x, x_mov); + update(state.mickey_counter_y, state.mickey_delta_y, y_mov); + speed_mickeys.Update(std::sqrt(x_mov * x_mov + y_mov * y_mov)); } static void move_cursor_captured(const float x_rel, const float y_rel) { // Update mickey counters - float dx = 0.0f; - float dy = 0.0f; - update_mickeys_on_move(dx, dy, x_rel, y_rel); + update_mickeys_on_move(x_rel, y_rel); // Apply mouse movement according to our acceleration model - pos_x += dx; - pos_y += dy; + pos_x += x_rel; + pos_y += y_rel; } static void move_cursor_seamless(const float x_rel, const float y_rel, const uint16_t x_abs, const uint16_t y_abs) { - // In automatic seamless mode do not update mickeys without - // captured mouse, as this makes games like DOOM behaving strangely - if (!mouse_video.autoseamless) { - float dx = 0.0f; - float dy = 0.0f; - update_mickeys_on_move(dx, dy, x_rel, y_rel); - } + // Update mickey counters + update_mickeys_on_move(x_rel, y_rel); auto calculate = [](const uint16_t absolute, const uint16_t res, @@ -879,26 +937,43 @@ static void move_cursor_seamless(const float x_rel, const float y_rel, } } -uint8_t MOUSEDOS_UpdateMoved() +static bool is_captured() +{ + // If DOS driver uses a mapped physical mouse, always consider it captured, + // as we have no absolute mouse position from the host OS + + return mouse_is_captured || is_mapped; +} + +static uint8_t move_cursor() { const auto old_pos_x = get_pos_x(); const auto old_pos_y = get_pos_y(); - const auto old_mickey_x = static_cast<int16_t>(state.mickey_x); - const auto old_mickey_y = static_cast<int16_t>(state.mickey_y); + const auto old_mickey_x = static_cast<int16_t>(state.mickey_counter_x); + const auto old_mickey_y = static_cast<int16_t>(state.mickey_counter_y); + + if (is_captured()) { + + // For raw mouse input use our built-in pointer acceleration model + const float acceleration_coeff = raw_input ? + MOUSE_GetBallisticsCoeff(speed_mickeys.Get() / state.double_speed_threshold) * 2.0f: + 2.0f; + + const float tmp_x = pending.x_rel * acceleration_coeff * state.sensitivity_coeff_x; + const float tmp_y = pending.y_rel * acceleration_coeff * state.sensitivity_coeff_y; + + move_cursor_captured(MOUSE_ClampRelativeMovement(tmp_x), + MOUSE_ClampRelativeMovement(tmp_y)); - const auto x_mov = MOUSE_ClampRelativeMovement(pending.x_rel * sensitivity_dos); - const auto y_mov = MOUSE_ClampRelativeMovement(pending.y_rel * sensitivity_dos); + } else + move_cursor_seamless(pending.x_rel, pending.y_rel, + pending.x_abs, pending.y_abs); // Pending relative movement is now consummed pending.x_rel = 0.0f; pending.y_rel = 0.0f; - if (mouse_is_captured) - move_cursor_captured(x_mov, y_mov); - else - move_cursor_seamless(x_mov, y_mov, pending.x_abs, pending.y_abs); - // Make sure cursor stays in the range defined by application limit_coordinates(); @@ -906,18 +981,8 @@ uint8_t MOUSEDOS_UpdateMoved() // which won't change guest side mouse state) const bool abs_changed = (old_pos_x != get_pos_x()) || (old_pos_y != get_pos_y()); - const bool rel_changed = (old_mickey_x != - static_cast<int16_t>(state.mickey_x)) || - (old_mickey_y != - static_cast<int16_t>(state.mickey_y)); - - // NOTE: It might be tempting to optimize the flow here, by skipping - // the whole event-queue-callback flow if there is no callback - // registered, no graphic cursor to draw, etc. Don't do this - there - // is at least one game (Master of Orion II), which does INT 0x33 - // calls with 0x0F parameter (changing the callback settings) - // constantly (don't ask me, why) - doing too much optimization - // can cause the game to skip mouse events. + const bool rel_changed = (old_mickey_x != state.mickey_counter_x) || + (old_mickey_y != state.mickey_counter_y); if (abs_changed || rel_changed) return static_cast<uint8_t>(MouseEventId::MouseHasMoved); @@ -925,6 +990,14 @@ uint8_t MOUSEDOS_UpdateMoved() return 0; } +uint8_t MOUSEDOS_UpdateMoved() +{ + if (mouse_config.mouse_dos_immediate) + return static_cast<uint8_t>(MouseEventId::MouseHasMoved); + else + return move_cursor(); +} + uint8_t MOUSEDOS_UpdateButtons(const MouseButtons12S new_buttons_12S) { if (buttons.data == new_buttons_12S.data) @@ -946,24 +1019,23 @@ uint8_t MOUSEDOS_UpdateButtons(const MouseButtons12S new_buttons_12S) if (new_buttons_12S.left && !buttons.left) { mark_pressed(0); mask |= static_cast<uint8_t>(MouseEventId::PressedLeft); - } - else if (!new_buttons_12S.left && buttons.left) { + } else if (!new_buttons_12S.left && buttons.left) { mark_released(0); mask |= static_cast<uint8_t>(MouseEventId::ReleasedLeft); } + if (new_buttons_12S.right && !buttons.right) { mark_pressed(1); mask |= static_cast<uint8_t>(MouseEventId::PressedRight); - } - else if (!new_buttons_12S.right && buttons.right) { + } else if (!new_buttons_12S.right && buttons.right) { mark_released(1); mask |= static_cast<uint8_t>(MouseEventId::ReleasedRight); } + if (new_buttons_12S.middle && !buttons.middle) { mark_pressed(2); mask |= static_cast<uint8_t>(MouseEventId::PressedMiddle); - } - else if (!new_buttons_12S.middle && buttons.middle) { + } else if (!new_buttons_12S.middle && buttons.middle) { mark_released(2); mask |= static_cast<uint8_t>(MouseEventId::ReleasedMiddle); } @@ -972,9 +1044,9 @@ uint8_t MOUSEDOS_UpdateButtons(const MouseButtons12S new_buttons_12S) return mask; } -uint8_t MOUSEDOS_UpdateWheel() +static uint8_t move_wheel() { - counter_w = clamp_to_int8(static_cast<int32_t>(counter_w + pending.w_rel)); + counter_w = MOUSE_ClampToInt8(static_cast<int32_t>(counter_w + pending.w_rel)); // Pending wheel scroll is now consummed pending.w_rel = 0; @@ -988,6 +1060,14 @@ uint8_t MOUSEDOS_UpdateWheel() return 0; } +uint8_t MOUSEDOS_UpdateWheel() +{ + if (mouse_config.mouse_dos_immediate) + return static_cast<uint8_t>(MouseEventId::WheelHasMoved); + else + return move_wheel(); +} + bool MOUSEDOS_NotifyMoved(const float x_rel, const float y_rel, const uint16_t x_abs, @@ -995,7 +1075,7 @@ bool MOUSEDOS_NotifyMoved(const float x_rel, { // Check if an event is needed bool event_needed = false; - if (mouse_is_captured) { + if (is_captured()) { // Uses relative mouse movements - processing is too complicated // to easily predict whether the event can be safely omitted event_needed = true; @@ -1022,26 +1102,34 @@ bool MOUSEDOS_NotifyMoved(const float x_rel, // calls with 0x0f parameter (changing the callback settings) // constantly (don't ask me, why) - doing too much optimization // can cause the game to skip mouse events. - // - // Also, do not update mouse state here - Ultima Underworld I and II - // do not like mouse states updated in real time, it ends up with - // mouse movements being ignored by the game randomly. - return event_needed; + if (!event_needed) + return 0; + + if (mouse_config.mouse_dos_immediate) + return (move_cursor() != 0); + else + return true; } bool MOUSEDOS_NotifyWheel(const int16_t w_rel) { - if (!state.cute_mouse) - return false; + if (!state.wheel_api) + return 0; // Although in some places it is possible for the guest code to get // wheel counter in 16-bit format, scrolling hundreds of lines in one // go would be insane - thus, limit the wheel counter to 8 bits and // reuse the code written for other mouse modules - pending.w_rel = clamp_to_int8(pending.w_rel + w_rel); + pending.w_rel = MOUSE_ClampToInt8(pending.w_rel + w_rel); - return (pending.w_rel != 0); + if (pending.w_rel == 0) + return 0; + + if (mouse_config.mouse_dos_immediate) + return (move_wheel() != 0); + else + return true; } static Bitu int33_handler() @@ -1068,7 +1156,7 @@ static Bitu int33_handler() restore_cursor_background_text(); ++state.hidden; break; - case 0x03: // MS MOUSE v1.0+ / CuteMouse - get position and button status + case 0x03: // MS MOUSE v1.0+ / WheelAPI v1.0+ - get position and button status reg_bl = buttons.data; reg_bh = get_reset_wheel_8bit(); // CuteMouse clears wheel counter too reg_cx = get_pos_x(); @@ -1087,10 +1175,10 @@ static Bitu int33_handler() MOUSEDOS_DrawCursor(); break; } - case 0x05: // MS MOUSE v1.0+ / CuteMouse - get button press / wheel data + case 0x05: // MS MOUSE v1.0+ / WheelAPI v1.0+ - get button press / wheel data { const uint16_t idx = reg_bx; // button index - if (idx == 0xffff && state.cute_mouse) { + if (idx == 0xffff && state.wheel_api) { // 'magic' index for checking wheel instead of button reg_bx = get_reset_wheel_16bit(); reg_cx = state.last_wheel_moved_x; @@ -1110,11 +1198,11 @@ static Bitu int33_handler() } break; } - case 0x06: // MS MOUSE v1.0+ / CuteMouse - get button release data / + case 0x06: // MS MOUSE v1.0+ / WheelAPI v1.0+ - get button release data / // mouse wheel data { const uint16_t idx = reg_bx; // button index - if (idx == 0xffff && state.cute_mouse) { + if (idx == 0xffff && state.wheel_api) { // 'magic' index for checking wheel instead of button reg_bx = get_reset_wheel_16bit(); reg_cx = state.last_wheel_moved_x; @@ -1209,13 +1297,13 @@ static Bitu int33_handler() reg_bx = state.text_xor_mask; [[fallthrough]]; case 0x0b: // MS MOUSE v1.0+ - read motion data - reg_cx = signed_to_reg16(state.mickey_x); - reg_dx = signed_to_reg16(state.mickey_y); - state.mickey_x = 0; - state.mickey_y = 0; + reg_cx = signed_to_reg16(state.mickey_counter_x); + reg_dx = signed_to_reg16(state.mickey_counter_y); + state.mickey_counter_x = 0; + state.mickey_counter_y = 0; break; case 0x0c: // MS MOUSE v1.0+ - define user callback parameters - state.user_callback_mask = reg_cx & 0xff; + state.user_callback_mask = reg_cx; state.user_callback_segment = SegValue(es); state.user_callback_offset = reg_dx; update_driver_active(); @@ -1237,17 +1325,17 @@ static Bitu int33_handler() state.update_region_y[1] = reg_to_signed16(reg_di); MOUSEDOS_DrawCursor(); break; - case 0x11: // CuteMouse - get mouse capabilities - reg_ax = 0x574d; // Identifier for detection purposes - reg_bx = 0; // Reserved capabilities flags - reg_cx = 1; // Wheel is supported - counter_w = 0; - state.cute_mouse = true; // This call enables CuteMouse extensions + case 0x11: // WheelAPI v1.0+ - get mouse capabilities + reg_ax = 0x574d; // Identifier for detection purposes + reg_bx = 0; // Reserved capabilities flags + reg_cx = 1; // Wheel is supported + state.wheel_api = true; // This call enables WheelAPI extensions + counter_w = 0; // Previous implementation provided Genius Mouse 9.06 function // to get number of buttons // (https://sourceforge.net/p/dosbox/patches/32/), it was // returning 0xffff in reg_ax and number of buttons in reg_bx; I - // suppose the CuteMouse extensions are more useful + // suppose the WheelAPI extensions are more useful break; case 0x12: // MS MOUSE - set large graphics cursor block LOG(LOG_MOUSE, LOG_ERROR)("Large graphics cursor block not implemented"); @@ -1281,21 +1369,29 @@ static Bitu int33_handler() case 0x17: // MS MOUSE v6.0+ - load driver state LOG(LOG_MOUSE, LOG_WARN)("Loading driver state..."); MEM_BlockRead(SegPhys(es) + reg_dx, &state, sizeof(state)); + pending.Reset(); update_driver_active(); - // TODO: we should probably fake an event for mouse movement, - // redraw cursor, etc. + set_sensitivity(state.sensitivity_x, + state.sensitivity_y, + state.unknown_01); + // TODO: we should probably also fake an event for mouse + // movement, redraw cursor, etc. break; case 0x18: // MS MOUSE v6.0+ - set alternate mouse user handler case 0x19: // MS MOUSE v6.0+ - set alternate mouse user handler LOG(LOG_MOUSE, LOG_ERROR)("Alternate mouse user handler not implemented"); break; case 0x1a: // MS MOUSE v6.0+ - set mouse sensitivity + // NOTE: Ralf Brown Interrupt List (and some other sources) claim, + // that this should duplicate functions 0x0f and 0x13 - this is + // not true at least for Mouse Systems driver v8.00 and + // IBM/Microsoft driver v8.20 set_sensitivity(reg_bx, reg_cx, reg_dx); break; case 0x1b: // MS MOUSE v6.0+ - get mouse sensitivity - reg_bx = state.senv_x_val; - reg_cx = state.senv_y_val; - reg_dx = state.double_speed_threshold; + reg_bx = state.sensitivity_x; + reg_cx = state.sensitivity_y; + reg_dx = state.unknown_01; break; case 0x1c: // MS MOUSE v6.0+ - set interrupt rate set_interrupt_rate(reg_bx); @@ -1431,7 +1527,7 @@ static Bitu int33_handler() return CBRET_NONE; } -static uintptr_t mouse_bd_handler() +static Bitu mouse_bd_handler() { // the stack contains offsets to register values uint16_t raxpt = real_readw(SegValue(ss), static_cast<uint16_t>(reg_sp + 0x0a)); @@ -1489,7 +1585,7 @@ static uintptr_t mouse_bd_handler() return CBRET_NONE; } -static uintptr_t user_callback_handler() +static Bitu user_callback_handler() { mouse_shared.dos_cb_running = false; return CBRET_NONE; @@ -1500,7 +1596,8 @@ bool MOUSEDOS_HasCallback(const uint8_t mask) return state.user_callback_mask & mask; } -uintptr_t MOUSEDOS_DoCallback(const uint8_t mask, const MouseButtons12S buttons_12S) +Bitu MOUSEDOS_DoCallback(const uint8_t mask, + const MouseButtons12S buttons_12S) { mouse_shared.dos_cb_running = true; const bool mouse_moved = mask & static_cast<uint8_t>(MouseEventId::MouseHasMoved); @@ -1513,15 +1610,15 @@ uintptr_t MOUSEDOS_DoCallback(const uint8_t mask, const MouseButtons12S buttons_ // - https://github.com/dosemu2/dosemu2/issues/1552#issuecomment-1100777880 // - https://github.com/dosemu2/dosemu2/commit/cd9d2dbc8e3d58dc7cbc92f172c0d447881526be // - https://github.com/joncampbell123/dosbox-x/commit/aec29ce28eb4b520f21ead5b2debf370183b9f28 - reg_ah = (!mouse_is_captured && mouse_moved) ? 1 : 0; + reg_ah = (!is_captured() && mouse_moved) ? 1 : 0; reg_al = mask; reg_bl = buttons_12S.data; reg_bh = wheel_moved ? get_reset_wheel_8bit() : 0; reg_cx = get_pos_x(); reg_dx = get_pos_y(); - reg_si = signed_to_reg16(state.mickey_x); - reg_di = signed_to_reg16(state.mickey_y); + reg_si = signed_to_reg16(state.mickey_counter_x); + reg_di = signed_to_reg16(state.mickey_counter_y); CPU_Push16(RealSeg(user_callback)); CPU_Push16(RealOff(user_callback)); @@ -1531,6 +1628,16 @@ uintptr_t MOUSEDOS_DoCallback(const uint8_t mask, const MouseButtons12S buttons_ return CBRET_NONE; } +void MOUSEDOS_NotifyMapped(const bool enabled) +{ + is_mapped = enabled; +} + +void MOUSEDOS_NotifyRawInput(const bool enabled) +{ + raw_input = enabled; +} + void MOUSEDOS_Init() { // Callback for mouse interrupt 0x33 @@ -1566,7 +1673,7 @@ void MOUSEDOS_Init() state.hidden = 1; // hide cursor on startup state.mode = UINT8_MAX; // non-existing mode + set_sensitivity(50, 50, 50); reset_hardware(); reset(); - set_sensitivity(50, 50, 50); } diff --git a/src/ints/mouse_ps2_bios.cpp b/src/hardware/mouse/mouseif_ps2_bios.cpp index fea414da2..461c59b99 100644 --- a/src/ints/mouse_ps2_bios.cpp +++ b/src/hardware/mouse/mouseif_ps2_bios.cpp @@ -18,7 +18,8 @@ */ #include "mouse.h" -#include "mouse_core.h" +#include "mouse_config.h" +#include "mouse_interfaces.h" #include <algorithm> #include <cmath> @@ -28,10 +29,11 @@ #include "callback.h" #include "checks.h" #include "cpu.h" -#include "int10.h" #include "pic.h" #include "regs.h" +#include "../../ints/int10.h" + using namespace bit::literals; CHECK_NARROWING(); @@ -44,28 +46,17 @@ CHECK_NARROWING(); // - https://isdaman.com/alsos/hardware/mouse/ps2interface.htm // - https://wiki.osdev.org/Mouse_Input -enum MouseType : uint8_t { // mouse type visible via PS/2 interface - None = 0xff, // dummy, just to trigger a log during startup - Standard = 0x00, // standard 2 or 3 button mouse - IntelliMouse = 0x03, // Microsoft IntelliMouse (3 buttons, wheel) -#ifdef ENABLE_EXPLORER_MOUSE - Explorer = 0x04, // Microsoft IntelliMouse Explorer (5 buttons, wheel) -#endif // ENABLE_EXPLORER_MOUSE -}; - static MouseButtonsAll buttons; // currently visible button state static MouseButtonsAll buttons_all; // state of all 5 buttons as on the host side static MouseButtons12S buttons_12S; // buttons with 3/4/5 quished together static float delta_x = 0.0f; // accumulated mouse movement since last reported static float delta_y = 0.0f; -static int8_t wheel_counter = 0; // NOTE: only fetch using 'GetResetWheel*'! +static int8_t counter_w = 0; // mouse wheel counter -static MouseType type = MouseType::None; // NOTE: only change using 'set_type'! +static MouseModelPS2 protocol = MouseModelPS2::Standard; static uint8_t unlock_idx_im = 0; // sequence index for unlocking extended protocol -#ifdef ENABLE_EXPLORER_MOUSE static uint8_t unlock_idx_xp = 0; -#endif // ENABLE_EXPLORER_MOUSE static uint8_t packet[4] = {0}; // packet to be transferred via BIOS interface @@ -88,45 +79,39 @@ void MOUSEPS2_UpdateButtonSquish() // - for PS/2 modes other than IntelliMouse Explorer there is // no standard way to report buttons 4 and 5 -#ifdef ENABLE_EXPLORER_MOUSE - const bool squish = mouse_shared.active_vmm || (type != MouseType::Explorer); + const bool squish = mouse_shared.active_vmm || (protocol != MouseModelPS2::Explorer); buttons.data = squish ? buttons_12S.data : buttons_all.data; -#else - buttons.data = buttons_12S.data; -#endif } static void terminate_unlick_sequence() { unlock_idx_im = 0; -#ifdef ENABLE_EXPLORER_MOUSE unlock_idx_xp = 0; -#endif // ENABLE_EXPLORER_MOUSE } -static void set_type(const MouseType new_type) +static void set_protocol(const MouseModelPS2 new_protocol) { terminate_unlick_sequence(); - if (type != new_type) { - type = new_type; - const char *type_name = nullptr; - switch (type) { - case MouseType::Standard: - type_name = "Standard, 3 buttons"; + static bool first_time = true; + if (first_time || protocol != new_protocol) { + first_time = false; + protocol = new_protocol; + const char *protocol_name = nullptr; + switch (protocol) { + case MouseModelPS2::Standard: + protocol_name = "Standard, 3 buttons"; break; - case MouseType::IntelliMouse: - type_name = "IntelliMouse, wheel, 3 buttons"; + case MouseModelPS2::IntelliMouse: + protocol_name = "IntelliMouse, wheel, 3 buttons"; break; -#ifdef ENABLE_EXPLORER_MOUSE - case MouseType::Explorer: - type_name = "IntelliMouse Explorer, wheel, 5 buttons"; + case MouseModelPS2::Explorer: + protocol_name = "IntelliMouse Explorer, wheel, 5 buttons"; break; -#endif // ENABLE_EXPLORER_MOUSE default: break; } - LOG_MSG("MOUSE (PS/2): %s", type_name); + LOG_MSG("MOUSE (PS/2): %s", protocol_name); packet[0] = 0; packet[1] = 0; @@ -137,23 +122,21 @@ static void set_type(const MouseType new_type) } } -#ifdef ENABLE_EXPLORER_MOUSE static uint8_t get_reset_wheel_4bit() { - const int8_t tmp = std::clamp(wheel_counter, + const int8_t tmp = std::clamp(counter_w, static_cast<int8_t>(-0x08), static_cast<int8_t>(0x07)); - wheel_counter = 0; // reading always clears the counter + counter_w = 0; // reading always clears the counter // 0x0f for -1, 0x0e for -2, etc. return static_cast<uint8_t>((tmp >= 0) ? tmp : 0x10 + tmp); } -#endif // ENABLE_EXPLORER_MOUSE static uint8_t get_reset_wheel_8bit() { - const auto tmp = wheel_counter; - wheel_counter = 0; // reading always clears thecounter + const auto tmp = counter_w; + counter_w = 0; // reading always clears thecounter // 0xff for -1, 0xfe for -2, etc. return static_cast<uint8_t>((tmp >= 0) ? tmp : 0x100 + tmp); @@ -181,9 +164,9 @@ static int16_t get_scaled_movement(const int16_t d) static void reset_counters() { - delta_x = 0.0f; - delta_y = 0.0f; - wheel_counter = 0; + delta_x = 0.0f; + delta_y = 0.0f; + counter_w = 0; } void MOUSEPS2_UpdatePacket() @@ -213,8 +196,7 @@ void MOUSEPS2_UpdatePacket() dx = get_scaled_movement(dx); dy = get_scaled_movement(static_cast<int16_t>(-dy)); -#ifdef ENABLE_EXPLORER_MOUSE - if (type == MouseType::Explorer) { + if (protocol == MouseModelPS2::Explorer) { // There is no overflow for 5-button mouse protocol, see // HT82M30A datasheet dx = std::clamp(dx, @@ -223,9 +205,7 @@ void MOUSEPS2_UpdatePacket() dy = std::clamp(dy, static_cast<int16_t>(-UINT8_MAX), static_cast<int16_t>(UINT8_MAX)); - } else -#endif // ENABLE_EXPLORER_MOUSE - { + } else { if ((dx > 0xff) || (dx < -0xff)) mdat.overflow_x = 1; if ((dy > 0xff) || (dy < -0xff)) @@ -248,17 +228,15 @@ void MOUSEPS2_UpdatePacket() packet[1] = static_cast<uint8_t>(dx); packet[2] = static_cast<uint8_t>(dy); - if (type == MouseType::IntelliMouse) + if (protocol == MouseModelPS2::IntelliMouse) packet[3] = get_reset_wheel_8bit(); -#ifdef ENABLE_EXPLORER_MOUSE - else if (type == MouseType::Explorer) { + else if (protocol == MouseModelPS2::Explorer) { packet[3] = get_reset_wheel_4bit(); if (buttons.extra_1) bit::set(packet[3], b4); if (buttons.extra_2) bit::set(packet[3], b5); } -#endif // ENABLE_EXPLORER_MOUSE else packet[3] = 0; } @@ -290,29 +268,29 @@ static void cmd_set_sample_rate(const uint8_t new_rate_hz) } else rate_hz = new_rate_hz; - // Update event queue settings - MOUSE_NotifyRatePS2(rate_hz); + // Update event queue settings and interface information + MouseInterface::GetPS2()->NotifyInterfaceRate(rate_hz); // Handle extended mouse protocol unlock sequences auto unlock = [](const std::vector<uint8_t> &sequence, uint8_t &idx, - const MouseType potential_type) { + const MouseModelPS2 potential_protocol) { if (sequence[idx] != rate_hz) idx = 0; else if (sequence.size() == ++idx) { - set_type(potential_type); + set_protocol(potential_protocol); } }; static const std::vector<uint8_t> seq_im = {200, 100, 80}; -#ifdef ENABLE_EXPLORER_MOUSE static const std::vector<uint8_t> seq_xp = {200, 200, 80}; -#endif // ENABLE_EXPLORER_MOUSE - unlock(seq_im, unlock_idx_im, MouseType::IntelliMouse); -#ifdef ENABLE_EXPLORER_MOUSE - unlock(seq_xp, unlock_idx_xp, MouseType::Explorer); -#endif // ENABLE_EXPLORER_MOUSE + if (mouse_config.model_ps2 == MouseModelPS2::IntelliMouse) + unlock(seq_im, unlock_idx_im, MouseModelPS2::IntelliMouse); + else if (mouse_config.model_ps2 == MouseModelPS2::Explorer) { + unlock(seq_im, unlock_idx_im, MouseModelPS2::IntelliMouse); + unlock(seq_xp, unlock_idx_xp, MouseModelPS2::Explorer); + } } static void cmd_set_defaults() @@ -320,15 +298,13 @@ static void cmd_set_defaults() cmd_set_resolution(4); cmd_set_sample_rate(100); -#ifdef ENABLE_EXPLORER_MOUSE MOUSEPS2_UpdateButtonSquish(); -#endif // ENABLE_EXPLORER_MOUSE } static void cmd_reset() { cmd_set_defaults(); - set_type(MouseType::Standard); + set_protocol(MouseModelPS2::Standard); reset_counters(); } @@ -347,8 +323,8 @@ bool MOUSEPS2_NotifyMoved(const float x_rel, const float y_rel) return (std::fabs(delta_x) >= 0.5f) || (std::fabs(delta_y) >= 0.5f); } -bool MOUSEPS2_NotifyPressedReleased(const MouseButtons12S new_buttons_12S, - const MouseButtonsAll new_buttons_all) +bool MOUSEPS2_NotifyButton(const MouseButtons12S new_buttons_12S, + const MouseButtonsAll new_buttons_all) { const auto buttons_old = buttons; @@ -361,18 +337,14 @@ bool MOUSEPS2_NotifyPressedReleased(const MouseButtons12S new_buttons_12S, bool MOUSEPS2_NotifyWheel(const int16_t w_rel) { -#ifdef ENABLE_EXPLORER_MOUSE - if (type != MouseType::IntelliMouse && type != MouseType::Explorer) - return false; -#else - if (type != MouseType::IntelliMouse) + if (protocol != MouseModelPS2::IntelliMouse && + protocol != MouseModelPS2::Explorer) return false; -#endif - wheel_counter = static_cast<int8_t>(std::clamp(static_cast<int32_t>(w_rel + wheel_counter), - static_cast<int32_t>(INT8_MIN), - static_cast<int32_t>(INT8_MAX))); - return true; + auto old_counter_w = counter_w; + counter_w = MOUSE_ClampToInt8(static_cast<int32_t>(counter_w + w_rel)); + + return (old_counter_w != counter_w); } // *************************************************************************** @@ -506,9 +478,9 @@ uint8_t MOUSEBIOS_GetStatus() return ret.data; } -uint8_t MOUSEBIOS_GetType() +uint8_t MOUSEBIOS_GetProtocol() { - return static_cast<uint8_t>(type); + return static_cast<uint8_t>(protocol); } static Bitu callback_ret() diff --git a/src/ints/mouse_vmware.cpp b/src/hardware/mouse/mouseif_virtual_machines.cpp index 102516be1..db65f34a1 100644 --- a/src/ints/mouse_vmware.cpp +++ b/src/hardware/mouse/mouseif_virtual_machines.cpp @@ -17,10 +17,10 @@ */ #include "mouse.h" -#include "mouse_core.h" +#include "mouse_config.h" +#include "mouse_interfaces.h" #include <algorithm> -#include <chrono> #include "checks.h" #include "inout.h" @@ -70,21 +70,20 @@ static constexpr uint32_t ABS_UPDATED = 4; // tells that new pointer // position is available static constexpr uint32_t ABS_NOT_UPDATED = 0; -static bool updated = false; // true = mouse state update waits to be picked up -static VMwareButtons buttons; // state of mouse buttons, in VMware format -static uint16_t scaled_x = 0x7fff; // absolute position scaled from 0 to 0xffff -static uint16_t scaled_y = 0x7fff; // 0x7fff is a center position -static int8_t wheel_counter = 0; // wheel movement counter +static bool raw_input = true; // true = no host mouse acceleration pre-applied +static bool is_mapped = false; // true = physical mouse is mapped to this interface +static bool updated = false; // true = mouse state update waits to be picked up +static VMwareButtons buttons; // state of mouse buttons, in VMware format +static uint16_t scaled_x = 0x7fff; // absolute position scaled from 0 to 0xffff +static uint16_t scaled_y = 0x7fff; // 0x7fff is a center position +static int8_t counter_w = 0; // wheel movement counter static float pos_x = 0.0f; static float pos_y = 0.0f; -// Speed measurement -static auto time_start = std::chrono::steady_clock::now(); -static auto pic_ticks_start = PIC_Ticks; -static float distance = 0.0f; // distance since last measurement - -static float speed = 0.0f; +// Multiply scale by 0.02f to put acceleration_vmm in a reasonable +// range, similar to sensitivity_dos or sensitivity_vmm) +static MouseSpeedCalculator speed_xy(0.02f * mouse_predefined.acceleration_vmm); // *************************************************************************** // VMware interface implementation @@ -106,8 +105,8 @@ static void MOUSEVMM_Activate() MOUSEPS2_UpdateButtonSquish(); MOUSE_NotifyStateChanged(); } - buttons.data = 0; - wheel_counter = 0; + buttons.data = 0; + counter_w = 0; } void MOUSEVMM_Deactivate() @@ -118,8 +117,18 @@ void MOUSEVMM_Deactivate() MOUSEPS2_UpdateButtonSquish(); MOUSE_NotifyStateChanged(); } - buttons.data = 0; - wheel_counter = 0; + buttons.data = 0; + counter_w = 0; +} + +void MOUSEVMM_NotifyMapped(const bool enabled) +{ + is_mapped = enabled; +} + +void MOUSEVMM_NotifyRawInput(const bool enabled) +{ + raw_input = enabled; } static void cmd_get_version() @@ -133,9 +142,9 @@ static void cmd_abs_pointer_data() reg_eax = buttons.data; reg_ebx = scaled_x; reg_ecx = scaled_y; - reg_edx = static_cast<uint32_t>((wheel_counter >= 0) ? wheel_counter : 0x100 + wheel_counter); + reg_edx = static_cast<uint32_t>((counter_w >= 0) ? counter_w : 0x100 + counter_w); - wheel_counter = 0; + counter_w = 0; } static void cmd_abs_pointer_status() @@ -176,62 +185,16 @@ static uint32_t port_read_vmware(const io_port_t, const io_width_t) return reg_eax; } -static void speed_update(const float x_rel, const float y_rel) -{ - constexpr auto n = static_cast<float>(std::chrono::steady_clock::period::num); - constexpr auto d = static_cast<float>(std::chrono::steady_clock::period::den); - constexpr auto period_ms = 1000.0f * n / d; - // For the measurement duration require no more than 400 milliseconds - // and at least 10 times the clock granularity - constexpr uint32_t max_diff_ms = 400; - constexpr uint32_t min_diff_ms = std::max(static_cast<uint32_t>(1), - static_cast<uint32_t>(period_ms * 10)); - - // Require at least 40 ticks of PIC emulator to pass - constexpr uint32_t min_diff_ticks = 40; - - const auto time_now = std::chrono::steady_clock::now(); - const auto diff_time = time_now - time_start; - const auto diff_ticks = PIC_Ticks - pic_ticks_start; - - const auto diff_ms = - std::chrono::duration_cast<std::chrono::milliseconds>(diff_time).count(); - - if (diff_ms > std::max(max_diff_ms, static_cast<uint32_t>(10 * period_ms))) { - // Do not wait any more for movement, consider speed to be 0 - speed = 0.0f; - } else if (diff_ms >= 0) { - // Update distance travelled by the cursor - distance += std::sqrt(x_rel * x_rel + y_rel * y_rel); - - // Make sure enough time passed for accurate speed calculation - if (diff_ms < min_diff_ms || diff_ticks < min_diff_ticks) - return; - - // Update cursor speed (multiply it by 20.0f to put acceleration_vmm in - // a reasonable range, similar to sensitivity_dos or sensitivity_vmm) - speed = 20.0f * acceleration_vmm * distance / static_cast<float>(diff_ms); - } - - // Start new measurement - distance = 0.0f; - time_start = time_now; - pic_ticks_start = PIC_Ticks; -} - bool MOUSEVMM_NotifyMoved(const float x_rel, const float y_rel, const uint16_t x_abs, const uint16_t y_abs) { if (!mouse_shared.active_vmm) return false; - const auto x_mov = x_rel * sensitivity_vmm; - const auto y_mov = y_rel * sensitivity_vmm; - - speed_update(x_mov, y_mov); + speed_xy.Update(std::sqrt(x_rel * x_rel + y_rel * y_rel)); - const auto old_x = scaled_x; - const auto old_y = scaled_y; + const auto old_scaled_x = scaled_x; + const auto old_scaled_y = scaled_y; auto calculate = [](float &position, const float relative, @@ -240,16 +203,19 @@ bool MOUSEVMM_NotifyMoved(const float x_rel, const float y_rel, const uint16_t clip) { assert(resolution > 1u); - if (mouse_is_captured) { + if (mouse_is_captured || is_mapped) { // Mouse is captured, there is no need for pointer // integration with host OS - we can use relative - // movement with configured sensitivity and built-in - // pointer acceleration model - - const auto coeff = MOUSE_GetBallisticsCoeff(speed); - const auto delta = relative * coeff; - - position += MOUSE_ClampRelativeMovement(delta); + // movement with configured sensitivity and (for + // raw mouse input) our built-in pointer acceleration + // model + + if (raw_input) { + const auto coeff = MOUSE_GetBallisticsCoeff(speed_xy.Get()); + position += MOUSE_ClampRelativeMovement(relative * coeff); + } + else + position += MOUSE_ClampRelativeMovement(relative); } else // Cursor position controlled by the host OS position = static_cast<float>(std::max(absolute - clip, 0)); @@ -265,24 +231,24 @@ bool MOUSEVMM_NotifyMoved(const float x_rel, const float y_rel, return static_cast<uint16_t>(tmp); }; - scaled_x = calculate(pos_x, x_mov, x_abs, mouse_video.res_x, mouse_video.clip_x); - scaled_y = calculate(pos_y, y_mov, y_abs, mouse_video.res_y, mouse_video.clip_y); + scaled_x = calculate(pos_x, x_rel, x_abs, mouse_video.res_x, mouse_video.clip_x); + scaled_y = calculate(pos_y, y_rel, y_abs, mouse_video.res_y, mouse_video.clip_y); // Filter out unneeded events (like sub-pixel mouse movements, // which won't change guest side mouse state) - if (old_x != scaled_x || old_y != scaled_y) { - updated = true; - return true; - } + if (GCC_UNLIKELY(old_scaled_x == scaled_x && old_scaled_y == scaled_y)) + return false; - return false; + updated = true; + return true; } -bool MOUSEVMM_NotifyPressedReleased(const MouseButtons12S buttons_12S) +bool MOUSEVMM_NotifyButton(const MouseButtons12S buttons_12S) { if (!mouse_shared.active_vmm) return false; + const auto old_buttons = buttons; buttons.data = 0; // Direct assignment of .data is not possible, as bit layout is different @@ -290,8 +256,10 @@ bool MOUSEVMM_NotifyPressedReleased(const MouseButtons12S buttons_12S) buttons.right = static_cast<bool>(buttons_12S.right); buttons.middle = static_cast<bool>(buttons_12S.middle); - updated = true; + if (GCC_UNLIKELY(old_buttons.data == buttons.data)) + return false; + updated = true; return true; } @@ -300,21 +268,21 @@ bool MOUSEVMM_NotifyWheel(const int16_t w_rel) if (!mouse_shared.active_vmm) return false; - const auto tmp = std::clamp(static_cast<int32_t>(w_rel + wheel_counter), - static_cast<int32_t>(INT8_MIN), - static_cast<int32_t>(INT8_MAX)); - wheel_counter = static_cast<int8_t>(tmp); - updated = true; + const auto old_counter_w = counter_w; + counter_w = MOUSE_ClampToInt8(static_cast<int32_t>(counter_w + w_rel)); + if (GCC_UNLIKELY(old_counter_w == counter_w)) + return false; + + updated = true; return true; } void MOUSEVMM_NewScreenParams(const uint16_t x_abs, const uint16_t y_abs) { // Report a fake mouse movement - if (MOUSEVMM_NotifyMoved(0.0f, 0.0f, x_abs, y_abs) && mouse_shared.active_vmm) - MOUSE_NotifyMovedFake(); + MOUSE_NotifyFakePS2(); } void MOUSEVMM_Init() diff --git a/src/hardware/serialport/serialmouse.cpp b/src/hardware/serialport/serialmouse.cpp index 2fc1413d3..bcdaf5ecf 100644 --- a/src/hardware/serialport/serialmouse.cpp +++ b/src/hardware/serialport/serialmouse.cpp @@ -28,63 +28,46 @@ #include "serialmouse.h" #include "checks.h" -#include "mouse.h" + +#include "../mouse/mouse_interfaces.h" CHECK_NARROWING(); -static constexpr uint16_t DIV_1200 = 96; // port clock divider for 1200 bauds + +// Port clock divider for 1200 baud transmission +static constexpr uint16_t divider_1200_baud = 96; +// 1200 baud serial mice is limited to about 40 Hz sampling rate +// due to serial port transmission constraints +static constexpr uint16_t rate_1200_baud = 40; + CSerialMouse::CSerialMouse(const uint8_t id, CommandLine *cmd) : CSerial(id, cmd), + port_id(id), port_num(static_cast<uint16_t>(id + 1)) { - std::string type_string; - bool use_default = !cmd->FindStringBegin("type:", type_string, false); - - if (type_string == "2btn") { - config_type = MouseType::Microsoft; - } else if (type_string == "2btn+msm") { - config_type = MouseType::Microsoft; - config_auto = true; - } else if (type_string == "3btn") { - config_type = MouseType::Logitech; - } else if (type_string == "3btn+msm") { - config_type = MouseType::Logitech; - config_auto = true; - } else if (type_string == "wheel") { - config_type = MouseType::Wheel; - } else if (type_string == "wheel+msm" || use_default) { - config_type = MouseType::Wheel; - config_auto = true; - } else if (type_string == "msm") { - config_type = MouseType::MouseSystems; - } else { - LOG_ERR("MOUSE (COM%d): Invalid type '%s'", - port_num, - type_string.c_str()); + auto interface = MouseInterface::GetSerial(port_id); + if (!interface) return; - } - std::string rate_string; - use_default = !cmd->FindStringBegin("rate:", rate_string, false); - - if (rate_string == "smooth" || use_default) { - // This is roughly equivalent of PS/2 mouse with sampling rate - // between 120 and 250 Hz, depends on mouse protocol, concrete - // events (reporting middle button state change or wheel - // movement needs more bandwidth for Logitech and wheel mice), - // and sometimes even driver sophistication (for Mouse Systems - // mouse a badly written driver can waste half of the movement - // sampling rate) - smooth_div = 5; - } else if (rate_string != "normal") { - LOG_ERR("MOUSE (COM%d): Invalid rate '%s'", - port_num, - rate_string.c_str()); - return; - } + // Get the parameters from the configuration file + + param_model = mouse_config.model_com; + param_auto_msm = mouse_config.model_com_auto_msm; - SetType(config_type); + // Handle deprecated parameters + + HandleDeprecatedOptions(cmd); + + // Override with parameters from command line or [serial] section + + std::string model_string; + if (cmd->FindStringBegin("model:", model_string, false) && + !MouseConfig::ParseSerialModel(model_string, param_model, param_auto_msm)) { + LOG_ERR("MOUSE (COM%d): Invalid model '%s'", + port_num, + model_string.c_str()); + } CSerial::Init_Registers(); setRI(false); @@ -92,57 +75,136 @@ CSerialMouse::CSerialMouse(const uint8_t id, CommandLine *cmd) setCD(false); setCTS(false); + interface->RegisterListener(*this); + interface->NotifyInterfaceRate(rate_1200_baud); InstallationSuccessful = true; - - MOUSESERIAL_RegisterListener(*this); } CSerialMouse::~CSerialMouse() { - MOUSESERIAL_UnRegisterListener(*this); + auto interface = MouseInterface::GetSerial(port_id); + if (interface) + interface->UnRegisterListener(); + removeEvent(SERIAL_TX_EVENT); // clear events - SetType(MouseType::NoMouse); + SetModel(MouseModelCOM::NoMouse); } -void CSerialMouse::SetType(const MouseType new_type) +void CSerialMouse::HandleDeprecatedOptions(CommandLine *cmd) { - if (type != new_type) { - type = new_type; + std::string option; + if (cmd->FindStringBegin("rate:", option, false)) + LOG_WARNING("MOUSE (COM%d): Deprecated option 'rate:' - ignored", port_num); + + const bool found_deprecated = cmd->FindStringBegin("type:", option, false); + + if (found_deprecated) { + LOG_WARNING("MOUSE (COM%d): Deprecated option 'type:'", port_num); + + if (option == "2btn") { + param_model = MouseModelCOM::Microsoft; + param_auto_msm = false; + } else if (option == "2btn+msm") { + param_model = MouseModelCOM::Microsoft; + param_auto_msm = true; + } else if (option == "3btn") { + param_model = MouseModelCOM::Logitech; + param_auto_msm = false; + } else if (option == "3btn+msm") { + param_model = MouseModelCOM::Logitech; + param_auto_msm = true; + } else if (option == "wheel") { + param_model = MouseModelCOM::Wheel; + param_auto_msm = false; + } else if (option == "wheel+msm") { + param_model = MouseModelCOM::Wheel; + param_auto_msm = true; + } else if (option == "msm") { + param_model = MouseModelCOM::MouseSystems; + param_auto_msm = false; + } else { + LOG_ERR("MOUSE (COM%d): Invalid type '%s'", + port_num, + option.c_str()); + return; + } + } +} + +void CSerialMouse::BoostRate(const uint16_t rate_hz) +{ + if (!rate_hz || model == MouseModelCOM::NoMouse) { + rate_coeff = 1.0f; + return; + } + + // Estimate current sampling rate, as precisely as possible + auto estimate = [](const uint16_t bauds, + const uint8_t byte_len, + const MouseModelCOM model) { + // In addition to byte_len, the mouse has to send + // 3 more bits per each byte: start, parity, stop + + if (model == MouseModelCOM::Microsoft || + model == MouseModelCOM::Logitech || + model == MouseModelCOM::Wheel) + // Microsoft-style protocol + // single movement needs exactly 3 bytes to be reported + return bauds / (static_cast<float>(byte_len + 3) * 3.0f); + else if (model == MouseModelCOM::MouseSystems) + // Mouse Systems protocol + // single movement needs per average 2.5 bytes to be reported + return bauds / (static_cast<float>(byte_len + 3) * 2.5f); + + assert(false); // unimplemented + return static_cast<float>(rate_1200_baud); + }; + + // Calculate coefficient to match requested rate + rate_coeff = estimate(1200, port_byte_len, model) / rate_hz; +} + +void CSerialMouse::SetModel(const MouseModelCOM new_model) +{ + if (model != new_model) { + model = new_model; const char *name = nullptr; - switch (new_type) { - case MouseType::NoMouse: // just to print out log in the destructor - name = "(none)"; + switch (model) { + case MouseModelCOM::NoMouse: // just to print out log in the destructor + name = "(none)"; + break; + case MouseModelCOM::Microsoft: + name = "Microsoft, 2 buttons"; + has_3rd_button = false; + has_wheel = false; break; - case MouseType::Microsoft: - name = "Microsoft, 2 buttons"; - byte_len = 7; - has_3rd_button = false; - has_wheel = false; + case MouseModelCOM::Logitech: + name = "Logitech, 3 buttons"; + has_3rd_button = true; + has_wheel = false; break; - case MouseType::Logitech: - name = "Logitech, 3 buttons"; - byte_len = 7; - has_3rd_button = true; - has_wheel = false; + case MouseModelCOM::Wheel: + name = "wheel, 3 buttons"; + has_3rd_button = true; + has_wheel = true; break; - case MouseType::Wheel: - name = "wheel, 3 buttons"; - byte_len = 7; - has_3rd_button = true; - has_wheel = true; + case MouseModelCOM::MouseSystems: + name = "Mouse Systems, 3 buttons"; + has_3rd_button = true; + has_wheel = false; break; - case MouseType::MouseSystems: - name = "Mouse Systems, 3 buttons"; - byte_len = 8; - has_3rd_button = true; - has_wheel = false; + default: + assert(false); // unimplemented break; - default: LogUnimplemented(); break; } if (name) LOG_MSG("MOUSE (COM%d): %s", port_num, name); } + + // So far all emulated mice are 1200 bauds, but report anyway + // to trigger rate_coeff recalculation + MouseInterface::GetSerial(port_id)->NotifyInterfaceRate(rate_1200_baud); } void CSerialMouse::AbortPacket() @@ -156,9 +218,9 @@ void CSerialMouse::AbortPacket() void CSerialMouse::ClearCounters() { - delta_x = 0; - delta_y = 0; - delta_w = 0; + counter_x = 0; + counter_y = 0; + counter_w = 0; } void CSerialMouse::MouseReset() @@ -171,11 +233,22 @@ void CSerialMouse::MouseReset() SetEventRX(); } -void CSerialMouse::OnMouseEventMoved(const int16_t new_delta_x, - const int16_t new_delta_y) +void CSerialMouse::NotifyMoved(const float x_rel, const float y_rel) { - delta_x += new_delta_x; - delta_y += new_delta_y; + delta_x = MOUSE_ClampRelativeMovement(delta_x + x_rel); + delta_y = MOUSE_ClampRelativeMovement(delta_y + y_rel); + + const auto dx = static_cast<int16_t>(std::lround(delta_x)); + const auto dy = static_cast<int16_t>(std::lround(delta_y)); + + if (dx == 0 && dy == 0) + return; // movement not significant enough + + counter_x = MOUSE_ClampToInt8(counter_x + dx); + counter_y = MOUSE_ClampToInt8(counter_y + dy); + + delta_x -= dx; + delta_y -= dy; // Initiate data transfer and form the packet to transmit. If another // packet is already transmitting now then wait for it to finish before @@ -187,7 +260,7 @@ void CSerialMouse::OnMouseEventMoved(const int16_t new_delta_x, another_move = true; } -void CSerialMouse::OnMouseEventButton(const uint8_t new_buttons, const uint8_t idx) +void CSerialMouse::NotifyButton(const uint8_t new_buttons, const uint8_t idx) { if (!has_3rd_button && idx > 1) return; @@ -200,12 +273,12 @@ void CSerialMouse::OnMouseEventButton(const uint8_t new_buttons, const uint8_t i another_button = true; } -void CSerialMouse::OnMouseEventWheel(const int8_t new_delta_w) +void CSerialMouse::NotifyWheel(const int16_t w_rel) { if (!has_wheel) return; - delta_w += new_delta_w; + counter_w = MOUSE_ClampToInt8(static_cast<int32_t>(counter_w + w_rel)); if (xmit_idx >= packet_len) StartPacketData(true); @@ -215,19 +288,19 @@ void CSerialMouse::OnMouseEventWheel(const int8_t new_delta_w) void CSerialMouse::StartPacketId() // send the mouse identifier { - if (!port_valid) + if (model == MouseModelCOM::NoMouse) return; AbortPacket(); ClearCounters(); packet_len = 0; - switch (type) { - case MouseType::Microsoft: packet[packet_len++] = 'M'; break; - case MouseType::Logitech: + switch (model) { + case MouseModelCOM::Microsoft: packet[packet_len++] = 'M'; break; + case MouseModelCOM::Logitech: packet[packet_len++] = 'M'; packet[packet_len++] = '3'; break; - case MouseType::Wheel: + case MouseModelCOM::Wheel: packet[packet_len++] = 'M'; packet[packet_len++] = 'Z'; packet[packet_len++] = '@'; // for some reason 86Box sends more @@ -236,8 +309,12 @@ void CSerialMouse::StartPacketId() // send the mouse identifier packet[packet_len++] = 0; packet[packet_len++] = 0; break; - case MouseType::MouseSystems: packet[packet_len++] = 'H'; break; - default: LogUnimplemented(); break; + case MouseModelCOM::MouseSystems: + packet[packet_len++] = 'H'; + break; + default: + assert(false); // unimplemented + break; } // send packet @@ -247,11 +324,11 @@ void CSerialMouse::StartPacketId() // send the mouse identifier void CSerialMouse::StartPacketData(const bool extended) { - if (!port_valid) + if (model == MouseModelCOM::NoMouse) return; - if (type == MouseType::Microsoft || type == MouseType::Logitech || - type == MouseType::Wheel) { + if (model == MouseModelCOM::Microsoft || model == MouseModelCOM::Logitech || + model == MouseModelCOM::Wheel) { // -- -- -- -- -- -- -- -- // Byte 0: X 1 LB RB Y7 Y6 X7 X6 // Byte 1: X 0 X5 X4 X3 X2 X1 X0 @@ -263,8 +340,8 @@ void CSerialMouse::StartPacketData(const bool extended) // movement possible. Microsoft Windows on the other hand // doesn't care if bit 7 is set. - const auto dx = ClampDelta(delta_x); - const auto dy = ClampDelta(delta_y); + const auto dx = ClampCounter(counter_x); + const auto dy = ClampCounter(counter_y); const auto bt = has_3rd_button ? (buttons & 7) : (buttons & 3); packet[0] = static_cast<uint8_t>( @@ -273,7 +350,9 @@ void CSerialMouse::StartPacketData(const bool extended) packet[1] = static_cast<uint8_t>(0x00 | (dx & 0x3f)); packet[2] = static_cast<uint8_t>(0x00 | (dy & 0x3f)); if (extended) { - uint8_t dw = std::clamp(delta_w, -0x10, 0x0f) & 0x0f; + uint8_t dw = std::clamp(counter_w, + static_cast<int8_t>(-0x10), + static_cast<int8_t>(0x0f)) & 0x0f; packet[3] = static_cast<uint8_t>(((bt & 4) ? 0x20 : 0) | dw); packet_len = 4; } else { @@ -281,7 +360,7 @@ void CSerialMouse::StartPacketData(const bool extended) } xmit_2part = false; - } else if (type == MouseType::MouseSystems) { + } else if (model == MouseModelCOM::MouseSystems) { // -- -- -- -- -- -- -- -- // Byte 0: 1 0 0 0 0 LB MB RB // Byte 1: X7 X6 X5 X4 X3 X2 X1 X0 @@ -292,15 +371,14 @@ void CSerialMouse::StartPacketData(const bool extended) packet[0] = static_cast<uint8_t>(0x80 | ((bt & 1) << 2) | ((bt & 2) >> 1) | ((bt & 4) >> 1)); - packet[1] = ClampDelta(delta_x); - packet[2] = ClampDelta(-delta_y); + packet[1] = ClampCounter(counter_x); + packet[2] = ClampCounter(-counter_y); packet_len = 3; xmit_2part = true; // next part contains mouse movement since // the start of the 1st part - } else { - LogUnimplemented(); - } + } else + assert(false); // unimplemented ClearCounters(); @@ -315,19 +393,18 @@ void CSerialMouse::StartPacketPart2() { // port settings are valid at this point - if (type == MouseType::MouseSystems) { + if (model == MouseModelCOM::MouseSystems) { // -- -- -- -- -- -- -- -- // Byte 3: X7 X6 X5 X4 X3 X2 X1 X0 // Byte 4: Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 - packet[0] = ClampDelta(delta_x); - packet[1] = ClampDelta(-delta_y); + packet[0] = ClampCounter(counter_x); + packet[1] = ClampCounter(-counter_y); packet_len = 2; xmit_2part = false; - } else { - LogUnimplemented(); - } + } else + assert(false); // unimplemented ClearCounters(); @@ -339,12 +416,12 @@ void CSerialMouse::StartPacketPart2() void CSerialMouse::SetEventTX() { - setEvent(SERIAL_TX_EVENT, bytetime / smooth_div); + setEvent(SERIAL_TX_EVENT, bytetime * rate_coeff); } void CSerialMouse::SetEventRX() { - setEvent(SERIAL_RX_EVENT, bytetime / smooth_div); + setEvent(SERIAL_RX_EVENT, bytetime * rate_coeff); } void CSerialMouse::SetEventTHR() @@ -352,14 +429,9 @@ void CSerialMouse::SetEventTHR() setEvent(SERIAL_THR_EVENT, bytetime / 10); } -void CSerialMouse::LogUnimplemented() const -{ - LOG_ERR("MOUSE (COM%d): Missing implementation", port_num); -} - -uint8_t CSerialMouse::ClampDelta(const int32_t delta) const +uint8_t CSerialMouse::ClampCounter(const int32_t counter) const { - const auto tmp = std::clamp(delta, + const auto tmp = std::clamp(counter, static_cast<int32_t>(INT8_MIN), static_cast<int32_t>(INT8_MAX)); return static_cast<uint8_t>(tmp); @@ -397,31 +469,57 @@ void CSerialMouse::updatePortConfig(const uint16_t divider, const uint8_t lcr) { AbortPacket(); - // Check whether port settings match mouse protocol, - // to prevent false device detections by guest software - - port_valid = true; + // We have to select between Microsoft-style protocol (this includes Logitech + // and wheel mice) and Mouse Systems Mouse protocol, or decide the port + // settings are not valid for any mouse - const auto port_byte_len = static_cast<uint8_t>((lcr & 0x3) + 5); + port_byte_len = static_cast<uint8_t>((lcr & 0x3) + 5); const auto one_stop_bit = !(lcr & 0x4); const auto parity_id = static_cast<uint8_t>((lcr & 0x38) >> 3); - if (divider != DIV_1200 || !one_stop_bit || // for mouse we need 1200 - // bauds and 1 stop bit - parity_id == 1 || parity_id == 3 || parity_id == 5 || - parity_id == 7) // parity has to be 'N' - port_valid = false; - - if (port_valid && config_auto) { // auto select mouse type to emulate - if (port_byte_len == 7) { - SetType(config_type); - } else if (port_byte_len == 8) { - SetType(MouseType::MouseSystems); - } else - port_valid = false; - } else if (port_byte_len != byte_len) // byte length has to match - // between port and protocol - port_valid = false; + // LOG_MSG("MOUSE (COM%d): lcr 0x%04x, divider %d, byte_len %d, stop %d, parity %d", + // port_num, lcr, divider, port_byte_len, one_stop_bit, parity_id); + + if (divider != divider_1200_baud) { + // We need 1200 bauds for a mouse; TODO:support faster serial mice, + // see https://man7.org/linux/man-pages/man4/mouse.4.html + SetModel(MouseModelCOM::NoMouse); + return; + } + + // Require 1 sop bit + if (!one_stop_bit) { + SetModel(MouseModelCOM::NoMouse); + return; + } + + // Require parity 'N' + if (parity_id == 1 || parity_id == 3 || parity_id == 5 || parity_id == 7) { + SetModel(MouseModelCOM::NoMouse); + return; + } + + // Check protocol compatibility with byte length + bool ok_microsoft = (param_model != MouseModelCOM::MouseSystems); + bool ok_mouse_systems = param_auto_msm || (param_model == MouseModelCOM::MouseSystems); + + // NOTE: It seems some software (at least The Settlers) tries to use + // Microsoft-style protocol by setting port to 8 bits per byte; + // we allow this if autodetection is not enabled, otherwise it is + // impossible to guess which protocol the guest software expects + + if (port_byte_len != 7 && !(!param_auto_msm && port_byte_len == 8)) + ok_microsoft = false; + if (port_byte_len != 8) + ok_mouse_systems = false; + + // Set the mouse protocol + if (ok_microsoft) + SetModel(param_model); + else if (ok_mouse_systems) + SetModel(MouseModelCOM::MouseSystems); + else + SetModel(MouseModelCOM::NoMouse); } void CSerialMouse::updateMSR() {} diff --git a/src/hardware/serialport/serialmouse.h b/src/hardware/serialport/serialmouse.h index 319c1232a..1cf05eb19 100644 --- a/src/hardware/serialport/serialmouse.h +++ b/src/hardware/serialport/serialmouse.h @@ -22,81 +22,78 @@ #include "serialport.h" +#include "../mouse/mouse_config.h" + class CSerialMouse : public CSerial { public: - CSerialMouse(const uint8_t id, CommandLine *cmd); - virtual ~CSerialMouse(); + CSerialMouse(const uint8_t id, CommandLine *cmd); + virtual ~CSerialMouse(); + + void NotifyMoved(const float x_rel, const float y_rel); + void NotifyButton(const uint8_t new_buttons, + const uint8_t idx); // changed button, staring from 0 + void NotifyWheel(const int16_t w_rel); - void OnMouseEventMoved(const int16_t new_delta_x, const int16_t new_delta_y); - void OnMouseEventButton(const uint8_t new_buttons, - const uint8_t idx); // idx - index of changed - // button, staring from 0 - void OnMouseEventWheel(const int8_t new_delta_w); + void BoostRate(const uint16_t rate_hz); // 0 = standard rate - void setRTSDTR(const bool rts, const bool dtr) override; - void setRTS(const bool val) override; - void setDTR(const bool val) override; + void setRTSDTR(const bool rts, const bool dtr) override; + void setRTS(const bool val) override; + void setDTR(const bool val) override; - void updatePortConfig(const uint16_t divider, const uint8_t lcr) override; - void updateMSR() override; - void transmitByte(const uint8_t val, const bool first) override; - void setBreak(const bool value) override; - void handleUpperEvent(const uint16_t event_type) override; + void updatePortConfig(const uint16_t divider, const uint8_t lcr) override; + void updateMSR() override; + void transmitByte(const uint8_t val, const bool first) override; + void setBreak(const bool value) override; + void handleUpperEvent(const uint16_t event_type) override; private: - enum class MouseType { - NoMouse, - Microsoft, - Logitech, - Wheel, - MouseSystems - }; - - void SetType(const MouseType new_type); - void AbortPacket(); - void ClearCounters(); - void MouseReset(); - void StartPacketId(); - void StartPacketData(const bool extended = false); - void StartPacketPart2(); - void SetEventTX(); - void SetEventRX(); - void SetEventTHR(); - void LogUnimplemented() const; - uint8_t ClampDelta(const int32_t delta) const; - - const uint16_t port_num = 0; - - MouseType config_type = MouseType::NoMouse; // mouse type as in the - // configuration file - bool config_auto = false; // true = autoswitch between config_type and - // Mouse Systems Mouse - - MouseType type = MouseType::NoMouse; // currently emulated mouse type - uint8_t byte_len = 0; // how many bits the emulated mouse transmits in a - // byte (serial port setting) - bool has_3rd_button = false; - bool has_wheel = false; - bool port_valid = false; // false = port settings incompatible with - // selected mouse - uint8_t smooth_div = 1; // time divider value, if > 1 mouse is more - // smooth than with real HW - bool send_ack = true; - uint8_t packet[6] = {}; - uint8_t packet_len = 0; - uint8_t xmit_idx = UINT8_MAX; // index of byte to send, if >= packet_len - // it means transmission ended - bool xmit_2part = false; // true = packet has a second part, which could - // not be evaluated yet - bool another_move = false; // true = while transmitting a packet we - // received mouse move event - bool another_button = false; // true = while transmitting a packet we - // received mouse button event - uint8_t buttons = 0; // bit 0 = left, bit 1 = right, bit 2 = middle - int32_t delta_x = 0; // movement since last transmitted package - int32_t delta_y = 0; - int32_t delta_w = 0; + + void HandleDeprecatedOptions(CommandLine *cmd); + void SetModel(const MouseModelCOM new_type); + void AbortPacket(); + void ClearCounters(); + void MouseReset(); + void StartPacketId(); + void StartPacketData(const bool extended = false); + void StartPacketPart2(); + void SetEventTX(); + void SetEventRX(); + void SetEventTHR(); + uint8_t ClampCounter(const int32_t counter) const; + + const uint8_t port_id = 0; + const uint16_t port_num = 0; // for logging purposes + + // Mouse model as specified in the parameter + MouseModelCOM param_model = MouseModelCOM::NoMouse; + // If true = autoswitch between param_model and Mouse Systems mouse + bool param_auto_msm = false; + + MouseModelCOM model = MouseModelCOM::NoMouse; // currently emulated model + + uint8_t port_byte_len = 0; // how many bits the port transmits in a byte + + bool has_3rd_button = false; + bool has_wheel = false; + float rate_coeff = 1.0f; // coefficient for boosted sampling rate + bool send_ack = true; + uint8_t packet[6] = {}; + uint8_t packet_len = 0; + uint8_t xmit_idx = UINT8_MAX; // index of byte to send, if >= packet_len + // it means transmission ended + bool xmit_2part = false; // true = packet has a second part, which could + // not be evaluated yet + bool another_move = false; // true = while transmitting a packet we + // received mouse move event + bool another_button = false; // true = while transmitting a packet we + // received mouse button event + uint8_t buttons = 0; // bit 0 = left, bit 1 = right, bit 2 = middle + float delta_x = 0.0f; // accumulated movements not yet reported + float delta_y = 0.0f; + int8_t counter_x = 0; // position counters, as visible on guest size + int8_t counter_y = 0; + int8_t counter_w = 0; }; #endif // DOSBOX_SERIALMOUSE_H diff --git a/src/ints/bios.cpp b/src/ints/bios.cpp index ebf1c700a..b766c35af 100644 --- a/src/ints/bios.cpp +++ b/src/ints/bios.cpp @@ -1000,8 +1000,8 @@ static Bitu INT15_Handler(void) { CALLBACK_SCF(false); reg_ah = 0; break; - case 0x04: // get type - reg_bh = MOUSEBIOS_GetType(); + case 0x04: // get mouse type/protocol + reg_bh = MOUSEBIOS_GetProtocol(); CALLBACK_SCF(false); reg_ah=0; break; diff --git a/src/ints/meson.build b/src/ints/meson.build index 3d64b9579..34c5859c6 100644 --- a/src/ints/meson.build +++ b/src/ints/meson.build @@ -13,11 +13,6 @@ libints_sources = files( 'int10_vesa.cpp', 'int10_video_state.cpp', 'int10_vptable.cpp', - 'mouse.cpp', - 'mouse_dos_driver.cpp', - 'mouse_ps2_bios.cpp', - 'mouse_serial.cpp', - 'mouse_vmware.cpp', 'xms.cpp', ) diff --git a/src/ints/mouse.cpp b/src/ints/mouse.cpp deleted file mode 100644 index 2dd7fb2c5..000000000 --- a/src/ints/mouse.cpp +++ /dev/null @@ -1,853 +0,0 @@ -/* - * Copyright (C) 2022 The DOSBox Staging Team - * Copyright (C) 2002-2021 The DOSBox Team - * - * 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 "mouse.h" -#include "mouse_core.h" - -#include <algorithm> -#include <array> - -#include "callback.h" -#include "checks.h" -#include "cpu.h" -#include "keyboard.h" -#include "mem.h" -#include "pic.h" -#include "regs.h" -#include "video.h" - -CHECK_NARROWING(); - -MouseShared mouse_shared; -MouseVideo mouse_video; - -bool mouse_seamless_driver = false; -bool mouse_suggest_show = false; - -static MouseButtons12 buttons_12 = 0; // host side buttons 1 (left), 2 (right) -static MouseButtons345 buttons_345 = 0; // host side buttons 3 (middle), 4, and 5 - -static bool raw_input = true; // true = relative input is raw data, without - // host OS mouse acceleration applied - -static Bitu int74_ret_callback = 0; - -// 5 milliseconds delay corresponds to 200 Hz mouse sampling rate (minimum allowed); -// this is a lot, Microsoft Mouse 8.20 sets 60 Hz on PS/2 mouse. -// Note that at least least Ultima Underworld I and II do not like too high values. -static constexpr uint8_t max_delay_ms = 5; - -// *************************************************************************** -// Debug code, normally not enabled -// *************************************************************************** - -// #define DEBUG_QUEUE_ENABLE - -#ifndef DEBUG_QUEUE_ENABLE -# define DEBUG_QUEUE(...) ; -#else -// TODO: after migrating to C++20, allow to skip the 2nd argument by using -// '__VA_OPT__(,) __VA_ARGS__' instead of ', __VA_ARGS__' -# define DEBUG_QUEUE(fmt, ...) \ - LOG_INFO("(queue) %04d: " fmt, DEBUG_GetDiffTicks(), __VA_ARGS__); - -static uint32_t DEBUG_GetDiffTicks() -{ - static uint32_t previous_ticks = 0; - uint32_t diff_ticks = 0; - - if (previous_ticks) - diff_ticks = PIC_Ticks - previous_ticks; - - previous_ticks = PIC_Ticks; - return diff_ticks; -} - -#endif - -// *************************************************************************** -// Mouse button helper functions -// *************************************************************************** - -static MouseButtonsAll get_buttons_joined() -{ - MouseButtonsAll buttons_all; - buttons_all.data = buttons_12.data | buttons_345.data; - - return buttons_all; -} - -static MouseButtons12S get_buttons_squished() -{ - MouseButtons12S buttons_12S; - - // Squish buttons 3/4/5 into single virtual middle button - buttons_12S.data = buttons_12.data; - if (buttons_345.data) - buttons_12S.middle = 1; - - return buttons_12S; -} - -// *************************************************************************** -// Mouse event queue implementation -// *************************************************************************** - -typedef struct MouseEvent { - bool request_ps2 = false; // if PS/2 mouse emulation needs an event - bool request_vmm = false; // if virtual machine mouse needs an event - bool request_dos = false; // if DOS mouse driver needs an event - - bool dos_moved = false; - bool dos_button = false; - bool dos_wheel = false; - MouseButtons12S buttons_12S = 0; - -} MouseEvent; - -static class MouseQueue final { -public: - MouseQueue(); - - void AddEvent(MouseEvent &ev); - void FetchEvent(MouseEvent &ev); - void ClearEventsDOS(); - void StartTimerIfNeeded(); - void Tick(); - - struct { // intial value of delay counters, in milliseconds - uint8_t ps2_ms = max_delay_ms; - uint8_t dos_ms = max_delay_ms; - } start_delay = {}; - -private: - MouseQueue(const MouseQueue &) = delete; - MouseQueue &operator=(const MouseQueue &) = delete; - - void AggregateDosEvents(MouseEvent &ev); - void UpdateDelayCounters(); - - // Time in milliseconds which has to elapse before event can take place - struct { - uint8_t ps2_ms = 0; - uint8_t dos_ms = 0; - } delay = {}; - - // Pending events, waiting to be passed to guest system - bool event_dos_moved = false; - bool event_dos_button = false; - bool event_dos_wheel = false; - bool event_ps2 = false; - - MouseButtons12S event_dos_buttons_state = 0; - - bool timer_in_progress = false; - uint32_t pic_ticks_start = 0; // PIC_Ticks value when timer starts - - // Helpers to check if there are events in the queue - bool HasEventDos() const; - bool HasEventPS2() const; - bool HasEventAny() const; - - // Helpers to check if there are events ready to be handled - bool HasReadyEventDos() const; - bool HasReadyEventPS2() const; - bool HasReadyEventAny() const; - -} queue; - -static void mouse_queue_tick(uint32_t) -{ - queue.Tick(); -} - -MouseQueue::MouseQueue() = default; - -void MouseQueue::AddEvent(MouseEvent &ev) -{ - DEBUG_QUEUE("AddEvent: %s %s %s", - ev.request_ps2 ? "PS2" : "---", - ev.request_vmm ? "VMM" : "---", - ev.request_dos ? "DOS" : "---"); - - // From now on, do not distinguish between VMM and PS/2 - // events - virtual machine managers only - // need to trigger PS/2 packet updates - ev.request_ps2 |= ev.request_vmm; - - // Prevent unnecessary processing - if (ev.request_dos) - AggregateDosEvents(ev); - if (!ev.request_dos && !ev.request_ps2) - return; // event not relevant any more - - bool restart_timer = false; - if (ev.request_ps2) { - if (!HasEventPS2() && timer_in_progress && !delay.ps2_ms) { - DEBUG_QUEUE("AddEvent: restart timer for %s", "PS2"); - // We do not want the timer to start only then DOS event - // gets processed - for minimum latency it is better to - // restart the timer - restart_timer = true; - } - - // Events for PS/2 interfaces (or virtualizer compatible - // drivers) do not carry any information - they are only - // notifications that new data is available for fetching - event_ps2 = true; - } - - if (ev.request_dos) { - if (!HasEventDos() && timer_in_progress && !delay.dos_ms) { - DEBUG_QUEUE("AddEvent: restart timer for %s", "DOS"); - // We do not want the timer to start only then PS/2 - // event gets processed - for minimum latency it is - // better to restart the timer - restart_timer = true; - } - - if (ev.dos_moved) - // Mouse has moved - event_dos_moved = true; - else if (ev.dos_wheel) - // Wheel has moved - event_dos_wheel = true; - else { - // Button press/release - event_dos_button = true; - event_dos_buttons_state.data = get_buttons_squished().data; - } - } - - if (restart_timer) { - timer_in_progress = false; - PIC_RemoveEvents(mouse_queue_tick); - UpdateDelayCounters(); - StartTimerIfNeeded(); - } else if (!timer_in_progress) { - DEBUG_QUEUE("ActivateIRQ, in %s", __FUNCTION__); - // If no timer in progress, handle the event now - PIC_ActivateIRQ(12); - } -} - -void MouseQueue::AggregateDosEvents(MouseEvent &ev) -{ - // We do not need duplicate move / wheel events - if (event_dos_moved) - ev.dos_moved = false; - if (event_dos_wheel) - ev.dos_wheel = false; - - // Same for mouse buttons - but in such case always update button data - if (event_dos_button && ev.dos_button) { - ev.dos_button = false; - event_dos_buttons_state.data = get_buttons_squished().data; - } - - // Check if we still need this event - if (!ev.dos_moved && !ev.dos_wheel && !ev.dos_button) - ev.request_dos = false; -} - -void MouseQueue::FetchEvent(MouseEvent &ev) -{ - // First try (prioritized) DOS events - if (HasReadyEventDos()) { - // Mark event as DOS one - ev.request_dos = true; - ev.dos_moved = event_dos_moved; - ev.dos_button = event_dos_button; - ev.dos_wheel = event_dos_wheel; - ev.buttons_12S = event_dos_buttons_state; - // Set delay before next DOS events - delay.dos_ms = start_delay.dos_ms; - // Clear event information - event_dos_moved = false; - event_dos_button = false; - event_dos_wheel = false; - return; - } - - // Try PS/2 event - if (HasReadyEventPS2()) { - // Mark event as PS/2 one - ev.request_ps2 = true; - // Set delay before next PS/2 events - delay.ps2_ms = start_delay.ps2_ms; - // Clear event information - event_ps2 = false; - return; - } - - // Nothing to provide to interrupt handler, - // event will stay empty -} - -void MouseQueue::ClearEventsDOS() -{ - // Clear DOS relevant part of the queue - event_dos_moved = false; - event_dos_button = false; - event_dos_wheel = false; - delay.dos_ms = 0; - - // If timer is not needed, stop it - if (!HasEventAny()) { - timer_in_progress = false; - PIC_RemoveEvents(mouse_queue_tick); - } -} - -void MouseQueue::StartTimerIfNeeded() -{ - // Do nothing if timer is already in progress - if (timer_in_progress) - return; - - bool timer_needed = false; - uint8_t delay_ms = UINT8_MAX; // dummy delay, will never be used - - if (HasEventPS2() || delay.ps2_ms) { - timer_needed = true; - delay_ms = std::min(delay_ms, delay.ps2_ms); - } - if (HasEventDos() || delay.dos_ms) { - timer_needed = true; - delay_ms = std::min(delay_ms, delay.dos_ms); - } - - // If queue is empty and all expired, we need no timer - if (!timer_needed) - return; - - // Enforce some non-zero delay between events; needed - // for example if DOS interrupt handler is busy - delay_ms = std::max(delay_ms, static_cast<uint8_t>(1)); - - // Start the timer - DEBUG_QUEUE("StartTimer, %d", delay_ms); - pic_ticks_start = PIC_Ticks; - timer_in_progress = true; - PIC_AddEvent(mouse_queue_tick, static_cast<double>(delay_ms)); -} - -void MouseQueue::UpdateDelayCounters() -{ - const uint32_t tmp = (PIC_Ticks > pic_ticks_start) ? (PIC_Ticks - pic_ticks_start) : 1; - uint8_t elapsed = static_cast<uint8_t>( - std::min(tmp, static_cast<uint32_t>(UINT8_MAX))); - if (!pic_ticks_start) - elapsed = 1; - - auto calc_new_delay = [](const uint8_t delay, const uint8_t elapsed) { - return static_cast<uint8_t>((delay > elapsed) ? (delay - elapsed) - : 0); - }; - - delay.ps2_ms = calc_new_delay(delay.ps2_ms, elapsed); - delay.dos_ms = calc_new_delay(delay.dos_ms, elapsed); - - pic_ticks_start = 0; -} - -void MouseQueue::Tick() -{ - DEBUG_QUEUE("%s", "Tick"); - - timer_in_progress = false; - UpdateDelayCounters(); - - // If we have anything to pass to guest side, activate interrupt; - // otherwise start the timer again - if (HasReadyEventAny()) { - DEBUG_QUEUE("ActivateIRQ, in %s", __FUNCTION__); - PIC_ActivateIRQ(12); - } else - StartTimerIfNeeded(); -} - -bool MouseQueue::HasEventDos() const -{ - return event_dos_moved || - event_dos_button || - event_dos_wheel; -} - -bool MouseQueue::HasEventPS2() const -{ - return event_ps2; -} - -bool MouseQueue::HasEventAny() const -{ - return HasEventDos() || HasEventPS2(); -} - -bool MouseQueue::HasReadyEventDos() const -{ - return HasEventDos() && !delay.dos_ms && - // do not launch DOS callback if it's busy - !mouse_shared.dos_cb_running; -} - -bool MouseQueue::HasReadyEventPS2() const -{ - return HasEventPS2() && !delay.ps2_ms; -} - -bool MouseQueue::HasReadyEventAny() const -{ - return HasReadyEventDos() || HasReadyEventPS2(); -} - -// *************************************************************************** -// Common helper calculations -// *************************************************************************** - -float MOUSE_GetBallisticsCoeff(const float speed) -{ - // This routine provides a function for mouse ballistics - //(cursor acceleration), to be reused by various mouse interfaces. - // Since this is a DOS emulator, the acceleration model is - // based on a historic PS/2 mouse specification. - - // Input: mouse speed - // Output: acceleration coefficient (1.0f for speed >= 6.0f) - - // If we don't have raw mouse input, stay with flat profile; - // in such case the acceleration is already handled by the host OS, - // adding our own could lead to hard to predict (most likely - // undesirable) effects - if (!raw_input) - return 1.0f; - - constexpr float a = 0.017153417f; - constexpr float b = 0.382477002f; - constexpr float lowest = 0.5f; - - // Normal PS/2 mouse 2:1 scaling algorithm is just a substitution: - // 0 => 0, 1 => 1, 2 => 1, 3 => 3, 4 => 6, 5 => 9, other x => x * 2 - // and the same for negatives. But we want smooth cursor movement, - // therefore we use approximation model (least square regression, - // 3rd degree polynomial, on points -6, -5, ..., 0, ... , 5, 6, - // here scaled to give f(6.0) = 6.0). Polynomial would be: - // - // f(x) = a*(x^3) + b*(x^1) = x*(a*(x^2) + b) - // - // This C++ function provides not the full polynomial, but rather - // a coefficient (0.0f ... 1.0f) calculated from supplied speed, - // by which the relative mouse measurement should be multiplied - - if (speed > -6.0f && speed < 6.0f) - return std::max((a * speed * speed + b), lowest); - else - return 1.0f; - - // Please consider this algorithm as yet another nod to the past, - // one more small touch of 20th century PC computing history :) -} - -float MOUSE_ClampRelativeMovement(const float rel) -{ - // Enforce upper limit of relative mouse movement - return std::clamp(rel, -2048.0f, 2048.0f); -} - -// *************************************************************************** -// Interrupt 74 implementation -// *************************************************************************** - -static Bitu int74_exit() -{ - SegSet16(cs, RealSeg(CALLBACK_RealPointer(int74_ret_callback))); - reg_ip = RealOff(CALLBACK_RealPointer(int74_ret_callback)); - - return CBRET_NONE; -} - -static Bitu int74_handler() -{ - MouseEvent ev; - queue.FetchEvent(ev); - DEBUG_QUEUE("FetchEvent: %s <<< %s", - ev.request_ps2 ? "PS2" : "---", - ev.request_dos ? "DOS" : "---"); - - // Handle DOS events - if (ev.request_dos) { - uint8_t mask = 0; - if (ev.dos_moved) { - mask = MOUSEDOS_UpdateMoved(); - - // Taken from DOSBox X: HERE within the IRQ 12 handler is the - // appropriate place to redraw the cursor. OSes like Windows 3.1 - // expect real-mode code to do it in response to IRQ 12, not - // "out of the blue" from the SDL event handler like the - // original DOSBox code did it. Doing this allows the INT 33h - // emulation to draw the cursor while not causing Windows 3.1 to - // crash or behave erratically. - if (mask) - MOUSEDOS_DrawCursor(); - } - if (ev.dos_button) - mask = static_cast<uint8_t>(mask | MOUSEDOS_UpdateButtons(ev.buttons_12S)); - if (ev.dos_wheel) - mask = static_cast<uint8_t>(mask | MOUSEDOS_UpdateWheel()); - - // If DOS driver's client is not interested in this particular - // type of event - skip it - if (!MOUSEDOS_HasCallback(mask)) - return int74_exit(); - - CPU_Push16(RealSeg(CALLBACK_RealPointer(int74_ret_callback))); - CPU_Push16(RealOff(static_cast<RealPt>(CALLBACK_RealPointer(int74_ret_callback)) + 7)); - - DEBUG_QUEUE("INT74: %s", "DOS"); - return MOUSEDOS_DoCallback(mask, ev.buttons_12S); - } - - // If BIOS interface is active, use it to handle the event - if (ev.request_ps2 && mouse_shared.active_bios) { - CPU_Push16(RealSeg(CALLBACK_RealPointer(int74_ret_callback))); - CPU_Push16(RealOff(CALLBACK_RealPointer(int74_ret_callback))); - - DEBUG_QUEUE("INT74: %s", "PS2"); - MOUSEPS2_UpdatePacket(); - return MOUSEBIOS_DoCallback(); - } - - // No mouse emulation module is interested in event - return int74_exit(); -} - -Bitu int74_ret_handler() -{ - queue.StartTimerIfNeeded(); - return CBRET_NONE; -} - -// *************************************************************************** -// External notifications -// *************************************************************************** - -void MOUSE_SetConfig(const bool new_raw_input) -{ - raw_input = new_raw_input; -} - -void MOUSE_NewScreenParams(const uint16_t clip_x, const uint16_t clip_y, - const uint16_t res_x, const uint16_t res_y, - const bool fullscreen, const uint16_t x_abs, - const uint16_t y_abs) -{ - mouse_video.clip_x = clip_x; - mouse_video.clip_y = clip_y; - - // Protection against strange window sizes, - // to prevent division by 0 in some places - mouse_video.res_x = std::max(res_x, static_cast<uint16_t>(2)); - mouse_video.res_y = std::max(res_y, static_cast<uint16_t>(2)); - - mouse_video.fullscreen = fullscreen; - - MOUSEVMM_NewScreenParams(x_abs, y_abs); - MOUSE_NotifyStateChanged(); -} - -uint8_t clamp_start_delay(float value_ms) -{ - constexpr long min_ms = 3; // 330 Hz sampling rate - constexpr long max_ms = 100; // 10 Hz sampling rate - - const auto tmp = std::clamp(std::lround(value_ms), min_ms, max_ms); - return static_cast<uint8_t>(tmp); -} - -void MOUSE_NotifyRateDOS(const uint8_t rate_hz) -{ - auto &val_ms = queue.start_delay.dos_ms; - - // Convert rate in Hz to delay in milliseconds - val_ms = clamp_start_delay(1000.0f / rate_hz); - - // Cheat a little, do not allow to set too high delay. Some old - // games might have tried to set lower rate to reduce number of IRQs - // and save CPU power - this is no longer necessary. Windows 3.1 also - // sets a suboptimal value in its PS/2 driver. - val_ms = std::min(val_ms, max_delay_ms); - - // TODO: make a configuration option(s) for boosting sampling rates, - // for DOS and PS/2 mice alike -} - -void MOUSE_NotifyRatePS2(const uint8_t rate_hz) -{ - auto &val_ps2 = queue.start_delay.ps2_ms; - - // Convert rate in Hz to delay in milliseconds - val_ps2 = clamp_start_delay(1000.0f / rate_hz); - - // Cheat a little, like with DOS driver - val_ps2 = std::min(val_ps2, max_delay_ms); -} - -void MOUSE_NotifyResetDOS() -{ - queue.ClearEventsDOS(); -} - -void MOUSE_NotifyStateChanged() -{ - const auto old_seamless_driver = mouse_seamless_driver; - const auto old_mouse_suggest_show = mouse_suggest_show; - - // Prepare suggestions to the GUI - mouse_seamless_driver = mouse_shared.active_vmm && !mouse_video.fullscreen; - mouse_suggest_show = !mouse_shared.active_bios && !mouse_shared.active_dos; - - // TODO: if active_dos, but mouse pointer is outside of defined - // range, suggest to show mouse pointer - - // Do not suggest to show host pointer if fullscreen - // or if autoseamless mode is disabled - mouse_suggest_show &= !mouse_video.fullscreen; - mouse_suggest_show &= !mouse_video.autoseamless; - - // If state has really changed, update the GUI - if (mouse_seamless_driver != old_seamless_driver || - mouse_suggest_show != old_mouse_suggest_show) - GFX_UpdateMouseState(); -} - -void MOUSE_EventMoved(const float x_rel, const float y_rel, - const uint16_t x_abs, const uint16_t y_abs) -{ - // From the GUI we are getting mouse movement data in two - // distinct formats: - // - // - relative; this one has a chance to be raw movements, - // it has to be fed to PS/2 mouse emulation, serial port - // mouse emulation, etc.; any guest side software accessing - // these mouse interfaces will most likely implement it's - // own mouse acceleration/smoothing/etc. - // - absolute; this follows host OS mouse behavior and should - // be fed to VMware seamless mouse emulation and similar - // interfaces - // - // Our DOS mouse driver (INT 33h) is a bit special, as it can - // act both ways (seamless and non-seamless mouse pointer), - // so it needs data in both formats. - - // Notify mouse interfaces - - MouseEvent ev; - - if (!mouse_video.autoseamless || mouse_is_captured) { - MOUSESERIAL_NotifyMoved(x_rel, y_rel); - ev.request_ps2 = MOUSEPS2_NotifyMoved(x_rel, y_rel); - } - ev.request_vmm = MOUSEVMM_NotifyMoved(x_rel, y_rel, x_abs, y_abs); - ev.request_dos = MOUSEDOS_NotifyMoved(x_rel, y_rel, x_abs, y_abs); - - ev.dos_moved = ev.request_dos; - queue.AddEvent(ev); -} - -void MOUSE_NotifyMovedFake() -{ - MouseEvent ev; - ev.request_vmm = true; - - queue.AddEvent(ev); -} - -void MOUSE_EventPressed(uint8_t idx) -{ - const auto buttons_12S_old = get_buttons_squished(); - - switch (idx) { - case 0: // left button - if (buttons_12.left) - return; - buttons_12.left = 1; - break; - case 1: // right button - if (buttons_12.right) - return; - buttons_12.right = 1; - break; - case 2: // middle button - if (buttons_345.middle) - return; - buttons_345.middle = 1; - break; - case 3: // extra button #1 - if (buttons_345.extra_1) - return; - buttons_345.extra_1 = 1; - break; - case 4: // extra button #2 - if (buttons_345.extra_2) - return; - buttons_345.extra_2 = 1; - break; - default: // button not supported - return; - } - - const auto buttons_12S = get_buttons_squished(); - const bool changed_12S = (buttons_12S_old.data != buttons_12S.data); - const uint8_t idx_12S = idx < 2 ? idx : 2; - - MouseEvent ev; - - if (!mouse_video.autoseamless || mouse_is_captured) { - if (changed_12S) { - MOUSESERIAL_NotifyPressedReleased(buttons_12S, idx_12S); - } - ev.request_ps2 = MOUSEPS2_NotifyPressedReleased(buttons_12S, - get_buttons_joined()); - } - if (changed_12S) { - ev.request_vmm = MOUSEVMM_NotifyPressedReleased(buttons_12S); - ev.request_dos = true; - } - - ev.dos_button = ev.request_dos; - queue.AddEvent(ev); -} - -void MOUSE_EventReleased(uint8_t idx) -{ - const auto buttons_12S_old = get_buttons_squished(); - - switch (idx) { - case 0: // left button - if (!buttons_12.left) - return; - buttons_12.left = 0; - break; - case 1: // right button - if (!buttons_12.right) - return; - buttons_12.right = 0; - break; - case 2: // middle button - if (!buttons_345.middle) - return; - buttons_345.middle = 0; - break; - case 3: // extra button #1 - if (!buttons_345.extra_1) - return; - buttons_345.extra_1 = 0; - break; - case 4: // extra button #2 - if (!buttons_345.extra_2) - return; - buttons_345.extra_2 = 0; - break; - default: // button not supported - return; - } - - const auto buttons_12S = get_buttons_squished(); - const bool changed_12S = (buttons_12S_old.data != buttons_12S.data); - const uint8_t idx_12S = idx < 2 ? idx : 2; - - MouseEvent ev; - - // Pass mouse release to all the mice even if host pointer is not - // captured, to prevent strange effects when pointer goes back in the - // window - - ev.request_ps2 = MOUSEPS2_NotifyPressedReleased(buttons_12S, - get_buttons_joined()); - if (changed_12S) { - ev.request_vmm = MOUSEVMM_NotifyPressedReleased(buttons_12S); - ev.request_dos = true; - MOUSESERIAL_NotifyPressedReleased(buttons_12S, idx_12S); - } - - ev.dos_button = ev.request_dos; - queue.AddEvent(ev); -} - -void MOUSE_EventWheel(const int16_t w_rel) -{ - MouseEvent ev; - - if (!mouse_video.autoseamless || mouse_is_captured) { - ev.request_ps2 = MOUSEPS2_NotifyWheel(w_rel); - MOUSESERIAL_NotifyWheel(w_rel); - } - - ev.request_vmm = MOUSEVMM_NotifyWheel(w_rel); - ev.request_dos = MOUSEDOS_NotifyWheel(w_rel); - - ev.dos_wheel = ev.request_dos; - queue.AddEvent(ev); -} - -// *************************************************************************** -// Initialization -// *************************************************************************** - -void MOUSE_Init(Section * /*sec*/) -{ - // Callback for ps2 irq - auto call_int74 = CALLBACK_Allocate(); - CALLBACK_Setup(call_int74, &int74_handler, CB_IRQ12, "int 74"); - // pseudocode for CB_IRQ12: - // sti - // push ds - // push es - // pushad - // callback int74_handler - // ps2 or user callback if requested - // otherwise jumps to CB_IRQ12_RET - // push ax - // mov al, 0x20 - // out 0xa0, al - // out 0x20, al - // pop ax - // cld - // retf - - int74_ret_callback = CALLBACK_Allocate(); - CALLBACK_Setup(int74_ret_callback, &int74_ret_handler, CB_IRQ12_RET, "int 74 ret"); - // pseudocode for CB_IRQ12_RET: - // cli - // mov al, 0x20 - // out 0xa0, al - // out 0x20, al - // callback int74_ret_handler - // popad - // pop es - // pop ds - // iret - - // (MOUSE_IRQ > 7) ? (0x70 + MOUSE_IRQ - 8) : (0x8 + MOUSE_IRQ); - RealSetVec(0x74, CALLBACK_RealPointer(call_int74)); - - MOUSEPS2_Init(); - MOUSEVMM_Init(); - MOUSEDOS_Init(); -} diff --git a/src/ints/mouse_core.h b/src/ints/mouse_core.h deleted file mode 100644 index 43962d2aa..000000000 --- a/src/ints/mouse_core.h +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2022 The DOSBox Staging Team - * - * 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 DOSBOX_MOUSE_CORE_H -#define DOSBOX_MOUSE_CORE_H - -#include "dosbox.h" - -#include "bit_view.h" - -// IntelliMouse Explorer emulation is currently disabled - there is probably -// no way to test it. The IntelliMouse 3.0 software can use it, but it seems -// to require physical PS/2 mouse registers to work correctly, and these -// are not emulated yet. - -// #define ENABLE_EXPLORER_MOUSE - -// *************************************************************************** -// Common defines -// *************************************************************************** - -// Mouse equalization for consistent user experience - please adjust values -// so that on full screen, with RAW mouse input, the mouse feel is similar -// to Windows 3.11 for Workgroups with PS/2 mouse driver and default settings -constexpr float sensitivity_dos = 1.0f; -constexpr float sensitivity_vmm = 3.0f; -// Constants to move 'intersection point' for the acceleration curve -// Requires raw mouse input, otherwise there is no effect -// Larger values = higher mouse acceleration -constexpr float acceleration_vmm = 1.0f; - -// *************************************************************************** -// Common structures and variables -// *************************************************************************** - -class MouseShared { -public: - bool active_bios = false; // true = BIOS has a registered callback - bool active_dos = false; // true = DOS driver has a functioning callback - bool active_vmm = false; // true = VMware-compatible driver is active - - bool dos_cb_running = false; // true = DOS callback is running -}; - -class MouseVideo { -public: - bool fullscreen = true; - - uint16_t res_x = 640; // resolution to which guest image is scaled, - uint16_t res_y = 400; // excluding black borders - - uint16_t clip_x = 0; // clipping = size of black border (one side) - uint16_t clip_y = 0; - - // TODO: once the mechanism is fully implemented, provide an option - // in the configuration file to enable it - bool autoseamless = false; -}; - -extern MouseShared mouse_shared; -extern MouseVideo mouse_video; - -extern bool mouse_is_captured; - -// *************************************************************************** -// Types for storing mouse buttons -// *************************************************************************** - -// NOTE: bit layouts has to be compatible with each other and with INT 33 -// (DOS driver) functions 0x03 / 0x05 / 0x06 and it's callback interface - -union MouseButtons12 { // for storing left and right buttons only - uint8_t data = 0; - - bit_view<0, 1> left; - bit_view<1, 1> right; - - MouseButtons12() : data(0) {} - MouseButtons12(const uint8_t data) : data(data) {} - MouseButtons12(const MouseButtons12 &other) : data(other.data) {} - MouseButtons12 &operator=(const MouseButtons12 &other) - { - data = other.data; - return *this; - } -}; - -union MouseButtons345 { // for storing middle and extra buttons - uint8_t data = 0; - - bit_view<2, 1> middle; - bit_view<3, 1> extra_1; - bit_view<4, 1> extra_2; - - MouseButtons345() : data(0) {} - MouseButtons345(const uint8_t data) : data(data) {} - MouseButtons345(const MouseButtons345 &other) : data(other.data) {} - MouseButtons345 &operator=(const MouseButtons345 &other) - { - data = other.data; - return *this; - } -}; - -union MouseButtonsAll { // for storing all 5 mouse buttons - uint8_t data = 0; - - bit_view<0, 1> left; - bit_view<1, 1> right; - bit_view<2, 1> middle; - bit_view<3, 1> extra_1; - bit_view<4, 1> extra_2; - - MouseButtonsAll() : data(0) {} - MouseButtonsAll(const uint8_t data) : data(data) {} - MouseButtonsAll(const MouseButtonsAll &other) : data(other.data) {} - MouseButtonsAll &operator=(const MouseButtonsAll &other) - { - data = other.data; - return *this; - } -}; - -union MouseButtons12S { // use where buttons 3/4/5 are squished into a virtual - // middle button - uint8_t data = 0; - - bit_view<0, 1> left; - bit_view<1, 1> right; - bit_view<2, 1> middle; - - MouseButtons12S() : data(0) {} - MouseButtons12S(const uint8_t data) : data(data) {} - MouseButtons12S(const MouseButtons12S &other) : data(other.data) {} - MouseButtons12S &operator=(const MouseButtons12S &other) - { - data = other.data; - return *this; - } -}; - -// *************************************************************************** -// Main mouse module -// *************************************************************************** - -float MOUSE_GetBallisticsCoeff(const float speed); -float MOUSE_ClampRelativeMovement(const float rel); - -void MOUSE_NotifyMovedFake(); // for VMware protocol support -void MOUSE_NotifyRateDOS(const uint8_t rate_hz); -void MOUSE_NotifyRatePS2(const uint8_t rate_hz); -void MOUSE_NotifyResetDOS(); -void MOUSE_NotifyStateChanged(); - -// *************************************************************************** -// Serial mouse -// *************************************************************************** - -// - needs relative movements -// - understands up to 3 buttons -// - needs index of button which changed state - -void MOUSESERIAL_NotifyMoved(const float x_rel, const float y_rel); -void MOUSESERIAL_NotifyPressedReleased(const MouseButtons12S buttons_12S, - const uint8_t idx); -void MOUSESERIAL_NotifyWheel(const int16_t w_rel); - -// *************************************************************************** -// PS/2 mouse -// *************************************************************************** - -void MOUSEPS2_Init(); -void MOUSEPS2_UpdateButtonSquish(); -void MOUSEPS2_PortWrite(const uint8_t byte); -void MOUSEPS2_UpdatePacket(); -bool MOUSEPS2_SendPacket(); - -// - needs relative movements -// - understands up to 5 buttons in Intellimouse Explorer mode -// - understands up to 3 buttons in other modes -// - provides a way to generate dummy event, for VMware mouse integration - -bool MOUSEPS2_NotifyMoved(const float x_rel, const float y_rel); -bool MOUSEPS2_NotifyPressedReleased(const MouseButtons12S buttons_12S, - const MouseButtonsAll buttons_all); -bool MOUSEPS2_NotifyWheel(const int16_t w_rel); - -// *************************************************************************** -// BIOS mouse interface for PS/2 mouse -// *************************************************************************** - -uintptr_t MOUSEBIOS_DoCallback(); - -// *************************************************************************** -// VMware protocol extension for PS/2 mouse -// *************************************************************************** - -void MOUSEVMM_Init(); -void MOUSEVMM_NewScreenParams(const uint16_t x_abs, const uint16_t y_abs); -void MOUSEVMM_Deactivate(); - -// - needs absolute mouse position -// - understands up to 3 buttons - -bool MOUSEVMM_NotifyMoved(const float x_rel, const float y_rel, - const uint16_t x_abs, const uint16_t y_abs); -bool MOUSEVMM_NotifyPressedReleased(const MouseButtons12S buttons_12S); -bool MOUSEVMM_NotifyWheel(const int16_t w_rel); - -// *************************************************************************** -// DOS mouse driver -// *************************************************************************** - -// This enum has to be compatible with mask in DOS driver function 0x0c -enum class MouseEventId : uint8_t { - NotDosEvent = 0, - MouseHasMoved = 1 << 0, - PressedLeft = 1 << 1, - ReleasedLeft = 1 << 2, - PressedRight = 1 << 3, - ReleasedRight = 1 << 4, - PressedMiddle = 1 << 5, - ReleasedMiddle = 1 << 6, - WheelHasMoved = 1 << 7, -}; - -void MOUSEDOS_Init(); -void MOUSEDOS_DrawCursor(); - -bool MOUSEDOS_HasCallback(const uint8_t mask); -uintptr_t MOUSEDOS_DoCallback(const uint8_t mask, const MouseButtons12S buttons_12S); -// - needs relative movements -// - understands up to 3 buttons -// - needs index of button which changed state - -bool MOUSEDOS_NotifyMoved(const float x_rel, - const float y_rel, - const uint16_t x_abs, - const uint16_t y_abs); -bool MOUSEDOS_NotifyWheel(const int16_t w_rel); - -uint8_t MOUSEDOS_UpdateMoved(); -uint8_t MOUSEDOS_UpdateButtons(const MouseButtons12S buttons_12S); -uint8_t MOUSEDOS_UpdateWheel(); - -#endif // DOSBOX_MOUSE_CORE_H diff --git a/src/ints/mouse_serial.cpp b/src/ints/mouse_serial.cpp deleted file mode 100644 index 004bbd91f..000000000 --- a/src/ints/mouse_serial.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2022 The DOSBox Staging Team - * - * 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 "mouse.h" -#include "mouse_core.h" - -#include <algorithm> - -#include "checks.h" - -#include "../hardware/serialport/serialmouse.h" - -CHECK_NARROWING(); - -// Implementation here is very primitive, it mainly just passes notifications -// to registered listeners, which emulate a particular mouse on a particular -// serial (COM) port - -static std::vector<CSerialMouse *> listeners; - -static float delta_x = 0.0f; // accumulated mouse movement since last reported -static float delta_y = 0.0f; - -// *************************************************************************** -// Serial interface implementation -// *************************************************************************** - -void MOUSESERIAL_RegisterListener(CSerialMouse &listener) -{ - listeners.push_back(&listener); -} - -void MOUSESERIAL_UnRegisterListener(CSerialMouse &listener) -{ - auto iter = std::find(listeners.begin(), listeners.end(), &listener); - if (iter != listeners.end()) - listeners.erase(iter); -} - -void MOUSESERIAL_NotifyMoved(const float x_rel, const float y_rel) -{ - delta_x += x_rel; - delta_y += y_rel; - - // Clamp the resulting values to something sane, just in case - delta_x = MOUSE_ClampRelativeMovement(delta_x); - delta_y = MOUSE_ClampRelativeMovement(delta_y); - - const int16_t dx = static_cast<int16_t>(std::lround(delta_x)); - const int16_t dy = static_cast<int16_t>(std::lround(delta_y)); - - if (dx != 0 || dy != 0) { - for (auto &listener : listeners) - listener->OnMouseEventMoved(dx, dy); - delta_x -= dx; - delta_y -= dy; - } -} - -void MOUSESERIAL_NotifyPressedReleased(const MouseButtons12S buttons_12S, - const uint8_t idx) -{ - for (auto &listener : listeners) - listener->OnMouseEventButton(buttons_12S.data, idx); -} - -void MOUSESERIAL_NotifyWheel(const int16_t w_rel) -{ - for (auto &listener : listeners) - listener->OnMouseEventWheel(static_cast<int8_t>( - std::clamp(w_rel, - static_cast<int16_t>(INT8_MIN), - static_cast<int16_t>(INT8_MAX)))); -} diff --git a/src/libs/manymouse/meson.build b/src/libs/manymouse/meson.build new file mode 100644 index 000000000..1e16bb73f --- /dev/null +++ b/src/libs/manymouse/meson.build @@ -0,0 +1,18 @@ +manymouse_sources = files([ + 'linux_evdev.c', + 'macosx_hidmanager.c', + 'macosx_hidutilities.c', + 'manymouse.c', + 'windows_wminput.c', + 'x11_xinput2.c', +]) + +libmanymouse = static_library( + 'manymouse', + manymouse_sources, + dependencies: [ + iokit_dep, + ], +) + +manymouse_dep = declare_dependency(link_with : libmanymouse) |