Skip to content

Commit 31e9ff6

Browse files
authored
V0 initial checkin
1 parent 3028d0f commit 31e9ff6

13 files changed

Lines changed: 1475 additions & 0 deletions

readme.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# HOJAM #
2+
3+
### *(Mouse-as-throttle -- experimental prototype)* ###
4+
5+
Mouselook Gang!
6+
7+
Are you tired of reaching back and forth, from your mouse to the dodgy 8-bit slider on your cheapo-joystick? Me too.
8+
9+
I wondered, what if we could control the throttle simply by *dragging* the mouse forward and back? After all, there isn't any other interaction in the 3D cockpit controlled by drag/drop which would interfere with that.
10+
11+
So I built this simple prototype as a proof-of-concept, to try it out -- it works by hooking mouse input in the background, watching for left-button-drag movement, and generating mousewheel-rotation input events in response. If you assign throttle-axis to the mousewheel in BMS, voila -- your mouse is now a HOTAS throttle! (With far fewer knobs and buttons.. but hey you can use it to look around.)
12+
13+
Currently this is just a crude prototype -- but I'd like to get feedback and hear ideas before developing it further. (Is this useful for anyone but me?) Here are the details for installation and tips for configuration:
14+
15+
## Instructions ##
16+
17+
#### *Download and Install:*
18+
19+
- <u>TODO: github link</u>
20+
- Simply download and unzip, anywhere you like. No installer, no dependencies, and no special permissions required.
21+
- Should run ok on Windows 10 version 1903 and later (requires .NET Framework 4.8).
22+
23+
#### *Review settings in `DragWheel.exe.config` file:*
24+
25+
- (optional) Adjust the `MouseResolution` value in `DragWheel.exe.config` if your mouse is more or less sensitive than mine. For a 1200 dpi mouse the total "throw" of travel for the throttle is about 2.5 inches of mouse movement.
26+
27+
- (protip) You can also change the `MouseButton` used to manipulate the throttle .. button #0 (left-button) is the default. But if you have a 5-button mouse I recommend using button #3 or #4 (aka zero-based index of button-4 or -5) so there's no possibility of an errant click, when beginning to drag.
28+
- (protip) I've also added the ability to use a `JoystickButton` instead of a mouse button. If your stick has a pinky/dx-shift configured, I've found that can work well for engaging the throttle.
29+
30+
#### *Review related settings in `falcon bms.cfg` file:*
31+
32+
- (recommended) Disable wheel-knobs to avoid grabbing and spinning random knobs by accident:
33+
`set g_bMouseWheelKnobs 0`
34+
- (recommended) Disable clickable-hotspot anchoring, to clear the way for smooth uniform, mouse dragging:
35+
`set g_b3DClickableCursorAnchored 0`
36+
37+
#### *Pre-flight:*
38+
39+
- Simply launch `DragWheel.exe` before launching BMS .. it will open in a console window. Left-click and drag the window up and down, by its titlebar, and observe the stream of console output to verify it's working.
40+
- Relocate your mouse to the left side of your keyboard .. wherever you'd place your hotas throttle if you had one.
41+
42+
#### *In BMS:*
43+
44+
- Setup / Controllers => reassign the throttle to the mousewheel.
45+
- (recommended) Set the mousewheel sensitivity slider all the way to minimum, for finely-tuned throttle control.
46+
- (consider) Whether you want to fly with clickable-cockpit on or off (mouselook mode) by default. (See protip below.)
47+
- (optional) Assign your throttle slider to something else if you want, eg. FOV or toe-brakes.
48+
49+
#### *In the jet:*
50+
51+
- Use right-button-drag to look around, as you normally do.
52+
- ***Use left-button-drag (up and down) to control the throttle (forward and back, respectively).***
53+
- (protip) If you prefer to fly with mouselook engaged, you can press and hold both left+right buttons simultaneously to move the throttle without panning the view up or down. (Reminder: [alt+3] is the default key binding to toggle between mouselook and clickable-cockpit modes.)
54+
55+
#### *Debrief:*
56+
57+
- Close DragWheel.exe by pressing [ctrl+C] or simply close its console window.
58+
59+
## Benefits
60+
61+
- HOTAS flying sensation, on a shoestring budget -- experience the ability to look around while keeping hands on stick and "throttle"!
62+
63+
- Vastly increased throttle sensitivity, compared to most cheap 8-bit consumer joystick sliders (eg. Thrustmaster T.16000M, or Logitech Extreme 3D Pro) .. makes easier landing, taxiing, holding formation, etc.
64+
65+
66+
## Caveats
67+
68+
- This is just a prototype / proof of concept. The most obvious downside to this approach is, you lose ability to control FOV with the mousewheel.
69+
- Setting `g_fNarrowFOV 40` in bms.cfg can help mitigate that, if you assign `FOVToggle` "Look Closer" to a stick button you can make regular use of that while keeping hotas.
70+
- If your stick has a throttle-slider, consider assigning that to control FOV.
71+
- Mapping mousewheel-to-throttle isn't well supported or documented by BMS. There are many quirks.. eg. if you click the middle-button it will still reset fov, like normal, but also it will snap your throttle to the 50% point. And beware taxiway-starts -- be ready on the wheelbrakes -- you will spawn in with throttle immediately set to 50%!
72+
- Sometimes the throttle in BMS will stop responding to mousewheel entirely .. when that happens, clicking the wheel (middle) button usually re-engages it.. usually.
73+
74+
## Future
75+
76+
If this idea generates enough interest, I plan to pursue a different approach based on vJoy (or similar framework) to drive a virtual-axis for the throttle instead of relying on mousewheel.
77+
78+
That should be more stable, supportable, and allow the mousewheel to return to its rightful role controlling FOV.
79+
80+
With full control of a virtual-axis, we can also add the possibility of configurable detents (eg. stop the throttle at MIL power; require a second drag to go into AB). Or perhaps add a 2nd axis to control two engines independently -- perhaps by dragging with buttons 4 & 5 held (for 5-button mice) or dragging with a modifier key held. Or .. whatever other crazy ideas we come up with.
81+

