Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dosbox-staging/dosbox-staging.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFeralChild64 <unknown>2022-10-07 08:29:11 +0300
committerkcgen <1557255+kcgen@users.noreply.github.com>2022-10-22 21:15:34 +0300
commitb2e3cef0eb5b297eed194c92e6fd16254bd50910 (patch)
treee9298418e2a39c4c70820323f3b1fadda64a31ea /src
parent1d8a5cfa903398d82513f86a155b22abec6901eb (diff)
Add mouse mapper, config tool and config section
Diffstat (limited to 'src')
-rw-r--r--src/config.h.in3
-rw-r--r--src/dos/dos_programs.cpp2
-rw-r--r--src/dos/meson.build1
-rw-r--r--src/dos/program_boot.cpp4
-rw-r--r--src/dos/program_mousectl.cpp522
-rw-r--r--src/dos/program_mousectl.h75
-rw-r--r--src/dosbox.cpp13
-rw-r--r--src/gui/sdlmain.cpp39
-rw-r--r--src/hardware/meson.build9
-rw-r--r--src/hardware/mouse/mouse.cpp630
-rw-r--r--src/hardware/mouse/mouse_common.cpp204
-rw-r--r--src/hardware/mouse/mouse_common.h193
-rw-r--r--src/hardware/mouse/mouse_config.cpp254
-rw-r--r--src/hardware/mouse/mouse_config.h105
-rw-r--r--src/hardware/mouse/mouse_interfaces.cpp878
-rw-r--r--src/hardware/mouse/mouse_interfaces.h241
-rw-r--r--src/hardware/mouse/mouse_manymouse.cpp466
-rw-r--r--src/hardware/mouse/mouse_manymouse.h116
-rw-r--r--src/hardware/mouse/mouse_queue.cpp312
-rw-r--r--src/hardware/mouse/mouse_queue.h84
-rw-r--r--src/hardware/mouse/mouseif_dos_driver.cpp (renamed from src/ints/mouse_dos_driver.cpp)473
-rw-r--r--src/hardware/mouse/mouseif_ps2_bios.cpp (renamed from src/ints/mouse_ps2_bios.cpp)134
-rw-r--r--src/hardware/mouse/mouseif_virtual_machines.cpp (renamed from src/ints/mouse_vmware.cpp)152
-rw-r--r--src/hardware/serialport/serialmouse.cpp382
-rw-r--r--src/hardware/serialport/serialmouse.h131
-rw-r--r--src/ints/bios.cpp4
-rw-r--r--src/ints/meson.build5
-rw-r--r--src/ints/mouse.cpp853
-rw-r--r--src/ints/mouse_core.h261
-rw-r--r--src/ints/mouse_serial.cpp89
-rw-r--r--src/libs/manymouse/meson.build18
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 = [&params](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> &params,
+ std::vector<MouseInterfaceId> &list_ids)
+{
+ while (params.size()) {
+ auto compare = [&params](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 &param)
+{
+ 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> &params,
+ 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 &param);
+ 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 &regex)
+{
+ // 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 &regex)
+{
+ 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 &regex)
+{
+ // 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 &regex);
+
+ 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)