Skip to content

Commit

Permalink
Add support for simulating laser pointer mouse as pen input
Browse files Browse the repository at this point in the history
- Add option to simulate mouse as pen input
- Simulate pen input instead of mouse in many (but not all) places if above is enabled
- Add special handling for pen input laser pointer override
- Use inline initialization for InputSimulator members

- Remove cursor default hotspot handling
This hasn't been correct in the first place, but usually ended up as 0-offset without effect. Restarting Desktop Duplication with pen cursor active would set offset values resulting in wrong coordinates, however.
  • Loading branch information
elvissteinjr committed Mar 28, 2024
1 parent 00635d0 commit c63288c
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 77 deletions.
2 changes: 2 additions & 0 deletions assets/lang/de.ini
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ tstr_SettingsMouseShowCursor=Zeige Cursor
tstr_SettingsMouseShowCursorGCUnsupported=Das Deaktivieren des Cursors für Graphics Capture-Overlays wird von diesem System nicht unterstützt
tstr_SettingsMouseShowCursorGCActiveWarning=Aktive Graphics Capture-Spiegelungen verhindern möglicherweise, dass der Cursor in Desktop Duplication-Overlays ausgeblendet werden kann
tstr_SettingsMouseScrollSmooth=Verwende weiches Scrollen
tstr_SettingsMouseSimulatePen=Simuliere als Stifteingabe
tstr_SettingsMouseSimulatePenUnsupported=Simulation von Stifteingaben wird von diesem System nicht unterstützt
tstr_SettingsMouseAllowLaserPointerOverride=Erlaube Laserpointer-Überbrückung
tstr_SettingsMouseAllowLaserPointerOverrideTip=Deaktiviert den Laserpointer, wenn die physische Maus ruckartig bewegt wird.\nKlicke auf ein Overlay um den Laserpointer wieder zu aktivieren.
tstr_SettingsMouseDoubleClickAssist=Doppelklick-Assistent
Expand Down
2 changes: 2 additions & 0 deletions assets/lang/en.ini
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ tstr_SettingsMouseShowCursor=Show Cursor
tstr_SettingsMouseShowCursorGCUnsupported=Disabling the cursor for Graphics Capture overlays is not supported on this system
tstr_SettingsMouseShowCursorGCActiveWarning=Active Graphics Capture mirrors may stop the cursor from being hidden in Desktop Duplication overlays
tstr_SettingsMouseScrollSmooth=Use Smooth Scrolling
tstr_SettingsMouseSimulatePen=Simulate as Pen Input
tstr_SettingsMouseSimulatePenUnsupported=Pen input simulation is not supported on this system
tstr_SettingsMouseAllowLaserPointerOverride=Allow Laser Pointer Override
tstr_SettingsMouseAllowLaserPointerOverrideTip=Disables the laser pointer when the physical mouse is moved rapidly.\nClick on an overlay to get the laser pointer back.
tstr_SettingsMouseDoubleClickAssist=Double-Click Assistant
Expand Down
20 changes: 20 additions & 0 deletions src/DesktopPlus/ElevatedMode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,26 @@ bool HandleIPCMessage(MSG msg)
input_sim.MouseWheelVertical(pun_cast<float, LPARAM>(msg.lParam));
break;
};
case ipceact_pen_move:
{
input_sim.PenMove(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
break;
};
case ipceact_pen_button_down:
{
(msg.lParam == 0) ? input_sim.PenSetPrimaryDown(true) : input_sim.PenSetSecondaryDown(true);
break;
};
case ipceact_pen_button_up:
{
(msg.lParam == 0) ? input_sim.PenSetPrimaryDown(false) : input_sim.PenSetSecondaryDown(false);
break;
};
case ipceact_pen_leave:
{
input_sim.PenLeave();
break;
};
case ipceact_key_down:
{
//Copy 3 keycodes from lparam
Expand Down
172 changes: 168 additions & 4 deletions src/DesktopPlus/InputSimulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,33 @@ enum KeyboardWin32KeystateFlags
kbd_w32keystate_flag_alt_down = 1 << 2
};

InputSimulator::InputSimulator() :
m_SpaceMultiplierX(1.0f), m_SpaceMultiplierY(1.0f), m_SpaceOffsetX(0), m_SpaceOffsetY(0), m_ForwardToElevatedModeProcess(false), m_ElevatedModeHasTextQueued(false)
fn_CreateSyntheticPointerDevice InputSimulator::s_p_CreateSyntheticPointerDevice = nullptr;
fn_InjectSyntheticPointerInput InputSimulator::s_p_InjectSyntheticPointerInput = nullptr;
fn_DestroySyntheticPointerDevice InputSimulator::s_p_DestroySyntheticPointerDevice = nullptr;

InputSimulator::InputSimulator()
{
RefreshScreenOffsets();

//Try to init pen functions if they're not loaded yet
if (!IsPenSimulationSupported())
{
LoadPenFunctions();
}

//Init pen state (rest can stay at default 0)
m_PenState.type = PT_PEN;
m_PenState.penInfo.pointerInfo.pointerType = PT_PEN;
m_PenState.penInfo.pressure = 1024; //Full pressure
m_PenState.penInfo.penMask = PEN_MASK_PRESSURE; //Marked as optional, but without, applications expecting it may read 0 pressure still
}

InputSimulator::~InputSimulator()
{
if (m_PenDevice != nullptr)
{
s_p_DestroySyntheticPointerDevice(m_PenDevice);
}
}

void InputSimulator::SetEventForMouseKeyCode(INPUT& input_event, unsigned char keycode, bool down)
Expand Down Expand Up @@ -123,15 +146,38 @@ bool InputSimulator::SetEventForKeyCode(INPUT& input_event, unsigned char keycod
return true;
}

void InputSimulator::LoadPenFunctions()
{
HMODULE h_user32 = ::LoadLibraryW(L"user32.dll");

if (h_user32 != nullptr)
{
s_p_CreateSyntheticPointerDevice = (fn_CreateSyntheticPointerDevice) GetProcAddress(h_user32, "CreateSyntheticPointerDevice");
s_p_InjectSyntheticPointerInput = (fn_InjectSyntheticPointerInput) GetProcAddress(h_user32, "InjectSyntheticPointerInput");
s_p_DestroySyntheticPointerDevice = (fn_DestroySyntheticPointerDevice)GetProcAddress(h_user32, "DestroySyntheticPointerDevice");
}
}

void InputSimulator::CreatePenDeviceIfNeeded()
{
if (m_PenDevice == nullptr)
{
m_PenDevice = s_p_CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_INDIRECT);
}
}