src/App.config

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<configuration>
3+
<startup>
4+
<!--run on Windows 10 version 1903 or later-->
5+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
6+
</startup>
7+
8+
<appSettings>
9+
<!--mouse sensitivity (DPI) .. for typical 1200 dpi mouse, the total "throw"
10+
distance of the throttle is approx 2.5 inches of mouse travel-->
11+
<add key="MouseResolution" value="1200"/>
12+
13+
<!--mouse button to engage throttle [0=LeftButton ... 4=XButton-Forward] -->
14+
<add key="MouseButton" value="0"/>
15+
16+
<!--(optional) joystick button to engage throttle [0-31] -->
17+
<add key="JoystickButton" value="0"/>
18+
</appSettings>
19+
20+
</configuration>

src/Config.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Configuration;
3+
using System.Diagnostics;
4+
5+
namespace DragWheel
6+
{
7+
internal static class Config
8+
{
9+
//----------------------------------------
10+
public static int MouseResolution
11+
{
12+
get
13+
{
14+
string configValue = ConfigurationManager.AppSettings["MouseResolution"];
15+
16+
if (String.IsNullOrEmpty(configValue))
17+
return defaultMouseResolution;
18+
19+
return Int32.Parse(configValue, System.Globalization.CultureInfo.InvariantCulture);
20+
}
21+
}
22+
static int defaultMouseResolution = 1200;//dpi
23+
24+
//----------------------------------------
25+
public static int? MouseButton
26+
{
27+
get
28+
{
29+
string configValue = ConfigurationManager.AppSettings["MouseButton"];
30+
31+
if (String.IsNullOrEmpty(configValue))
32+
return null;
33+
34+
int value = Int32.Parse(configValue, System.Globalization.CultureInfo.InvariantCulture);
35+
if (value < 0 || value > 4)
36+
throw new ConfigurationErrorsException("MouseButton must be in range [0-4]");
37+
38+
return value;
39+
}
40+
}
41+
42+
//----------------------------------------
43+
public static int? JoystickButton
44+
{
45+
get
46+
{
47+
string configValue = ConfigurationManager.AppSettings["JoystickButton"];
48+
49+
if (String.IsNullOrEmpty(configValue))
50+
return null;
51+
52+
int value = Int32.Parse(configValue, System.Globalization.CultureInfo.InvariantCulture);
53+
if (value < 0 || value > 31)
54+
throw new ConfigurationErrorsException("JoystickButton must be in range [0-31]");
55+
56+
return value;
57+
}
58+
}
59+
}
60+
61+
//------------------------------------------------------------------
62+
internal static partial class Tests
63+
{
64+
internal static void Config_NullOrEmpty( )
65+
{
66+
string configValue = ConfigurationManager.AppSettings["__NonExistent__"];
67+
Debug.Assert(String.IsNullOrEmpty(configValue));
68+
}
69+
}
70+
}

