/** * 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" #include "../HIDReports.h" #include static constexpr uint8_t HID_KEYBOARD_USAGE_MUTE = 0x7f; static constexpr uint8_t HID_KEYBOARD_USAGE_VOLUME_UP = 0x80; static constexpr uint8_t HID_KEYBOARD_USAGE_VOLUME_DOWN = 0x81; static constexpr uint16_t HID_CONSUMER_USAGE_MUTE = 0xe2; static constexpr uint16_t HID_CONSUMER_USAGE_VOLUME_UP = 0xe9; static constexpr uint16_t HID_CONSUMER_USAGE_VOLUME_DOWN = 0xea; 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::ResetReports(); } void CInputPipeClient::OnPipeConnected() { m_lastSequence = 0; DEBUG_INFO("Connected to the LGIdd input transport"); } void CInputPipeClient::OnPipeDisconnected() { m_lastSequence = 0; CHIDDevice::ResetReports(); 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.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"); CHIDDevice::ResetReports(); return false; } bool handled = false; switch (message.type) { case LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE: handled = HandleMouseRelative( message.payload, message.payloadSize); break; case LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE: handled = HandleMouseAbsolute( message.payload, message.payloadSize); break; case LG_INPUT_PIPE_MESSAGE_KEYBOARD: handled = HandleKeyboard(message.payload, message.payloadSize); break; default: return false; } if (!handled) return false; m_lastSequence = message.sequence; return true; } bool CInputPipeClient::HandleMouseRelative( const void * payload, size_t size) { if (size != sizeof(LGInputPipeMouseRelative)) return false; LGInputPipeMouseRelative input = {}; memcpy(&input, payload, sizeof(input)); if (input.deltaX < LG_INPUT_MOUSE_DELTA_MIN || input.deltaX > LG_INPUT_MOUSE_DELTA_MAX || input.deltaY < LG_INPUT_MOUSE_DELTA_MIN || input.deltaY > LG_INPUT_MOUSE_DELTA_MAX || input.wheel < LG_INPUT_MOUSE_WHEEL_MIN_TOTAL || input.wheel > LG_INPUT_MOUSE_WHEEL_MAX) return false; int32_t x = input.deltaX; int32_t y = input.deltaY; int32_t wheel = input.wheel; do { const int16_t reportX = x > INT16_MAX ? INT16_MAX : x < INT16_MIN ? INT16_MIN : static_cast(x); const int16_t reportY = y > INT16_MAX ? INT16_MAX : y < INT16_MIN ? INT16_MIN : static_cast(y); const int8_t reportWheel = wheel > INT8_MAX ? INT8_MAX : wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN : static_cast(wheel); const HIDMouseRelativeReport report = { HID_REPORT_ID_MOUSE_RELATIVE, input.buttons, reportX, reportY, reportWheel, }; if (!SubmitReport(&report, sizeof(report))) return false; x -= reportX; y -= reportY; wheel -= reportWheel; } while (x || y || wheel); return true; } bool CInputPipeClient::HandleMouseAbsolute( const void * payload, size_t size) { if (size != sizeof(LGInputPipeMouseAbsolute)) return false; LGInputPipeMouseAbsolute input = {}; memcpy(&input, payload, sizeof(input)); if (input.x > LG_INPUT_MOUSE_ABSOLUTE_MAX || input.y > LG_INPUT_MOUSE_ABSOLUTE_MAX || input.wheel < LG_INPUT_MOUSE_WHEEL_MIN_TOTAL || input.wheel > LG_INPUT_MOUSE_WHEEL_MAX || input.reserved) return false; int32_t wheel = input.wheel; do { const int8_t reportWheel = wheel > INT8_MAX ? INT8_MAX : wheel < LG_INPUT_MOUSE_WHEEL_MIN ? LG_INPUT_MOUSE_WHEEL_MIN : static_cast(wheel); const HIDMouseAbsoluteReport report = { HID_REPORT_ID_MOUSE_ABSOLUTE, input.buttons, input.x, input.y, reportWheel, }; if (!SubmitReport(&report, sizeof(report))) return false; wheel -= reportWheel; } while (wheel); return true; } bool CInputPipeClient::HandleKeyboard( const void * payload, size_t size) { if (size != sizeof(LGInputPipeKeyboard)) return false; LGInputPipeKeyboard input = {}; memcpy(&input, payload, sizeof(input)); for (size_t i = 0; i < sizeof(input.reserved); ++i) if (input.reserved[i]) return false; HIDKeyboardReport report = {}; report.reportId = HID_REPORT_ID_KEYBOARD; report.modifiers = input.modifiers; // The Consumer Control report carries one active array usage. uint16_t consumerUsage = 0; size_t keyIndex = 0; for (size_t i = 0; i < LG_INPUT_KEYBOARD_KEY_COUNT; ++i) { if (input.keys[i] > LG_INPUT_KEYBOARD_USAGE_MAX) return false; switch (input.keys[i]) { case HID_KEYBOARD_USAGE_MUTE: if (!consumerUsage) consumerUsage = HID_CONSUMER_USAGE_MUTE; break; case HID_KEYBOARD_USAGE_VOLUME_UP: if (!consumerUsage) consumerUsage = HID_CONSUMER_USAGE_VOLUME_UP; break; case HID_KEYBOARD_USAGE_VOLUME_DOWN: if (!consumerUsage) consumerUsage = HID_CONSUMER_USAGE_VOLUME_DOWN; break; default: report.keys[keyIndex++] = input.keys[i]; break; } } if (!SubmitReport(&report, sizeof(report))) return false; const HIDConsumerReport consumer = { HID_REPORT_ID_CONSUMER, consumerUsage, }; return SubmitReport(&consumer, sizeof(consumer)); } bool CInputPipeClient::SubmitReport( const void * report, size_t size) { const NTSTATUS status = CHIDDevice::SubmitReport(report, size); if (status == STATUS_INVALID_PARAMETER) return false; if (status == STATUS_BUFFER_OVERFLOW) { DEBUG_WARN("LGInput HID report queue overflowed; resetting input state"); CHIDDevice::ResetReports(); return false; } if (!NT_SUCCESS(status)) { if (status != STATUS_DEVICE_NOT_READY) DEBUG_WARN_HR(status, "Failed to submit an LGInput HID report"); return false; } return true; }