Files
0417_QT_code/LOGIC/Lgc_Core.cpp

1011 lines
30 KiB
C++
Raw Normal View History

2026-04-17 16:25:19 +08:00
#include "LOGIC/Lgc_Core_Private.h"
2026-03-26 10:45:29 +08:00
#include <QtCore/QDateTime>
#include <Windows.h>
namespace
{
2026-04-17 16:25:19 +08:00
constexpr qint64 kCdcRefreshIntervalMs = 1000;
constexpr qint64 kPendingCommandTimeoutMs = 1800;
constexpr int kTestLogMaxLineCount = 240;
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
quint64 Lgc_Core_ProtocolTypeBit(Com_Enum_ProtocolType Type)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
switch (Type)
{
case Com_Enum_ProtocolType_Bitmap:
case Com_Enum_ProtocolType_TimeSync:
case Com_Enum_ProtocolType_ThemeRgb:
break;
default:
return 0;
}
const quint32 RawType = static_cast<quint32>(Type);
return RawType < 64U ? (1ULL << RawType) : 0ULL;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
bool Lgc_Core_TryRefreshCdcPort(Lgc_Core_Struct_State* p_State, QString* p_TextStatus)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if ((p_State == nullptr) ||
!p_State->IsStarted ||
p_State->DeviceReady ||
p_State->DriCdcPort.IsOpened)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return false;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
const qint64 NowMs = QDateTime::currentMSecsSinceEpoch();
if ((p_State->LastCdcRefreshAttemptMs != 0) &&
((NowMs - p_State->LastCdcRefreshAttemptMs) < kCdcRefreshIntervalMs))
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return false;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
p_State->LastCdcRefreshAttemptMs = NowMs;
const bool IsOpened =
Dri_Cdc_Init(&p_State->DriCdcPort, p_State->DeviceConfig, p_TextStatus);
if (IsOpened)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
p_State->IsUsbHelloSent = false;
p_State->LastUsbHelloAttemptMs = 0;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
return IsOpened;
}
bool Lgc_Core_LogNusSummaryIfChanged(Lgc_Core_Struct_State* p_State)
{
if (p_State == nullptr)
{
return false;
}
const QString CurrentSummary = p_State->DriNusPort.TextEndpointSummary.trimmed();
if (CurrentSummary.isEmpty() ||
(CurrentSummary == p_State->LastLoggedNusEndpointSummary))
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return false;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
p_State->LastLoggedNusEndpointSummary = CurrentSummary;
Lgc_Core_TestAppendLog(
p_State,
QStringLiteral("BLE status: %1").arg(CurrentSummary));
return true;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
bool Lgc_Core_UpdateNusConnectionWindow(Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return false;
}
const bool IsConnectedNow = p_State->DriNusPort.IsConnected;
if (IsConnectedNow == p_State->WasNusConnectedLastPoll)
{
return false;
}
p_State->WasNusConnectedLastPoll = IsConnectedNow;
if (IsConnectedNow)
{
p_State->NusReadySinceMs = QDateTime::currentMSecsSinceEpoch();
p_State->NusHelloRetryCount = 0;
p_State->LastNusHelloAttemptMs = 0;
p_State->HasLoggedNusWriteAck = false;
p_State->HasLoggedNusHelloTimeout = false;
Lgc_Core_TestAppendLog(
2026-03-26 10:45:29 +08:00
p_State,
2026-04-17 16:25:19 +08:00
QStringLiteral("BLE handshake window opened. Wait briefly before the first HelloReq."));
}
else
{
p_State->NusReadySinceMs = 0;
p_State->NusHelloRetryCount = 0;
p_State->LastNusHelloAttemptMs = 0;
p_State->IsNusHelloSent = false;
p_State->HasLoggedNusWriteAck = false;
p_State->HasLoggedNusHelloTimeout = false;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
return true;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QString Lgc_Core_FormatPendingCommandBits(quint64 PendingBits)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
QStringList TypeList;
if ((PendingBits & Lgc_Core_ProtocolTypeBit(Com_Enum_ProtocolType_Bitmap)) != 0)
{
TypeList.append(QStringLiteral("Bitmap"));
}
if ((PendingBits & Lgc_Core_ProtocolTypeBit(Com_Enum_ProtocolType_TimeSync)) != 0)
{
TypeList.append(QStringLiteral("TimeSync"));
}
if ((PendingBits & Lgc_Core_ProtocolTypeBit(Com_Enum_ProtocolType_ThemeRgb)) != 0)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
TypeList.append(QStringLiteral("ThemeRgb"));
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
return TypeList.isEmpty()
? QStringLiteral("Unknown")
: TypeList.join(QStringLiteral(", "));
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
bool Lgc_Core_ExpirePendingCommands(Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
{
return false;
}
const qint64 NowMs = QDateTime::currentMSecsSinceEpoch();
bool IsChanged = false;
const auto TryExpire = [&](Com_Enum_RawPacketSource Source)
{
quint64* p_PendingBits = nullptr;
qint64* p_PendingSinceMs = nullptr;
switch (Source)
{
case Com_Enum_RawPacketSource_UsbCdc:
p_PendingBits = &p_State->PendingUsbCommandBits;
p_PendingSinceMs = &p_State->PendingUsbCommandSinceMs;
break;
case Com_Enum_RawPacketSource_BleNus:
p_PendingBits = &p_State->PendingNusCommandBits;
p_PendingSinceMs = &p_State->PendingNusCommandSinceMs;
break;
default:
return;
}
if ((*p_PendingBits == 0) ||
(*p_PendingSinceMs == 0) ||
((NowMs - *p_PendingSinceMs) < kPendingCommandTimeoutMs))
{
return;
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
const quint64 ExpiredBits = *p_PendingBits;
*p_PendingBits = 0;
*p_PendingSinceMs = 0;
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
if ((ExpiredBits & Lgc_Core_ProtocolTypeBit(Com_Enum_ProtocolType_Bitmap)) != 0)
{
p_State->BitmapSent = false;
p_State->BitmapDirty = true;
p_State->BitmapNextSendMs = NowMs;
}
p_State->TextFunctionStatus =
QStringLiteral("%1 ACK timeout for %2. Pending state was cleared.")
.arg(Lgc_Core_GetTransportText(Source))
.arg(Lgc_Core_FormatPendingCommandBits(ExpiredBits));
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
IsChanged = true;
};
TryExpire(Com_Enum_RawPacketSource_UsbCdc);
TryExpire(Com_Enum_RawPacketSource_BleNus);
return IsChanged;
}
} // namespace
QString Lgc_Core_GetTransportText(Com_Enum_RawPacketSource Source)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
switch (Source)
{
case Com_Enum_RawPacketSource_UsbCdc:
return QStringLiteral("USB CDC");
case Com_Enum_RawPacketSource_BleNus:
return QStringLiteral("BLE NUS");
default:
return QStringLiteral("Unknown transport");
}
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QString Lgc_Core_GetProtocolTypeText(Com_Enum_ProtocolType Type)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
switch (Type)
{
case Com_Enum_ProtocolType_HelloReq: return QStringLiteral("HelloReq");
case Com_Enum_ProtocolType_HelloRsp: return QStringLiteral("HelloRsp");
case Com_Enum_ProtocolType_Bitmap: return QStringLiteral("Bitmap");
case Com_Enum_ProtocolType_FunctionKeyEvent: return QStringLiteral("FunctionKeyEvent");
case Com_Enum_ProtocolType_LedState: return QStringLiteral("LedState");
case Com_Enum_ProtocolType_TimeSync: return QStringLiteral("TimeSync");
case Com_Enum_ProtocolType_ThemeRgb: return QStringLiteral("ThemeRgb");
case Com_Enum_ProtocolType_Ack: return QStringLiteral("Ack");
case Com_Enum_ProtocolType_Error: return QStringLiteral("Error");
default: return QStringLiteral("Unknown");
}
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QString Lgc_Core_GetProtocolErrorText(quint32 ErrorCode)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
switch (ErrorCode)
{
case Com_Enum_ProtocolErrorCode_None: return QStringLiteral("NONE");
case Com_Enum_ProtocolErrorCode_UnknownType: return QStringLiteral("UNKNOWN_TYPE");
case Com_Enum_ProtocolErrorCode_InvalidLength: return QStringLiteral("INVALID_LENGTH");
case Com_Enum_ProtocolErrorCode_InvalidParam: return QStringLiteral("INVALID_PARAM");
case Com_Enum_ProtocolErrorCode_NotReady: return QStringLiteral("NOT_READY");
default: return QStringLiteral("UNKNOWN_ERROR");
}
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QString Lgc_Core_FormatPacketHex(const QByteArray& PacketBody)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return PacketBody.isEmpty()
? QStringLiteral("(empty)")
: QString::fromLatin1(PacketBody.toHex(' ').toUpper());
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_TestAppendLog(Lgc_Core_Struct_State* p_State, const QString& LineText)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if ((p_State == nullptr) || LineText.trimmed().isEmpty())
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
const QString TimePrefix =
QDateTime::currentDateTime().toString(QStringLiteral("HH:mm:ss.zzz"));
p_State->TestLogLines.append(QStringLiteral("[%1] %2").arg(TimePrefix, LineText.trimmed()));
while (p_State->TestLogLines.size() > kTestLogMaxLineCount)
{
p_State->TestLogLines.removeFirst();
}
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_TestRecordTxPacket(
Lgc_Core_Struct_State* p_State,
Com_Enum_RawPacketSource Source,
Com_Enum_ProtocolType Type,
const QByteArray& PacketBody,
const QString& NoteText)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
return;
}
2026-04-17 16:25:19 +08:00
switch (Type)
{
case Com_Enum_ProtocolType_HelloReq:
++p_State->TestTxHelloReqCount;
break;
case Com_Enum_ProtocolType_Bitmap:
++p_State->TestTxBitmapCount;
break;
case Com_Enum_ProtocolType_TimeSync:
++p_State->TestTxTimeSyncCount;
break;
case Com_Enum_ProtocolType_ThemeRgb:
++p_State->TestTxThemeRgbCount;
break;
default:
break;
}
p_State->TestLastTxBytes = PacketBody;
p_State->TestLastTxSummary =
QStringLiteral("%1 TX %2")
.arg(Lgc_Core_GetTransportText(Source), Lgc_Core_GetProtocolTypeText(Type));
if (!NoteText.trimmed().isEmpty())
{
p_State->TestLastTxSummary += QStringLiteral(" - %1").arg(NoteText.trimmed());
}
Lgc_Core_TestAppendLog(
p_State,
QStringLiteral("%1 | %2")
.arg(p_State->TestLastTxSummary, Lgc_Core_FormatPacketHex(PacketBody)));
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_TestRecordRxPacket(
Lgc_Core_Struct_State* p_State,
const Com_Struct_RawPacket& Packet)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
{
return;
}
switch (Packet.ProtocolType)
{
case Com_Enum_ProtocolType_HelloRsp:
++p_State->TestRxHelloRspCount;
break;
case Com_Enum_ProtocolType_FunctionKeyEvent:
++p_State->TestRxFunctionKeyEventCount;
break;
case Com_Enum_ProtocolType_LedState:
++p_State->TestRxLedStateCount;
break;
case Com_Enum_ProtocolType_Ack:
++p_State->TestRxAckCount;
break;
case Com_Enum_ProtocolType_Error:
++p_State->TestRxErrorCount;
break;
default:
break;
}
p_State->TestLastRxBytes = Packet.ByteArray;
p_State->TestLastRxSummary =
QStringLiteral("%1 RX %2")
.arg(Lgc_Core_GetTransportText(Packet.Source))
.arg(Lgc_Core_GetProtocolTypeText(Packet.ProtocolType));
Lgc_Core_TestAppendLog(
p_State,
QStringLiteral("%1 | %2")
.arg(p_State->TestLastRxSummary, Lgc_Core_FormatPacketHex(Packet.ByteArray)));
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_ResetProtocolState(Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
p_State->IsUsbProtocolReady = false;
p_State->IsNusProtocolReady = false;
p_State->IsUsbHelloSent = false;
p_State->IsNusHelloSent = false;
p_State->LastUsbHelloAttemptMs = 0;
p_State->LastNusHelloAttemptMs = 0;
p_State->NusReadySinceMs = 0;
p_State->NusHelloRetryCount = 0;
p_State->WasNusConnectedLastPoll = false;
p_State->HasLoggedNusWriteAck = false;
p_State->HasLoggedNusHelloTimeout = false;
p_State->HelloResponse = Com_Struct_ProtocolHelloRsp();
p_State->DeviceLedMask = 0;
p_State->LastAckedType = 0;
p_State->LastErrorType = 0;
p_State->LastErrorCode = 0;
p_State->PendingUsbCommandBits = 0;
p_State->PendingUsbCommandSinceMs = 0;
p_State->PendingNusCommandBits = 0;
p_State->PendingNusCommandSinceMs = 0;
p_State->DeviceReady = false;
p_State->BitmapSent = false;
p_State->BitmapDirty = true;
p_State->BitmapRetryCount = 0;
p_State->BitmapNextSendMs = 0;
Lgc_Core_ClearDeviceReportedKeyStates(p_State);
}
void Lgc_Core_ResetProtocolStateForSource(
Lgc_Core_Struct_State* p_State,
Com_Enum_RawPacketSource Source)
{
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
switch (Source)
{
case Com_Enum_RawPacketSource_UsbCdc:
p_State->IsUsbProtocolReady = false;
p_State->IsUsbHelloSent = false;
p_State->LastUsbHelloAttemptMs = 0;
p_State->PendingUsbCommandBits = 0;
p_State->PendingUsbCommandSinceMs = 0;
break;
case Com_Enum_RawPacketSource_BleNus:
p_State->IsNusProtocolReady = false;
p_State->IsNusHelloSent = false;
p_State->LastNusHelloAttemptMs = 0;
p_State->NusReadySinceMs = p_State->DriNusPort.IsConnected
? QDateTime::currentMSecsSinceEpoch()
: 0;
p_State->NusHelloRetryCount = 0;
p_State->WasNusConnectedLastPoll = p_State->DriNusPort.IsConnected;
p_State->HasLoggedNusWriteAck = false;
p_State->HasLoggedNusHelloTimeout = false;
p_State->PendingNusCommandBits = 0;
p_State->PendingNusCommandSinceMs = 0;
break;
default:
break;
}
p_State->DeviceReady = p_State->IsUsbProtocolReady || p_State->IsNusProtocolReady;
if (!p_State->DeviceReady)
{
p_State->HelloResponse = Com_Struct_ProtocolHelloRsp();
p_State->DeviceLedMask = 0;
p_State->LastAckedType = 0;
p_State->LastErrorType = 0;
p_State->LastErrorCode = 0;
}
p_State->BitmapSent = false;
p_State->BitmapDirty = true;
p_State->BitmapRetryCount = 0;
p_State->BitmapNextSendMs = 0;
Lgc_Core_ClearDeviceReportedKeyStates(p_State);
}
bool Lgc_Core_DeviceSupportsPacketType(
const Lgc_Core_Struct_State* p_State,
Com_Enum_ProtocolType Type)
{
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return false;
}
switch (Type)
{
case Com_Enum_ProtocolType_Bitmap:
return (p_State->HelloResponse.CapabilityFlags & (1U << 0)) != 0;
case Com_Enum_ProtocolType_TimeSync:
return (p_State->HelloResponse.CapabilityFlags & (1U << 1)) != 0;
case Com_Enum_ProtocolType_ThemeRgb:
return (p_State->HelloResponse.CapabilityFlags & (1U << 2)) != 0;
case Com_Enum_ProtocolType_LedState:
return (p_State->HelloResponse.CapabilityFlags & (1U << 3)) != 0;
case Com_Enum_ProtocolType_FunctionKeyEvent:
return (p_State->HelloResponse.CapabilityFlags & (1U << 4)) != 0;
default:
return true;
2026-03-26 10:45:29 +08:00
}
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_AddPendingCommand(
Lgc_Core_Struct_State* p_State,
Com_Enum_RawPacketSource Source,
Com_Enum_ProtocolType Type)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
const quint64 TypeBit = Lgc_Core_ProtocolTypeBit(Type);
if ((p_State == nullptr) || (TypeBit == 0))
{
return;
}
switch (Source)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
case Com_Enum_RawPacketSource_UsbCdc:
if (p_State->PendingUsbCommandBits == 0)
{
p_State->PendingUsbCommandSinceMs = QDateTime::currentMSecsSinceEpoch();
}
p_State->PendingUsbCommandBits |= TypeBit;
break;
case Com_Enum_RawPacketSource_BleNus:
if (p_State->PendingNusCommandBits == 0)
{
p_State->PendingNusCommandSinceMs = QDateTime::currentMSecsSinceEpoch();
}
p_State->PendingNusCommandBits |= TypeBit;
break;
default:
break;
2026-03-26 10:45:29 +08:00
}
}
2026-04-17 16:25:19 +08:00
bool Lgc_Core_ClearPendingCommand(
2026-03-26 10:45:29 +08:00
Lgc_Core_Struct_State* p_State,
2026-04-17 16:25:19 +08:00
Com_Enum_RawPacketSource Source,
Com_Enum_ProtocolType Type)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
const quint64 TypeBit = Lgc_Core_ProtocolTypeBit(Type);
if ((p_State == nullptr) || (TypeBit == 0))
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return false;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
switch (Source)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
case Com_Enum_RawPacketSource_UsbCdc:
{
const bool WasPending = (p_State->PendingUsbCommandBits & TypeBit) != 0;
p_State->PendingUsbCommandBits &= ~TypeBit;
if (p_State->PendingUsbCommandBits == 0)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
p_State->PendingUsbCommandSinceMs = 0;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
return WasPending;
}
case Com_Enum_RawPacketSource_BleNus:
{
const bool WasPending = (p_State->PendingNusCommandBits & TypeBit) != 0;
p_State->PendingNusCommandBits &= ~TypeBit;
if (p_State->PendingNusCommandBits == 0)
{
p_State->PendingNusCommandSinceMs = 0;
}
return WasPending;
}
default:
2026-03-26 10:45:29 +08:00
return false;
}
}
2026-04-17 16:25:19 +08:00
bool Lgc_Core_HasPendingCommand(
const Lgc_Core_Struct_State* p_State,
Com_Enum_RawPacketSource Source,
Com_Enum_ProtocolType Type)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
const quint64 TypeBit = Lgc_Core_ProtocolTypeBit(Type);
if ((p_State == nullptr) || (TypeBit == 0))
2026-03-26 10:45:29 +08:00
{
return false;
}
2026-04-17 16:25:19 +08:00
switch (Source)
{
case Com_Enum_RawPacketSource_UsbCdc:
return (p_State->PendingUsbCommandBits & TypeBit) != 0;
case Com_Enum_RawPacketSource_BleNus:
return (p_State->PendingNusCommandBits & TypeBit) != 0;
default:
return false;
}
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_Init(Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
{
return;
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
*p_State = Lgc_Core_Struct_State();
p_State->TextFunctionStatus = QStringLiteral("Waiting for function-key activity.");
Lgc_Core_ClearAllKeyStates(p_State);
2026-03-26 10:45:29 +08:00
p_State->IsSystemNumLockOn = (GetKeyState(VK_NUMLOCK) & 0x0001) != 0;
2026-04-17 16:25:19 +08:00
Lgc_Core_FillMaskAllEnabled(&p_State->FunctionMaskBitmap);
Lgc_Core_FillMaskAllEnabled(&p_State->KeyboardMaskBitmap);
p_State->FunctionButtonConfig = Lgc_FunctionButton_Config();
Lgc_Core_ApplyFunctionConfig(p_State);
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
void Lgc_Core_SetWindowHandle(Lgc_Core_Struct_State* p_State, void* WindowHandle)
{
if (p_State != nullptr)
{
p_State->WindowHandle = WindowHandle;
}
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_HandleNativeMessage(Lgc_Core_Struct_State* p_State, void* p_Message)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
Q_UNUSED(p_State);
Q_UNUSED(p_Message);
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
void Lgc_Core_Start(Lgc_Core_Struct_State* p_State)
{
if ((p_State == nullptr) || p_State->IsStarted)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
p_State->IsStarted = true;
Lgc_Core_RefreshDevice(p_State);
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_Close(Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
{
return;
}
Lgc_Core_CloseAllPorts(p_State);
Lgc_Core_ClearAllKeyStates(p_State);
Lgc_Core_ResetProtocolState(p_State);
p_State->LastCdcRefreshAttemptMs = 0;
p_State->LastNusRefreshAttemptMs = 0;
p_State->TextFunctionStatus = QStringLiteral("Waiting for function-key activity.");
p_State->IsConnected = false;
p_State->IsStarted = false;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_RefreshDevice(Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
{
return;
}
Lgc_Core_CloseAllPorts(p_State);
Lgc_Core_ClearAllKeyStates(p_State);
QString CdcTextStatus;
QString NusTextStatus;
Dri_Cdc_Init(&p_State->DriCdcPort, p_State->DeviceConfig, &CdcTextStatus);
Dri_Nus_Init(&p_State->DriNusPort, p_State->DeviceConfig, &NusTextStatus);
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
Lgc_Core_ResetProtocolState(p_State);
const qint64 NowMs = QDateTime::currentMSecsSinceEpoch();
p_State->LastCdcRefreshAttemptMs = NowMs;
p_State->LastNusRefreshAttemptMs = NowMs;
p_State->IsConnected = false;
if (!CdcTextStatus.isEmpty())
{
p_State->TextFunctionStatus = CdcTextStatus;
}
else if (!NusTextStatus.isEmpty())
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
p_State->TextFunctionStatus = NusTextStatus;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
Lgc_Core_SendHello(p_State);
Lgc_Core_SyncSystemState(p_State);
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
bool Lgc_Core_Poll(Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
return false;
}
bool IsChanged = false;
2026-04-17 16:25:19 +08:00
Com_Struct_RawPacket Packet;
QString TextStatus;
if (Lgc_Core_TryRefreshCdcPort(p_State, &TextStatus))
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
IsChanged = true;
}
if (!TextStatus.isEmpty())
{
p_State->TextFunctionStatus = TextStatus;
IsChanged = true;
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
TextStatus.clear();
if (p_State->IsStarted &&
!p_State->DeviceReady &&
!p_State->DriNusPort.IsOpened)
{
const qint64 NowMs = QDateTime::currentMSecsSinceEpoch();
if ((p_State->LastNusRefreshAttemptMs == 0) ||
((NowMs - p_State->LastNusRefreshAttemptMs) >= kCdcRefreshIntervalMs))
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
p_State->LastNusRefreshAttemptMs = NowMs;
if (Dri_Nus_Init(&p_State->DriNusPort, p_State->DeviceConfig, &TextStatus))
{
p_State->IsNusHelloSent = false;
p_State->LastNusHelloAttemptMs = 0;
p_State->NusReadySinceMs = 0;
p_State->NusHelloRetryCount = 0;
p_State->WasNusConnectedLastPoll = false;
p_State->HasLoggedNusHelloTimeout = false;
IsChanged = true;
}
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
}
if (!TextStatus.isEmpty())
{
2026-03-26 10:45:29 +08:00
p_State->TextFunctionStatus = TextStatus;
IsChanged = true;
}
2026-04-17 16:25:19 +08:00
IsChanged |= Lgc_Core_UpdateNusConnectionWindow(p_State);
Lgc_Core_SendHello(p_State);
IsChanged |= Lgc_Core_LogNusSummaryIfChanged(p_State);
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
TextStatus.clear();
if (Dri_Cdc_Read(&p_State->DriCdcPort, &Packet, &TextStatus))
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
Lgc_Core_HandleCdcPacket(p_State, Packet);
IsChanged = true;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
else if (!TextStatus.isEmpty())
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
p_State->TextFunctionStatus = TextStatus;
IsChanged = true;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
TextStatus.clear();
if (Dri_Nus_Read(&p_State->DriNusPort, &Packet, &TextStatus))
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
Lgc_Core_HandleNusPacket(p_State, Packet);
IsChanged = true;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
else if (!TextStatus.isEmpty())
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
p_State->TextFunctionStatus = TextStatus;
IsChanged = true;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
IsChanged |= Lgc_Core_ExpirePendingCommands(p_State);
IsChanged |= Lgc_Core_ProcessBitmapSend(p_State);
IsChanged |= Lgc_Core_HandleFunctionButtons(p_State);
IsChanged |= Lgc_Core_SyncSystemState(p_State);
return IsChanged;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
Lgc_Core_Struct_View Lgc_Core_GetView(const Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
Lgc_Core_Struct_View View;
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return View;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
View.TextFunctionStatus = p_State->TextFunctionStatus;
View.IsConnected = p_State->IsConnected;
View.HasOpenTransport =
p_State->DriCdcPort.IsOpened ||
p_State->DriNusPort.IsOpened ||
p_State->DriNusPort.IsConnected;
View.IsSystemNumLockOn = p_State->IsSystemNumLockOn;
View.IsVisibleKeyStateValid = p_State->IsVisibleKeyStateValid;
View.VisibleUsageList = p_State->VisibleUsageList;
View.IsPhysicalKeyStateValid = p_State->IsPhysicalKeyStateValid;
View.PhysicalUsageList = p_State->PhysicalUsageList;
View.IsFunctionSequenceRecording = p_State->IsFunctionSequenceRecording;
return View;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_SetStatusText(Lgc_Core_Struct_State* p_State, const QString& TextStatus)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State != nullptr)
{
p_State->TextFunctionStatus = TextStatus;
}
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QVector<int> Lgc_Core_GetFeatureIdList(const Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return p_State == nullptr
? QVector<int>()
: Lgc_FunctionButton_GetFeatureIdList(p_State->FunctionButtonConfig);
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
Lgc_FunctionFeature_Definition Lgc_Core_GetFeature(
const Lgc_Core_Struct_State* p_State,
int FeatureId)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return p_State == nullptr
? Lgc_FunctionFeature_Definition()
: Lgc_FunctionButton_GetFeature(p_State->FunctionButtonConfig, FeatureId);
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
bool Lgc_Core_HasFeature(const Lgc_Core_Struct_State* p_State, int FeatureId)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return Lgc_Core_GetFeature(p_State, FeatureId).Id > 0;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QString Lgc_Core_GetUsageShortText(quint16 Usage)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return Lgc_FunctionButton_GetUsageShortText(Usage);
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QString Lgc_Core_GetFeatureTypeText(Lgc_FunctionFeature_Type Type)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return Lgc_FunctionButton_GetFeatureTypeText(Type);
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
QString Lgc_Core_GetFeatureNameById(const Lgc_Core_Struct_State* p_State, int FeatureId)
{
const Lgc_FunctionFeature_Definition Feature = Lgc_Core_GetFeature(p_State, FeatureId);
return Feature.Id > 0
? Lgc_FunctionButton_GetFeatureName(Feature)
: QStringLiteral("No Function");
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QString Lgc_Core_GetFeatureDescriptionById(
const Lgc_Core_Struct_State* p_State,
int FeatureId)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return p_State == nullptr
? QString()
: Lgc_FunctionButton_GetFeatureDescriptionById(
p_State->FunctionButtonConfig,
FeatureId);
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QString Lgc_Core_GetFeatureBindingSummary(
const Lgc_Core_Struct_State* p_State,
int FeatureId)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return p_State == nullptr
? QStringLiteral("No function selected yet.")
: Lgc_FunctionButton_GetFeatureBindingSummary(
p_State->FunctionButtonConfig,
FeatureId);
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
int Lgc_Core_GetUsageFeatureId(const Lgc_Core_Struct_State* p_State, quint16 Usage)
{
return p_State == nullptr
? 0
: Lgc_FunctionButton_GetUsageFeatureId(p_State->FunctionButtonConfig, Usage);
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
bool Lgc_Core_HasUsageFeature(const Lgc_Core_Struct_State* p_State, quint16 Usage)
{
return p_State != nullptr &&
Lgc_FunctionButton_HasUsageFeature(p_State->FunctionButtonConfig, Usage);
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_ClearTestLog(Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
p_State->TestTxHelloReqCount = 0;
p_State->TestTxBitmapCount = 0;
p_State->TestTxTimeSyncCount = 0;
p_State->TestTxThemeRgbCount = 0;
p_State->TestRxHelloRspCount = 0;
p_State->TestRxFunctionKeyEventCount = 0;
p_State->TestRxLedStateCount = 0;
p_State->TestRxAckCount = 0;
p_State->TestRxErrorCount = 0;
p_State->TestLastTxSummary.clear();
p_State->TestLastRxSummary.clear();
p_State->TestLastTxBytes.clear();
p_State->TestLastRxBytes.clear();
p_State->TestLastFunctionEventBitmap.clear();
p_State->TestLogLines.clear();
p_State->LastLoggedNusEndpointSummary.clear();
}
Lgc_Core_Struct_TestView Lgc_Core_GetTestView(const Lgc_Core_Struct_State* p_State)
{
Lgc_Core_Struct_TestView View;
if (p_State == nullptr)
{
return View;
}
View.StatusText = p_State->TextFunctionStatus;
View.UsbPortName = p_State->DriCdcPort.PortName;
View.NusEndpointSummary = p_State->DriNusPort.TextEndpointSummary;
View.IsUsbOpened = p_State->DriCdcPort.IsOpened;
View.IsNusOpened = p_State->DriNusPort.IsOpened;
View.IsNusConnected = p_State->DriNusPort.IsConnected;
View.IsUsbProtocolReady = p_State->IsUsbProtocolReady;
View.IsNusProtocolReady = p_State->IsNusProtocolReady;
View.DeviceReady = p_State->DeviceReady;
View.HelloProtocolVersion = p_State->HelloResponse.ProtocolVersion;
View.HelloVendorId = p_State->HelloResponse.VendorId;
View.HelloProductId = p_State->HelloResponse.ProductId;
View.HelloFirmwareMajor = p_State->HelloResponse.FirmwareMajor;
View.HelloFirmwareMinor = p_State->HelloResponse.FirmwareMinor;
View.HelloCapabilityFlags = p_State->HelloResponse.CapabilityFlags;
View.DeviceLedMask = p_State->DeviceLedMask;
View.LastAckedType = p_State->LastAckedType;
View.LastErrorType = p_State->LastErrorType;
View.LastErrorCode = p_State->LastErrorCode;
View.PendingUsbCommandBits = p_State->PendingUsbCommandBits;
View.PendingNusCommandBits = p_State->PendingNusCommandBits;
View.LastTxSummary = p_State->TestLastTxSummary;
View.LastRxSummary = p_State->TestLastRxSummary;
View.LastTxHex = Lgc_Core_FormatPacketHex(p_State->TestLastTxBytes);
View.LastRxHex = Lgc_Core_FormatPacketHex(p_State->TestLastRxBytes);
View.FunctionMaskHex = Lgc_Core_FormatPacketHex(p_State->FunctionMaskBitmap);
View.LastFunctionEventHex = Lgc_Core_FormatPacketHex(p_State->TestLastFunctionEventBitmap);
View.TxHelloReqCount = p_State->TestTxHelloReqCount;
View.TxBitmapCount = p_State->TestTxBitmapCount;
View.TxTimeSyncCount = p_State->TestTxTimeSyncCount;
View.TxThemeRgbCount = p_State->TestTxThemeRgbCount;
View.RxHelloRspCount = p_State->TestRxHelloRspCount;
View.RxFunctionKeyEventCount = p_State->TestRxFunctionKeyEventCount;
View.RxLedStateCount = p_State->TestRxLedStateCount;
View.RxAckCount = p_State->TestRxAckCount;
View.RxErrorCount = p_State->TestRxErrorCount;
View.LogText = p_State->TestLogLines.join(QLatin1Char('\n'));
return View;
}
bool Lgc_Core_BeginSequenceRecording(Lgc_Core_Struct_State* p_State, int FeatureId)
{
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return false;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
const Lgc_FunctionFeature_Definition Feature = Lgc_Core_GetFeature(p_State, FeatureId);
if (Feature.Id <= 0)
{
return false;
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
if ((Feature.Type != Lgc_FunctionFeature_Type::KeyCombination) &&
(Feature.Type != Lgc_FunctionFeature_Type::KeySequence))
{
return false;
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
p_State->IsFunctionSequenceRecording = true;
p_State->TextFunctionStatus =
Feature.Type == Lgc_FunctionFeature_Type::KeySequence
? QStringLiteral("Sequence recording started. Press keys to append combinations.")
: QStringLiteral("Combination recording started. Press the target combo.");
return true;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_EndSequenceRecording(Lgc_Core_Struct_State* p_State)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
p_State->IsFunctionSequenceRecording = false;
p_State->TextFunctionStatus = QStringLiteral("Sequence recording stopped. Changes saved.");
}
void Lgc_Core_UpdateSequenceRecordingStatus(
Lgc_Core_Struct_State* p_State,
const QString& SequenceText)
{
if ((p_State == nullptr) || SequenceText.trimmed().isEmpty())
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
p_State->TextFunctionStatus =
QStringLiteral("Recording: %1").arg(SequenceText.trimmed());
}
2026-03-26 10:45:29 +08:00
2026-04-17 16:25:19 +08:00
void Lgc_Core_HandleUiKeyPress(Lgc_Core_Struct_State* p_State, quint16 Usage)
{
if (p_State == nullptr)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
QString TextStatus;
if (Lgc_Core_HasUsageFeature(p_State, Usage))
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if (!Lgc_FunctionButton_RunBinding(*p_State, Usage, TextStatus))
{
TextStatus = QStringLiteral("%1 has no runnable feature.")
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
}
}
else if (Lgc_FunctionButton_SendUsageToWindows(Usage, true))
{
p_State->UiPressedUsageSet.insert(Usage);
TextStatus = QStringLiteral("Pressed %1.")
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
}
else
{
TextStatus = QStringLiteral("Failed to press %1.")
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
if (!TextStatus.isEmpty())
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
p_State->TextFunctionStatus = TextStatus;
2026-03-26 10:45:29 +08:00
}
}
2026-04-17 16:25:19 +08:00
void Lgc_Core_HandleUiKeyRelease(Lgc_Core_Struct_State* p_State, quint16 Usage)
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
if ((p_State == nullptr) || !p_State->UiPressedUsageSet.remove(Usage))
2026-03-26 10:45:29 +08:00
{
2026-04-17 16:25:19 +08:00
return;
2026-03-26 10:45:29 +08:00
}
2026-04-17 16:25:19 +08:00
p_State->TextFunctionStatus =
Lgc_FunctionButton_SendUsageToWindows(Usage, false)
? QStringLiteral("Released %1.").arg(Lgc_FunctionButton_GetUsageShortText(Usage))
: QStringLiteral("Failed to release %1.")
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
2026-03-26 10:45:29 +08:00
}