[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.
This commit is contained in:
Geoffrey McRae
2026-08-08 19:06:50 +10:00
parent 214665bedd
commit 241ab3fad2
24 changed files with 1463 additions and 478 deletions

View File

@@ -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 <string.h>
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<uint32_t>(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;
}

View File

@@ -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 <stddef.h>
#include <stdint.h>
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;

View File

@@ -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<LGPipeMsg> queued;
queued.swap(m_queue);
HandleT<EventTraits> 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<const LGPipeMsg *>(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(

View File

@@ -23,40 +23,31 @@
#include <windows.h>
#include <wdf.h>
#include <stdint.h>
#include <wrl.h>
#include <vector>
#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<HANDLETraits> m_pipe;
HandleT<HANDLENullTraits> m_thread;
HandleT<EventTraits> m_signal;
std::vector<LGPipeMsg> m_queue;
bool m_running = false;
bool m_connected = false;
CPipeEndpoint m_endpoint;
SRWLOCK m_queueLock = SRWLOCK_INIT;
std::vector<LGPipeMsg> 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(); }