src/DragWheel.csproj

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<ProjectGuid>{68C3A8AD-3FA0-4CF9-A942-F7056E61EDC1}</ProjectGuid>
8+
<OutputType>Exe</OutputType>
9+
<RootNamespace>DragWheel</RootNamespace>
10+
<AssemblyName>DragWheel</AssemblyName>
11+
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
12+
<FileAlignment>512</FileAlignment>
13+
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
14+
<Deterministic>true</Deterministic>
15+
</PropertyGroup>
16+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
17+
<PlatformTarget>AnyCPU</PlatformTarget>
18+
<DebugSymbols>true</DebugSymbols>
19+
<DebugType>full</DebugType>
20+
<Optimize>false</Optimize>
21+
<OutputPath>bin\Debug\</OutputPath>
22+
<DefineConstants>DEBUG;TRACE</DefineConstants>
23+
<ErrorReport>prompt</ErrorReport>
24+
<WarningLevel>4</WarningLevel>
25+
<Prefer32Bit>false</Prefer32Bit>
26+
</PropertyGroup>
27+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
28+
<PlatformTarget>AnyCPU</PlatformTarget>
29+
<DebugType>pdbonly</DebugType>
30+
<Optimize>true</Optimize>
31+
<OutputPath>bin\Release\</OutputPath>
32+
<DefineConstants>TRACE</DefineConstants>
33+
<ErrorReport>prompt</ErrorReport>
34+
<WarningLevel>4</WarningLevel>
35+
<Prefer32Bit>false</Prefer32Bit>
36+
</PropertyGroup>
37+
<ItemGroup>
38+
<Reference Include="System" />
39+
<Reference Include="System.Configuration" />
40+
<Reference Include="System.Core" />
41+
<Reference Include="Microsoft.CSharp" />
42+
</ItemGroup>
43+
<ItemGroup>
44+
<Compile Include="Config.cs" />
45+
<Compile Include="RawInputJoystickHandler.cs" />
46+
<Compile Include="MouseDragTracker.cs" />
47+
<Compile Include="RawInputMouseHandler.cs" />
48+
<Compile Include="Win32_RawInput.cs" />
49+
<Compile Include="Win32_MessageWindow.cs" />
50+
<Compile Include="Win32_SendInput.cs" />
51+
<Compile Include="Program.cs" />
52+
<Compile Include="Properties\AssemblyInfo.cs" />
53+
</ItemGroup>
54+
<ItemGroup>
55+
<None Include="App.config" />
56+
</ItemGroup>
57+
<ItemGroup>
58+
<WCFMetadata Include="Connected Services\" />
59+
</ItemGroup>
60+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
61+
</Project>

