From 241ab3fad2faab349a724a5344e7aeaaa6c454f8 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sat, 8 Aug 2026 19:06:50 +1000 Subject: [PATCH] [idd] ipc: connect LGInput to LGIdd Move the reusable named-pipe endpoint and shared support code into the LGCommon static library. Run a dedicated server in LGIdd and a reconnecting client in LGInput, with device-lifecycle handling and report framing. Refactor the helper pipe to use the same endpoint implementation. --- idd/LGCommon/CPipeEndpoint.cpp | 585 ++++++++++++++++++++ idd/LGCommon/CPipeEndpoint.h | 131 +++++ idd/LGCommon/InputPipeProtocol.h | 51 ++ idd/LGCommon/LGCommon.vcxproj | 83 +++ idd/LGCommon/LGCommon.vcxproj.filters | 48 ++ idd/LGCommon/PipeMsg.h | 4 +- idd/LGIdd.sln | 17 +- idd/LGIdd/Driver.cpp | 11 + idd/LGIdd/LGIdd.vcxproj | 10 +- idd/LGIdd/LGIdd.vcxproj.filters | 15 +- idd/LGIdd/ipc/CInputPipeServer.cpp | 75 +++ idd/LGIdd/ipc/CInputPipeServer.h | 49 ++ idd/LGIdd/ipc/CPipeServer.cpp | 271 ++------- idd/LGIdd/ipc/CPipeServer.h | 27 +- idd/LGIddHelper/CPipeClient.cpp | 216 ++------ idd/LGIddHelper/CPipeClient.h | 23 +- idd/LGIddHelper/LGIddHelper.vcxproj | 24 +- idd/LGIddHelper/LGIddHelper.vcxproj.filters | 4 +- idd/LGInput/CHIDDevice.cpp | 124 ++++- idd/LGInput/CHIDDevice.h | 1 + idd/LGInput/LGInput.vcxproj | 16 +- idd/LGInput/LGInput.vcxproj.filters | 19 +- idd/LGInput/ipc/CInputPipeClient.cpp | 93 ++++ idd/LGInput/ipc/CInputPipeClient.h | 44 ++ 24 files changed, 1463 insertions(+), 478 deletions(-) create mode 100644 idd/LGCommon/CPipeEndpoint.cpp create mode 100644 idd/LGCommon/CPipeEndpoint.h create mode 100644 idd/LGCommon/InputPipeProtocol.h create mode 100644 idd/LGCommon/LGCommon.vcxproj create mode 100644 idd/LGCommon/LGCommon.vcxproj.filters create mode 100644 idd/LGIdd/ipc/CInputPipeServer.cpp create mode 100644 idd/LGIdd/ipc/CInputPipeServer.h create mode 100644 idd/LGInput/ipc/CInputPipeClient.cpp create mode 100644 idd/LGInput/ipc/CInputPipeClient.h diff --git a/idd/LGCommon/CPipeEndpoint.cpp b/idd/LGCommon/CPipeEndpoint.cpp new file mode 100644 index 00000000..5d9e413b --- /dev/null +++ b/idd/LGCommon/CPipeEndpoint.cpp @@ -0,0 +1,585 @@ +/** + * 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 "CPipeEndpoint.h" + +#include "CDebug.h" + +#include +#include +#include + +const DWORD CPipeEndpoint::CLIENT_RETRY_INITIAL_MS = 100; +const DWORD CPipeEndpoint::CLIENT_RETRY_MAX_MS = 2000; +const DWORD CPipeEndpoint::SERVER_RETRY_MS = 1000; +const DWORD CPipeEndpoint::WRITE_TIMEOUT_MS = 250; +const DWORD CPipeEndpoint::WAIT_FIRST_OBJECT_VALUE = 0; + +bool CPipeEndpoint::IsDisconnectedError(DWORD error) +{ + return error == ERROR_BROKEN_PIPE || + error == ERROR_NO_DATA || + error == ERROR_PIPE_NOT_CONNECTED; +} + +CPipeEndpoint::PipeIoResult CPipeEndpoint::WaitForOverlapped( + HANDLE pipe, + HANDLE ioEvent, + OVERLAPPED * overlapped, + DWORD * transferred, + DWORD timeoutMs) +{ + const HANDLE waitHandles[] = { ioEvent, m_stopEvent }; + const DWORD waitResult = WaitForMultipleObjects( + _countof(waitHandles), waitHandles, FALSE, timeoutMs); + + if (waitResult == WAIT_TIMEOUT) + { + CancelIoEx(pipe, overlapped); + if (GetOverlappedResult(pipe, overlapped, transferred, TRUE)) + return PipeIoResult::Success; + DEBUG_WARN("Named pipe write timed out"); + return PipeIoResult::Error; + } + + if (waitResult == WAIT_FIRST_OBJECT_VALUE + 1) + { + CancelIoEx(pipe, overlapped); + GetOverlappedResult(pipe, overlapped, transferred, TRUE); + return PipeIoResult::Stopped; + } + + if (waitResult != WAIT_FIRST_OBJECT_VALUE) + { + DEBUG_ERROR_HR(GetLastError(), "Failed to wait for named pipe I/O"); + CancelIoEx(pipe, overlapped); + GetOverlappedResult(pipe, overlapped, transferred, TRUE); + return PipeIoResult::Error; + } + + if (GetOverlappedResult(pipe, overlapped, transferred, FALSE)) + return PipeIoResult::Success; + + const DWORD error = GetLastError(); + if (error == ERROR_OPERATION_ABORTED && + WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) + return PipeIoResult::Stopped; + + if (IsDisconnectedError(error)) + return PipeIoResult::Disconnected; + + DEBUG_WARN_HR(error, "Named pipe I/O failed"); + return PipeIoResult::Error; +} + +CPipeEndpoint::PipeIoResult CPipeEndpoint::ReadMessage( + HANDLE pipe, + HANDLE ioEvent, + void * message, + DWORD messageSize, + DWORD * bytesRead) +{ + ResetEvent(ioEvent); + OVERLAPPED overlapped = {}; + overlapped.hEvent = ioEvent; + + if (ReadFile(pipe, message, messageSize, bytesRead, &overlapped)) + return PipeIoResult::Success; + + const DWORD error = GetLastError(); + if (error == ERROR_IO_PENDING) + return WaitForOverlapped( + pipe, ioEvent, &overlapped, bytesRead); + + if (error == ERROR_OPERATION_ABORTED && + WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) + return PipeIoResult::Stopped; + + if (IsDisconnectedError(error)) + return PipeIoResult::Disconnected; + + if (error == ERROR_MORE_DATA) + { + DEBUG_ERROR("Named pipe message exceeds the negotiated frame size"); + return PipeIoResult::Error; + } + + DEBUG_WARN_HR(error, "Failed to read from named pipe"); + return PipeIoResult::Error; +} + +CPipeEndpoint::PipeIoResult CPipeEndpoint::WriteMessage( + HANDLE pipe, + const void * message, + DWORD messageSize) +{ + HANDLE ioEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!ioEvent) + { + DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe write event"); + return PipeIoResult::Error; + } + + OVERLAPPED overlapped = {}; + overlapped.hEvent = ioEvent; + DWORD bytesWritten = 0; + PipeIoResult result = PipeIoResult::Success; + + if (!WriteFile(pipe, message, messageSize, &bytesWritten, &overlapped)) + { + const DWORD error = GetLastError(); + if (error == ERROR_IO_PENDING) + result = WaitForOverlapped( + pipe, + ioEvent, + &overlapped, + &bytesWritten, + WRITE_TIMEOUT_MS); + else if (IsDisconnectedError(error)) + result = PipeIoResult::Disconnected; + else if (error == ERROR_OPERATION_ABORTED && + WaitForSingleObject(m_stopEvent, 0) == + WAIT_FIRST_OBJECT_VALUE) + result = PipeIoResult::Stopped; + else + { + DEBUG_WARN_HR(error, "Failed to write to named pipe"); + result = PipeIoResult::Error; + } + } + + if (result == PipeIoResult::Success && bytesWritten != messageSize) + { + DEBUG_ERROR( + "Short named pipe write, expected %lu bytes, wrote %lu bytes", + messageSize, + bytesWritten); + result = PipeIoResult::Error; + } + + CloseHandle(ioEvent); + return result; +} + +CPipeEndpoint::~CPipeEndpoint() +{ + Stop(); +} + +bool CPipeEndpoint::Start( + const wchar_t * pipeName, + Mode mode, + size_t messageSize) +{ + Stop(); + + if (!pipeName || !*pipeName || !messageSize || messageSize > MAXDWORD) + return false; + + m_pipeName = pipeName; + m_mode = mode; + m_messageSize = messageSize; + m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!m_stopEvent) + { + DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe stop event"); + return false; + } + + if (m_mode == Mode::Server) + { + HANDLE pipe = CreateServerPipe(); + if (pipe == INVALID_HANDLE_VALUE) + { + CloseHandle(m_stopEvent); + m_stopEvent = nullptr; + return false; + } + PublishPipe(pipe); + } + + m_running.store(true); + m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr); + if (!m_thread) + { + DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe thread"); + m_running.store(false); + + AcquireSRWLockExclusive(&m_pipeLock); + if (m_pipe != INVALID_HANDLE_VALUE) + { + CloseHandle(m_pipe); + m_pipe = INVALID_HANDLE_VALUE; + } + ReleaseSRWLockExclusive(&m_pipeLock); + + CloseHandle(m_stopEvent); + m_stopEvent = nullptr; + return false; + } + + return true; +} + +void CPipeEndpoint::Stop() +{ + m_running.store(false); + if (m_stopEvent) + SetEvent(m_stopEvent); + + AcquireSRWLockShared(&m_pipeLock); + if (m_pipe != INVALID_HANDLE_VALUE) + CancelIoEx(m_pipe, nullptr); + ReleaseSRWLockShared(&m_pipeLock); + + if (m_thread) + { + WaitForSingleObject(m_thread, INFINITE); + CloseHandle(m_thread); + m_thread = nullptr; + } + + AcquireSRWLockExclusive(&m_pipeLock); + if (m_pipe != INVALID_HANDLE_VALUE) + { + CloseHandle(m_pipe); + m_pipe = INVALID_HANDLE_VALUE; + } + ReleaseSRWLockExclusive(&m_pipeLock); + + if (m_stopEvent) + { + CloseHandle(m_stopEvent); + m_stopEvent = nullptr; + } + + m_connected.store(false); +} + +bool CPipeEndpoint::Send(const void * message, size_t size) +{ + if (!message || size != m_messageSize || !IsRunning() || !IsConnected()) + return false; + + bool success = false; + AcquireSRWLockExclusive(&m_pipeLock); + if (m_pipe != INVALID_HANDLE_VALUE && IsConnected()) + { + const PipeIoResult result = WriteMessage( + m_pipe, + message, + static_cast(size)); + success = result == PipeIoResult::Success; + if (!success) + { + m_connected.store(false); + CancelIoEx(m_pipe, nullptr); + } + } + ReleaseSRWLockExclusive(&m_pipeLock); + return success; +} + +DWORD WINAPI CPipeEndpoint::ThreadProc(void * context) +{ + static_cast(context)->Thread(); + return 0; +} + +void CPipeEndpoint::Thread() +{ + if (m_mode == Mode::Server) + RunServer(); + else + RunClient(); + + m_running.store(false); + m_connected.store(false); +} + +HANDLE CPipeEndpoint::CreateServerPipe() +{ + const DWORD bufferSize = static_cast( + std::max(m_messageSize, 1024)); + + HANDLE pipe = CreateNamedPipeW( + m_pipeName.c_str(), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + 1, + bufferSize, + bufferSize, + 0, + nullptr); + if (pipe == INVALID_HANDLE_VALUE) + DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe %ls", m_pipeName.c_str()); + return pipe; +} + +void CPipeEndpoint::RunServer() +{ + HANDLE ioEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!ioEvent) + { + DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe I/O event"); + AcquireSRWLockShared(&m_pipeLock); + const HANDLE pipe = m_pipe; + ReleaseSRWLockShared(&m_pipeLock); + if (pipe != INVALID_HANDLE_VALUE) + ClosePipe(pipe); + return; + } + + HANDLE pipe = INVALID_HANDLE_VALUE; + AcquireSRWLockShared(&m_pipeLock); + pipe = m_pipe; + ReleaseSRWLockShared(&m_pipeLock); + + while (IsRunning()) + { + if (pipe == INVALID_HANDLE_VALUE) + { + pipe = CreateServerPipe(); + if (pipe == INVALID_HANDLE_VALUE) + { + if (!WaitForRetry(SERVER_RETRY_MS)) + break; + continue; + } + PublishPipe(pipe); + } + + ResetEvent(ioEvent); + OVERLAPPED overlapped = {}; + overlapped.hEvent = ioEvent; + DWORD transferred = 0; + PipeIoResult connectResult = PipeIoResult::Success; + + if (!ConnectNamedPipe(pipe, &overlapped)) + { + const DWORD error = GetLastError(); + if (error == ERROR_PIPE_CONNECTED) + connectResult = PipeIoResult::Success; + else if (error == ERROR_IO_PENDING) + connectResult = WaitForOverlapped( + pipe, ioEvent, &overlapped, &transferred); + else if (error == ERROR_OPERATION_ABORTED && !IsRunning()) + connectResult = PipeIoResult::Stopped; + else + { + DEBUG_WARN_HR(error, "Failed to accept named pipe client"); + connectResult = PipeIoResult::Error; + } + } + + if (connectResult != PipeIoResult::Success) + { + ClosePipe(pipe); + pipe = INVALID_HANDLE_VALUE; + if (connectResult == PipeIoResult::Stopped || !IsRunning()) + break; + continue; + } + + if (!IsRunning()) + break; + + if (!IsRunning() || + WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) + break; + + m_connected.store(true); + DEBUG_INFO("Named pipe client connected: %ls", m_pipeName.c_str()); + if (m_handler) + m_handler->OnPipeConnected(); + + ReadMessages(pipe); + + m_connected.store(false); + if (m_handler) + m_handler->OnPipeDisconnected(); + DEBUG_INFO("Named pipe client disconnected: %ls", m_pipeName.c_str()); + + if (!DisconnectNamedPipe(pipe)) + { + const DWORD error = GetLastError(); + if (error != ERROR_PIPE_NOT_CONNECTED) + { + DEBUG_WARN_HR(error, "Failed to disconnect named pipe client"); + ClosePipe(pipe); + pipe = INVALID_HANDLE_VALUE; + } + } + } + + if (pipe != INVALID_HANDLE_VALUE) + ClosePipe(pipe); + CloseHandle(ioEvent); +} + +void CPipeEndpoint::RunClient() +{ + DWORD retryDelay = CLIENT_RETRY_INITIAL_MS; + DWORD lastConnectError = ERROR_SUCCESS; + + while (IsRunning()) + { + if (m_handler && !m_handler->ShouldReconnect()) + break; + + HANDLE pipe = CreateFileW( + m_pipeName.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + nullptr); + + if (pipe == INVALID_HANDLE_VALUE) + { + const DWORD error = GetLastError(); + if (error != lastConnectError) + { + DEBUG_TRACE_HR( + error, "Named pipe is not available yet: %ls", m_pipeName.c_str()); + lastConnectError = error; + } + + if (!WaitForRetry(retryDelay)) + break; + retryDelay = (std::min)(retryDelay * 2, CLIENT_RETRY_MAX_MS); + continue; + } + + DWORD mode = PIPE_READMODE_MESSAGE; + if (!SetNamedPipeHandleState(pipe, &mode, nullptr, nullptr)) + { + DEBUG_WARN_HR(GetLastError(), "Failed to set named pipe message mode"); + CloseHandle(pipe); + if (!WaitForRetry(retryDelay)) + break; + retryDelay = (std::min)(retryDelay * 2, CLIENT_RETRY_MAX_MS); + continue; + } + + if (!IsRunning() || + WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) + { + CloseHandle(pipe); + break; + } + + PublishPipe(pipe); + m_connected.store(true); + retryDelay = CLIENT_RETRY_INITIAL_MS; + lastConnectError = ERROR_SUCCESS; + DEBUG_INFO("Named pipe connected: %ls", m_pipeName.c_str()); + if (m_handler) + m_handler->OnPipeConnected(); + + ReadMessages(pipe); + + m_connected.store(false); + if (m_handler) + m_handler->OnPipeDisconnected(); + DEBUG_INFO("Named pipe disconnected: %ls", m_pipeName.c_str()); + ClosePipe(pipe); + + if (!WaitForRetry(retryDelay)) + break; + } +} + +bool CPipeEndpoint::ReadMessages(HANDLE pipe) +{ + HANDLE ioEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!ioEvent) + { + DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe read event"); + return false; + } + + std::vector message(m_messageSize); + bool success = true; + while (IsRunning() && IsConnected()) + { + DWORD bytesRead = 0; + const PipeIoResult result = ReadMessage( + pipe, + ioEvent, + message.data(), + static_cast(message.size()), + &bytesRead); + if (result != PipeIoResult::Success) + { + success = result == PipeIoResult::Disconnected || + result == PipeIoResult::Stopped; + break; + } + + if (bytesRead != message.size()) + { + DEBUG_ERROR( + "Invalid named pipe frame size, expected %llu bytes, received %lu", + static_cast(message.size()), + bytesRead); + success = false; + break; + } + + if (!IsRunning() || + WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) + break; + + if (m_handler && !m_handler->OnPipeMessage( + message.data(), message.size())) + { + DEBUG_ERROR("Named pipe peer sent an invalid message"); + success = false; + break; + } + } + + CloseHandle(ioEvent); + return success; +} + +bool CPipeEndpoint::WaitForRetry(DWORD delayMs) +{ + return IsRunning() && + WaitForSingleObject(m_stopEvent, delayMs) == WAIT_TIMEOUT; +} + +void CPipeEndpoint::PublishPipe(HANDLE pipe) +{ + AcquireSRWLockExclusive(&m_pipeLock); + m_pipe = pipe; + ReleaseSRWLockExclusive(&m_pipeLock); +} + +void CPipeEndpoint::ClosePipe(HANDLE pipe) +{ + AcquireSRWLockExclusive(&m_pipeLock); + if (m_pipe == pipe) + m_pipe = INVALID_HANDLE_VALUE; + CloseHandle(pipe); + ReleaseSRWLockExclusive(&m_pipeLock); +} diff --git a/idd/LGCommon/CPipeEndpoint.h b/idd/LGCommon/CPipeEndpoint.h new file mode 100644 index 00000000..d03396eb --- /dev/null +++ b/idd/LGCommon/CPipeEndpoint.h @@ -0,0 +1,131 @@ +/** + * 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 + +#include +#include +#include + +class IPipeEndpointHandler +{ +public: + virtual ~IPipeEndpointHandler() = default; + + virtual void OnPipeConnected() {} + virtual void OnPipeDisconnected() {} + virtual bool ShouldReconnect() { return true; } + virtual bool OnPipeMessage( + _In_reads_bytes_(size) const void * message, + _In_ size_t size) = 0; +}; + +class CPipeEndpoint +{ +public: + enum class Mode + { + Server, + Client, + }; + + CPipeEndpoint() = default; + ~CPipeEndpoint(); + + CPipeEndpoint(const CPipeEndpoint &) = delete; + CPipeEndpoint & operator=(const CPipeEndpoint &) = delete; + + bool Start( + _In_z_ const wchar_t * pipeName, + _In_ Mode mode, + _In_ size_t messageSize); + void Stop(); + + bool Send( + _In_reads_bytes_(size) const void * message, + _In_ size_t size); + + bool IsRunning() const { return m_running.load(); } + bool IsConnected() const { return m_connected.load(); } + + void SetHandler(_In_opt_ IPipeEndpointHandler * handler) + { + m_handler = handler; + } + +private: + enum class PipeIoResult + { + Success, + Disconnected, + Stopped, + Error, + }; + + static const DWORD CLIENT_RETRY_INITIAL_MS; + static const DWORD CLIENT_RETRY_MAX_MS; + static const DWORD SERVER_RETRY_MS; + static const DWORD WRITE_TIMEOUT_MS; + static const DWORD WAIT_FIRST_OBJECT_VALUE; + + static bool IsDisconnectedError(_In_ DWORD error); + PipeIoResult WaitForOverlapped( + _In_ HANDLE pipe, + _In_ HANDLE ioEvent, + _Inout_ OVERLAPPED * overlapped, + _Out_ DWORD * transferred, + _In_ DWORD timeoutMs = INFINITE); + PipeIoResult ReadMessage( + _In_ HANDLE pipe, + _In_ HANDLE ioEvent, + _Out_writes_bytes_(messageSize) void * message, + _In_ DWORD messageSize, + _Out_ DWORD * bytesRead); + PipeIoResult WriteMessage( + _In_ HANDLE pipe, + _In_reads_bytes_(messageSize) const void * message, + _In_ DWORD messageSize); + + static DWORD WINAPI ThreadProc(_In_ void * context); + + void Thread(); + void RunServer(); + void RunClient(); + bool ReadMessages(_In_ HANDLE pipe); + HANDLE CreateServerPipe(); + bool WaitForRetry(_In_ DWORD delayMs); + void PublishPipe(_In_ HANDLE pipe); + void ClosePipe(_In_ HANDLE pipe); + + std::wstring m_pipeName; + size_t m_messageSize = 0; + Mode m_mode = Mode::Client; + IPipeEndpointHandler * m_handler = nullptr; + + std::atomic m_running { false }; + std::atomic m_connected { false }; + + SRWLOCK m_pipeLock = SRWLOCK_INIT; + HANDLE m_pipe = INVALID_HANDLE_VALUE; + HANDLE m_thread = nullptr; + HANDLE m_stopEvent = nullptr; +}; diff --git a/idd/LGCommon/InputPipeProtocol.h b/idd/LGCommon/InputPipeProtocol.h new file mode 100644 index 00000000..869dc137 --- /dev/null +++ b/idd/LGCommon/InputPipeProtocol.h @@ -0,0 +1,51 @@ +/** + * 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 +#include + +static constexpr wchar_t LG_INPUT_PIPE_NAME[] = + L"\\\\.\\pipe\\LookingGlassIDDInput"; + +static constexpr uint32_t LG_INPUT_PIPE_MAGIC = 0x5049474c; +static constexpr uint16_t LG_INPUT_PIPE_VERSION = 1; +static constexpr size_t LG_INPUT_PIPE_MAX_REPORT_SIZE = 64; + +enum LGInputPipeMessageType : uint16_t +{ + LG_INPUT_PIPE_MESSAGE_REPORT = 1, +}; + +#pragma pack(push, 1) +struct LGInputPipeMessage +{ + uint32_t magic; + uint16_t version; + uint16_t type; + uint32_t payloadSize; + uint64_t sequence; + uint8_t payload[LG_INPUT_PIPE_MAX_REPORT_SIZE]; +}; +#pragma pack(pop) + +static_assert(sizeof(LGInputPipeMessage) == 84, + "LGInputPipeMessage wire layout changed"); diff --git a/idd/LGCommon/LGCommon.vcxproj b/idd/LGCommon/LGCommon.vcxproj new file mode 100644 index 00000000..0cb9c83a --- /dev/null +++ b/idd/LGCommon/LGCommon.vcxproj @@ -0,0 +1,83 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {ACB90E34-01CA-4B86-813B-3D20904994C6} + LGCommon + 10.0.26100.0 + 2 + Debug + Win32 + + + + StaticLibrary + WindowsUserModeDriver10.0 + Windows10 + Universal + UMDF + 25 + 25 + <_NT_TARGET_VERSION>0xA000005 + Spectre + + + true + + + false + + + + + + + + + LGCommon + + + + MultiThreaded + _ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions) + /EHsc /D_ATL_NO_WIN_SUPPORT %(AdditionalOptions) + $(ProjectDir);%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + + + + + + diff --git a/idd/LGCommon/LGCommon.vcxproj.filters b/idd/LGCommon/LGCommon.vcxproj.filters new file mode 100644 index 00000000..c363bd92 --- /dev/null +++ b/idd/LGCommon/LGCommon.vcxproj.filters @@ -0,0 +1,48 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;cc + + + {93995380-89BD-4B04-88EB-625FBE52EBFB} + h;hh;hpp;hxx + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + diff --git a/idd/LGCommon/PipeMsg.h b/idd/LGCommon/PipeMsg.h index 1e4d19ef..3a70e00d 100644 --- a/idd/LGCommon/PipeMsg.h +++ b/idd/LGCommon/PipeMsg.h @@ -22,7 +22,7 @@ #include -#define LG_PIPE_NAME "\\\\.\\pipe\\LookingGlassIDD" +static constexpr wchar_t LG_PIPE_NAME[] = L"\\\\.\\pipe\\LookingGlassIDD"; struct LGPipeMsg { @@ -68,3 +68,5 @@ struct LGPipeMsg resolutionRejected; }; }; + +static_assert(sizeof(LGPipeMsg) == 20, "LGPipeMsg wire layout changed"); diff --git a/idd/LGIdd.sln b/idd/LGIdd.sln index 30bd53ca..229856b8 100644 --- a/idd/LGIdd.sln +++ b/idd/LGIdd.sln @@ -16,14 +16,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LGMP", "..\repos\LGMP\LGMP. EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LGIddHelper", "LGIddHelper\LGIddHelper.vcxproj", "{0045D7AD-3F26-4B87-81CB-78D18839596D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LGCommon", "LGCommon", "{ACB90E34-01CA-4B86-813B-3D20904994C6}" - ProjectSection(SolutionItems) = preProject - LGCommon\CDebug.cpp = LGCommon\CDebug.cpp - LGCommon\CDebug.h = LGCommon\CDebug.h - LGCommon\CSRWLock.h = LGCommon\CSRWLock.h - LGCommon\DefaultDisplayModes.h = LGCommon\DefaultDisplayModes.h - LGCommon\PipeMsg.h = LGCommon\PipeMsg.h - EndProjectSection +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LGCommon", "LGCommon\LGCommon.vcxproj", "{ACB90E34-01CA-4B86-813B-3D20904994C6}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LGIddInstall", "LGIddInstall\LGIddInstall.vcxproj", "{23EC8370-5F3B-4D18-8C31-E89A154F3666}" EndProject @@ -47,6 +40,14 @@ Global {1CBF3DAA-0726-4F5F-88A2-04D95FB6591A}.Release-LGInput|x64.ActiveCfg = Release-LGInput|x64 {1CBF3DAA-0726-4F5F-88A2-04D95FB6591A}.Release-LGInput|x64.Build.0 = Release-LGInput|x64 {1CBF3DAA-0726-4F5F-88A2-04D95FB6591A}.Release-LGInput|x64.Deploy.0 = Release-LGInput|x64 + {ACB90E34-01CA-4B86-813B-3D20904994C6}.Debug|x64.ActiveCfg = Debug|x64 + {ACB90E34-01CA-4B86-813B-3D20904994C6}.Debug|x64.Build.0 = Debug|x64 + {ACB90E34-01CA-4B86-813B-3D20904994C6}.Debug-LGInput|x64.ActiveCfg = Debug|x64 + {ACB90E34-01CA-4B86-813B-3D20904994C6}.Debug-LGInput|x64.Build.0 = Debug|x64 + {ACB90E34-01CA-4B86-813B-3D20904994C6}.Release|x64.ActiveCfg = Release|x64 + {ACB90E34-01CA-4B86-813B-3D20904994C6}.Release|x64.Build.0 = Release|x64 + {ACB90E34-01CA-4B86-813B-3D20904994C6}.Release-LGInput|x64.ActiveCfg = Release|x64 + {ACB90E34-01CA-4B86-813B-3D20904994C6}.Release-LGInput|x64.Build.0 = Release|x64 {2477B25B-CB62-4AD9-A260-CE5F00D77EEB}.Debug|x64.ActiveCfg = Debug|x64 {2477B25B-CB62-4AD9-A260-CE5F00D77EEB}.Debug|x64.Build.0 = Debug|x64 {2477B25B-CB62-4AD9-A260-CE5F00D77EEB}.Debug-LGInput|x64.ActiveCfg = Debug|x64 diff --git a/idd/LGIdd/Driver.cpp b/idd/LGIdd/Driver.cpp index c6dc8e9d..0f3c2a26 100644 --- a/idd/LGIdd/Driver.cpp +++ b/idd/LGIdd/Driver.cpp @@ -24,6 +24,7 @@ #include "CDebug.h" #include "platform/CPlatformInfo.h" #include "VersionInfo.h" +#include "ipc/CInputPipeServer.h" #include "ipc/CPipeServer.h" NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) @@ -45,6 +46,13 @@ NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING Reg goto fail; } + if (!g_inputPipeServer.Init()) + { + status = STATUS_UNSUCCESSFUL; + DEBUG_ERROR("Failed to setup the LGInput IPC pipe"); + goto fail; + } + WDF_DRIVER_CONFIG config; WDF_OBJECT_ATTRIBUTES attributes; @@ -66,6 +74,8 @@ NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING Reg return status; fail: + g_inputPipeServer.DeInit(); + g_pipe.DeInit(); #if UMDF_VERSION_MAJOR == 2 && UMDF_VERSION_MINOR == 0 WPP_CLEANUP(); #else @@ -91,6 +101,7 @@ VOID LGIddEvtDriverContextCleanup(_In_ WDFOBJECT DriverObject) TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry"); + g_inputPipeServer.DeInit(); g_pipe.DeInit(); #if UMDF_VERSION_MAJOR == 2 && UMDF_VERSION_MINOR == 0 diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index 3edb4197..cdf953a8 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -32,9 +32,9 @@ - + @@ -70,11 +70,11 @@ - + @@ -303,6 +303,12 @@ + + {acb90e34-01ca-4b86-813b-3d20904994c6} + Configuration=$(LGBaseConfiguration) + Platform=$(Platform) + SolutionDir=$(LGDriverSolutionDir) + {2477b25b-cb62-4ad9-a260-ce5f00d77eeb} false diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index 03dd0305..dd16de15 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -35,9 +35,6 @@ {1C677205-7587-4037-9E36-92DFE600B985} - - {938E49D6-F954-4EBE-80AB-E0F67E677F27} - {98768720-86D6-4A83-9EBD-2C8BFB51D793} @@ -52,9 +49,6 @@ - - Common - Driver @@ -70,6 +64,9 @@ IPC + + IPC + Display @@ -195,9 +192,6 @@ - - Common - Driver @@ -207,6 +201,9 @@ IPC + + IPC + Display diff --git a/idd/LGIdd/ipc/CInputPipeServer.cpp b/idd/LGIdd/ipc/CInputPipeServer.cpp new file mode 100644 index 00000000..bcf43fce --- /dev/null +++ b/idd/LGIdd/ipc/CInputPipeServer.cpp @@ -0,0 +1,75 @@ +/** + * 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 "ipc/CInputPipeServer.h" + +#include "CDebug.h" +#include "InputPipeProtocol.h" + +#include + +CInputPipeServer g_inputPipeServer; + +bool CInputPipeServer::Init() +{ + AcquireSRWLockExclusive(&m_sendLock); + m_sequence = 0; + ReleaseSRWLockExclusive(&m_sendLock); + + m_endpoint.SetHandler(this); + return m_endpoint.Start( + LG_INPUT_PIPE_NAME, + CPipeEndpoint::Mode::Server, + sizeof(LGInputPipeMessage)); +} + +void CInputPipeServer::DeInit() +{ + m_endpoint.Stop(); +} + +bool CInputPipeServer::SendReport(const void * report, size_t size) +{ + if (!report || !size || size > LG_INPUT_PIPE_MAX_REPORT_SIZE) + return false; + + LGInputPipeMessage message = {}; + message.magic = LG_INPUT_PIPE_MAGIC; + message.version = LG_INPUT_PIPE_VERSION; + message.type = LG_INPUT_PIPE_MESSAGE_REPORT; + message.payloadSize = static_cast(size); + memcpy(message.payload, report, size); + + AcquireSRWLockExclusive(&m_sendLock); + message.sequence = ++m_sequence; + const bool sent = m_endpoint.Send(&message, sizeof(message)); + ReleaseSRWLockExclusive(&m_sendLock); + return sent; +} + +bool CInputPipeServer::OnPipeMessage( + const void * message, + size_t size) +{ + UNREFERENCED_PARAMETER(message); + UNREFERENCED_PARAMETER(size); + DEBUG_WARN("LGInput sent an unexpected message"); + return false; +} diff --git a/idd/LGIdd/ipc/CInputPipeServer.h b/idd/LGIdd/ipc/CInputPipeServer.h new file mode 100644 index 00000000..21705156 --- /dev/null +++ b/idd/LGIdd/ipc/CInputPipeServer.h @@ -0,0 +1,49 @@ +/** + * 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 "CPipeEndpoint.h" + +#include +#include + +class CInputPipeServer : private IPipeEndpointHandler +{ +public: + ~CInputPipeServer() { DeInit(); } + + bool Init(); + void DeInit(); + + bool SendReport( + _In_reads_bytes_(size) const void * report, + _In_ size_t size); + bool IsConnected() const { return m_endpoint.IsConnected(); } + +private: + bool OnPipeMessage(const void * message, size_t size) override; + + CPipeEndpoint m_endpoint; + SRWLOCK m_sendLock = SRWLOCK_INIT; + uint64_t m_sequence = 0; +}; + +extern CInputPipeServer g_inputPipeServer; diff --git a/idd/LGIdd/ipc/CPipeServer.cpp b/idd/LGIdd/ipc/CPipeServer.cpp index d98b904b..b4ab96e2 100644 --- a/idd/LGIdd/ipc/CPipeServer.cpp +++ b/idd/LGIdd/ipc/CPipeServer.cpp @@ -26,234 +26,73 @@ CPipeServer g_pipe; bool CPipeServer::Init() { - _DeInit(); - - m_pipe.Attach(CreateNamedPipeA( + m_endpoint.SetHandler(this); + return m_endpoint.Start( LG_PIPE_NAME, - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, - 1, - 1024, - 1024, - 0, - NULL)); - - if (!m_pipe.IsValid()) - { - DEBUG_ERROR_HR(GetLastError(), "Failed to create the named pipe"); - return false; - } - - m_signal.Attach(CreateEvent(NULL, TRUE, FALSE, NULL)); - if (!m_signal.IsValid()) - { - DEBUG_ERROR_HR(GetLastError(), "Failed to create pipe signal event"); - return false; - } - - m_running = true; - m_thread.Attach(CreateThread( - NULL, - 0, - _pipeThread, - (LPVOID)this, - 0, - NULL)); - - if (!m_thread.IsValid()) - { - DEBUG_ERROR_HR(GetLastError(), "Failed to create the pipe thread"); - return false; - } - - DEBUG_TRACE("Pipe Initialized"); - return true; -} - -void CPipeServer::_DeInit() -{ - m_running = false; - m_connected = false; - if (m_signal.IsValid()) - SetEvent(m_signal.Get()); - - if (m_thread.IsValid()) - { - WaitForSingleObject(m_thread.Get(), INFINITE); - m_thread.Close(); - } - - if (m_pipe.IsValid()) - { - FlushFileBuffers(m_pipe.Get()); - m_pipe.Close(); - } - - m_signal.Close(); + CPipeEndpoint::Mode::Server, + sizeof(LGPipeMsg)); } void CPipeServer::DeInit() -{ - DEBUG_TRACE("Pipe Stopping"); - _DeInit(); - DEBUG_TRACE("Pipe Stopped"); +{ + m_endpoint.Stop(); } -void CPipeServer::Thread() +void CPipeServer::OnPipeConnected() { - DEBUG_TRACE("Pipe thread started"); + AcquireSRWLockExclusive(&m_queueLock); + std::vector queued; + queued.swap(m_queue); - HandleT ioEvent(CreateEvent(NULL, TRUE, FALSE, NULL)); - if (!ioEvent.IsValid()) - { - DEBUG_ERROR_HR(GetLastError(), "Can't create event for overlapped I/O!"); - WaitForSingleObject(m_signal.Get(), 5000); - return; - } - - while(m_running) - { - m_connected = false; - - OVERLAPPED overlapped = { 0 }; - overlapped.hEvent = ioEvent.Get(); - - if (!ConnectNamedPipe(m_pipe.Get(), &overlapped)) + for (size_t i = 0; i < queued.size(); ++i) + if (!m_endpoint.Send(&queued[i], sizeof(queued[i]))) { - DWORD dwError = GetLastError(); - switch (dwError) { - case ERROR_PIPE_CONNECTED: - break; - case ERROR_IO_PENDING: - { - HANDLE hWait[] = { ioEvent.Get(), m_signal.Get() }; - switch (WaitForMultipleObjects(2, hWait, FALSE, INFINITE)) - { - case WAIT_OBJECT_0: - break; - case WAIT_OBJECT_0 + 1: - DEBUG_INFO("Connect interrupted by signal"); - CancelIo(m_pipe.Get()); - WaitForSingleObject(ioEvent.Get(), INFINITE); - continue; - } - break; - } - default: - DEBUG_ERROR_HR(dwError, "Error connecting to the named pipe"); - goto end; - } + for (; i < queued.size(); ++i) + QueueMsgLocked(queued[i]); + break; + } + ReleaseSRWLockExclusive(&m_queueLock); +} + +bool CPipeServer::OnPipeMessage(const void * message, size_t size) +{ + if (size != sizeof(LGPipeMsg)) + return false; + + const LGPipeMsg & msg = *static_cast(message); + if (msg.size != sizeof(msg)) + return false; + + switch (msg.type) + { + case LGPipeMsg::RELOADSETTINGS: + HandleReloadSettings(); + return true; + + default: + DEBUG_ERROR("Unknown message type %d", msg.type); + return true; + } +} + +void CPipeServer::QueueMsgLocked(const LGPipeMsg & msg) +{ + for (LGPipeMsg & queued : m_queue) + if (queued.type == msg.type) + { + queued = msg; + return; } - DEBUG_TRACE("Client connected"); - - m_connected = true; - - for (const auto& msg : m_queue) - WriteMsg(msg); - m_queue.clear(); - - while (m_running && m_connected) - { - LGPipeMsg msg; - - if (!ReadFile(m_pipe.Get(), &msg, sizeof(msg), NULL, &overlapped)) - { - DWORD dwError = GetLastError(); - if (dwError != ERROR_IO_PENDING) - { - DEBUG_ERROR_HR(dwError, "ReadFile Failed"); - break; - } - - HANDLE hWait[] = { ioEvent.Get(), m_signal.Get() }; - switch (WaitForMultipleObjects(2, hWait, FALSE, INFINITE)) - { - case WAIT_OBJECT_0: - break; - case WAIT_OBJECT_0 + 1: - DEBUG_INFO("I/O interrupted by signal"); - CancelIo(m_pipe.Get()); - WaitForSingleObject(ioEvent.Get(), INFINITE); - continue; - } - } - - DWORD bytesRead; - GetOverlappedResult(m_pipe.Get(), &overlapped, &bytesRead, TRUE); - - if (bytesRead != sizeof(msg)) - { - DEBUG_ERROR("Corrupted data, expected %lld bytes, read %lld bytes", sizeof msg, bytesRead); - break; - } - - if (msg.size != sizeof(msg)) - { - DEBUG_ERROR("Corrupted data, expected %lld bytes, actual message size: %lld bytes", sizeof msg, msg.size); - break; - } - - switch (msg.type) - { - case LGPipeMsg::RELOADSETTINGS: - HandleReloadSettings(); - break; - - default: - DEBUG_ERROR("Unknown message type %d", msg.type); - break; - } - } - - DEBUG_TRACE("Client disconnected"); - DisconnectNamedPipe(m_pipe.Get()); - - if (m_running) - ResetEvent(m_signal.Get()); - } - -end: - m_running = false; - m_connected = false; - DEBUG_TRACE("Pipe thread shutdown"); + m_queue.push_back(msg); } void CPipeServer::WriteMsg(const LGPipeMsg & msg) { - if (!m_connected) - { - // Not connected yet: keep only the latest message of each type. These are - // all latest-state-wins messages, so a burst (e.g. display mode changes - // while resizing) must collapse to the final state rather than replay every - // intermediate value when the helper reconnects. - for (auto & queued : m_queue) - if (queued.type == msg.type) - { - queued = msg; - return; - } - m_queue.push_back(msg); - return; - } - - DWORD written; - if (!WriteFile(m_pipe.Get(), &msg, sizeof(msg), &written, NULL)) - { - DWORD err = GetLastError(); - if (err == ERROR_BROKEN_PIPE || err == ERROR_NO_DATA) - { - DEBUG_WARN_HR(err, "Client disconnected, failed to write"); - m_connected = false; - SetEvent(m_signal.Get()); - return; - } - - DEBUG_WARN_HR(err, "WriteFile failed on the pipe"); - return; - } - - FlushFileBuffers(m_pipe.Get()); + AcquireSRWLockExclusive(&m_queueLock); + if (!m_endpoint.Send(&msg, sizeof(msg))) + QueueMsgLocked(msg); + ReleaseSRWLockExclusive(&m_queueLock); } void CPipeServer::HandleReloadSettings() @@ -276,7 +115,7 @@ void CPipeServer::SetDeviceContext(CDeviceContext * context) void CPipeServer::SetCursorPos(uint32_t x, uint32_t y) { // do not send cursor messages if we are not connected or they will end up queued - if (!m_connected) + if (!m_endpoint.IsConnected()) return; LGPipeMsg msg = {}; @@ -284,7 +123,9 @@ void CPipeServer::SetCursorPos(uint32_t x, uint32_t y) msg.type = LGPipeMsg::SETCURSORPOS; msg.curorPos.x = x; msg.curorPos.y = y; - WriteMsg(msg); + // Cursor position is transient. If the connection is lost during this + // write, drop it instead of replaying stale coordinates after reconnect. + m_endpoint.Send(&msg, sizeof(msg)); } void CPipeServer::SetDisplayMode( diff --git a/idd/LGIdd/ipc/CPipeServer.h b/idd/LGIdd/ipc/CPipeServer.h index 7e1f5138..3bba819b 100644 --- a/idd/LGIdd/ipc/CPipeServer.h +++ b/idd/LGIdd/ipc/CPipeServer.h @@ -23,40 +23,31 @@ #include #include #include -#include #include +#include "CPipeEndpoint.h" #include "PipeMsg.h" -using namespace Microsoft::WRL; -using namespace Microsoft::WRL::Wrappers; -using namespace Microsoft::WRL::Wrappers::HandleTraits; - class CDeviceContext; -class CPipeServer +class CPipeServer : private IPipeEndpointHandler { private: - HandleT m_pipe; - HandleT m_thread; - HandleT m_signal; - std::vector m_queue; - - bool m_running = false; - bool m_connected = false; + CPipeEndpoint m_endpoint; + SRWLOCK m_queueLock = SRWLOCK_INIT; + std::vector m_queue; SRWLOCK m_deviceContextLock = SRWLOCK_INIT; CDeviceContext * m_deviceContext = nullptr; - void _DeInit(); - - static DWORD WINAPI _pipeThread(LPVOID lpParam) { ((CPipeServer*)lpParam)->Thread(); return 0; } - void Thread(); - void WriteMsg(const LGPipeMsg & msg); + void QueueMsgLocked(const LGPipeMsg & msg); void HandleReloadSettings(); + void OnPipeConnected() override; + bool OnPipeMessage(const void * message, size_t size) override; + public: ~CPipeServer() { DeInit(); } diff --git a/idd/LGIddHelper/CPipeClient.cpp b/idd/LGIddHelper/CPipeClient.cpp index 09e86df9..5970c906 100644 --- a/idd/LGIddHelper/CPipeClient.cpp +++ b/idd/LGIddHelper/CPipeClient.cpp @@ -114,51 +114,16 @@ bool CPipeClient::Init() return false; } - m_signal.Attach(CreateEvent(NULL, TRUE, FALSE, NULL)); - if (!m_signal.IsValid()) - { - DEBUG_ERROR_HR(GetLastError(), "Failed to create pipe signal event"); - return false; - } - - m_running = true; - m_thread.Attach(CreateThread( - NULL, - 0, - _pipeThread, - (LPVOID)this, - 0, - NULL)); - - if (!m_thread.IsValid()) - { - DEBUG_ERROR_HR(GetLastError(), "Failed to create the pipe thread"); - return false; - } - - return true; + m_endpoint.SetHandler(this); + return m_endpoint.Start( + LG_PIPE_NAME, + CPipeEndpoint::Mode::Client, + sizeof(LGPipeMsg)); } void CPipeClient::DeInit() { - m_connected = false; - m_running = false; - if (m_signal.IsValid()) - SetEvent(m_signal.Get()); - - if (m_thread.IsValid()) - { - WaitForSingleObject(m_thread.Get(), INFINITE); - m_thread.Close(); - } - - if (m_pipe.IsValid()) - { - FlushFileBuffers(m_pipe.Get()); - m_pipe.Close(); - } - - m_signal.Close(); + m_endpoint.Stop(); } bool CPipeClient::IsLGIddDeviceAttached() @@ -228,28 +193,12 @@ void CPipeClient::SetActiveDesktop() void CPipeClient::WriteMsg(const LGPipeMsg& msg) { - DWORD written; - if (!WriteFile(m_pipe.Get(), &msg, sizeof(msg), &written, NULL)) - { - DWORD err = GetLastError(); - if (err == ERROR_BROKEN_PIPE) - { - DEBUG_WARN_HR(err, "Client disconnected, failed to write"); - m_connected = false; - SetEvent(m_signal.Get()); - return; - } - - DEBUG_WARN_HR(err, "WriteFile failed on the pipe"); - return; - } - - FlushFileBuffers(m_pipe.Get()); + m_endpoint.Send(&msg, sizeof(msg)); } void CPipeClient::ReloadSettings() { - if (!m_connected) + if (!m_endpoint.IsConnected()) return; LGPipeMsg msg = {}; @@ -258,6 +207,14 @@ void CPipeClient::ReloadSettings() WriteMsg(msg); } +bool CPipeClient::ShouldReconnect() +{ + const bool attached = IsLGIddDeviceAttached(); + if (!attached) + DEBUG_INFO("Looking Glass Indirect Display Device was removed"); + return attached; +} + bool CPipeClient::EnsureOnlyDisplayLocked() { std::vector displays; @@ -372,124 +329,37 @@ bool CPipeClient::EnsureOnlyDisplay() return result; } -void CPipeClient::Thread() +bool CPipeClient::OnPipeMessage(const void * message, size_t size) { - DEBUG_INFO("Pipe thread started"); + if (size != sizeof(LGPipeMsg)) + return false; - HandleT ioEvent(CreateEvent(NULL, TRUE, FALSE, NULL)); - if (!ioEvent.IsValid()) + const LGPipeMsg & msg = *static_cast(message); + if (msg.size != sizeof(msg)) + return false; + + switch (msg.type) { - DEBUG_ERROR("Can't create event for overlapped I/O!"); - WaitForSingleObject(m_signal.Get(), 5000); - return; + case LGPipeMsg::SETCURSORPOS: + HandleSetCursorPos(msg); + return true; + + case LGPipeMsg::SETDISPLAYMODE: + HandleSetDisplayMode(msg); + return true; + + case LGPipeMsg::GPUSTATUS: + HandleGPUStatus(msg); + return true; + + case LGPipeMsg::RESOLUTIONREJECTED: + HandleResolutionRejected(msg); + return true; + + default: + DEBUG_ERROR("Unknown message type %d", msg.type); + return true; } - - while (m_running) - { - if (!IsLGIddDeviceAttached()) - { - m_running = false; - DEBUG_ERROR("Device is no longer available, shutting down"); - break; - } - - m_pipe.Attach(CreateFile( - TEXT(LG_PIPE_NAME), - GENERIC_READ | GENERIC_WRITE, - 0, - NULL, - OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, - NULL - )); - - if (!m_pipe.IsValid()) - { - DEBUG_ERROR_HR(GetLastError(), "Failed to open the named pipe"); - WaitForSingleObject(m_signal.Get(), 5000); - continue; - } - - m_connected = true; - DEBUG_INFO("Pipe connected"); - - while (m_running && m_connected) - { - LGPipeMsg msg; - - OVERLAPPED overlapped = { 0 }; - overlapped.hEvent = ioEvent.Get(); - - if (!ReadFile(m_pipe.Get(), &msg, sizeof(msg), NULL, &overlapped)) - { - DWORD dwError = GetLastError(); - if (dwError != ERROR_IO_PENDING) - { - DEBUG_ERROR_HR(dwError, "ReadFile Failed"); - break; - } - - HANDLE hWait[] = { ioEvent.Get(), m_signal.Get() }; - switch (WaitForMultipleObjects(2, hWait, FALSE, INFINITE)) - { - case WAIT_OBJECT_0: - break; - case WAIT_OBJECT_0 + 1: - DEBUG_INFO("I/O interrupted by signal"); - CancelIo(m_pipe.Get()); - WaitForSingleObject(ioEvent.Get(), INFINITE); - continue; - } - } - - DWORD bytesRead; - GetOverlappedResult(m_pipe.Get(), &overlapped, &bytesRead, TRUE); - - if (bytesRead != sizeof(msg)) - { - DEBUG_ERROR("Corrupted data, expected %lld bytes, read %lld bytes", sizeof msg, bytesRead); - break; - } - - if (msg.size != sizeof(msg)) - { - DEBUG_ERROR("Corrupted data, expected %lld bytes, actual message size: %lld bytes", sizeof msg, msg.size); - break; - } - - switch (msg.type) - { - case LGPipeMsg::SETCURSORPOS: - HandleSetCursorPos(msg); - break; - - case LGPipeMsg::SETDISPLAYMODE: - HandleSetDisplayMode(msg); - break; - - case LGPipeMsg::GPUSTATUS: - HandleGPUStatus(msg); - break; - - case LGPipeMsg::RESOLUTIONREJECTED: - HandleResolutionRejected(msg); - break; - - default: - DEBUG_ERROR("Unknown message type %d", msg.type); - break; - } - } - - m_pipe.Close(); - m_connected = false; - DEBUG_INFO("Pipe closed"); - - if (m_running) - ResetEvent(m_signal.Get()); - } - - DEBUG_INFO("Pipe thread shutdown"); } void CPipeClient::HandleSetCursorPos(const LGPipeMsg& msg) diff --git a/idd/LGIddHelper/CPipeClient.h b/idd/LGIddHelper/CPipeClient.h index 2d0ed065..b51a64a4 100644 --- a/idd/LGIddHelper/CPipeClient.h +++ b/idd/LGIddHelper/CPipeClient.h @@ -22,28 +22,16 @@ #include #include -#include +#include "CPipeEndpoint.h" #include "PipeMsg.h" -using namespace Microsoft::WRL; -using namespace Microsoft::WRL::Wrappers; -using namespace Microsoft::WRL::Wrappers::HandleTraits; - -class CPipeClient +class CPipeClient : private IPipeEndpointHandler { private: - HandleT m_pipe; - HandleT m_thread; - HandleT m_signal; - - bool m_running = false; - bool m_connected = false; + CPipeEndpoint m_endpoint; SRWLOCK m_displayLock = SRWLOCK_INIT; - static DWORD WINAPI _pipeThread(LPVOID lpParam) { ((CPipeClient*)lpParam)->Thread(); return 0; } - void Thread(); - void WriteMsg(const LGPipeMsg& msg); void SetActiveDesktop(); @@ -55,6 +43,9 @@ private: void HandleGPUStatus(const LGPipeMsg& msg); void HandleResolutionRejected(const LGPipeMsg& msg); + bool ShouldReconnect() override; + bool OnPipeMessage(const void * message, size_t size) override; + public: ~CPipeClient() { DeInit(); } @@ -62,7 +53,7 @@ public: bool Init(); void DeInit(); - bool IsRunning() { return m_running; } + bool IsRunning() { return m_endpoint.IsRunning(); } void ReloadSettings(); bool EnsureOnlyDisplay(); diff --git a/idd/LGIddHelper/LGIddHelper.vcxproj b/idd/LGIddHelper/LGIddHelper.vcxproj index 5ad99977..363f89e5 100644 --- a/idd/LGIddHelper/LGIddHelper.vcxproj +++ b/idd/LGIddHelper/LGIddHelper.vcxproj @@ -75,7 +75,7 @@ Level3 true - WIN32;_DEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) + WIN32;_CONSOLE;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) true $(SolutionDir)LGCommon;$(SolutionDir)..\common\include;%(AdditionalIncludeDirectories) stdcpp17 @@ -127,12 +127,11 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd Level3 true - _DEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) + _CONSOLE;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) true stdcpp17 Default $(SolutionDir)LGCommon;$(SolutionDir)..\common\include;%(AdditionalIncludeDirectories) - MultiThreadedDebug Windows @@ -161,7 +160,6 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd stdcpp17 Default $(SolutionDir)LGCommon;$(SolutionDir)..\common\include;%(AdditionalIncludeDirectories) - MultiThreaded Windows @@ -181,8 +179,16 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd $(ProjectDir)HighDPI.manifest + + + MultiThreaded + _ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions) + false + false + StdCall + + - @@ -199,7 +205,6 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd - @@ -224,6 +229,11 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd + + + {acb90e34-01ca-4b86-813b-3d20904994c6} + + @@ -251,4 +261,4 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd - \ No newline at end of file + diff --git a/idd/LGIddHelper/LGIddHelper.vcxproj.filters b/idd/LGIddHelper/LGIddHelper.vcxproj.filters index 55d6c061..0cd5b124 100644 --- a/idd/LGIddHelper/LGIddHelper.vcxproj.filters +++ b/idd/LGIddHelper/LGIddHelper.vcxproj.filters @@ -18,7 +18,6 @@ Source Files - Source Files @@ -60,7 +59,6 @@ - Header Files @@ -115,4 +113,4 @@ - \ No newline at end of file + diff --git a/idd/LGInput/CHIDDevice.cpp b/idd/LGInput/CHIDDevice.cpp index edad1cc3..317c0fdd 100644 --- a/idd/LGInput/CHIDDevice.cpp +++ b/idd/LGInput/CHIDDevice.cpp @@ -20,10 +20,13 @@ #include "CHIDDevice.h" +#include "CDebug.h" #include "CSRWLock.h" #include "HIDReports.h" +#include "ipc/CInputPipeClient.h" #include +#include static constexpr USHORT LG_INPUT_VENDOR_ID = 0x0000; static constexpr USHORT LG_INPUT_PRODUCT_ID = 0x0000; @@ -42,12 +45,14 @@ struct HIDDeviceContext HID_DEVICE_ATTRIBUTES attributes; HID_DESCRIPTOR descriptor; UCHAR keyboardLeds; + SRWLOCK lifecycleLock; SRWLOCK reportLock; bool active; bool stopping; size_t reportHead; size_t reportCount; HIDQueuedReport reports[REPORT_QUEUE_LENGTH]; + CInputPipeClient * inputPipe; }; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(HIDDeviceContext, HIDGetDeviceContext); @@ -57,6 +62,12 @@ static HIDDeviceContext * s_device = nullptr; EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL HIDEvtIoDeviceControl; EVT_WDF_OBJECT_CONTEXT_CLEANUP HIDEvtReportQueueCleanup; +EVT_WDF_OBJECT_CONTEXT_CLEANUP HIDEvtDeviceCleanup; +EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT HIDEvtSelfManagedIoInit; +EVT_WDF_DEVICE_SELF_MANAGED_IO_CLEANUP HIDEvtSelfManagedIoCleanup; +EVT_WDF_DEVICE_SELF_MANAGED_IO_FLUSH HIDEvtSelfManagedIoFlush; +EVT_WDF_DEVICE_SELF_MANAGED_IO_SUSPEND HIDEvtSelfManagedIoSuspend; +EVT_WDF_DEVICE_SELF_MANAGED_IO_RESTART HIDEvtSelfManagedIoRestart; static NTSTATUS CopyToRequest( _In_ WDFREQUEST request, @@ -191,6 +202,7 @@ static NTSTATUS ReadReport( static NTSTATUS ActivateDevice(_Inout_ HIDDeviceContext * context) { + CSRWExclusiveLock lifecycleLock(&context->lifecycleLock); CSRWExclusiveLock lock(&context->reportLock); if (context->stopping) return STATUS_DEVICE_NOT_READY; @@ -202,13 +214,16 @@ static NTSTATUS ActivateDevice(_Inout_ HIDDeviceContext * context) static NTSTATUS DeactivateDevice(_Inout_ HIDDeviceContext * context) { - CSRWExclusiveLock lock(&context->reportLock); - if (context->stopping) - return STATUS_DEVICE_NOT_READY; + CSRWExclusiveLock lifecycleLock(&context->lifecycleLock); + { + CSRWExclusiveLock lock(&context->reportLock); + if (context->stopping) + return STATUS_DEVICE_NOT_READY; - context->active = false; - context->reportHead = 0; - context->reportCount = 0; + context->active = false; + context->reportHead = 0; + context->reportCount = 0; + } WdfIoQueuePurgeSynchronously(context->reportQueue); return STATUS_SUCCESS; } @@ -241,8 +256,21 @@ NTSTATUS CHIDDevice::Create(_Inout_ PWDFDEVICE_INIT deviceInit) { WdfFdoInitSetFilter(deviceInit); + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = HIDEvtSelfManagedIoInit; + pnpPowerCallbacks.EvtDeviceSelfManagedIoCleanup = + HIDEvtSelfManagedIoCleanup; + pnpPowerCallbacks.EvtDeviceSelfManagedIoFlush = HIDEvtSelfManagedIoFlush; + pnpPowerCallbacks.EvtDeviceSelfManagedIoSuspend = + HIDEvtSelfManagedIoSuspend; + pnpPowerCallbacks.EvtDeviceSelfManagedIoRestart = + HIDEvtSelfManagedIoRestart; + WdfDeviceInitSetPnpPowerEventCallbacks(deviceInit, &pnpPowerCallbacks); + WDF_OBJECT_ATTRIBUTES attributes; WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, HIDDeviceContext); + attributes.EvtCleanupCallback = HIDEvtDeviceCleanup; WDFDEVICE device; NTSTATUS status = WdfDeviceCreate(&deviceInit, &attributes, &device); @@ -251,8 +279,12 @@ NTSTATUS CHIDDevice::Create(_Inout_ PWDFDEVICE_INIT deviceInit) HIDDeviceContext * context = HIDGetDeviceContext(device); RtlZeroMemory(context, sizeof(*context)); + InitializeSRWLock(&context->lifecycleLock); InitializeSRWLock(&context->reportLock); context->active = true; + context->inputPipe = new (std::nothrow) CInputPipeClient; + if (!context->inputPipe) + return STATUS_INSUFFICIENT_RESOURCES; context->attributes.Size = static_cast(sizeof(context->attributes)); @@ -277,6 +309,11 @@ NTSTATUS CHIDDevice::Create(_Inout_ PWDFDEVICE_INIT deviceInit) { CSRWExclusiveLock lock(&s_deviceLock); + if (s_device) + { + DEBUG_ERROR("Only one LGInput device instance is supported"); + return STATUS_DEVICE_BUSY; + } s_device = context; } return STATUS_SUCCESS; @@ -324,6 +361,22 @@ NTSTATUS CHIDDevice::SubmitReport( return status; } +NTSTATUS CHIDDevice::ClearReports() +{ + CSRWSharedLock deviceLock(&s_deviceLock); + HIDDeviceContext * context = s_device; + if (!context) + return STATUS_DEVICE_NOT_READY; + + CSRWExclusiveLock reportLock(&context->reportLock); + if (context->stopping) + return STATUS_DEVICE_NOT_READY; + + context->reportHead = 0; + context->reportCount = 0; + return STATUS_SUCCESS; +} + VOID HIDEvtIoDeviceControl( _In_ WDFQUEUE queue, _In_ WDFREQUEST request, @@ -398,3 +451,62 @@ VOID HIDEvtReportQueueCleanup(_In_ WDFOBJECT object) context->reportCount = 0; } } + +VOID HIDEvtDeviceCleanup(_In_ WDFOBJECT object) +{ + HIDDeviceContext * context = HIDGetDeviceContext((WDFDEVICE)object); + if (context->inputPipe) + { + context->inputPipe->Stop(); + delete context->inputPipe; + context->inputPipe = nullptr; + } + + CSRWExclusiveLock deviceLock(&s_deviceLock); + if (s_device == context) + s_device = nullptr; +} + +NTSTATUS HIDEvtSelfManagedIoInit(_In_ WDFDEVICE device) +{ + HIDDeviceContext * context = HIDGetDeviceContext(device); + if (!context->inputPipe || !context->inputPipe->Start()) + { + DEBUG_ERROR("Failed to start the LGIdd input pipe client"); + return STATUS_INSUFFICIENT_RESOURCES; + } + return STATUS_SUCCESS; +} + +VOID HIDEvtSelfManagedIoCleanup(_In_ WDFDEVICE device) +{ + HIDDeviceContext * context = HIDGetDeviceContext(device); + if (context->inputPipe) + context->inputPipe->Stop(); +} + +VOID HIDEvtSelfManagedIoFlush(_In_ WDFDEVICE device) +{ + HIDDeviceContext * context = HIDGetDeviceContext(device); + if (context->inputPipe) + context->inputPipe->Stop(); +} + +NTSTATUS HIDEvtSelfManagedIoSuspend(_In_ WDFDEVICE device) +{ + HIDDeviceContext * context = HIDGetDeviceContext(device); + if (context->inputPipe) + context->inputPipe->Stop(); + return STATUS_SUCCESS; +} + +NTSTATUS HIDEvtSelfManagedIoRestart(_In_ WDFDEVICE device) +{ + HIDDeviceContext * context = HIDGetDeviceContext(device); + if (!context->inputPipe || !context->inputPipe->Start()) + { + DEBUG_ERROR("Failed to restart the LGIdd input pipe client"); + return STATUS_INSUFFICIENT_RESOURCES; + } + return STATUS_SUCCESS; +} diff --git a/idd/LGInput/CHIDDevice.h b/idd/LGInput/CHIDDevice.h index 86385a28..5f5f8ece 100644 --- a/idd/LGInput/CHIDDevice.h +++ b/idd/LGInput/CHIDDevice.h @@ -30,4 +30,5 @@ public: static NTSTATUS SubmitReport( _In_reads_bytes_(size) const void * report, _In_ size_t size); + static NTSTATUS ClearReports(); }; diff --git a/idd/LGInput/LGInput.vcxproj b/idd/LGInput/LGInput.vcxproj index 1923159e..51659b0d 100644 --- a/idd/LGInput/LGInput.vcxproj +++ b/idd/LGInput/LGInput.vcxproj @@ -20,23 +20,16 @@ - - Common\CDebug.cpp - + - - Common\CDebug.h - - - Common\CSRWLock.h - + @@ -99,6 +92,11 @@ + + + {acb90e34-01ca-4b86-813b-3d20904994c6} + + diff --git a/idd/LGInput/LGInput.vcxproj.filters b/idd/LGInput/LGInput.vcxproj.filters index d47340fd..afd12333 100644 --- a/idd/LGInput/LGInput.vcxproj.filters +++ b/idd/LGInput/LGInput.vcxproj.filters @@ -8,8 +8,8 @@ {D9688549-A0BE-46E6-8FAB-AE454D90E0FA} - - {938E49D6-F954-4EBE-80AB-E0F67E677F27} + + {6A9D97A5-1A6C-4A95-A2D0-A7C2D6BE459D} @@ -18,12 +18,6 @@ - - Common - - - Common - Driver @@ -33,14 +27,14 @@ HID + + IPC + Driver - - Common - Driver @@ -50,5 +44,8 @@ HID + + IPC + diff --git a/idd/LGInput/ipc/CInputPipeClient.cpp b/idd/LGInput/ipc/CInputPipeClient.cpp new file mode 100644 index 00000000..5af1e058 --- /dev/null +++ b/idd/LGInput/ipc/CInputPipeClient.cpp @@ -0,0 +1,93 @@ +/** + * 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 "CInputPipeClient.h" + +#include "CDebug.h" +#include "InputPipeProtocol.h" +#include "../CHIDDevice.h" + +bool CInputPipeClient::Start() +{ + m_lastSequence = 0; + m_endpoint.SetHandler(this); + return m_endpoint.Start( + LG_INPUT_PIPE_NAME, + CPipeEndpoint::Mode::Client, + sizeof(LGInputPipeMessage)); +} + +void CInputPipeClient::Stop() +{ + const bool wasRunning = m_endpoint.IsRunning(); + m_endpoint.Stop(); + m_lastSequence = 0; + if (wasRunning) + CHIDDevice::ClearReports(); +} + +void CInputPipeClient::OnPipeConnected() +{ + m_lastSequence = 0; + DEBUG_INFO("Connected to the LGIdd input transport"); +} + +void CInputPipeClient::OnPipeDisconnected() +{ + m_lastSequence = 0; + CHIDDevice::ClearReports(); + DEBUG_INFO("Disconnected from the LGIdd input transport; reconnecting"); +} + +bool CInputPipeClient::OnPipeMessage( + const void * frame, + size_t size) +{ + if (size != sizeof(LGInputPipeMessage)) + return false; + + const LGInputPipeMessage & message = + *static_cast(frame); + if (message.magic != LG_INPUT_PIPE_MAGIC || + message.version != LG_INPUT_PIPE_VERSION || + message.type != LG_INPUT_PIPE_MESSAGE_REPORT || + !message.payloadSize || + message.payloadSize > sizeof(message.payload)) + return false; + + if (!message.sequence || + (m_lastSequence && message.sequence != m_lastSequence + 1)) + { + DEBUG_WARN("LGInput pipe report sequence changed unexpectedly"); + return false; + } + m_lastSequence = message.sequence; + + const NTSTATUS status = + CHIDDevice::SubmitReport(message.payload, message.payloadSize); + if (status == STATUS_INVALID_PARAMETER) + return false; + + if (status == STATUS_BUFFER_OVERFLOW) + DEBUG_WARN("LGInput HID report queue is full; dropping a report"); + else if (!NT_SUCCESS(status) && status != STATUS_DEVICE_NOT_READY) + DEBUG_WARN_HR(status, "Failed to submit an LGInput HID report"); + return true; +} diff --git a/idd/LGInput/ipc/CInputPipeClient.h b/idd/LGInput/ipc/CInputPipeClient.h new file mode 100644 index 00000000..0d2fe2a0 --- /dev/null +++ b/idd/LGInput/ipc/CInputPipeClient.h @@ -0,0 +1,44 @@ +/** + * 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 "CPipeEndpoint.h" + +#include +#include + +class CInputPipeClient : private IPipeEndpointHandler +{ +public: + ~CInputPipeClient() { Stop(); } + + bool Start(); + void Stop(); + bool IsConnected() const { return m_endpoint.IsConnected(); } + +private: + void OnPipeConnected() override; + void OnPipeDisconnected() override; + bool OnPipeMessage(const void * message, size_t size) override; + + CPipeEndpoint m_endpoint; + uint64_t m_lastSequence = 0; +};