mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
[idd] input: receive LGMP input reports
Add an optional input transport and a dedicated LGMP receiver that validates source ownership, generations, ordered sequences, and leases. Poll input outside the 10 ms control timer. Forward mouse and keyboard state through the LGInput pipe, and neutralize the endpoint before ownership changes or transport failures can leave input held.
This commit is contained in:
@@ -65,6 +65,7 @@
|
||||
<ClCompile Include="transport\lgmp\CLGMPControl.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPFrameTransport.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPHost.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPInputTransport.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPTransport.cpp" />
|
||||
<ClCompile Include="config\CSettings.cpp" />
|
||||
<ClCompile Include="platform\CPlatformInfo.cpp" />
|
||||
@@ -74,6 +75,7 @@
|
||||
<ClInclude Include="Driver.h" />
|
||||
<ClInclude Include="Public.h" />
|
||||
<ClInclude Include="Trace.h" />
|
||||
<ClInclude Include="input\IInputSink.h" />
|
||||
<ClInclude Include="ipc\CInputPipeServer.h" />
|
||||
<ClInclude Include="ipc\CPipeServer.h" />
|
||||
<ClInclude Include="display\CDisplayConfiguration.h" />
|
||||
@@ -107,6 +109,7 @@
|
||||
<ClInclude Include="transport\FrameMemoryLimits.h" />
|
||||
<ClInclude Include="transport\IControlTransport.h" />
|
||||
<ClInclude Include="transport\IFrameTransport.h" />
|
||||
<ClInclude Include="transport\IInputTransport.h" />
|
||||
<ClInclude Include="transport\ITransport.h" />
|
||||
<ClInclude Include="transport\PreparedFrameBuffer.h" />
|
||||
<ClInclude Include="transport\TransportFactory.h" />
|
||||
@@ -114,6 +117,7 @@
|
||||
<ClInclude Include="transport\lgmp\CLGMPControl.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPFrameTransport.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPHost.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPInputTransport.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPTransport.h" />
|
||||
<ClInclude Include="config\CSettings.h" />
|
||||
<ClInclude Include="platform\CPlatformInfo.h" />
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<Filter Include="IPC">
|
||||
<UniqueIdentifier>{6A9D97A5-1A6C-4A95-A2D0-A7C2D6BE459D}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Input">
|
||||
<UniqueIdentifier>{B37D983B-12F1-41AE-8375-1B9B90A64F87}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Display">
|
||||
<UniqueIdentifier>{D2C16E51-6087-4E08-8DA4-5AD1A60EF58E}</UniqueIdentifier>
|
||||
</Filter>
|
||||
@@ -61,6 +64,9 @@
|
||||
<ClInclude Include="Trace.h">
|
||||
<Filter>Driver</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="input\IInputSink.h">
|
||||
<Filter>Input</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ipc\CPipeServer.h">
|
||||
<Filter>IPC</Filter>
|
||||
</ClInclude>
|
||||
@@ -160,6 +166,9 @@
|
||||
<ClInclude Include="transport\IFrameTransport.h">
|
||||
<Filter>Transport</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\IInputTransport.h">
|
||||
<Filter>Transport</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\ITransport.h">
|
||||
<Filter>Transport</Filter>
|
||||
</ClInclude>
|
||||
@@ -181,6 +190,9 @@
|
||||
<ClInclude Include="transport\lgmp\CLGMPHost.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\lgmp\CLGMPInputTransport.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\lgmp\CLGMPTransport.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
@@ -291,6 +303,9 @@
|
||||
<ClCompile Include="transport\lgmp\CLGMPHost.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\lgmp\CLGMPInputTransport.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\lgmp\CLGMPTransport.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
#include "display/CDeviceContext.h"
|
||||
|
||||
#include "display/IddCxCompat.h"
|
||||
#include "ipc/CInputPipeServer.h"
|
||||
#include "ipc/CPipeServer.h"
|
||||
#include "transport/IFrameTransport.h"
|
||||
#include "transport/IInputTransport.h"
|
||||
#include "transport/TransportFactory.h"
|
||||
#include "CDebug.h"
|
||||
|
||||
@@ -55,6 +57,9 @@ CDeviceContext::~CDeviceContext()
|
||||
WdfTimerStop(m_transportTimer, TRUE);
|
||||
m_transportTimer = nullptr;
|
||||
}
|
||||
|
||||
if (m_transport && m_transport->Input())
|
||||
m_transport->Input()->Stop();
|
||||
}
|
||||
|
||||
void CDeviceContext::QueryIddCxCapabilities()
|
||||
@@ -462,9 +467,8 @@ bool CDeviceContext::SetupTransport(size_t alignSize)
|
||||
{
|
||||
// Frame buffers cannot be allocated until the GPU-specific alignment is
|
||||
// known. The swap-chain path may call this again after setup completed.
|
||||
if (m_transport->Frames().GetMaxFrameSize())
|
||||
return true;
|
||||
|
||||
if (!m_transport->Frames().GetMaxFrameSize())
|
||||
{
|
||||
if (!InitializeTransport() || !m_transport->Setup(alignSize))
|
||||
return false;
|
||||
|
||||
@@ -480,8 +484,8 @@ bool CDeviceContext::SetupTransport(size_t alignSize)
|
||||
config.AutomaticSerialization = FALSE;
|
||||
|
||||
/**
|
||||
* Documentation states that Dispatch is not available under UMDF, however
|
||||
* using Passive returns a not-supported error and Dispatch works.
|
||||
* Documentation states that Dispatch is not available under UMDF,
|
||||
* however using Passive returns a not-supported error and Dispatch works.
|
||||
*/
|
||||
WDF_OBJECT_ATTRIBUTES attribs;
|
||||
WDF_OBJECT_ATTRIBUTES_INIT(&attribs);
|
||||
@@ -497,6 +501,15 @@ bool CDeviceContext::SetupTransport(size_t alignSize)
|
||||
}
|
||||
|
||||
WdfTimerStart(m_transportTimer, WDF_REL_TIMEOUT_IN_MS(10));
|
||||
}
|
||||
|
||||
IInputTransport * input = m_transport->Input();
|
||||
if (input && !input->Start(g_inputPipeServer))
|
||||
{
|
||||
DEBUG_ERROR("Failed to start input transport");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
39
idd/LGIdd/input/IInputSink.h
Normal file
39
idd/LGIdd/input/IInputSink.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class IInputSink
|
||||
{
|
||||
public:
|
||||
virtual ~IInputSink() = default;
|
||||
|
||||
virtual bool IsAvailable() const = 0;
|
||||
virtual uint64_t GetGeneration() const = 0;
|
||||
virtual bool SendMouseRelative(int32_t deltaX, int32_t deltaY,
|
||||
int32_t wheel, uint32_t buttons) = 0;
|
||||
virtual bool SendMouseAbsolute(uint16_t x, uint16_t y,
|
||||
int32_t wheel, uint32_t buttons) = 0;
|
||||
virtual bool SendKeyboard(
|
||||
uint8_t modifiers, const uint8_t * keys) = 0;
|
||||
virtual bool Reset() = 0;
|
||||
};
|
||||
@@ -21,17 +21,34 @@
|
||||
#include "ipc/CInputPipeServer.h"
|
||||
|
||||
#include "CDebug.h"
|
||||
#include "CSRWLock.h"
|
||||
#include "InputPipeProtocol.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
CInputPipeServer g_inputPipeServer;
|
||||
|
||||
static constexpr int32_t MAX_SPLIT_REPORTS = 4;
|
||||
static constexpr int32_t MAX_MOUSE_DELTA =
|
||||
INT16_MAX * MAX_SPLIT_REPORTS;
|
||||
static constexpr int32_t MIN_MOUSE_DELTA =
|
||||
INT16_MIN * MAX_SPLIT_REPORTS;
|
||||
static constexpr int32_t MAX_MOUSE_WHEEL =
|
||||
INT8_MAX * MAX_SPLIT_REPORTS;
|
||||
static constexpr int32_t MIN_MOUSE_WHEEL =
|
||||
LG_INPUT_MOUSE_WHEEL_MIN * MAX_SPLIT_REPORTS;
|
||||
|
||||
bool CInputPipeServer::Init()
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_sendLock);
|
||||
m_state.store(0, std::memory_order_release);
|
||||
{
|
||||
CSRWExclusiveLock lock(&m_sendLock);
|
||||
m_sequence = 0;
|
||||
ReleaseSRWLockExclusive(&m_sendLock);
|
||||
m_mouseMode = MouseMode::NONE;
|
||||
m_absoluteValid = false;
|
||||
m_absoluteX = 0;
|
||||
m_absoluteY = 0;
|
||||
}
|
||||
|
||||
m_endpoint.SetHandler(this);
|
||||
return m_endpoint.Start(
|
||||
@@ -42,53 +59,134 @@ bool CInputPipeServer::Init()
|
||||
|
||||
void CInputPipeServer::DeInit()
|
||||
{
|
||||
Invalidate();
|
||||
m_endpoint.Stop();
|
||||
}
|
||||
|
||||
bool CInputPipeServer::SendMouseRelative(
|
||||
int16_t deltaX,
|
||||
int16_t deltaY,
|
||||
int8_t wheel,
|
||||
uint8_t buttons)
|
||||
int32_t deltaX,
|
||||
int32_t deltaY,
|
||||
int32_t wheel,
|
||||
uint32_t buttons)
|
||||
{
|
||||
if (wheel < LG_INPUT_MOUSE_WHEEL_MIN ||
|
||||
(buttons & ~LG_INPUT_MOUSE_BUTTON_MASK))
|
||||
if (deltaX < MIN_MOUSE_DELTA || deltaX > MAX_MOUSE_DELTA ||
|
||||
deltaY < MIN_MOUSE_DELTA || deltaY > MAX_MOUSE_DELTA ||
|
||||
wheel < MIN_MOUSE_WHEEL || wheel > MAX_MOUSE_WHEEL ||
|
||||
(buttons & ~static_cast<uint32_t>(LG_INPUT_MOUSE_BUTTON_MASK)))
|
||||
return false;
|
||||
|
||||
const LGInputPipeMouseRelative payload = {
|
||||
buttons,
|
||||
deltaX,
|
||||
deltaY,
|
||||
wheel,
|
||||
CSRWExclusiveLock lock(&m_sendLock);
|
||||
if (!IsAvailable())
|
||||
return false;
|
||||
|
||||
if (m_mouseMode == MouseMode::ABSOLUTE)
|
||||
{
|
||||
const LGInputPipeMouseAbsolute neutral = {
|
||||
0,
|
||||
m_absoluteX,
|
||||
m_absoluteY,
|
||||
0,
|
||||
};
|
||||
return SendMessage(
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE,
|
||||
&payload,
|
||||
sizeof(payload));
|
||||
if (!SendMessageLocked(
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE,
|
||||
&neutral,
|
||||
sizeof(neutral)))
|
||||
{
|
||||
Invalidate();
|
||||
return false;
|
||||
}
|
||||
m_mouseMode = MouseMode::NONE;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
const int16_t x = deltaX > INT16_MAX ? INT16_MAX :
|
||||
deltaX < INT16_MIN ? INT16_MIN : static_cast<int16_t>(deltaX);
|
||||
const int16_t y = deltaY > INT16_MAX ? INT16_MAX :
|
||||
deltaY < INT16_MIN ? INT16_MIN : static_cast<int16_t>(deltaY);
|
||||
const int8_t wheelDelta = wheel > INT8_MAX ? INT8_MAX :
|
||||
wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN :
|
||||
static_cast<int8_t>(wheel);
|
||||
|
||||
const LGInputPipeMouseRelative payload = {
|
||||
static_cast<uint8_t>(buttons),
|
||||
x,
|
||||
y,
|
||||
wheelDelta,
|
||||
};
|
||||
if (!SendMessageLocked(
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE, &payload, sizeof(payload)))
|
||||
{
|
||||
Invalidate();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_mouseMode = MouseMode::RELATIVE;
|
||||
deltaX -= x;
|
||||
deltaY -= y;
|
||||
wheel -= wheelDelta;
|
||||
}
|
||||
while (deltaX || deltaY || wheel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CInputPipeServer::SendMouseAbsolute(
|
||||
uint16_t x,
|
||||
uint16_t y,
|
||||
int8_t wheel,
|
||||
uint8_t buttons)
|
||||
int32_t wheel,
|
||||
uint32_t buttons)
|
||||
{
|
||||
if (x > LG_INPUT_MOUSE_ABSOLUTE_MAX ||
|
||||
y > LG_INPUT_MOUSE_ABSOLUTE_MAX ||
|
||||
wheel < LG_INPUT_MOUSE_WHEEL_MIN ||
|
||||
(buttons & ~LG_INPUT_MOUSE_BUTTON_MASK))
|
||||
wheel < MIN_MOUSE_WHEEL || wheel > MAX_MOUSE_WHEEL ||
|
||||
(buttons & ~static_cast<uint32_t>(LG_INPUT_MOUSE_BUTTON_MASK)))
|
||||
return false;
|
||||
|
||||
CSRWExclusiveLock lock(&m_sendLock);
|
||||
if (!IsAvailable())
|
||||
return false;
|
||||
|
||||
if (m_mouseMode == MouseMode::RELATIVE)
|
||||
{
|
||||
const LGInputPipeMouseRelative neutral = {};
|
||||
if (!SendMessageLocked(
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE,
|
||||
&neutral,
|
||||
sizeof(neutral)))
|
||||
{
|
||||
Invalidate();
|
||||
return false;
|
||||
}
|
||||
m_mouseMode = MouseMode::NONE;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
const int8_t wheelDelta = wheel > INT8_MAX ? INT8_MAX :
|
||||
wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN :
|
||||
static_cast<int8_t>(wheel);
|
||||
const LGInputPipeMouseAbsolute payload = {
|
||||
buttons,
|
||||
static_cast<uint8_t>(buttons),
|
||||
x,
|
||||
y,
|
||||
wheel,
|
||||
wheelDelta,
|
||||
};
|
||||
return SendMessage(
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE,
|
||||
&payload,
|
||||
sizeof(payload));
|
||||
if (!SendMessageLocked(
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE, &payload, sizeof(payload)))
|
||||
{
|
||||
Invalidate();
|
||||
return false;
|
||||
}
|
||||
m_mouseMode = MouseMode::ABSOLUTE;
|
||||
m_absoluteValid = true;
|
||||
m_absoluteX = x;
|
||||
m_absoluteY = y;
|
||||
wheel -= wheelDelta;
|
||||
}
|
||||
while (wheel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CInputPipeServer::SendKeyboard(
|
||||
@@ -107,13 +205,58 @@ bool CInputPipeServer::SendKeyboard(
|
||||
payload.keys[i] = keys[i];
|
||||
}
|
||||
|
||||
return SendMessage(
|
||||
CSRWExclusiveLock lock(&m_sendLock);
|
||||
const bool sent = IsAvailable() &&
|
||||
SendMessageLocked(
|
||||
LG_INPUT_PIPE_MESSAGE_KEYBOARD,
|
||||
&payload,
|
||||
sizeof(payload));
|
||||
if (!sent)
|
||||
Invalidate();
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool CInputPipeServer::SendMessage(
|
||||
bool CInputPipeServer::Reset()
|
||||
{
|
||||
CSRWExclusiveLock lock(&m_sendLock);
|
||||
const bool reset = IsAvailable() &&
|
||||
ResetLocked();
|
||||
if (!reset)
|
||||
Invalidate();
|
||||
return reset;
|
||||
}
|
||||
|
||||
bool CInputPipeServer::ResetLocked()
|
||||
{
|
||||
const LGInputPipeMouseRelative relative = {};
|
||||
const LGInputPipeMouseAbsolute absolute = {
|
||||
0,
|
||||
m_absoluteX,
|
||||
m_absoluteY,
|
||||
0,
|
||||
};
|
||||
const LGInputPipeKeyboard keyboard = {};
|
||||
|
||||
if (!SendMessageLocked(
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE,
|
||||
&relative,
|
||||
sizeof(relative)) ||
|
||||
(m_absoluteValid &&
|
||||
!SendMessageLocked(
|
||||
LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE,
|
||||
&absolute,
|
||||
sizeof(absolute))) ||
|
||||
!SendMessageLocked(
|
||||
LG_INPUT_PIPE_MESSAGE_KEYBOARD,
|
||||
&keyboard,
|
||||
sizeof(keyboard)))
|
||||
return false;
|
||||
|
||||
m_mouseMode = MouseMode::NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CInputPipeServer::SendMessageLocked(
|
||||
LGInputPipeMessageType type,
|
||||
const void * payload,
|
||||
size_t size)
|
||||
@@ -128,13 +271,40 @@ bool CInputPipeServer::SendMessage(
|
||||
message.payloadSize = static_cast<uint32_t>(size);
|
||||
memcpy(message.payload, payload, size);
|
||||
|
||||
AcquireSRWLockExclusive(&m_sendLock);
|
||||
message.sequence = ++m_sequence;
|
||||
const bool sent = m_endpoint.Send(&message, sizeof(message));
|
||||
ReleaseSRWLockExclusive(&m_sendLock);
|
||||
return sent;
|
||||
}
|
||||
|
||||
void CInputPipeServer::Invalidate()
|
||||
{
|
||||
uint64_t state = m_state.load(std::memory_order_acquire);
|
||||
while ((state & 1) && !m_state.compare_exchange_weak(
|
||||
state, state + 1, std::memory_order_acq_rel))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void CInputPipeServer::OnPipeConnected()
|
||||
{
|
||||
Invalidate();
|
||||
|
||||
CSRWExclusiveLock lock(&m_sendLock);
|
||||
const bool ready = ResetLocked();
|
||||
if (!ready)
|
||||
{
|
||||
DEBUG_WARN("Failed to neutralize the LGInput endpoint");
|
||||
return;
|
||||
}
|
||||
|
||||
m_state.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void CInputPipeServer::OnPipeDisconnected()
|
||||
{
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
bool CInputPipeServer::OnPipeMessage(
|
||||
const void * message,
|
||||
size_t size)
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
|
||||
#include "CPipeEndpoint.h"
|
||||
#include "InputPipeProtocol.h"
|
||||
#include "input/IInputSink.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
class CInputPipeServer : private IPipeEndpointHandler
|
||||
class CInputPipeServer : public IInputSink, private IPipeEndpointHandler
|
||||
{
|
||||
public:
|
||||
~CInputPipeServer() { DeInit(); }
|
||||
@@ -34,34 +36,61 @@ public:
|
||||
bool Init();
|
||||
void DeInit();
|
||||
|
||||
// Mouse buttons use LGInputMouseButton bits. Wheel values are -127..127.
|
||||
bool IsAvailable() const override
|
||||
{
|
||||
return (m_state.load(std::memory_order_acquire) & 1) != 0;
|
||||
}
|
||||
uint64_t GetGeneration() const override
|
||||
{
|
||||
return m_state.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
// Mouse buttons use LGInputMouseButton bits. Values outside the pipe report
|
||||
// ranges are split into multiple reports without losing motion.
|
||||
bool SendMouseRelative(
|
||||
_In_ int16_t deltaX,
|
||||
_In_ int16_t deltaY,
|
||||
_In_ int8_t wheel,
|
||||
_In_ uint8_t buttons);
|
||||
_In_ int32_t deltaX,
|
||||
_In_ int32_t deltaY,
|
||||
_In_ int32_t wheel,
|
||||
_In_ uint32_t buttons) override;
|
||||
// Absolute coordinates are normalized to 0..32767 on each axis.
|
||||
bool SendMouseAbsolute(
|
||||
_In_range_(0, LG_INPUT_MOUSE_ABSOLUTE_MAX) uint16_t x,
|
||||
_In_range_(0, LG_INPUT_MOUSE_ABSOLUTE_MAX) uint16_t y,
|
||||
_In_ int8_t wheel,
|
||||
_In_ uint8_t buttons);
|
||||
_In_ int32_t wheel,
|
||||
_In_ uint32_t buttons) override;
|
||||
// Keys are USB HID Keyboard/Keypad usage IDs; zero marks an empty slot.
|
||||
bool SendKeyboard(
|
||||
_In_ uint8_t modifiers,
|
||||
_In_reads_(LG_INPUT_KEYBOARD_KEY_COUNT) const uint8_t * keys);
|
||||
_In_reads_(LG_INPUT_KEYBOARD_KEY_COUNT) const uint8_t * keys) override;
|
||||
bool Reset() override;
|
||||
bool IsConnected() const { return m_endpoint.IsConnected(); }
|
||||
|
||||
private:
|
||||
bool SendMessage(
|
||||
bool SendMessageLocked(
|
||||
_In_ LGInputPipeMessageType type,
|
||||
_In_reads_bytes_(size) const void * payload,
|
||||
_In_ size_t size);
|
||||
bool ResetLocked();
|
||||
void Invalidate();
|
||||
void OnPipeConnected() override;
|
||||
void OnPipeDisconnected() override;
|
||||
bool OnPipeMessage(const void * message, size_t size) override;
|
||||
|
||||
CPipeEndpoint m_endpoint;
|
||||
// Odd states are available. Each endpoint transition advances the state.
|
||||
std::atomic<uint64_t> m_state { 0 };
|
||||
SRWLOCK m_sendLock = SRWLOCK_INIT;
|
||||
uint64_t m_sequence = 0;
|
||||
enum class MouseMode
|
||||
{
|
||||
NONE,
|
||||
RELATIVE,
|
||||
ABSOLUTE,
|
||||
};
|
||||
MouseMode m_mouseMode = MouseMode::NONE;
|
||||
bool m_absoluteValid = false;
|
||||
uint16_t m_absoluteX = 0;
|
||||
uint16_t m_absoluteY = 0;
|
||||
};
|
||||
|
||||
extern CInputPipeServer g_inputPipeServer;
|
||||
|
||||
32
idd/LGIdd/transport/IInputTransport.h
Normal file
32
idd/LGIdd/transport/IInputTransport.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class IInputSink;
|
||||
|
||||
class IInputTransport
|
||||
{
|
||||
public:
|
||||
virtual ~IInputTransport() = default;
|
||||
|
||||
virtual bool Start(IInputSink& sink) = 0;
|
||||
virtual void Stop() = 0;
|
||||
};
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
class IControlTransport;
|
||||
class IFrameTransport;
|
||||
class IInputTransport;
|
||||
|
||||
class ITransportEvents
|
||||
{
|
||||
@@ -60,4 +61,5 @@ public:
|
||||
|
||||
virtual IFrameTransport& Frames() = 0;
|
||||
virtual IControlTransport& Control() = 0;
|
||||
virtual IInputTransport * Input() { return nullptr; }
|
||||
};
|
||||
|
||||
@@ -51,7 +51,8 @@ bool CLGMPHost::Initialize(CIVSHMEM& ivshmem)
|
||||
kvmfr.features =
|
||||
KVMFR_FEATURE_SETCURSORPOS |
|
||||
KVMFR_FEATURE_WINDOWSIZE |
|
||||
KVMFR_FEATURE_FRAME_SCHEDULE;
|
||||
KVMFR_FEATURE_FRAME_SCHEDULE |
|
||||
KVMFR_FEATURE_INPUT;
|
||||
strncpy_s(kvmfr.hostver, LG_VERSION_STR, sizeof(kvmfr.hostver) - 1);
|
||||
ss.write(reinterpret_cast<const char *>(&kvmfr), sizeof(kvmfr));
|
||||
}
|
||||
|
||||
518
idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp
Normal file
518
idd/LGIdd/transport/lgmp/CLGMPInputTransport.cpp
Normal file
@@ -0,0 +1,518 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "transport/lgmp/CLGMPInputTransport.h"
|
||||
|
||||
#include "input/IInputSink.h"
|
||||
#include "transport/lgmp/CLGMPHost.h"
|
||||
#include "CDebug.h"
|
||||
#include "CSRWLock.h"
|
||||
|
||||
#include "common/KVMFRInput.h"
|
||||
#include "common/LGMPConfig.h"
|
||||
|
||||
#include <avrt.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
|
||||
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
|
||||
#endif
|
||||
|
||||
static const LGMPQueueConfig INPUT_QUEUE_CONFIG =
|
||||
{
|
||||
LGMP_Q_INPUT,
|
||||
LGMP_Q_INPUT_LEN,
|
||||
1000,
|
||||
};
|
||||
|
||||
static constexpr int32_t MAX_SPLIT_REPORTS = 4;
|
||||
static constexpr int32_t MAX_MOUSE_DELTA =
|
||||
INT16_MAX * MAX_SPLIT_REPORTS;
|
||||
static constexpr int32_t MIN_MOUSE_DELTA =
|
||||
INT16_MIN * MAX_SPLIT_REPORTS;
|
||||
static constexpr int32_t MAX_MOUSE_WHEEL =
|
||||
INT8_MAX * MAX_SPLIT_REPORTS;
|
||||
static constexpr int32_t MIN_MOUSE_WHEEL =
|
||||
-INT8_MAX * MAX_SPLIT_REPORTS;
|
||||
|
||||
static bool IsZero(const void * data, size_t size)
|
||||
{
|
||||
const uint8_t * byte = static_cast<const uint8_t *>(data);
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
if (byte[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ArmPollTimer(HANDLE timer, bool active)
|
||||
{
|
||||
LARGE_INTEGER due = {};
|
||||
due.QuadPart = active ? -2500 : -10000;
|
||||
return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE;
|
||||
}
|
||||
|
||||
CLGMPInputTransport::~CLGMPInputTransport()
|
||||
{
|
||||
DeInit();
|
||||
}
|
||||
|
||||
bool CLGMPInputTransport::Initialize()
|
||||
{
|
||||
if (m_queue)
|
||||
return true;
|
||||
|
||||
const LGMP_STATUS status = m_host.CreateQueue(
|
||||
INPUT_QUEUE_CONFIG, &m_queue);
|
||||
if (status != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostQueueCreate Failed (Input): %s",
|
||||
lgmpStatusString(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CLGMPInputTransport::DeInit()
|
||||
{
|
||||
Stop();
|
||||
m_queue = nullptr;
|
||||
}
|
||||
|
||||
bool CLGMPInputTransport::Start(IInputSink& sink)
|
||||
{
|
||||
CSRWExclusiveLock lock(&m_lifecycleLock);
|
||||
if (m_thread)
|
||||
{
|
||||
const DWORD state = WaitForSingleObject(m_thread, 0);
|
||||
if (state == WAIT_TIMEOUT)
|
||||
return true;
|
||||
if (state != WAIT_OBJECT_0)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(),
|
||||
"Failed to inspect LGMP input worker");
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseHandle(m_thread);
|
||||
CloseHandle(m_pollTimer);
|
||||
CloseHandle(m_stopEvent);
|
||||
m_thread = nullptr;
|
||||
m_pollTimer = nullptr;
|
||||
m_stopEvent = nullptr;
|
||||
m_sink = nullptr;
|
||||
}
|
||||
|
||||
if (!m_queue)
|
||||
return false;
|
||||
|
||||
m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
m_pollTimer = CreateWaitableTimerExW(nullptr, nullptr,
|
||||
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
||||
if (!m_pollTimer)
|
||||
m_pollTimer = CreateWaitableTimerExW(
|
||||
nullptr, nullptr, 0, TIMER_ALL_ACCESS);
|
||||
|
||||
if (!m_stopEvent || !m_pollTimer)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(),
|
||||
"Failed to create LGMP input worker resources");
|
||||
if (m_pollTimer)
|
||||
CloseHandle(m_pollTimer);
|
||||
if (m_stopEvent)
|
||||
CloseHandle(m_stopEvent);
|
||||
m_pollTimer = nullptr;
|
||||
m_stopEvent = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sink = &sink;
|
||||
m_sinkGeneration = sink.GetGeneration();
|
||||
m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr);
|
||||
if (!m_thread)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(), "Failed to create LGMP input worker");
|
||||
m_sink = nullptr;
|
||||
m_sinkGeneration = 0;
|
||||
CloseHandle(m_pollTimer);
|
||||
CloseHandle(m_stopEvent);
|
||||
m_pollTimer = nullptr;
|
||||
m_stopEvent = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CLGMPInputTransport::Stop()
|
||||
{
|
||||
HANDLE thread;
|
||||
{
|
||||
CSRWExclusiveLock lock(&m_lifecycleLock);
|
||||
thread = m_thread;
|
||||
if (m_stopEvent)
|
||||
SetEvent(m_stopEvent);
|
||||
}
|
||||
|
||||
if (thread)
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
|
||||
CSRWExclusiveLock lock(&m_lifecycleLock);
|
||||
if (m_thread)
|
||||
{
|
||||
CloseHandle(m_thread);
|
||||
m_thread = nullptr;
|
||||
}
|
||||
if (m_pollTimer)
|
||||
{
|
||||
CloseHandle(m_pollTimer);
|
||||
m_pollTimer = nullptr;
|
||||
}
|
||||
if (m_stopEvent)
|
||||
{
|
||||
CloseHandle(m_stopEvent);
|
||||
m_stopEvent = nullptr;
|
||||
}
|
||||
m_sink = nullptr;
|
||||
m_ownerClientID = 0;
|
||||
m_ownerGeneration = 0;
|
||||
m_ownerSequence = 0;
|
||||
m_ownerDeadline = 0;
|
||||
m_sinkGeneration = 0;
|
||||
}
|
||||
|
||||
bool CLGMPInputTransport::IsOwner(
|
||||
uint32_t sourceClientID, uint32_t generation) const
|
||||
{
|
||||
return m_ownerClientID == sourceClientID &&
|
||||
m_ownerGeneration == generation;
|
||||
}
|
||||
|
||||
bool CLGMPInputTransport::Claim(
|
||||
uint32_t sourceClientID, const KVMFRInputMessage& message)
|
||||
{
|
||||
if (message.sequence != 1 || !m_sink || !m_sink->IsAvailable())
|
||||
return false;
|
||||
|
||||
const uint64_t sinkGeneration = m_sink->GetGeneration();
|
||||
if (sinkGeneration != m_sinkGeneration || !m_sink->Reset() ||
|
||||
!m_sink->IsAvailable() ||
|
||||
m_sink->GetGeneration() != sinkGeneration)
|
||||
return false;
|
||||
|
||||
m_ownerClientID = sourceClientID;
|
||||
m_ownerGeneration = message.generation;
|
||||
m_ownerSequence = message.sequence;
|
||||
RenewLease();
|
||||
DEBUG_INFO("Input owner %u generation %u acquired",
|
||||
m_ownerClientID, m_ownerGeneration);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CLGMPInputTransport::RenewLease()
|
||||
{
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
}
|
||||
|
||||
void CLGMPInputTransport::ReleaseOwner(
|
||||
bool reset, const char * reason)
|
||||
{
|
||||
if (!m_ownerClientID)
|
||||
return;
|
||||
|
||||
const uint32_t clientID = m_ownerClientID;
|
||||
const uint32_t generation = m_ownerGeneration;
|
||||
if (reset && m_sink)
|
||||
m_sink->Reset();
|
||||
|
||||
m_ownerClientID = 0;
|
||||
m_ownerGeneration = 0;
|
||||
m_ownerSequence = 0;
|
||||
m_ownerDeadline = 0;
|
||||
DEBUG_INFO("Input owner %u generation %u released (%s)",
|
||||
clientID, generation, reason);
|
||||
}
|
||||
|
||||
void CLGMPInputTransport::CheckOwner()
|
||||
{
|
||||
if (!m_sink)
|
||||
return;
|
||||
|
||||
const uint64_t generation = m_sink->GetGeneration();
|
||||
if (generation != m_sinkGeneration)
|
||||
{
|
||||
m_sinkGeneration = generation;
|
||||
ReleaseOwner(true, "input endpoint changed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_ownerClientID)
|
||||
return;
|
||||
|
||||
if (!m_sink->IsAvailable())
|
||||
{
|
||||
ReleaseOwner(true, "input unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetTickCount64() >= m_ownerDeadline)
|
||||
ReleaseOwner(true, "lease expired");
|
||||
}
|
||||
|
||||
bool CLGMPInputTransport::ValidatePayload(
|
||||
const KVMFRInputMessage& message) const
|
||||
{
|
||||
switch (message.type)
|
||||
{
|
||||
case KVMFR_INPUT_MESSAGE_CLAIM:
|
||||
case KVMFR_INPUT_MESSAGE_RELEASE:
|
||||
case KVMFR_INPUT_MESSAGE_KEEPALIVE:
|
||||
case KVMFR_INPUT_MESSAGE_RESET:
|
||||
return IsZero(&message.payload, sizeof(message.payload));
|
||||
|
||||
case KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE:
|
||||
return message.payload.mouseRelative.deltaX >= MIN_MOUSE_DELTA &&
|
||||
message.payload.mouseRelative.deltaX <= MAX_MOUSE_DELTA &&
|
||||
message.payload.mouseRelative.deltaY >= MIN_MOUSE_DELTA &&
|
||||
message.payload.mouseRelative.deltaY <= MAX_MOUSE_DELTA &&
|
||||
message.payload.mouseRelative.wheel >= MIN_MOUSE_WHEEL &&
|
||||
message.payload.mouseRelative.wheel <= MAX_MOUSE_WHEEL;
|
||||
|
||||
case KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE:
|
||||
return message.payload.mouseAbsolute.x <=
|
||||
KVMFR_INPUT_MOUSE_ABSOLUTE_MAX &&
|
||||
message.payload.mouseAbsolute.y <=
|
||||
KVMFR_INPUT_MOUSE_ABSOLUTE_MAX &&
|
||||
message.payload.mouseAbsolute.wheel >= MIN_MOUSE_WHEEL &&
|
||||
message.payload.mouseAbsolute.wheel <= MAX_MOUSE_WHEEL &&
|
||||
!message.payload.mouseAbsolute.reserved;
|
||||
|
||||
case KVMFR_INPUT_MESSAGE_KEYBOARD:
|
||||
if (!IsZero(message.payload.keyboard.reserved,
|
||||
sizeof(message.payload.keyboard.reserved)))
|
||||
return false;
|
||||
for (size_t i = 0; i < KVMFR_INPUT_KEYBOARD_KEY_COUNT; ++i)
|
||||
if (message.payload.keyboard.keys[i] >
|
||||
KVMFR_INPUT_KEYBOARD_USAGE_MAX)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CLGMPInputTransport::ProcessMessage(
|
||||
uint32_t sourceClientID, const KVMFRInputMessage& message)
|
||||
{
|
||||
const bool owner = IsOwner(sourceClientID, message.generation);
|
||||
const uint64_t sinkGeneration = m_sink ?
|
||||
m_sink->GetGeneration() : m_sinkGeneration;
|
||||
if (sinkGeneration != m_sinkGeneration)
|
||||
{
|
||||
m_sinkGeneration = sinkGeneration;
|
||||
if (m_ownerClientID)
|
||||
{
|
||||
ReleaseOwner(true, "input endpoint changed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message.generation || !message.sequence || message.reserved ||
|
||||
!ValidatePayload(message))
|
||||
{
|
||||
if (owner)
|
||||
ReleaseOwner(true, "invalid input message");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (message.type == KVMFR_INPUT_MESSAGE_CLAIM)
|
||||
{
|
||||
if (m_ownerClientID)
|
||||
{
|
||||
if (!owner)
|
||||
return true;
|
||||
if (message.sequence == 1 && m_ownerSequence == 1)
|
||||
return true;
|
||||
|
||||
ReleaseOwner(true, "sequence discontinuity");
|
||||
return false;
|
||||
}
|
||||
return Claim(sourceClientID, message);
|
||||
}
|
||||
|
||||
if (!owner)
|
||||
return true;
|
||||
|
||||
uint32_t expectedSequence = m_ownerSequence + 1;
|
||||
if (!expectedSequence)
|
||||
expectedSequence = 1;
|
||||
if (message.sequence != expectedSequence)
|
||||
{
|
||||
ReleaseOwner(true, "sequence discontinuity");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool accepted = false;
|
||||
switch (message.type)
|
||||
{
|
||||
case KVMFR_INPUT_MESSAGE_RELEASE:
|
||||
ReleaseOwner(true, "client release");
|
||||
return true;
|
||||
|
||||
case KVMFR_INPUT_MESSAGE_KEEPALIVE:
|
||||
accepted = true;
|
||||
break;
|
||||
|
||||
case KVMFR_INPUT_MESSAGE_RESET:
|
||||
accepted = m_sink && m_sink->Reset();
|
||||
break;
|
||||
|
||||
case KVMFR_INPUT_MESSAGE_MOUSE_RELATIVE:
|
||||
accepted = m_sink && m_sink->SendMouseRelative(
|
||||
message.payload.mouseRelative.deltaX,
|
||||
message.payload.mouseRelative.deltaY,
|
||||
message.payload.mouseRelative.wheel,
|
||||
message.payload.mouseRelative.buttons);
|
||||
break;
|
||||
|
||||
case KVMFR_INPUT_MESSAGE_MOUSE_ABSOLUTE:
|
||||
accepted = m_sink && m_sink->SendMouseAbsolute(
|
||||
message.payload.mouseAbsolute.x,
|
||||
message.payload.mouseAbsolute.y,
|
||||
message.payload.mouseAbsolute.wheel,
|
||||
message.payload.mouseAbsolute.buttons);
|
||||
break;
|
||||
|
||||
case KVMFR_INPUT_MESSAGE_KEYBOARD:
|
||||
accepted = m_sink && m_sink->SendKeyboard(
|
||||
message.payload.keyboard.modifiers,
|
||||
message.payload.keyboard.keys);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!accepted)
|
||||
{
|
||||
ReleaseOwner(true, "input delivery failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint64_t deliveredGeneration = m_sink->GetGeneration();
|
||||
if (deliveredGeneration != m_sinkGeneration)
|
||||
{
|
||||
m_sinkGeneration = deliveredGeneration;
|
||||
ReleaseOwner(true, "input endpoint changed");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ownerSequence = message.sequence;
|
||||
RenewLease();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CLGMPInputTransport::DrainMessages()
|
||||
{
|
||||
bool received = false;
|
||||
for (unsigned count = 0; count < 256; ++count)
|
||||
{
|
||||
uint8_t data[LGMP_MSGS_SIZE] = {};
|
||||
size_t size = 0;
|
||||
uint32_t sourceClientID = 0;
|
||||
const LGMP_STATUS status = lgmpHostReadDataWithSource(
|
||||
m_queue, data, &size, &sourceClientID);
|
||||
if (status == LGMP_ERR_QUEUE_EMPTY)
|
||||
break;
|
||||
if (status != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostReadData Failed (Input): %s",
|
||||
lgmpStatusString(status));
|
||||
break;
|
||||
}
|
||||
|
||||
received = true;
|
||||
if (size != sizeof(KVMFRInputMessage))
|
||||
{
|
||||
DEBUG_WARN("Ignoring invalid KVMFR input message size");
|
||||
if (sourceClientID == m_ownerClientID)
|
||||
ReleaseOwner(true, "invalid input message");
|
||||
}
|
||||
else
|
||||
{
|
||||
KVMFRInputMessage message = {};
|
||||
memcpy(&message, data, sizeof(message));
|
||||
ProcessMessage(sourceClientID, message);
|
||||
}
|
||||
|
||||
lgmpHostAckData(m_queue);
|
||||
}
|
||||
return received;
|
||||
}
|
||||
|
||||
DWORD CALLBACK CLGMPInputTransport::ThreadProc(void * context)
|
||||
{
|
||||
static_cast<CLGMPInputTransport *>(context)->Thread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CLGMPInputTransport::Thread()
|
||||
{
|
||||
DWORD avTask = 0;
|
||||
HANDLE avTaskHandle =
|
||||
AvSetMmThreadCharacteristicsW(L"Distribution", &avTask);
|
||||
if (avTaskHandle &&
|
||||
!AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH))
|
||||
DEBUG_WARN("Failed to raise input MMCSS priority: %lu",
|
||||
GetLastError());
|
||||
|
||||
ULONGLONG activeUntil = 0;
|
||||
const HANDLE waitHandles[] = { m_stopEvent, m_pollTimer };
|
||||
for (;;)
|
||||
{
|
||||
CheckOwner();
|
||||
if (DrainMessages())
|
||||
activeUntil = GetTickCount64() + ACTIVE_POLL_MS;
|
||||
|
||||
const bool active = GetTickCount64() < activeUntil;
|
||||
if (!ArmPollTimer(m_pollTimer, active))
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(), "Failed to arm LGMP input timer");
|
||||
if (WaitForSingleObject(m_stopEvent, 1) != WAIT_TIMEOUT)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
const DWORD wait = WaitForMultipleObjects(
|
||||
_countof(waitHandles), waitHandles, FALSE, INFINITE);
|
||||
if (wait == WAIT_OBJECT_0)
|
||||
break;
|
||||
if (wait != WAIT_OBJECT_0 + 1)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(), "LGMP input worker wait failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ReleaseOwner(true, "transport stopped");
|
||||
if (avTaskHandle)
|
||||
AvRevertMmThreadCharacteristics(avTaskHandle);
|
||||
}
|
||||
87
idd/LGIdd/transport/lgmp/CLGMPInputTransport.h
Normal file
87
idd/LGIdd/transport/lgmp/CLGMPInputTransport.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "transport/IInputTransport.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern "C" {
|
||||
#include "lgmp/host.h"
|
||||
}
|
||||
|
||||
class CLGMPHost;
|
||||
class IInputSink;
|
||||
struct KVMFRInputMessage;
|
||||
|
||||
class CLGMPInputTransport final : public IInputTransport
|
||||
{
|
||||
private:
|
||||
static constexpr ULONGLONG OWNER_LEASE_MS = 500;
|
||||
static constexpr ULONGLONG ACTIVE_POLL_MS = 50;
|
||||
|
||||
CLGMPHost& m_host;
|
||||
|
||||
PLGMPHostQueue m_queue = nullptr;
|
||||
IInputSink * m_sink = nullptr;
|
||||
|
||||
SRWLOCK m_lifecycleLock = SRWLOCK_INIT;
|
||||
HANDLE m_stopEvent = nullptr;
|
||||
HANDLE m_pollTimer = nullptr;
|
||||
HANDLE m_thread = nullptr;
|
||||
|
||||
uint32_t m_ownerClientID = 0;
|
||||
uint32_t m_ownerGeneration = 0;
|
||||
uint32_t m_ownerSequence = 0;
|
||||
ULONGLONG m_ownerDeadline = 0;
|
||||
uint64_t m_sinkGeneration = 0;
|
||||
|
||||
bool Initialize();
|
||||
void DeInit();
|
||||
bool DrainMessages();
|
||||
bool ProcessMessage(uint32_t sourceClientID,
|
||||
const KVMFRInputMessage& message);
|
||||
bool ValidatePayload(const KVMFRInputMessage& message) const;
|
||||
bool IsOwner(uint32_t sourceClientID, uint32_t generation) const;
|
||||
bool Claim(uint32_t sourceClientID,
|
||||
const KVMFRInputMessage& message);
|
||||
void RenewLease();
|
||||
void ReleaseOwner(bool reset, const char * reason);
|
||||
void CheckOwner();
|
||||
void Thread();
|
||||
|
||||
static DWORD CALLBACK ThreadProc(void * context);
|
||||
|
||||
friend class CLGMPTransport;
|
||||
|
||||
public:
|
||||
explicit CLGMPInputTransport(CLGMPHost& host) :
|
||||
m_host(host) {}
|
||||
~CLGMPInputTransport() override;
|
||||
|
||||
CLGMPInputTransport(const CLGMPInputTransport&) = delete;
|
||||
CLGMPInputTransport& operator=(const CLGMPInputTransport&) = delete;
|
||||
|
||||
bool Start(IInputSink& sink) override;
|
||||
void Stop() override;
|
||||
};
|
||||
@@ -49,7 +49,8 @@ static bool TranslateFrameScheduleFlags(
|
||||
|
||||
CLGMPTransport::CLGMPTransport() :
|
||||
m_control(m_host),
|
||||
m_frames(m_host, m_ivshmem)
|
||||
m_frames(m_host, m_ivshmem),
|
||||
m_input(m_host)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -69,9 +70,10 @@ bool CLGMPTransport::Initialize()
|
||||
if (!m_host.Initialize(m_ivshmem))
|
||||
return false;
|
||||
|
||||
// Preserve the shared-memory layout: frame queues precede the pointer queue
|
||||
// and its retained cursor and color-transform allocations.
|
||||
if (!m_frames.Initialize() || !m_control.Initialize())
|
||||
// Preserve the existing shared-memory layout by appending input after the
|
||||
// frame and pointer queues and retained pointer state allocations.
|
||||
if (!m_frames.Initialize() || !m_control.Initialize() ||
|
||||
!m_input.Initialize())
|
||||
return false;
|
||||
|
||||
m_frames.SealMemoryLayout();
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "transport/lgmp/CLGMPControl.h"
|
||||
#include "transport/lgmp/CLGMPFrameTransport.h"
|
||||
#include "transport/lgmp/CLGMPHost.h"
|
||||
#include "transport/lgmp/CLGMPInputTransport.h"
|
||||
|
||||
class CLGMPTransport final : public ITransport
|
||||
{
|
||||
@@ -35,6 +36,7 @@ private:
|
||||
CLGMPHost m_host;
|
||||
CLGMPControl m_control;
|
||||
CLGMPFrameTransport m_frames;
|
||||
CLGMPInputTransport m_input;
|
||||
|
||||
public:
|
||||
CLGMPTransport();
|
||||
@@ -53,4 +55,5 @@ public:
|
||||
|
||||
IFrameTransport& Frames() override { return m_frames; }
|
||||
IControlTransport& Control() override { return m_control; }
|
||||
IInputTransport * Input() override { return &m_input; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user