void InputSimulator::RefreshScreenOffsets()
{
if (m_ForwardToElevatedModeProcess)
{
IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_refresh);
}

m_SpaceMultiplierX = 65536.0f / GetSystemMetrics(SM_CXVIRTUALSCREEN);
m_SpaceMultiplierY = 65536.0f / GetSystemMetrics(SM_CYVIRTUALSCREEN);
m_SpaceMaxX = GetSystemMetrics(SM_CXVIRTUALSCREEN);
m_SpaceMaxY = GetSystemMetrics(SM_CYVIRTUALSCREEN);

m_SpaceMultiplierX = 65536.0f / m_SpaceMaxX;
m_SpaceMultiplierY = 65536.0f / m_SpaceMaxY;

m_SpaceOffsetX = GetSystemMetrics(SM_XVIRTUALSCREEN) * -1;
m_SpaceOffsetY = GetSystemMetrics(SM_YVIRTUALSCREEN) * -1;
Expand Down Expand Up @@ -204,6 +250,119 @@ void InputSimulator::MouseWheelVertical(float delta)
::SendInput(1, &input_event, sizeof(INPUT));
}

void InputSimulator::PenMove(int x, int y)
{
if (!IsPenSimulationSupported())
return;

if (m_ForwardToElevatedModeProcess)
{
IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_pen_move, MAKELPARAM(x, y));
return;
}

CreatePenDeviceIfNeeded();

m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_INRANGE | POINTER_FLAG_UPDATE;

//Pen input position doesn't appear to be clamped by OS like mouse input and can do weird things if it goes out of range
m_PenState.penInfo.pointerInfo.ptPixelLocation.x = clamp(x, -m_SpaceOffsetX, m_SpaceMaxX - m_SpaceOffsetX);
m_PenState.penInfo.pointerInfo.ptPixelLocation.y = clamp(y, -m_SpaceOffsetY, m_SpaceMaxY - m_SpaceOffsetY);

s_p_InjectSyntheticPointerInput(m_PenDevice, &m_PenState, 1);
}

void InputSimulator::PenSetPrimaryDown(bool down)
{
if (!IsPenSimulationSupported())
return;

if (m_ForwardToElevatedModeProcess)
{
IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, (down) ? ipceact_pen_button_down : ipceact_pen_button_up, 0);
return;
}

CreatePenDeviceIfNeeded();

if (down)
{
m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN | POINTER_FLAG_FIRSTBUTTON;
m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_DOWN;
}
else
{
m_PenState.penInfo.pointerInfo.pointerFlags &= ~(POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN | POINTER_FLAG_FIRSTBUTTON);
m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_UP;
m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_UP;
}

m_PenState.penInfo.pointerInfo.pointerFlags &= ~POINTER_FLAG_UPDATE;

s_p_InjectSyntheticPointerInput(m_PenDevice, &m_PenState, 1);

m_PenState.penInfo.pointerInfo.pointerFlags &= ~(POINTER_FLAG_DOWN | POINTER_FLAG_UP);
m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_NONE;
}

void InputSimulator::PenSetSecondaryDown(bool down)
{
if (!IsPenSimulationSupported())
return;

if (m_ForwardToElevatedModeProcess)
{
IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, (down) ? ipceact_pen_button_down : ipceact_pen_button_up, 1);
return;
}

CreatePenDeviceIfNeeded();