src/DragWheel.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.31025.194
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DragWheel", "DragWheel.csproj", "{68C3A8AD-3FA0-4CF9-A942-F7056E61EDC1}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{68C3A8AD-3FA0-4CF9-A942-F7056E61EDC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{68C3A8AD-3FA0-4CF9-A942-F7056E61EDC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{68C3A8AD-3FA0-4CF9-A942-F7056E61EDC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{68C3A8AD-3FA0-4CF9-A942-F7056E61EDC1}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {502EB76F-6CC1-4ACA-9AA5-D4CBAAF7DDF7}
24+
EndGlobalSection
25+
EndGlobal

src/MouseDragTracker.cs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+

2+
/*
3+
* While left button is down (or optional joystick button held) track the net drag distance, and
4+
* high/low water marks from start of the drag. When drag distance exceeds the threshold in any
5+
* direction (ie. distance above low-water mark, or below high-water mark) generate a mousewheel
6+
* command, then update the high/low water marks accordingly.
7+
*/
8+
using System;
9+
using System.Diagnostics;
10+
11+
namespace DragWheel
12+
{
13+
internal static class MouseDragTracker
14+
{
15+
// Virtualized coords, measured from start-of-drag position.
16+
static int _xPos, _yPos;
17+
static int _xHi, _yHi;
18+
static int _xLo, _yLo;
19+
20+
//--------------------------------------------------------------
21+
// Interface
22+
23+
//----------------------------------------
24+
static MouseDragTracker( )
25+
{
26+
ResetDragDeltas();
27+
}
28+
29+
//----------------------------------------
30+
public static void ResetDragDeltas( )
31+
{
32+
_xPos = _yPos = 0;
33+
_xHi = _yHi = 0;
34+
_xLo = _yLo = 0;
35+
return;
36+
}
37+
38+
//----------------------------------------
39+
public static int TrackDragDeltas( int deltaX, int deltaY )
40+
{
41+
// Accum drag-distance, and ratchet up or down the water marks.
42+
_xPos += deltaX;
43+
_yPos += deltaY;
44+
45+
_xHi = Math.Max(_xPos, _xHi);
46+
_yHi = Math.Max(_yPos, _yHi);
47+
48+
_xLo = Math.Min(_xPos, _xLo);
49+
_yLo = Math.Min(_yPos, _yLo);
50+
51+
// If drag exceeds threshold distance from baseline, send mousewheel command(s) and update baseline.
52+
// The total "throw" of the throttle in BMS is 125 mousewheel-clicks. Mapping that to 2.50 inches of
53+
// mouse travel, is 50 wheel-clicks/inch of travel. For a typical 1200dpi mouse, that works out
54+
// to 24 mickies per wheel-click.
55+
int distanceThreshold = Config.MouseResolution / 50; // @1200dpi => 24 mickies (~0.02in)
56+
57+
if (_yPos >= (_yLo + distanceThreshold))
58+
return CalcWheelDeltaAndUpdateBaseline(_yPos - _yLo);
59+
60+
if (_yPos <= (_yHi - distanceThreshold))
61+
return CalcWheelDeltaAndUpdateBaseline(_yPos - _yHi);
62+
63+
return 0;
64+
65+
int CalcWheelDeltaAndUpdateBaseline( int draggedDistance )
66+
{
67+
int quantizedDistance = (int)(draggedDistance / distanceThreshold);
68+
69+
if (quantizedDistance < 0)
70+
_yHi += (int)(quantizedDistance * distanceThreshold);
71+
72+
if (quantizedDistance > 0)
73+
_yLo += (int)(quantizedDistance * distanceThreshold);
74+
75+
return quantizedDistance * 120;//WHEEL_DELTA, one standardized "notch" of mousewheel rotation
76+
}
77+
}
78+
}
79+
80+
//------------------------------------------------------------------
81+
internal static partial class Tests
82+
{
83+
//----------------------------------------
84+
internal static void MouseTracker_Up( )
85+
{
86+
MouseDragTracker.ResetDragDeltas();
87+
88+
int dy = Config.MouseResolution / 50 + 1;
89+
Debug.Assert(+120 == MouseDragTracker.TrackDragDeltas(0, +dy));
90+
Debug.Assert(+120 == MouseDragTracker.TrackDragDeltas(0, +dy));
91+
}
92+
93+
//----------------------------------------
94+
internal static void MouseTracker_Down( )
95+
{
96+
MouseDragTracker.ResetDragDeltas();
97+
MouseTracker_Up();
98+
99+
int dy = Config.MouseResolution / 50 + 1;
100+
Debug.Assert(-120 == MouseDragTracker.TrackDragDeltas(0, -dy));
101+
Debug.Assert(-120 == MouseDragTracker.TrackDragDeltas(0, -dy));
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)