1011 lines
30 KiB
C++
1011 lines
30 KiB
C++
#include "LOGIC/Lgc_Core_Private.h"
|
|
|
|
#include <QtCore/QDateTime>
|
|
#include <Windows.h>
|
|
|
|
namespace
|
|
{
|
|
|
|
constexpr qint64 kCdcRefreshIntervalMs = 1000;
|
|
constexpr qint64 kPendingCommandTimeoutMs = 1800;
|
|
constexpr int kTestLogMaxLineCount = 240;
|
|
|
|
quint64 Lgc_Core_ProtocolTypeBit(Com_Enum_ProtocolType Type)
|
|
{
|
|
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;
|
|
}
|
|
|
|
bool Lgc_Core_TryRefreshCdcPort(Lgc_Core_Struct_State* p_State, QString* p_TextStatus)
|
|
{
|
|
if ((p_State == nullptr) ||
|
|
!p_State->IsStarted ||
|
|
p_State->DeviceReady ||
|
|
p_State->DriCdcPort.IsOpened)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const qint64 NowMs = QDateTime::currentMSecsSinceEpoch();
|
|
if ((p_State->LastCdcRefreshAttemptMs != 0) &&
|
|
((NowMs - p_State->LastCdcRefreshAttemptMs) < kCdcRefreshIntervalMs))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
p_State->LastCdcRefreshAttemptMs = NowMs;
|
|
const bool IsOpened =
|
|
Dri_Cdc_Init(&p_State->DriCdcPort, p_State->DeviceConfig, p_TextStatus);
|
|
if (IsOpened)
|
|
{
|
|
p_State->IsUsbHelloSent = false;
|
|
p_State->LastUsbHelloAttemptMs = 0;
|
|
}
|
|
|
|
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))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
p_State->LastLoggedNusEndpointSummary = CurrentSummary;
|
|
Lgc_Core_TestAppendLog(
|
|
p_State,
|
|
QStringLiteral("BLE status: %1").arg(CurrentSummary));
|
|
return true;
|
|
}
|
|
|
|
bool Lgc_Core_UpdateNusConnectionWindow(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
if (p_State == nullptr)
|
|
{
|
|
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(
|
|
p_State,
|
|
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;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QString Lgc_Core_FormatPendingCommandBits(quint64 PendingBits)
|
|
{
|
|
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)
|
|
{
|
|
TypeList.append(QStringLiteral("ThemeRgb"));
|
|
}
|
|
|
|
return TypeList.isEmpty()
|
|
? QStringLiteral("Unknown")
|
|
: TypeList.join(QStringLiteral(", "));
|
|
}
|
|
|
|
bool Lgc_Core_ExpirePendingCommands(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
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;
|
|
}
|
|
|
|
const quint64 ExpiredBits = *p_PendingBits;
|
|
*p_PendingBits = 0;
|
|
*p_PendingSinceMs = 0;
|
|
|
|
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)
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
|
|
QString Lgc_Core_GetProtocolTypeText(Com_Enum_ProtocolType Type)
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
|
|
QString Lgc_Core_GetProtocolErrorText(quint32 ErrorCode)
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
|
|
QString Lgc_Core_FormatPacketHex(const QByteArray& PacketBody)
|
|
{
|
|
return PacketBody.isEmpty()
|
|
? QStringLiteral("(empty)")
|
|
: QString::fromLatin1(PacketBody.toHex(' ').toUpper());
|
|
}
|
|
|
|
void Lgc_Core_TestAppendLog(Lgc_Core_Struct_State* p_State, const QString& LineText)
|
|
{
|
|
if ((p_State == nullptr) || LineText.trimmed().isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
void Lgc_Core_TestRecordTxPacket(
|
|
Lgc_Core_Struct_State* p_State,
|
|
Com_Enum_RawPacketSource Source,
|
|
Com_Enum_ProtocolType Type,
|
|
const QByteArray& PacketBody,
|
|
const QString& NoteText)
|
|
{
|
|
if (p_State == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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)));
|
|
}
|
|
|
|
void Lgc_Core_TestRecordRxPacket(
|
|
Lgc_Core_Struct_State* p_State,
|
|
const Com_Struct_RawPacket& Packet)
|
|
{
|
|
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)));
|
|
}
|
|
|
|
void Lgc_Core_ResetProtocolState(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
if (p_State == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
void Lgc_Core_AddPendingCommand(
|
|
Lgc_Core_Struct_State* p_State,
|
|
Com_Enum_RawPacketSource Source,
|
|
Com_Enum_ProtocolType Type)
|
|
{
|
|
const quint64 TypeBit = Lgc_Core_ProtocolTypeBit(Type);
|
|
if ((p_State == nullptr) || (TypeBit == 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (Source)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
bool Lgc_Core_ClearPendingCommand(
|
|
Lgc_Core_Struct_State* p_State,
|
|
Com_Enum_RawPacketSource Source,
|
|
Com_Enum_ProtocolType Type)
|
|
{
|
|
const quint64 TypeBit = Lgc_Core_ProtocolTypeBit(Type);
|
|
if ((p_State == nullptr) || (TypeBit == 0))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch (Source)
|
|
{
|
|
case Com_Enum_RawPacketSource_UsbCdc:
|
|
{
|
|
const bool WasPending = (p_State->PendingUsbCommandBits & TypeBit) != 0;
|
|
p_State->PendingUsbCommandBits &= ~TypeBit;
|
|
if (p_State->PendingUsbCommandBits == 0)
|
|
{
|
|
p_State->PendingUsbCommandSinceMs = 0;
|
|
}
|
|
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:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Lgc_Core_HasPendingCommand(
|
|
const Lgc_Core_Struct_State* p_State,
|
|
Com_Enum_RawPacketSource Source,
|
|
Com_Enum_ProtocolType Type)
|
|
{
|
|
const quint64 TypeBit = Lgc_Core_ProtocolTypeBit(Type);
|
|
if ((p_State == nullptr) || (TypeBit == 0))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void Lgc_Core_Init(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
if (p_State == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
*p_State = Lgc_Core_Struct_State();
|
|
p_State->TextFunctionStatus = QStringLiteral("Waiting for function-key activity.");
|
|
Lgc_Core_ClearAllKeyStates(p_State);
|
|
p_State->IsSystemNumLockOn = (GetKeyState(VK_NUMLOCK) & 0x0001) != 0;
|
|
Lgc_Core_FillMaskAllEnabled(&p_State->FunctionMaskBitmap);
|
|
Lgc_Core_FillMaskAllEnabled(&p_State->KeyboardMaskBitmap);
|
|
p_State->FunctionButtonConfig = Lgc_FunctionButton_Config();
|
|
Lgc_Core_ApplyFunctionConfig(p_State);
|
|
}
|
|
|
|
void Lgc_Core_SetWindowHandle(Lgc_Core_Struct_State* p_State, void* WindowHandle)
|
|
{
|
|
if (p_State != nullptr)
|
|
{
|
|
p_State->WindowHandle = WindowHandle;
|
|
}
|
|
}
|
|
|
|
void Lgc_Core_HandleNativeMessage(Lgc_Core_Struct_State* p_State, void* p_Message)
|
|
{
|
|
Q_UNUSED(p_State);
|
|
Q_UNUSED(p_Message);
|
|
}
|
|
|
|
void Lgc_Core_Start(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
if ((p_State == nullptr) || p_State->IsStarted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
p_State->IsStarted = true;
|
|
Lgc_Core_RefreshDevice(p_State);
|
|
}
|
|
|
|
void Lgc_Core_Close(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
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;
|
|
}
|
|
|
|
void Lgc_Core_RefreshDevice(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
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);
|
|
|
|
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())
|
|
{
|
|
p_State->TextFunctionStatus = NusTextStatus;
|
|
}
|
|
|
|
Lgc_Core_SendHello(p_State);
|
|
Lgc_Core_SyncSystemState(p_State);
|
|
}
|
|
|
|
bool Lgc_Core_Poll(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
if (p_State == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool IsChanged = false;
|
|
Com_Struct_RawPacket Packet;
|
|
QString TextStatus;
|
|
|
|
if (Lgc_Core_TryRefreshCdcPort(p_State, &TextStatus))
|
|
{
|
|
IsChanged = true;
|
|
}
|
|
if (!TextStatus.isEmpty())
|
|
{
|
|
p_State->TextFunctionStatus = TextStatus;
|
|
IsChanged = true;
|
|
}
|
|
|
|
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))
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
if (!TextStatus.isEmpty())
|
|
{
|
|
p_State->TextFunctionStatus = TextStatus;
|
|
IsChanged = true;
|
|
}
|
|
|
|
IsChanged |= Lgc_Core_UpdateNusConnectionWindow(p_State);
|
|
Lgc_Core_SendHello(p_State);
|
|
IsChanged |= Lgc_Core_LogNusSummaryIfChanged(p_State);
|
|
|
|
TextStatus.clear();
|
|
if (Dri_Cdc_Read(&p_State->DriCdcPort, &Packet, &TextStatus))
|
|
{
|
|
Lgc_Core_HandleCdcPacket(p_State, Packet);
|
|
IsChanged = true;
|
|
}
|
|
else if (!TextStatus.isEmpty())
|
|
{
|
|
p_State->TextFunctionStatus = TextStatus;
|
|
IsChanged = true;
|
|
}
|
|
|
|
TextStatus.clear();
|
|
if (Dri_Nus_Read(&p_State->DriNusPort, &Packet, &TextStatus))
|
|
{
|
|
Lgc_Core_HandleNusPacket(p_State, Packet);
|
|
IsChanged = true;
|
|
}
|
|
else if (!TextStatus.isEmpty())
|
|
{
|
|
p_State->TextFunctionStatus = TextStatus;
|
|
IsChanged = true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
Lgc_Core_Struct_View Lgc_Core_GetView(const Lgc_Core_Struct_State* p_State)
|
|
{
|
|
Lgc_Core_Struct_View View;
|
|
if (p_State == nullptr)
|
|
{
|
|
return View;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void Lgc_Core_SetStatusText(Lgc_Core_Struct_State* p_State, const QString& TextStatus)
|
|
{
|
|
if (p_State != nullptr)
|
|
{
|
|
p_State->TextFunctionStatus = TextStatus;
|
|
}
|
|
}
|
|
|
|
QVector<int> Lgc_Core_GetFeatureIdList(const Lgc_Core_Struct_State* p_State)
|
|
{
|
|
return p_State == nullptr
|
|
? QVector<int>()
|
|
: Lgc_FunctionButton_GetFeatureIdList(p_State->FunctionButtonConfig);
|
|
}
|
|
|
|
Lgc_FunctionFeature_Definition Lgc_Core_GetFeature(
|
|
const Lgc_Core_Struct_State* p_State,
|
|
int FeatureId)
|
|
{
|
|
return p_State == nullptr
|
|
? Lgc_FunctionFeature_Definition()
|
|
: Lgc_FunctionButton_GetFeature(p_State->FunctionButtonConfig, FeatureId);
|
|
}
|
|
|
|
bool Lgc_Core_HasFeature(const Lgc_Core_Struct_State* p_State, int FeatureId)
|
|
{
|
|
return Lgc_Core_GetFeature(p_State, FeatureId).Id > 0;
|
|
}
|
|
|
|
QString Lgc_Core_GetUsageShortText(quint16 Usage)
|
|
{
|
|
return Lgc_FunctionButton_GetUsageShortText(Usage);
|
|
}
|
|
|
|
QString Lgc_Core_GetFeatureTypeText(Lgc_FunctionFeature_Type Type)
|
|
{
|
|
return Lgc_FunctionButton_GetFeatureTypeText(Type);
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
QString Lgc_Core_GetFeatureDescriptionById(
|
|
const Lgc_Core_Struct_State* p_State,
|
|
int FeatureId)
|
|
{
|
|
return p_State == nullptr
|
|
? QString()
|
|
: Lgc_FunctionButton_GetFeatureDescriptionById(
|
|
p_State->FunctionButtonConfig,
|
|
FeatureId);
|
|
}
|
|
|
|
QString Lgc_Core_GetFeatureBindingSummary(
|
|
const Lgc_Core_Struct_State* p_State,
|
|
int FeatureId)
|
|
{
|
|
return p_State == nullptr
|
|
? QStringLiteral("No function selected yet.")
|
|
: Lgc_FunctionButton_GetFeatureBindingSummary(
|
|
p_State->FunctionButtonConfig,
|
|
FeatureId);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
bool Lgc_Core_HasUsageFeature(const Lgc_Core_Struct_State* p_State, quint16 Usage)
|
|
{
|
|
return p_State != nullptr &&
|
|
Lgc_FunctionButton_HasUsageFeature(p_State->FunctionButtonConfig, Usage);
|
|
}
|
|
|
|
void Lgc_Core_ClearTestLog(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
if (p_State == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const Lgc_FunctionFeature_Definition Feature = Lgc_Core_GetFeature(p_State, FeatureId);
|
|
if (Feature.Id <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ((Feature.Type != Lgc_FunctionFeature_Type::KeyCombination) &&
|
|
(Feature.Type != Lgc_FunctionFeature_Type::KeySequence))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void Lgc_Core_EndSequenceRecording(Lgc_Core_Struct_State* p_State)
|
|
{
|
|
if (p_State == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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())
|
|
{
|
|
return;
|
|
}
|
|
|
|
p_State->TextFunctionStatus =
|
|
QStringLiteral("Recording: %1").arg(SequenceText.trimmed());
|
|
}
|
|
|
|
void Lgc_Core_HandleUiKeyPress(Lgc_Core_Struct_State* p_State, quint16 Usage)
|
|
{
|
|
if (p_State == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString TextStatus;
|
|
if (Lgc_Core_HasUsageFeature(p_State, Usage))
|
|
{
|
|
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));
|
|
}
|
|
|
|
if (!TextStatus.isEmpty())
|
|
{
|
|
p_State->TextFunctionStatus = TextStatus;
|
|
}
|
|
}
|
|
|
|
void Lgc_Core_HandleUiKeyRelease(Lgc_Core_Struct_State* p_State, quint16 Usage)
|
|
{
|
|
if ((p_State == nullptr) || !p_State->UiPressedUsageSet.remove(Usage))
|
|
{
|
|
return;
|
|
}
|
|
|
|
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));
|
|
}
|