if (down)
{
m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN | POINTER_FLAG_SECONDBUTTON;
m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_SECONDBUTTON_DOWN;
m_PenState.penInfo.penFlags = PEN_FLAG_BARREL;
}
else
{
m_PenState.penInfo.pointerInfo.pointerFlags &= ~(POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN | POINTER_FLAG_SECONDBUTTON);
m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_UP;
m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_SECONDBUTTON_UP;
m_PenState.penInfo.penFlags = 0;
}

m_PenState.penInfo.pointerInfo.pointerFlags &= ~POINTER_FLAG_UPDATE;

s_p_InjectSyntheticPointerInput(m_PenDevice, &m_PenState, 1);

m_PenState.penInfo.pointerInfo.pointerFlags &= ~(POINTER_FLAG_DOWN | POINTER_FLAG_UP);
m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_NONE;
}

void InputSimulator::PenLeave()
{
if (!IsPenSimulationSupported())
return;

if (m_ForwardToElevatedModeProcess)
{
IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_pen_leave);
return;
}

CreatePenDeviceIfNeeded();

if (m_PenState.penInfo.pointerInfo.pointerFlags & POINTER_FLAG_INRANGE)
{
m_PenState.penInfo.pointerInfo.pointerFlags = POINTER_FLAG_UPDATE;
m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_NONE;
m_PenState.penInfo.penFlags = 0;

s_p_InjectSyntheticPointerInput(m_PenDevice, &m_PenState, 1);
}
}

void InputSimulator::KeyboardSetDown(unsigned char keycode)
{
if (keycode == 0)
Expand Down Expand Up @@ -698,3 +857,8 @@ bool InputSimulator::IsKeyDown(unsigned char keycode)

return (::GetAsyncKeyState(keycode) < 0);
}

bool InputSimulator::IsPenSimulationSupported()
{
return (s_p_CreateSyntheticPointerDevice != nullptr);
}
38 changes: 32 additions & 6 deletions src/DesktopPlus/InputSimulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,44 @@

enum IPCKeyboardKeystateFlags : unsigned char;

//SyntheticPointer functions are loaded manually to not require OS support to run the application (Windows 10 1809+ should have them though)
typedef HANDLE HSYNTHETICPOINTERDEVICE;

typedef HSYNTHETICPOINTERDEVICE (WINAPI* fn_CreateSyntheticPointerDevice) (_In_ POINTER_INPUT_TYPE pointerType, _In_ ULONG maxCount, _In_ POINTER_FEEDBACK_MODE mode);
typedef BOOL (WINAPI* fn_InjectSyntheticPointerInput) (_In_ HSYNTHETICPOINTERDEVICE device, _In_reads_(count) CONST POINTER_TYPE_INFO* pointerInfo, _In_ UINT32 count);
typedef void (WINAPI* fn_DestroySyntheticPointerDevice)(_In_ HSYNTHETICPOINTERDEVICE device);

class InputSimulator
{
private:
float m_SpaceMultiplierX;
float m_SpaceMultiplierY;
int m_SpaceOffsetX;
int m_SpaceOffsetY;
int m_SpaceMaxX = 0;
int m_SpaceMaxY = 0;
float m_SpaceMultiplierX = 1.0f;
float m_SpaceMultiplierY = 1.0f;
int m_SpaceOffsetX = 0;
int m_SpaceOffsetY = 0;

static fn_CreateSyntheticPointerDevice s_p_CreateSyntheticPointerDevice;
static fn_InjectSyntheticPointerInput s_p_InjectSyntheticPointerInput;
static fn_DestroySyntheticPointerDevice s_p_DestroySyntheticPointerDevice;

HSYNTHETICPOINTERDEVICE m_PenDevice = nullptr;
POINTER_TYPE_INFO m_PenState = {0};

std::vector<INPUT> m_KeyboardTextQueue;
bool m_ForwardToElevatedModeProcess;
bool m_ElevatedModeHasTextQueued;
bool m_ForwardToElevatedModeProcess = false;
bool m_ElevatedModeHasTextQueued = false;

void CreatePenDeviceIfNeeded();

static void LoadPenFunctions();
static void SetEventForMouseKeyCode(INPUT& input_event, unsigned char keycode, bool down);
//Set the event if it would change key state. Returns if anything was written to input_event
static bool SetEventForKeyCode(INPUT& input_event, unsigned char keycode, bool down, bool skip_check = false);

public:
InputSimulator();
~InputSimulator();
void RefreshScreenOffsets();

void MouseMove(int x, int y);
Expand All @@ -39,6 +59,11 @@ class InputSimulator
void MouseWheelHorizontal(float delta);
void MouseWheelVertical(float delta);

void PenMove(int x, int y);
void PenSetPrimaryDown(bool down);
void PenSetSecondaryDown(bool down);
void PenLeave();

void KeyboardSetDown(unsigned char keycode);
void KeyboardSetDown(unsigned char keycode, bool down);
void KeyboardSetUp(unsigned char keycode);
Expand All @@ -55,6 +80,7 @@ class InputSimulator

void SetElevatedModeForwardingActive(bool do_forward);

static bool IsPenSimulationSupported();
static bool IsKeyDown(unsigned char keycode);
};

Expand Down
Loading

0 comments on commit c63288c

Please sign in to comment.