Push layered Qt host source files
This commit is contained in:
@@ -1,41 +0,0 @@
|
||||
#include "LOGIC/Lgc_Consumer.h"
|
||||
|
||||
void Lgc_Consumer_Func_Parse(const QByteArray& ByteArray, Lgc_Consumer_Struct_Result* p_Result)
|
||||
{
|
||||
// 当前调用链内部固定传有效结果对象,这里直接清成默认状态。
|
||||
*p_Result = Lgc_Consumer_Struct_Result();
|
||||
|
||||
// DRI 这轮没有交上来数据时,直接给出提示。
|
||||
if (ByteArray.isEmpty())
|
||||
{
|
||||
p_Result->TextExplain = QStringLiteral("Consumer 端口没有收到数据。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 第 0 字节不是 0x03,就说明这不是 Consumer 包。
|
||||
if (static_cast<quint8>(ByteArray.at(0)) != Mid_Enum_ReportId_Consumer)
|
||||
{
|
||||
p_Result->TextExplain = QStringLiteral("这不是 report id 0x03。");
|
||||
return;
|
||||
}
|
||||
|
||||
p_Result->IsMatch = true;
|
||||
|
||||
// 当前固件里的 0x03 包固定就是 3 字节。
|
||||
if (ByteArray.size() != MID_CONST_PACKET_SIZE_CONSUMER)
|
||||
{
|
||||
p_Result->TextExplain = QStringLiteral("0x03 包长度不对。");
|
||||
return;
|
||||
}
|
||||
|
||||
p_Result->IsLengthOk = true;
|
||||
|
||||
// 第 1、2 字节按 little-endian 规则还原成 16 位 usage。
|
||||
p_Result->Usage =
|
||||
static_cast<quint8>(ByteArray.at(1)) |
|
||||
(static_cast<quint16>(static_cast<quint8>(ByteArray.at(2))) << 8);
|
||||
|
||||
// 当前项目里只保留简短解释,避免调试日志过于冗长。
|
||||
p_Result->TextExplain = QStringLiteral("0x03 Consumer:%1")
|
||||
.arg(Mid_Func_GetConsumerUsageText(p_Result->Usage));
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
|
||||
/*
|
||||
* 这份文件负责解析 report id 0x03 的 Consumer 包。
|
||||
*
|
||||
* 当前下位机真实结构很简单:
|
||||
* 1. 第 0 字节是 report id,固定为 0x03
|
||||
* 2. 第 1、2 字节拼成一个 16 位 consumer usage
|
||||
* 3. 整包固定长度 3 字节
|
||||
*
|
||||
* 所以这里不需要像 0x01 / 0x04 那样去拆 modifier 和 usage 位图。
|
||||
*/
|
||||
|
||||
struct Lgc_Consumer_Struct_Result
|
||||
{
|
||||
// 这一包是否匹配 report id 0x03。
|
||||
bool IsMatch = false;
|
||||
|
||||
// 如果匹配 0x03,长度是否也正确。
|
||||
bool IsLengthOk = false;
|
||||
|
||||
// 解析得到的 consumer usage。
|
||||
quint16 Usage = 0;
|
||||
|
||||
// 给调试窗口显示的简短中文说明。
|
||||
QString TextExplain;
|
||||
};
|
||||
|
||||
void Lgc_Consumer_Func_Parse(const QByteArray& ByteArray, Lgc_Consumer_Struct_Result* p_Result);
|
||||
1398
LOGIC/Lgc_Core.cpp
1398
LOGIC/Lgc_Core.cpp
File diff suppressed because it is too large
Load Diff
248
LOGIC/Lgc_Core.h
248
LOGIC/Lgc_Core.h
@@ -1,85 +1,219 @@
|
||||
#pragma once
|
||||
|
||||
#include "DRI/Dri_Consumer.h"
|
||||
#include "DRI/Dri_NkroRaw.h"
|
||||
#include "DRI/Dri_Vendor.h"
|
||||
#include "LOGIC/Lgc_Consumer.h"
|
||||
#include "COM/Com_Def.h"
|
||||
#include "DRI/Dri_Cdc.h"
|
||||
#include "DRI/Dri_Nus.h"
|
||||
#include "LOGIC/Lgc_Func_Button.h"
|
||||
#include "LOGIC/Lgc_Nkro.h"
|
||||
#include "LOGIC/Lgc_Vendor.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
/*
|
||||
* Lgc_Core:贯穿 UI / LOGIC / DRI 的单一状态容器。
|
||||
* 高密注释使得学生在不翻源码的情况下即可看懂所有成员职责。
|
||||
*/
|
||||
struct Lgc_Core_Struct_State
|
||||
{
|
||||
/* DRI 端口:分别承接 RAW NKRO / Consumer / Vendor 通道 */
|
||||
Dri_NkroRaw_Struct_Port DriNkroPort;
|
||||
Dri_Consumer_Struct_Port DriConsumerPort;
|
||||
Dri_Vendor_Struct_Port DriVendorPort;
|
||||
Dri_Nus_Struct_Port DriNusPort;
|
||||
Dri_Cdc_Struct_Port DriCdcPort;
|
||||
|
||||
/* 设备与 UI 文本状态 */
|
||||
Mid_Struct_DeviceConfig DeviceConfig;
|
||||
QString TextConnection;
|
||||
QString TextLog;
|
||||
Com_Struct_DeviceConfig DeviceConfig;
|
||||
QString TextFunctionStatus;
|
||||
|
||||
/* 可视化按键:UI 模型(来自解析 NKRO/Consumer 结果) */
|
||||
bool IsVisibleKeyStateValid = false;
|
||||
quint8 VisibleModifier = 0;
|
||||
QVector<quint16> VisibleUsageList;
|
||||
|
||||
/* 物理按键:直接来自硬件,供 Swap/功能逻辑判断 */
|
||||
bool IsPhysicalKeyStateValid = false;
|
||||
quint8 PhysicalModifier = 0;
|
||||
QVector<quint16> PhysicalUsageList;
|
||||
QVector<quint16> LastPhysicalUsageList;
|
||||
|
||||
/* 键盘模式控制:NumLock、功能掩码、Swap 掩码等 */
|
||||
bool IsSystemNumLockOn = false;
|
||||
QByteArray FunctionMaskBitmap;
|
||||
QByteArray SwapMaskBitmap;
|
||||
QByteArray KeyboardMaskBitmap;
|
||||
|
||||
/* 功能键配置及 Swap 对应的运行期状态 */
|
||||
Lgc_Func_Button_Struct_Config FunctionButtonConfig;
|
||||
bool IsSwapModeOn = false;
|
||||
quint16 SwapUsageLeft = 0;
|
||||
quint16 SwapUsageRight = 0;
|
||||
bool IsSwapLeftPhysicalPressed = false;
|
||||
bool IsSwapRightPhysicalPressed = false;
|
||||
bool IsUsbProtocolReady = false;
|
||||
bool IsNusProtocolReady = false;
|
||||
bool IsUsbHelloSent = false;
|
||||
bool IsNusHelloSent = false;
|
||||
qint64 LastUsbHelloAttemptMs = 0;
|
||||
qint64 LastNusHelloAttemptMs = 0;
|
||||
qint64 NusReadySinceMs = 0;
|
||||
int NusHelloRetryCount = 0;
|
||||
bool WasNusConnectedLastPoll = false;
|
||||
bool HasLoggedNusWriteAck = false;
|
||||
bool HasLoggedNusHelloTimeout = false;
|
||||
qint64 LastCdcRefreshAttemptMs = 0;
|
||||
qint64 LastNusRefreshAttemptMs = 0;
|
||||
Com_Struct_ProtocolHelloRsp HelloResponse;
|
||||
quint32 DeviceLedMask = 0;
|
||||
quint32 LastAckedType = 0;
|
||||
quint32 LastErrorType = 0;
|
||||
quint32 LastErrorCode = 0;
|
||||
quint64 PendingUsbCommandBits = 0;
|
||||
qint64 PendingUsbCommandSinceMs = 0;
|
||||
quint64 PendingNusCommandBits = 0;
|
||||
qint64 PendingNusCommandSinceMs = 0;
|
||||
bool DeviceReady = false;
|
||||
bool BitmapSent = false;
|
||||
bool BitmapDirty = false;
|
||||
int BitmapRetryCount = 0;
|
||||
qint64 BitmapNextSendMs = 0;
|
||||
|
||||
Lgc_FunctionButton_Config FunctionButtonConfig;
|
||||
bool IsAltThemeEnabled = false;
|
||||
|
||||
/* 互操作:窗口句柄与核心运行状态 */
|
||||
void* WindowHandle = nullptr;
|
||||
bool IsConnected = false;
|
||||
bool IsStarted = false;
|
||||
bool IsFunctionSequenceRecording = false;
|
||||
QSet<quint16> UiPressedUsageSet;
|
||||
|
||||
int TestTxHelloReqCount = 0;
|
||||
int TestTxBitmapCount = 0;
|
||||
int TestTxTimeSyncCount = 0;
|
||||
int TestTxThemeRgbCount = 0;
|
||||
int TestRxHelloRspCount = 0;
|
||||
int TestRxFunctionKeyEventCount = 0;
|
||||
int TestRxLedStateCount = 0;
|
||||
int TestRxAckCount = 0;
|
||||
int TestRxErrorCount = 0;
|
||||
QString TestLastTxSummary;
|
||||
QString TestLastRxSummary;
|
||||
QByteArray TestLastTxBytes;
|
||||
QByteArray TestLastRxBytes;
|
||||
QByteArray TestLastFunctionEventBitmap;
|
||||
QStringList TestLogLines;
|
||||
QString LastLoggedNusEndpointSummary;
|
||||
};
|
||||
|
||||
/* 初始化核心:清空状态并准备 DRI 端口。 */
|
||||
void Lgc_Core_Func_Init(Lgc_Core_Struct_State* p_State);
|
||||
/* 告诉 LGC 使用哪个 HWND 接收 RAWINPUT。 */
|
||||
void Lgc_Core_Func_SetWindowHandle(Lgc_Core_Struct_State* p_State, void* WindowHandle);
|
||||
/* 每个 Windows 消息都交给核心处理。 */
|
||||
void Lgc_Core_Func_HandleNativeMessage(Lgc_Core_Struct_State* p_State, void* p_Message);
|
||||
/* 启动:打开设备、刷新按键状态并进入轮询。 */
|
||||
void Lgc_Core_Func_Start(Lgc_Core_Struct_State* p_State);
|
||||
/* 关闭:释放 DRI 端口与所有资源。 */
|
||||
void Lgc_Core_Func_Close(Lgc_Core_Struct_State* p_State);
|
||||
/* 当 VID/PID 变化或用户点击刷新时调用。 */
|
||||
void Lgc_Core_Func_RefreshDevice(Lgc_Core_Struct_State* p_State);
|
||||
/* 清空 LOG 文本,UI 立即同步。 */
|
||||
void Lgc_Core_Func_ClearLog(Lgc_Core_Struct_State* p_State);
|
||||
/* 轮询 DRI 队列,返回“是否发生变化”以供 UI 决定是否刷新。 */
|
||||
bool Lgc_Core_Func_Poll(Lgc_Core_Struct_State* p_State);
|
||||
/* 设置单个 Usage 是否是“功能模式” */
|
||||
bool Lgc_Core_Func_SetUsageFunctionMode(Lgc_Core_Struct_State* p_State, quint16 Usage, bool IsEnabled);
|
||||
/* 打开/关闭 Swap 模式,并指定左右 Usage */
|
||||
bool Lgc_Core_Func_SetSwapMode(
|
||||
struct Lgc_Core_Struct_View
|
||||
{
|
||||
QString TextFunctionStatus;
|
||||
bool IsConnected = false;
|
||||
bool HasOpenTransport = false;
|
||||
bool IsSystemNumLockOn = false;
|
||||
bool IsVisibleKeyStateValid = false;
|
||||
QVector<quint16> VisibleUsageList;
|
||||
bool IsPhysicalKeyStateValid = false;
|
||||
QVector<quint16> PhysicalUsageList;
|
||||
bool IsFunctionSequenceRecording = false;
|
||||
};
|
||||
|
||||
struct Lgc_Core_Struct_TestView
|
||||
{
|
||||
QString StatusText;
|
||||
QString UsbPortName;
|
||||
QString NusEndpointSummary;
|
||||
bool IsUsbOpened = false;
|
||||
bool IsNusOpened = false;
|
||||
bool IsNusConnected = false;
|
||||
bool IsUsbProtocolReady = false;
|
||||
bool IsNusProtocolReady = false;
|
||||
bool DeviceReady = false;
|
||||
quint32 HelloProtocolVersion = 0;
|
||||
quint32 HelloVendorId = 0;
|
||||
quint32 HelloProductId = 0;
|
||||
quint32 HelloFirmwareMajor = 0;
|
||||
quint32 HelloFirmwareMinor = 0;
|
||||
quint32 HelloCapabilityFlags = 0;
|
||||
quint32 DeviceLedMask = 0;
|
||||
quint32 LastAckedType = 0;
|
||||
quint32 LastErrorType = 0;
|
||||
quint32 LastErrorCode = 0;
|
||||
quint64 PendingUsbCommandBits = 0;
|
||||
quint64 PendingNusCommandBits = 0;
|
||||
QString LastTxSummary;
|
||||
QString LastRxSummary;
|
||||
QString LastTxHex;
|
||||
QString LastRxHex;
|
||||
QString FunctionMaskHex;
|
||||
QString LastFunctionEventHex;
|
||||
int TxHelloReqCount = 0;
|
||||
int TxBitmapCount = 0;
|
||||
int TxTimeSyncCount = 0;
|
||||
int TxThemeRgbCount = 0;
|
||||
int RxHelloRspCount = 0;
|
||||
int RxFunctionKeyEventCount = 0;
|
||||
int RxLedStateCount = 0;
|
||||
int RxAckCount = 0;
|
||||
int RxErrorCount = 0;
|
||||
QString LogText;
|
||||
};
|
||||
|
||||
void Lgc_Core_Init(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_SetWindowHandle(Lgc_Core_Struct_State* p_State, void* WindowHandle);
|
||||
void Lgc_Core_HandleNativeMessage(Lgc_Core_Struct_State* p_State, void* p_Message);
|
||||
void Lgc_Core_Start(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_Close(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_RefreshDevice(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_Poll(Lgc_Core_Struct_State* p_State);
|
||||
|
||||
Lgc_Core_Struct_View Lgc_Core_GetView(const Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_SetStatusText(Lgc_Core_Struct_State* p_State, const QString& TextStatus);
|
||||
|
||||
QVector<int> Lgc_Core_GetFeatureIdList(const Lgc_Core_Struct_State* p_State);
|
||||
Lgc_FunctionFeature_Definition Lgc_Core_GetFeature(
|
||||
const Lgc_Core_Struct_State* p_State,
|
||||
int FeatureId);
|
||||
bool Lgc_Core_HasFeature(const Lgc_Core_Struct_State* p_State, int FeatureId);
|
||||
QString Lgc_Core_GetUsageShortText(quint16 Usage);
|
||||
QString Lgc_Core_GetFeatureTypeText(Lgc_FunctionFeature_Type Type);
|
||||
QString Lgc_Core_GetFeatureNameById(const Lgc_Core_Struct_State* p_State, int FeatureId);
|
||||
QString Lgc_Core_GetFeatureDescriptionById(
|
||||
const Lgc_Core_Struct_State* p_State,
|
||||
int FeatureId);
|
||||
QString Lgc_Core_GetFeatureBindingSummary(
|
||||
const Lgc_Core_Struct_State* p_State,
|
||||
int FeatureId);
|
||||
int Lgc_Core_GetUsageFeatureId(const Lgc_Core_Struct_State* p_State, quint16 Usage);
|
||||
bool Lgc_Core_HasUsageFeature(const Lgc_Core_Struct_State* p_State, quint16 Usage);
|
||||
|
||||
bool Lgc_Core_LoadFunctionConfig(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_SaveFunctionConfig(Lgc_Core_Struct_State* p_State);
|
||||
int Lgc_Core_AddFeature(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_UpdateFeature(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
quint16 UsageLeft,
|
||||
quint16 UsageRight,
|
||||
bool IsEnabled);
|
||||
/* 查询给定 Usage 当前是否处于功能模式 */
|
||||
bool Lgc_Core_Func_IsUsageFunctionMode(const Lgc_Core_Struct_State* p_State, quint16 Usage);
|
||||
const Lgc_FunctionFeature_Definition& Feature);
|
||||
bool Lgc_Core_DeleteFeature(Lgc_Core_Struct_State* p_State, int FeatureId);
|
||||
bool Lgc_Core_BindUsageToFeature(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
quint16 Usage,
|
||||
int FeatureId);
|
||||
|
||||
bool Lgc_Core_BeginSequenceRecording(Lgc_Core_Struct_State* p_State, int FeatureId);
|
||||
void Lgc_Core_EndSequenceRecording(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_UpdateSequenceRecordingStatus(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const QString& SequenceText);
|
||||
void Lgc_Core_HandleUiKeyPress(Lgc_Core_Struct_State* p_State, quint16 Usage);
|
||||
void Lgc_Core_HandleUiKeyRelease(Lgc_Core_Struct_State* p_State, quint16 Usage);
|
||||
|
||||
bool Lgc_Core_ApplyFunctionConfig(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_TestSendHello(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource = Com_Enum_RawPacketSource_None);
|
||||
bool Lgc_Core_TestSendBitmapCurrentConfig(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource = Com_Enum_RawPacketSource_None);
|
||||
bool Lgc_Core_TestSendBitmapAllEnabled(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource = Com_Enum_RawPacketSource_None);
|
||||
bool Lgc_Core_TestSendBitmapAllDisabled(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource = Com_Enum_RawPacketSource_None);
|
||||
bool Lgc_Core_TestSendTimeSync(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource = Com_Enum_RawPacketSource_None);
|
||||
bool Lgc_Core_SendTimeSync(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_SendThemeRgb(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
quint8 Red,
|
||||
quint8 Green,
|
||||
quint8 Blue);
|
||||
bool Lgc_Core_TestSendThemeRgb(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
quint8 Red,
|
||||
quint8 Green,
|
||||
quint8 Blue,
|
||||
Com_Enum_RawPacketSource TargetSource = Com_Enum_RawPacketSource_None);
|
||||
bool Lgc_Core_SendThemeSwitch(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_ClearTestLog(Lgc_Core_Struct_State* p_State);
|
||||
Lgc_Core_Struct_TestView Lgc_Core_GetTestView(const Lgc_Core_Struct_State* p_State);
|
||||
|
||||
813
LOGIC/Lgc_Core_Command.cpp
Normal file
813
LOGIC/Lgc_Core_Command.cpp
Normal file
@@ -0,0 +1,813 @@
|
||||
#include "LOGIC/Lgc_Core_Private.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr qint64 kHelloRetryIntervalMs = 700;
|
||||
constexpr qint64 kNusHelloInitialDelayMs = 600;
|
||||
constexpr qint64 kNusHelloRetryAfterWriteAckMs = 1800;
|
||||
constexpr int kNusHelloRetryMaxCount = 6;
|
||||
constexpr qint64 kBitmapInitialSendDelayMs = 300;
|
||||
constexpr qint64 kBitmapRetryDelayMs = 200;
|
||||
|
||||
struct Lgc_Core_Struct_ThemeColor
|
||||
{
|
||||
quint8 Red;
|
||||
quint8 Green;
|
||||
quint8 Blue;
|
||||
};
|
||||
|
||||
QString Lgc_Core_FormatUsageListText(const QVector<quint16>& UsageList)
|
||||
{
|
||||
if (UsageList.isEmpty())
|
||||
{
|
||||
return QStringLiteral("(none)");
|
||||
}
|
||||
|
||||
QStringList KeyList;
|
||||
for (quint16 Usage : UsageList)
|
||||
{
|
||||
KeyList.append(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
}
|
||||
|
||||
return KeyList.join(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
QString Lgc_Core_FormatUsageBitmapSummary(const QByteArray& UsageBitmap)
|
||||
{
|
||||
if (!Com_Protocol_IsUsageBitmapValid(UsageBitmap))
|
||||
{
|
||||
return QStringLiteral("(invalid)");
|
||||
}
|
||||
|
||||
return Lgc_Core_FormatUsageListText(
|
||||
Com_Protocol_BuildPressedUsageList(UsageBitmap));
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendPacket(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_ProtocolType Type,
|
||||
const QByteArray& PacketBody);
|
||||
bool Lgc_Core_SendTestPacketToTarget(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource,
|
||||
Com_Enum_ProtocolType Type,
|
||||
const QByteArray& PacketBody,
|
||||
const QString& NoteText);
|
||||
|
||||
Lgc_Core_Struct_ThemeColor Lgc_Core_GetNextThemeColor(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if ((p_State == nullptr) || !p_State->IsAltThemeEnabled)
|
||||
{
|
||||
return { 0xF7, 0x25, 0x85 };
|
||||
}
|
||||
|
||||
return { 0x4C, 0xC9, 0xF0 };
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendBitmapNow(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const QByteArray& UsageBitmap,
|
||||
Com_Enum_RawPacketSource TargetSource = Com_Enum_RawPacketSource_None)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Com_Protocol_IsUsageBitmapValid(UsageBitmap))
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("Bitmap bytes are invalid.");
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_State->KeyboardMaskBitmap = UsageBitmap;
|
||||
p_State->BitmapSent = false;
|
||||
p_State->BitmapDirty = false;
|
||||
p_State->BitmapRetryCount = 0;
|
||||
p_State->BitmapNextSendMs = 0;
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral("Bitmap payload keys: %1")
|
||||
.arg(Lgc_Core_FormatUsageBitmapSummary(UsageBitmap))); // FIX: show which keys are in the manual bitmap payload.
|
||||
const QByteArray PacketBody = Com_Protocol_EncodeBitmap(UsageBitmap);
|
||||
return (TargetSource == Com_Enum_RawPacketSource_None)
|
||||
? Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Com_Enum_ProtocolType_Bitmap,
|
||||
PacketBody)
|
||||
: Lgc_Core_SendTestPacketToTarget(
|
||||
p_State,
|
||||
TargetSource,
|
||||
Com_Enum_ProtocolType_Bitmap,
|
||||
PacketBody,
|
||||
QStringLiteral("manual target"));
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendTestPacketToTarget(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource,
|
||||
Com_Enum_ProtocolType Type,
|
||||
const QByteArray& PacketBody,
|
||||
const QString& NoteText)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool RequiresAck =
|
||||
(Type == Com_Enum_ProtocolType_Bitmap) ||
|
||||
(Type == Com_Enum_ProtocolType_TimeSync) ||
|
||||
(Type == Com_Enum_ProtocolType_ThemeRgb);
|
||||
const bool IsHelloReq = Type == Com_Enum_ProtocolType_HelloReq;
|
||||
|
||||
if (PacketBody.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("%1 build failed.").arg(Lgc_Core_GetProtocolTypeText(Type));
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
QString TextStatus;
|
||||
auto FinalizeSuccess = [&](Com_Enum_RawPacketSource Source)
|
||||
{
|
||||
const QString SummaryNote = NoteText.isEmpty()
|
||||
? TextStatus
|
||||
: (TextStatus.isEmpty()
|
||||
? NoteText
|
||||
: QStringLiteral("%1 | %2").arg(NoteText, TextStatus));
|
||||
Lgc_Core_TestRecordTxPacket(
|
||||
p_State,
|
||||
Source,
|
||||
Type,
|
||||
PacketBody,
|
||||
SummaryNote);
|
||||
if (Type == Com_Enum_ProtocolType_Bitmap)
|
||||
{
|
||||
Lgc_Core_ClearDeviceReportedKeyStates(p_State);
|
||||
}
|
||||
if (RequiresAck)
|
||||
{
|
||||
Lgc_Core_AddPendingCommand(p_State, Source, Type);
|
||||
p_State->TextFunctionStatus =
|
||||
TextStatus.isEmpty()
|
||||
? QStringLiteral("%1 sent on %2. Waiting for ACK.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
.arg(Lgc_Core_GetTransportText(Source))
|
||||
: QStringLiteral("%1 Waiting for %2 ACK.")
|
||||
.arg(TextStatus)
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type));
|
||||
}
|
||||
else if (!TextStatus.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus = TextStatus;
|
||||
}
|
||||
};
|
||||
|
||||
switch (TargetSource)
|
||||
{
|
||||
case Com_Enum_RawPacketSource_UsbCdc:
|
||||
if (IsHelloReq)
|
||||
{
|
||||
if (!p_State->DriCdcPort.IsOpened)
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("USB CDC is not open yet. HelloReq was not sent.");
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!p_State->IsUsbProtocolReady)
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("USB CDC handshake is not ready yet. Skip %1.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type));
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
else if (!Lgc_Core_DeviceSupportsPacketType(p_State, Type))
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("Device capability does not support %1 on USB CDC. caps=0x%2")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
.arg(p_State->HelloResponse.CapabilityFlags, 0, 16);
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Dri_Cdc_Write(&p_State->DriCdcPort, PacketBody, &TextStatus))
|
||||
{
|
||||
p_State->TextFunctionStatus = TextStatus.isEmpty()
|
||||
? QStringLiteral("USB CDC send failed for %1.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
: TextStatus;
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
FinalizeSuccess(Com_Enum_RawPacketSource_UsbCdc);
|
||||
return true;
|
||||
|
||||
case Com_Enum_RawPacketSource_BleNus:
|
||||
if (IsHelloReq)
|
||||
{
|
||||
if (!p_State->DriNusPort.IsConnected)
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("BLE NUS is not connected yet. HelloReq was not sent.");
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!p_State->IsNusProtocolReady)
|
||||
{
|
||||
p_State->TextFunctionStatus = p_State->DriNusPort.IsConnected
|
||||
? QStringLiteral("BLE NUS handshake is not ready yet. Skip %1.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
: QStringLiteral("BLE NUS is not connected yet. Skip %1.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type));
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
else if (!Lgc_Core_DeviceSupportsPacketType(p_State, Type))
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("Device capability does not support %1 on BLE NUS. caps=0x%2")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
.arg(p_State->HelloResponse.CapabilityFlags, 0, 16);
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Dri_Nus_Write(&p_State->DriNusPort, PacketBody, &TextStatus))
|
||||
{
|
||||
p_State->TextFunctionStatus = TextStatus.isEmpty()
|
||||
? QStringLiteral("BLE NUS send failed for %1.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
: TextStatus;
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
FinalizeSuccess(Com_Enum_RawPacketSource_BleNus);
|
||||
return true;
|
||||
|
||||
default:
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("Test target transport is invalid for %1.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type));
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendPacket(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_ProtocolType Type,
|
||||
const QByteArray& PacketBody)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool RequiresAck =
|
||||
(Type == Com_Enum_ProtocolType_Bitmap) ||
|
||||
(Type == Com_Enum_ProtocolType_TimeSync) ||
|
||||
(Type == Com_Enum_ProtocolType_ThemeRgb);
|
||||
|
||||
if (PacketBody.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("%1 build failed.").arg(Lgc_Core_GetProtocolTypeText(Type));
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!p_State->DeviceReady)
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("Handshake is not finished. Skip %1.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type));
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Lgc_Core_DeviceSupportsPacketType(p_State, Type))
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("Device capability does not support %1. caps=0x%2")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
.arg(p_State->HelloResponse.CapabilityFlags, 0, 16);
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
QString TextStatus;
|
||||
if (p_State->IsUsbProtocolReady &&
|
||||
Dri_Cdc_Write(&p_State->DriCdcPort, PacketBody, &TextStatus))
|
||||
{
|
||||
Lgc_Core_TestRecordTxPacket(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_UsbCdc,
|
||||
Type,
|
||||
PacketBody,
|
||||
TextStatus);
|
||||
if (Type == Com_Enum_ProtocolType_Bitmap)
|
||||
{
|
||||
Lgc_Core_ClearDeviceReportedKeyStates(p_State);
|
||||
}
|
||||
if (RequiresAck)
|
||||
{
|
||||
Lgc_Core_AddPendingCommand(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_UsbCdc,
|
||||
Type);
|
||||
p_State->TextFunctionStatus =
|
||||
TextStatus.isEmpty()
|
||||
? QStringLiteral("%1 sent on USB CDC. Waiting for ACK.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
: QStringLiteral("%1 Waiting for %2 ACK.")
|
||||
.arg(TextStatus)
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type));
|
||||
}
|
||||
else if (!TextStatus.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus = TextStatus;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_State->IsNusProtocolReady &&
|
||||
Dri_Nus_Write(&p_State->DriNusPort, PacketBody, &TextStatus))
|
||||
{
|
||||
Lgc_Core_TestRecordTxPacket(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_BleNus,
|
||||
Type,
|
||||
PacketBody,
|
||||
TextStatus);
|
||||
if (Type == Com_Enum_ProtocolType_Bitmap)
|
||||
{
|
||||
Lgc_Core_ClearDeviceReportedKeyStates(p_State);
|
||||
}
|
||||
if (RequiresAck)
|
||||
{
|
||||
Lgc_Core_AddPendingCommand(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_BleNus,
|
||||
Type);
|
||||
p_State->TextFunctionStatus =
|
||||
TextStatus.isEmpty()
|
||||
? QStringLiteral("%1 sent on BLE NUS. Waiting for ACK.")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
: QStringLiteral("%1 Waiting for %2 ACK.")
|
||||
.arg(TextStatus)
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type));
|
||||
}
|
||||
else if (!TextStatus.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus = TextStatus;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
p_State->TextFunctionStatus = TextStatus.isEmpty()
|
||||
? QStringLiteral("%1 send failed.").arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
: TextStatus;
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral("TX %1 failed: %2")
|
||||
.arg(Lgc_Core_GetProtocolTypeText(Type))
|
||||
.arg(p_State->TextFunctionStatus));
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Lgc_Core_FillMaskAllEnabled(QByteArray* p_UsageBitmap)
|
||||
{
|
||||
*p_UsageBitmap = QByteArray(COM_CONST_USAGE_BITMAP_SIZE, static_cast<char>(0xFF));
|
||||
}
|
||||
|
||||
void Lgc_Core_ScheduleBitmapSend(Lgc_Core_Struct_State* p_State, qint64 DelayMs)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_State->BitmapDirty = true;
|
||||
p_State->BitmapSent = false;
|
||||
p_State->BitmapNextSendMs =
|
||||
QDateTime::currentMSecsSinceEpoch() + (DelayMs > 0 ? DelayMs : 0);
|
||||
}
|
||||
|
||||
bool Lgc_Core_ProcessBitmapSend(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if ((p_State == nullptr) ||
|
||||
!p_State->BitmapDirty ||
|
||||
p_State->BitmapSent ||
|
||||
!p_State->DeviceReady ||
|
||||
!Lgc_Core_DeviceSupportsPacketType(p_State, Com_Enum_ProtocolType_Bitmap) ||
|
||||
Lgc_Core_HasPendingCommand(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_UsbCdc,
|
||||
Com_Enum_ProtocolType_Bitmap) ||
|
||||
Lgc_Core_HasPendingCommand(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_BleNus,
|
||||
Com_Enum_ProtocolType_Bitmap))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const qint64 NowMs = QDateTime::currentMSecsSinceEpoch();
|
||||
if ((p_State->BitmapNextSendMs != 0) && (NowMs < p_State->BitmapNextSendMs))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
p_State->KeyboardMaskBitmap = p_State->FunctionMaskBitmap;
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral("Bitmap auto-sync keys: %1")
|
||||
.arg(Lgc_Core_FormatUsageBitmapSummary(p_State->KeyboardMaskBitmap))); // FIX: log the exact auto-sync bitmap before send.
|
||||
const bool IsSent = Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Com_Enum_ProtocolType_Bitmap,
|
||||
Com_Protocol_EncodeBitmap(p_State->KeyboardMaskBitmap));
|
||||
if (!IsSent)
|
||||
{
|
||||
p_State->BitmapNextSendMs = NowMs + kBitmapRetryDelayMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("Bitmap queued. Waiting for ACK.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendHello(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray PacketBody = Com_Protocol_EncodeHelloReq();
|
||||
if (PacketBody.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("HelloReq build failed.");
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
const qint64 NowMs = QDateTime::currentMSecsSinceEpoch();
|
||||
bool IsSent = false;
|
||||
bool IsDeferred = false;
|
||||
QString TextStatus;
|
||||
|
||||
if (p_State->DriNusPort.IsConnected &&
|
||||
!p_State->IsNusProtocolReady)
|
||||
{
|
||||
const qint64 NusHelloRetryIntervalMs =
|
||||
p_State->DriNusPort.HasWriteAck
|
||||
? kNusHelloRetryAfterWriteAckMs
|
||||
: kHelloRetryIntervalMs;
|
||||
|
||||
if (p_State->DriNusPort.HasWriteAck && !p_State->HasLoggedNusWriteAck)
|
||||
{
|
||||
p_State->HasLoggedNusWriteAck = true;
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("BLE RX characteristic acknowledged HelloReq. Waiting for HelloRsp.");
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
}
|
||||
|
||||
if ((p_State->NusReadySinceMs != 0) &&
|
||||
((NowMs - p_State->NusReadySinceMs) < kNusHelloInitialDelayMs))
|
||||
{
|
||||
// The firmware enables NUS TX only after the notify subscription settles.
|
||||
IsDeferred = true;
|
||||
}
|
||||
else if (p_State->NusHelloRetryCount >= kNusHelloRetryMaxCount)
|
||||
{
|
||||
if (!p_State->HasLoggedNusHelloTimeout)
|
||||
{
|
||||
p_State->HasLoggedNusHelloTimeout = true;
|
||||
p_State->TextFunctionStatus = p_State->DriNusPort.HasWriteAck
|
||||
? QStringLiteral("BLE transport is writable, but HelloRsp is still missing. Keep the link open and inspect firmware logs.")
|
||||
: QStringLiteral("BLE link is up, but HelloRsp is still missing. Keep the link open and inspect firmware logs.");
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
}
|
||||
IsDeferred = true;
|
||||
}
|
||||
else if ((p_State->LastNusHelloAttemptMs == 0) ||
|
||||
((NowMs - p_State->LastNusHelloAttemptMs) >= NusHelloRetryIntervalMs))
|
||||
{
|
||||
if (Dri_Nus_Write(&p_State->DriNusPort, PacketBody, &TextStatus))
|
||||
{
|
||||
p_State->IsNusHelloSent = true;
|
||||
p_State->LastNusHelloAttemptMs = NowMs;
|
||||
++p_State->NusHelloRetryCount;
|
||||
p_State->HasLoggedNusHelloTimeout = false;
|
||||
Lgc_Core_TestRecordTxPacket(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_BleNus,
|
||||
Com_Enum_ProtocolType_HelloReq,
|
||||
PacketBody,
|
||||
QStringLiteral("hello attempt %1 | %2")
|
||||
.arg(p_State->NusHelloRetryCount)
|
||||
.arg(TextStatus));
|
||||
IsSent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p_State->DriCdcPort.IsOpened &&
|
||||
!p_State->IsUsbProtocolReady &&
|
||||
((p_State->LastUsbHelloAttemptMs == 0) ||
|
||||
((NowMs - p_State->LastUsbHelloAttemptMs) >= kHelloRetryIntervalMs)) &&
|
||||
Dri_Cdc_Write(&p_State->DriCdcPort, PacketBody, &TextStatus))
|
||||
{
|
||||
p_State->IsUsbHelloSent = true;
|
||||
p_State->LastUsbHelloAttemptMs = NowMs;
|
||||
Lgc_Core_TestRecordTxPacket(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_UsbCdc,
|
||||
Com_Enum_ProtocolType_HelloReq,
|
||||
PacketBody,
|
||||
TextStatus);
|
||||
IsSent = true;
|
||||
}
|
||||
|
||||
if (!TextStatus.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus = TextStatus;
|
||||
}
|
||||
else if (!IsSent && !IsDeferred)
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("No transport is ready for auto HelloReq.");
|
||||
}
|
||||
|
||||
return IsSent;
|
||||
}
|
||||
|
||||
bool Lgc_Core_ApplyFunctionConfig(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert local feature bindings into the 29-byte device bitmap.
|
||||
p_State->FunctionMaskBitmap = Com_Protocol_CreateUsageBitmap();
|
||||
|
||||
for (quint16 Usage : Lgc_FunctionButton_GetConfigurableUsages())
|
||||
{
|
||||
if (Lgc_FunctionButton_HasUsageFeature(p_State->FunctionButtonConfig, Usage))
|
||||
{
|
||||
Com_Protocol_SetUsageBitmapBit(&p_State->FunctionMaskBitmap, Usage, true);
|
||||
}
|
||||
}
|
||||
|
||||
p_State->BitmapRetryCount = 0;
|
||||
p_State->BitmapSent = false;
|
||||
Lgc_Core_ScheduleBitmapSend(
|
||||
p_State,
|
||||
p_State->DeviceReady ? 0 : kHelloRetryIntervalMs); // FIX: when already ready, sync the new bitmap immediately.
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral("Function config updated. Bitmap auto-sync scheduled.")); // FIX
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral("Function config keys: %1")
|
||||
.arg(Lgc_Core_FormatUsageBitmapSummary(p_State->FunctionMaskBitmap))); // FIX
|
||||
if (p_State->DeviceReady)
|
||||
{
|
||||
(void)Lgc_Core_ProcessBitmapSend(p_State); // FIX: keep right-click binding aligned with the old immediate-sync behavior.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendTimeSync(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
return Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Com_Enum_ProtocolType_TimeSync,
|
||||
Com_Protocol_EncodeTimeSync(
|
||||
1,
|
||||
0,
|
||||
QDateTime::currentDateTime().offsetFromUtc() / 60,
|
||||
static_cast<quint64>(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()),
|
||||
1000));
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendThemeRgb(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
quint8 Red,
|
||||
quint8 Green,
|
||||
quint8 Blue)
|
||||
{
|
||||
return Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Com_Enum_ProtocolType_ThemeRgb,
|
||||
Com_Protocol_EncodeThemeRgb(Red, Green, Blue));
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendThemeSwitch(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
const Lgc_Core_Struct_ThemeColor ThemeColor = Lgc_Core_GetNextThemeColor(p_State);
|
||||
if (!Lgc_Core_SendThemeRgb(
|
||||
p_State,
|
||||
ThemeColor.Red,
|
||||
ThemeColor.Green,
|
||||
ThemeColor.Blue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_State != nullptr)
|
||||
{
|
||||
p_State->IsAltThemeEnabled = !p_State->IsAltThemeEnabled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_Core_TestSendHello(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray PacketBody = Com_Protocol_EncodeHelloReq();
|
||||
if (PacketBody.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("HelloReq build failed.");
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
const qint64 NowMs = QDateTime::currentMSecsSinceEpoch();
|
||||
if (TargetSource == Com_Enum_RawPacketSource_UsbCdc)
|
||||
{
|
||||
const bool IsSent = Lgc_Core_SendTestPacketToTarget(
|
||||
p_State,
|
||||
TargetSource,
|
||||
Com_Enum_ProtocolType_HelloReq,
|
||||
PacketBody,
|
||||
QStringLiteral("manual target"));
|
||||
if (IsSent)
|
||||
{
|
||||
p_State->IsUsbHelloSent = true;
|
||||
p_State->LastUsbHelloAttemptMs = NowMs;
|
||||
}
|
||||
return IsSent;
|
||||
}
|
||||
|
||||
if (TargetSource == Com_Enum_RawPacketSource_BleNus)
|
||||
{
|
||||
const bool IsSent = Lgc_Core_SendTestPacketToTarget(
|
||||
p_State,
|
||||
TargetSource,
|
||||
Com_Enum_ProtocolType_HelloReq,
|
||||
PacketBody,
|
||||
QStringLiteral("manual target"));
|
||||
if (IsSent)
|
||||
{
|
||||
p_State->IsNusHelloSent = true;
|
||||
p_State->LastNusHelloAttemptMs = NowMs;
|
||||
p_State->NusHelloRetryCount = 1;
|
||||
p_State->HasLoggedNusHelloTimeout = false;
|
||||
}
|
||||
return IsSent;
|
||||
}
|
||||
|
||||
bool IsSent = false;
|
||||
QString TextStatus;
|
||||
if (p_State->DriCdcPort.IsOpened &&
|
||||
Dri_Cdc_Write(&p_State->DriCdcPort, PacketBody, &TextStatus))
|
||||
{
|
||||
p_State->IsUsbHelloSent = true;
|
||||
p_State->LastUsbHelloAttemptMs = NowMs;
|
||||
Lgc_Core_TestRecordTxPacket(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_UsbCdc,
|
||||
Com_Enum_ProtocolType_HelloReq,
|
||||
PacketBody,
|
||||
QStringLiteral("manual"));
|
||||
IsSent = true;
|
||||
}
|
||||
|
||||
if (p_State->DriNusPort.IsConnected &&
|
||||
Dri_Nus_Write(&p_State->DriNusPort, PacketBody, &TextStatus))
|
||||
{
|
||||
p_State->IsNusHelloSent = true;
|
||||
p_State->LastNusHelloAttemptMs = NowMs;
|
||||
p_State->NusHelloRetryCount = 1;
|
||||
p_State->HasLoggedNusHelloTimeout = false;
|
||||
Lgc_Core_TestRecordTxPacket(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_BleNus,
|
||||
Com_Enum_ProtocolType_HelloReq,
|
||||
PacketBody,
|
||||
QStringLiteral("manual"));
|
||||
IsSent = true;
|
||||
}
|
||||
|
||||
if (!TextStatus.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus = TextStatus;
|
||||
}
|
||||
else if (!IsSent)
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("No active transport is ready for HelloReq.");
|
||||
Lgc_Core_TestAppendLog(p_State, p_State->TextFunctionStatus);
|
||||
}
|
||||
|
||||
return IsSent;
|
||||
}
|
||||
|
||||
bool Lgc_Core_TestSendBitmapCurrentConfig(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource)
|
||||
{
|
||||
return Lgc_Core_SendBitmapNow(
|
||||
p_State,
|
||||
p_State == nullptr ? QByteArray() : p_State->FunctionMaskBitmap,
|
||||
TargetSource);
|
||||
}
|
||||
|
||||
bool Lgc_Core_TestSendBitmapAllEnabled(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource)
|
||||
{
|
||||
return Lgc_Core_SendBitmapNow(
|
||||
p_State,
|
||||
QByteArray(COM_CONST_USAGE_BITMAP_SIZE, static_cast<char>(0xFF)),
|
||||
TargetSource);
|
||||
}
|
||||
|
||||
bool Lgc_Core_TestSendBitmapAllDisabled(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource)
|
||||
{
|
||||
return Lgc_Core_SendBitmapNow(
|
||||
p_State,
|
||||
QByteArray(COM_CONST_USAGE_BITMAP_SIZE, 0),
|
||||
TargetSource);
|
||||
}
|
||||
|
||||
bool Lgc_Core_TestSendTimeSync(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource TargetSource)
|
||||
{
|
||||
const QByteArray PacketBody = Com_Protocol_EncodeTimeSync(
|
||||
1,
|
||||
0,
|
||||
QDateTime::currentDateTime().offsetFromUtc() / 60,
|
||||
static_cast<quint64>(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()),
|
||||
1000);
|
||||
return (TargetSource == Com_Enum_RawPacketSource_None)
|
||||
? Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Com_Enum_ProtocolType_TimeSync,
|
||||
PacketBody)
|
||||
: Lgc_Core_SendTestPacketToTarget(
|
||||
p_State,
|
||||
TargetSource,
|
||||
Com_Enum_ProtocolType_TimeSync,
|
||||
PacketBody,
|
||||
QStringLiteral("manual target"));
|
||||
}
|
||||
|
||||
bool Lgc_Core_TestSendThemeRgb(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
quint8 Red,
|
||||
quint8 Green,
|
||||
quint8 Blue,
|
||||
Com_Enum_RawPacketSource TargetSource)
|
||||
{
|
||||
const QByteArray PacketBody = Com_Protocol_EncodeThemeRgb(Red, Green, Blue);
|
||||
return (TargetSource == Com_Enum_RawPacketSource_None)
|
||||
? Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Com_Enum_ProtocolType_ThemeRgb,
|
||||
PacketBody)
|
||||
: Lgc_Core_SendTestPacketToTarget(
|
||||
p_State,
|
||||
TargetSource,
|
||||
Com_Enum_ProtocolType_ThemeRgb,
|
||||
PacketBody,
|
||||
QStringLiteral("manual target"));
|
||||
}
|
||||
366
LOGIC/Lgc_Core_Config.cpp
Normal file
366
LOGIC/Lgc_Core_Config.cpp
Normal file
@@ -0,0 +1,366 @@
|
||||
#include "LOGIC/Lgc_Core_Private.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QSaveFile>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr int kFunctionConfigVersion = 1;
|
||||
constexpr const char* kFunctionConfigFileName = "function_config.json";
|
||||
|
||||
QString Lgc_Core_GetFeatureTypeJsonKey(Lgc_FunctionFeature_Type Type)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case Lgc_FunctionFeature_Type::KeyCombination:
|
||||
return QStringLiteral("key_combination");
|
||||
case Lgc_FunctionFeature_Type::KeySequence:
|
||||
return QStringLiteral("key_sequence");
|
||||
case Lgc_FunctionFeature_Type::Website:
|
||||
return QStringLiteral("website");
|
||||
default:
|
||||
return QStringLiteral("key_combination");
|
||||
}
|
||||
}
|
||||
|
||||
Lgc_FunctionFeature_Type Lgc_Core_ParseFeatureTypeJsonKey(const QString& JsonKey)
|
||||
{
|
||||
const QString NormalizedKey = JsonKey.trimmed().toLower();
|
||||
if ((NormalizedKey == QStringLiteral("key_combination")) ||
|
||||
(NormalizedKey == QStringLiteral("combination")) ||
|
||||
(NormalizedKey == QStringLiteral("shortcut")))
|
||||
{
|
||||
return Lgc_FunctionFeature_Type::KeyCombination;
|
||||
}
|
||||
|
||||
if ((NormalizedKey == QStringLiteral("key_sequence")) ||
|
||||
(NormalizedKey == QStringLiteral("sequence")))
|
||||
{
|
||||
return Lgc_FunctionFeature_Type::KeySequence;
|
||||
}
|
||||
|
||||
if ((NormalizedKey == QStringLiteral("website")) ||
|
||||
(NormalizedKey == QStringLiteral("url")) ||
|
||||
(NormalizedKey == QStringLiteral("web")))
|
||||
{
|
||||
return Lgc_FunctionFeature_Type::Website;
|
||||
}
|
||||
|
||||
return Lgc_FunctionFeature_Type::KeyCombination;
|
||||
}
|
||||
|
||||
bool Lgc_Core_IsConfigurableUsage(quint16 Usage)
|
||||
{
|
||||
return Lgc_FunctionButton_GetConfigurableUsages().contains(Usage);
|
||||
}
|
||||
|
||||
QVector<quint16> Lgc_Core_GetUsageListForFeature(
|
||||
const Lgc_Core_Struct_State* p_State,
|
||||
int FeatureId)
|
||||
{
|
||||
QVector<quint16> UsageList;
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return UsageList;
|
||||
}
|
||||
|
||||
for (quint16 Usage : Lgc_FunctionButton_GetConfigurableUsages())
|
||||
{
|
||||
if (Lgc_FunctionButton_GetUsageFeatureId(p_State->FunctionButtonConfig, Usage) == FeatureId)
|
||||
{
|
||||
UsageList.append(Usage);
|
||||
}
|
||||
}
|
||||
return UsageList;
|
||||
}
|
||||
|
||||
QJsonObject Lgc_Core_BuildFeatureJson(
|
||||
const Lgc_Core_Struct_State* p_State,
|
||||
const Lgc_FunctionFeature_Definition& Feature)
|
||||
{
|
||||
QJsonObject JsonObject;
|
||||
JsonObject.insert(QStringLiteral("id"), Feature.Id);
|
||||
JsonObject.insert(QStringLiteral("name"), Feature.Name);
|
||||
JsonObject.insert(QStringLiteral("description"), Feature.Description);
|
||||
JsonObject.insert(QStringLiteral("type"), Lgc_Core_GetFeatureTypeJsonKey(Feature.Type));
|
||||
JsonObject.insert(QStringLiteral("sequence_text"), Feature.SequenceText);
|
||||
JsonObject.insert(QStringLiteral("website_url"), Feature.WebsiteUrl);
|
||||
|
||||
QJsonArray UsageArray;
|
||||
for (quint16 Usage : Lgc_Core_GetUsageListForFeature(p_State, Feature.Id))
|
||||
{
|
||||
UsageArray.append(static_cast<int>(Usage));
|
||||
}
|
||||
JsonObject.insert(QStringLiteral("usage_list"), UsageArray);
|
||||
return JsonObject;
|
||||
}
|
||||
|
||||
bool Lgc_Core_ParseFunctionConfig(
|
||||
const QJsonObject& RootObject,
|
||||
Lgc_FunctionButton_Config* p_Config,
|
||||
QString* p_TextError)
|
||||
{
|
||||
auto SetError = [p_TextError](const QString& Text)
|
||||
{
|
||||
if (p_TextError != nullptr)
|
||||
{
|
||||
*p_TextError = Text;
|
||||
}
|
||||
};
|
||||
|
||||
if (p_Config == nullptr)
|
||||
{
|
||||
SetError(QStringLiteral("Function config target is null."));
|
||||
return false;
|
||||
}
|
||||
|
||||
*p_Config = Lgc_FunctionButton_Config();
|
||||
if (p_TextError != nullptr)
|
||||
{
|
||||
p_TextError->clear();
|
||||
}
|
||||
|
||||
const QJsonValue VersionValue = RootObject.value(QStringLiteral("version"));
|
||||
if (VersionValue.isDouble() &&
|
||||
(VersionValue.toInt(kFunctionConfigVersion) > kFunctionConfigVersion))
|
||||
{
|
||||
SetError(QStringLiteral("Unsupported config version: %1")
|
||||
.arg(VersionValue.toInt(kFunctionConfigVersion)));
|
||||
return false;
|
||||
}
|
||||
|
||||
const QJsonArray FunctionArray = RootObject.value(QStringLiteral("functions")).toArray();
|
||||
for (const QJsonValue& FunctionValue : FunctionArray)
|
||||
{
|
||||
if (!FunctionValue.isObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const QJsonObject FunctionObject = FunctionValue.toObject();
|
||||
const int FeatureId = FunctionObject.value(QStringLiteral("id")).toInt();
|
||||
if (FeatureId <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Lgc_FunctionFeature_Definition Feature;
|
||||
Feature.Id = FeatureId;
|
||||
Feature.Name = FunctionObject.value(QStringLiteral("name")).toString();
|
||||
Feature.Description = FunctionObject.value(QStringLiteral("description")).toString();
|
||||
Feature.SequenceText = FunctionObject.value(QStringLiteral("sequence_text")).toString();
|
||||
Feature.WebsiteUrl = FunctionObject.value(QStringLiteral("website_url")).toString();
|
||||
Feature.Type = Lgc_Core_ParseFeatureTypeJsonKey(
|
||||
FunctionObject.value(QStringLiteral("type")).toString());
|
||||
|
||||
p_Config->FeatureMap.insert(FeatureId, Feature);
|
||||
|
||||
const QJsonArray UsageArray = FunctionObject.value(QStringLiteral("usage_list")).toArray();
|
||||
for (const QJsonValue& UsageValue : UsageArray)
|
||||
{
|
||||
const int Usage = UsageValue.toInt(-1);
|
||||
if ((Usage >= 0) && Lgc_Core_IsConfigurableUsage(static_cast<quint16>(Usage)))
|
||||
{
|
||||
p_Config->UsageFeatureIdMap.insert(static_cast<quint16>(Usage), FeatureId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Lgc_Core_GetFunctionConfigFilePathInternal()
|
||||
{
|
||||
const QDir AppDir(QCoreApplication::applicationDirPath());
|
||||
const QStringList CandidatePathList = {
|
||||
QDir::current().absoluteFilePath(QLatin1String(kFunctionConfigFileName)),
|
||||
AppDir.absoluteFilePath(QLatin1String(kFunctionConfigFileName)),
|
||||
AppDir.absoluteFilePath(QStringLiteral("../") + QLatin1String(kFunctionConfigFileName)),
|
||||
AppDir.absoluteFilePath(QStringLiteral("../../") + QLatin1String(kFunctionConfigFileName))
|
||||
};
|
||||
|
||||
for (const QString& CandidatePath : CandidatePathList)
|
||||
{
|
||||
const QString CleanPath = QDir::cleanPath(CandidatePath);
|
||||
if (QFileInfo::exists(CleanPath))
|
||||
{
|
||||
return CleanPath;
|
||||
}
|
||||
}
|
||||
|
||||
return QDir::cleanPath(CandidatePathList.first());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Lgc_Core_LoadFunctionConfig(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString FilePath = Lgc_Core_GetFunctionConfigFilePathInternal();
|
||||
if (!QFileInfo::exists(FilePath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QFile ConfigFile(FilePath);
|
||||
if (!ConfigFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("Function config load failed: %1")
|
||||
.arg(ConfigFile.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonParseError ParseError;
|
||||
const QJsonDocument JsonDocument =
|
||||
QJsonDocument::fromJson(ConfigFile.readAll(), &ParseError);
|
||||
if ((ParseError.error != QJsonParseError::NoError) || !JsonDocument.isObject())
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("Function config parse failed: %1")
|
||||
.arg(ParseError.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
Lgc_FunctionButton_Config LoadedConfig;
|
||||
QString TextError;
|
||||
if (!Lgc_Core_ParseFunctionConfig(JsonDocument.object(), &LoadedConfig, &TextError))
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("Function config parse failed: %1")
|
||||
.arg(TextError);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_State->FunctionButtonConfig = LoadedConfig;
|
||||
Lgc_Core_ApplyFunctionConfig(p_State);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_Core_SaveFunctionConfig(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString FilePath = Lgc_Core_GetFunctionConfigFilePathInternal();
|
||||
const QFileInfo FileInfo(FilePath);
|
||||
QDir ParentDir(FileInfo.absolutePath());
|
||||
if (!ParentDir.exists() && !ParentDir.mkpath(QStringLiteral(".")))
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("Function config save failed: %1")
|
||||
.arg(QStringLiteral("Could not create config directory: %1")
|
||||
.arg(FileInfo.absolutePath()));
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject RootObject;
|
||||
RootObject.insert(QStringLiteral("version"), kFunctionConfigVersion);
|
||||
|
||||
QJsonArray FunctionArray;
|
||||
for (int FeatureId : Lgc_FunctionButton_GetFeatureIdList(p_State->FunctionButtonConfig))
|
||||
{
|
||||
const Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(p_State->FunctionButtonConfig, FeatureId);
|
||||
if (Feature.Id > 0)
|
||||
{
|
||||
FunctionArray.append(Lgc_Core_BuildFeatureJson(p_State, Feature));
|
||||
}
|
||||
}
|
||||
RootObject.insert(QStringLiteral("functions"), FunctionArray);
|
||||
|
||||
QSaveFile ConfigFile(FilePath);
|
||||
if (!ConfigFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("Function config save failed: %1")
|
||||
.arg(ConfigFile.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray JsonData = QJsonDocument(RootObject).toJson(QJsonDocument::Indented);
|
||||
if (ConfigFile.write(JsonData) != JsonData.size())
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("Function config save failed: %1")
|
||||
.arg(ConfigFile.errorString());
|
||||
ConfigFile.cancelWriting();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ConfigFile.commit())
|
||||
{
|
||||
p_State->TextFunctionStatus = QStringLiteral("Function config save failed: %1")
|
||||
.arg(ConfigFile.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int Lgc_Core_AddFeature(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
return p_State == nullptr ? 0 : Lgc_FunctionButton_AddFeature(p_State->FunctionButtonConfig);
|
||||
}
|
||||
|
||||
bool Lgc_Core_UpdateFeature(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const Lgc_FunctionFeature_Definition& Feature)
|
||||
{
|
||||
if ((p_State == nullptr) || (Feature.Id <= 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Lgc_FunctionButton_SetFeature(p_State->FunctionButtonConfig, Feature);
|
||||
Lgc_Core_ApplyFunctionConfig(p_State);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_Core_DeleteFeature(Lgc_Core_Struct_State* p_State, int FeatureId)
|
||||
{
|
||||
if ((p_State == nullptr) || (FeatureId <= 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString FeatureName = Lgc_Core_GetFeatureNameById(p_State, FeatureId);
|
||||
Lgc_FunctionButton_RemoveFeature(p_State->FunctionButtonConfig, FeatureId);
|
||||
Lgc_Core_ApplyFunctionConfig(p_State);
|
||||
p_State->TextFunctionStatus = QStringLiteral("宸插垹闄ゅ姛鑳斤細%1").arg(FeatureName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_Core_BindUsageToFeature(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
quint16 Usage,
|
||||
int FeatureId)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((FeatureId > 0) && !Lgc_Core_HasFeature(p_State, FeatureId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Lgc_FunctionButton_SetUsageFeatureId(p_State->FunctionButtonConfig, Usage, FeatureId);
|
||||
Lgc_Core_ApplyFunctionConfig(p_State);
|
||||
p_State->TextFunctionStatus = FeatureId > 0
|
||||
? QStringLiteral("Bound key %1 to %2.")
|
||||
.arg(
|
||||
Lgc_FunctionButton_GetUsageShortText(Usage),
|
||||
Lgc_Core_GetFeatureNameById(p_State, FeatureId))
|
||||
: QStringLiteral("Cleared the function binding on key %1.")
|
||||
.arg(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
return true;
|
||||
}
|
||||
320
LOGIC/Lgc_Core_Control.cpp
Normal file
320
LOGIC/Lgc_Core_Control.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
#include "LOGIC/Lgc_Core_Private.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QTimeZone>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct Lgc_Core_Struct_ThemeColor
|
||||
{
|
||||
quint8 Red;
|
||||
quint8 Green;
|
||||
quint8 Blue;
|
||||
};
|
||||
|
||||
QString Lgc_Core_FormatThemeColor(quint8 Red, quint8 Green, quint8 Blue)
|
||||
{
|
||||
return QStringLiteral("%1 %2 %3")
|
||||
.arg(Red, 2, 16, QLatin1Char('0'))
|
||||
.arg(Green, 2, 16, QLatin1Char('0'))
|
||||
.arg(Blue, 2, 16, QLatin1Char('0'))
|
||||
.toUpper();
|
||||
}
|
||||
|
||||
Lgc_Core_Struct_ThemeColor Lgc_Core_GetNextThemeColor(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
p_State->IsAltThemeEnabled = !p_State->IsAltThemeEnabled;
|
||||
|
||||
if (p_State->IsAltThemeEnabled)
|
||||
{
|
||||
return { 0xF7, 0x25, 0x85 };
|
||||
}
|
||||
|
||||
return { 0x4C, 0xC9, 0xF0 };
|
||||
}
|
||||
|
||||
bool Lgc_Core_IsUsageInRange(quint16 Usage)
|
||||
{
|
||||
return Usage <= MID_CONST_KEYBOARD_USAGE_MAX;
|
||||
}
|
||||
|
||||
bool Lgc_Core_IsUsageEnabled(const QByteArray& Bitmap, quint16 Usage)
|
||||
{
|
||||
if (!Lgc_Core_IsUsageInRange(Usage) || (Bitmap.size() < MID_CONST_USAGE_BITMAP_SIZE))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const int ByteIndex = Usage / 8;
|
||||
const quint8 BitMask = static_cast<quint8>(1U << (Usage % 8));
|
||||
const quint8 ByteValue = static_cast<quint8>(Bitmap.at(ByteIndex));
|
||||
return (ByteValue & BitMask) != 0;
|
||||
}
|
||||
|
||||
void Lgc_Core_SetUsageEnabled(QByteArray* p_Bitmap, quint16 Usage, bool IsEnabled)
|
||||
{
|
||||
if (!Lgc_Core_IsUsageInRange(Usage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_Bitmap->size() < MID_CONST_USAGE_BITMAP_SIZE)
|
||||
{
|
||||
Lgc_Core_FillMaskAllEnabled(p_Bitmap);
|
||||
}
|
||||
|
||||
const int ByteIndex = Usage / 8;
|
||||
const quint8 BitMask = static_cast<quint8>(1U << (Usage % 8));
|
||||
quint8 ByteValue = static_cast<quint8>(p_Bitmap->at(ByteIndex));
|
||||
|
||||
if (IsEnabled)
|
||||
{
|
||||
ByteValue = static_cast<quint8>(ByteValue | BitMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteValue = static_cast<quint8>(ByteValue & ~BitMask);
|
||||
}
|
||||
|
||||
(*p_Bitmap)[ByteIndex] = static_cast<char>(ByteValue);
|
||||
}
|
||||
|
||||
void Lgc_Core_RebuildKeyboardMask(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State->FunctionMaskBitmap.size() < MID_CONST_USAGE_BITMAP_SIZE)
|
||||
{
|
||||
Lgc_Core_FillMaskAllEnabled(&p_State->FunctionMaskBitmap);
|
||||
}
|
||||
|
||||
p_State->KeyboardMaskBitmap.resize(MID_CONST_USAGE_BITMAP_SIZE);
|
||||
for (int Index = 0; Index < MID_CONST_USAGE_BITMAP_SIZE; ++Index)
|
||||
{
|
||||
p_State->KeyboardMaskBitmap[Index] = p_State->FunctionMaskBitmap.at(Index);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray Lgc_Core_BuildMaskPacket(const Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
QByteArray Packet(MID_CONST_PACKET_SIZE_VENDOR, 0);
|
||||
Packet[0] = static_cast<char>(Mid_Enum_ReportId_Vendor);
|
||||
for (int Index = 0; (Index < MID_CONST_USAGE_BITMAP_SIZE) && (Index < p_State->KeyboardMaskBitmap.size()); ++Index)
|
||||
{
|
||||
Packet[Index + 2] = p_State->KeyboardMaskBitmap.at(Index);
|
||||
}
|
||||
return Packet;
|
||||
}
|
||||
|
||||
QByteArray Lgc_Core_BuildCommandPacket(quint8 CommandId, const QByteArray& Payload)
|
||||
{
|
||||
QByteArray Packet(MID_CONST_PACKET_SIZE_VENDOR_COMMAND, 0);
|
||||
Packet[0] = static_cast<char>(Mid_Enum_ReportId_VendorCommand);
|
||||
Packet[1] = static_cast<char>(CommandId);
|
||||
for (int Index = 0; (Index < MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA) && (Index < Payload.size()); ++Index)
|
||||
{
|
||||
Packet[Index + 2] = Payload.at(Index);
|
||||
}
|
||||
return Packet;
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendPacket(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const QByteArray& Packet,
|
||||
const QString& ExplainText)
|
||||
{
|
||||
QString RouteText;
|
||||
QString TextStatus;
|
||||
const auto AppendStatus = [&TextStatus](const QString& StatusText)
|
||||
{
|
||||
if (StatusText.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TextStatus.isEmpty())
|
||||
{
|
||||
TextStatus.append(QLatin1Char('\n'));
|
||||
}
|
||||
TextStatus.append(StatusText);
|
||||
};
|
||||
const auto AppendRoute = [&RouteText](const QString& RouteName)
|
||||
{
|
||||
if (RouteName.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RouteText.isEmpty())
|
||||
{
|
||||
RouteText.append(QStringLiteral(" -> "));
|
||||
}
|
||||
RouteText.append(RouteName);
|
||||
};
|
||||
const auto TrySendUsb = [&]() -> bool
|
||||
{
|
||||
if (!p_State->DriVendorPort.ReadPort.IsOpened)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString UsbStatus;
|
||||
const QString RouteName = p_State->DriVendorPort.ReadPort.PortName;
|
||||
if (Dri_Vendor_Write(&p_State->DriVendorPort, Packet, &UsbStatus))
|
||||
{
|
||||
Lgc_Core_AppendPacketLog(
|
||||
p_State,
|
||||
QStringLiteral("发送"),
|
||||
RouteName,
|
||||
Packet,
|
||||
ExplainText);
|
||||
Lgc_Core_AppendStatusLog(p_State, UsbStatus);
|
||||
return true;
|
||||
}
|
||||
|
||||
AppendRoute(RouteName);
|
||||
AppendStatus(UsbStatus);
|
||||
return false;
|
||||
};
|
||||
const auto TrySendBle = [&]() -> bool
|
||||
{
|
||||
if (!p_State->DriBlePort.IsConnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString BleStatus;
|
||||
if (Dri_Ble_Write(&p_State->DriBlePort, Packet, &BleStatus))
|
||||
{
|
||||
Lgc_Core_AppendPacketLog(
|
||||
p_State,
|
||||
QStringLiteral("发送"),
|
||||
QStringLiteral("Bluetooth/HID Vendor"),
|
||||
Packet,
|
||||
ExplainText);
|
||||
Lgc_Core_AppendStatusLog(p_State, BleStatus);
|
||||
return true;
|
||||
}
|
||||
|
||||
AppendRoute(QStringLiteral("Bluetooth/HID Vendor"));
|
||||
AppendStatus(BleStatus);
|
||||
return false;
|
||||
};
|
||||
|
||||
if (p_State->DriBlePort.IsConnected)
|
||||
{
|
||||
if (TrySendBle() || TrySendUsb())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (TrySendUsb())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (RouteText.isEmpty())
|
||||
{
|
||||
RouteText = QStringLiteral("Vendor");
|
||||
}
|
||||
if (TextStatus.isEmpty())
|
||||
{
|
||||
TextStatus = QStringLiteral("No connected USB/Bluetooth device was found.");
|
||||
}
|
||||
|
||||
Lgc_Core_AppendPacketLog(
|
||||
p_State,
|
||||
QStringLiteral("发送失败"),
|
||||
RouteText,
|
||||
Packet,
|
||||
ExplainText);
|
||||
Lgc_Core_AppendStatusLog(p_State, TextStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 Lgc_Core_GetBeijingTimestampMs()
|
||||
{
|
||||
return QDateTime::currentDateTimeUtc()
|
||||
.toTimeZone(QTimeZone("Asia/Shanghai"))
|
||||
.toMSecsSinceEpoch() + (8LL * 60LL * 60LL * 1000LL);
|
||||
}
|
||||
|
||||
QString Lgc_Core_GetBeijingTimeText(qint64 TimestampMs)
|
||||
{
|
||||
const QTimeZone BeijingTimeZone("Asia/Shanghai");
|
||||
return QDateTime::fromMSecsSinceEpoch(TimestampMs, Qt::UTC)
|
||||
.toTimeZone(BeijingTimeZone)
|
||||
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Lgc_Core_FillMaskAllEnabled(QByteArray* p_UsageBitmap)
|
||||
{
|
||||
*p_UsageBitmap = QByteArray(MID_CONST_USAGE_BITMAP_SIZE, static_cast<char>(0xFF));
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendCurrentMask(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
Lgc_Core_RebuildKeyboardMask(p_State);
|
||||
return Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Lgc_Core_BuildMaskPacket(p_State),
|
||||
QStringLiteral("0x04 键盘掩码同步"));
|
||||
}
|
||||
|
||||
bool Lgc_Core_ApplyFunctionConfig(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
Lgc_Core_FillMaskAllEnabled(&p_State->FunctionMaskBitmap);
|
||||
|
||||
const QVector<quint16> UsageList = Lgc_FunctionButton_GetConfigurableUsages();
|
||||
for (quint16 Usage : UsageList)
|
||||
{
|
||||
if (Lgc_FunctionButton_HasUsageFeature(p_State->FunctionButtonConfig, Usage))
|
||||
{
|
||||
Lgc_Core_SetUsageEnabled(&p_State->FunctionMaskBitmap, Usage, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (p_State->IsStarted &&
|
||||
(p_State->DriVendorPort.ReadPort.IsOpened || p_State->DriBlePort.IsConnected))
|
||||
{
|
||||
Lgc_Core_SendCurrentMask(p_State);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendTimeSync(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
const qint64 TimestampMs = Lgc_Core_GetBeijingTimestampMs();
|
||||
QByteArray Payload(MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA, 0);
|
||||
for (int Index = 0; Index < MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA; ++Index)
|
||||
{
|
||||
Payload[Index] = static_cast<char>((static_cast<quint64>(TimestampMs) >> (Index * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
return Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Lgc_Core_BuildCommandPacket(0x02, Payload),
|
||||
QStringLiteral("0x05 0x02 时间同步(北京时间毫秒值:%1,北京时间:%2)")
|
||||
.arg(QString::number(TimestampMs), Lgc_Core_GetBeijingTimeText(TimestampMs)));
|
||||
}
|
||||
|
||||
bool Lgc_Core_SendThemeSwitch(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
const Lgc_Core_Struct_ThemeColor ThemeColor = Lgc_Core_GetNextThemeColor(p_State);
|
||||
QByteArray Payload(MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA, 0);
|
||||
Payload[0] = static_cast<char>(ThemeColor.Red);
|
||||
Payload[1] = static_cast<char>(ThemeColor.Green);
|
||||
Payload[2] = static_cast<char>(ThemeColor.Blue);
|
||||
|
||||
return Lgc_Core_SendPacket(
|
||||
p_State,
|
||||
Lgc_Core_BuildCommandPacket(0x01, Payload),
|
||||
QStringLiteral("0x05 0x01 theme switch (RGB:%1)")
|
||||
.arg(Lgc_Core_FormatThemeColor(
|
||||
ThemeColor.Red,
|
||||
ThemeColor.Green,
|
||||
ThemeColor.Blue)));
|
||||
}
|
||||
|
||||
702
LOGIC/Lgc_Core_Input.cpp
Normal file
702
LOGIC/Lgc_Core_Input.cpp
Normal file
@@ -0,0 +1,702 @@
|
||||
#include "LOGIC/Lgc_Core_Private.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QStringList>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr quint32 kProtocolVersionCurrent = 1U;
|
||||
constexpr qint64 kBitmapInitialSendDelayMs = 300;
|
||||
constexpr qint64 kBitmapRetryDelayMs = 200;
|
||||
constexpr int kBitmapRetryMaxCount = 5;
|
||||
|
||||
QString Lgc_Core_FormatUsageListText(const QVector<quint16>& UsageList)
|
||||
{
|
||||
if (UsageList.isEmpty())
|
||||
{
|
||||
return QStringLiteral("(none)");
|
||||
}
|
||||
|
||||
QStringList KeyList;
|
||||
for (quint16 Usage : UsageList)
|
||||
{
|
||||
KeyList.append(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
}
|
||||
|
||||
return KeyList.join(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
QByteArray Lgc_Core_BuildUsageBitmapFromList(const QVector<quint16>& UsageList)
|
||||
{
|
||||
QByteArray UsageBitmap = Com_Protocol_CreateUsageBitmap();
|
||||
for (quint16 Usage : UsageList)
|
||||
{
|
||||
(void)Com_Protocol_SetUsageBitmapBit(&UsageBitmap, Usage, true);
|
||||
}
|
||||
|
||||
return UsageBitmap;
|
||||
}
|
||||
|
||||
Com_Enum_ProtocolType Lgc_Core_NormalizePacketTypeValue(quint32 RawType)
|
||||
{
|
||||
switch (RawType)
|
||||
{
|
||||
case 1: return Com_Enum_ProtocolType_HelloReq;
|
||||
case 2: return Com_Enum_ProtocolType_HelloRsp;
|
||||
case 3: return Com_Enum_ProtocolType_Bitmap;
|
||||
case 4: return Com_Enum_ProtocolType_FunctionKeyEvent;
|
||||
case 5: return Com_Enum_ProtocolType_LedState;
|
||||
case 6: return Com_Enum_ProtocolType_TimeSync;
|
||||
case 7: return Com_Enum_ProtocolType_ThemeRgb;
|
||||
case 8: return Com_Enum_ProtocolType_Ack;
|
||||
case 9: return Com_Enum_ProtocolType_Error;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const auto Type = static_cast<Com_Enum_ProtocolType>(RawType & 0xFFU);
|
||||
switch (Type)
|
||||
{
|
||||
case Com_Enum_ProtocolType_HelloReq:
|
||||
case Com_Enum_ProtocolType_HelloRsp:
|
||||
case Com_Enum_ProtocolType_Bitmap:
|
||||
case Com_Enum_ProtocolType_FunctionKeyEvent:
|
||||
case Com_Enum_ProtocolType_LedState:
|
||||
case Com_Enum_ProtocolType_TimeSync:
|
||||
case Com_Enum_ProtocolType_ThemeRgb:
|
||||
case Com_Enum_ProtocolType_Ack:
|
||||
case Com_Enum_ProtocolType_Error:
|
||||
return (static_cast<quint32>(Type) == RawType)
|
||||
? Type
|
||||
: Com_Enum_ProtocolType_None;
|
||||
default:
|
||||
return Com_Enum_ProtocolType_None;
|
||||
}
|
||||
}
|
||||
|
||||
QString Lgc_Core_FormatVidPid(quint32 VendorId, quint32 ProductId)
|
||||
{
|
||||
return QStringLiteral("%1:%2")
|
||||
.arg(VendorId, 4, 16, QLatin1Char('0'))
|
||||
.arg(ProductId, 4, 16, QLatin1Char('0'))
|
||||
.toUpper();
|
||||
}
|
||||
|
||||
QString Lgc_Core_GetPacketTypeText(quint32 RawType)
|
||||
{
|
||||
const Com_Enum_ProtocolType Type = Lgc_Core_NormalizePacketTypeValue(RawType);
|
||||
if (Type == Com_Enum_ProtocolType_None)
|
||||
{
|
||||
return QStringLiteral("0x%1").arg(RawType, 2, 16, QLatin1Char('0')).toUpper();
|
||||
}
|
||||
|
||||
const QString TypeText = Lgc_Core_GetProtocolTypeText(Type);
|
||||
return (static_cast<quint32>(Type) == RawType)
|
||||
? TypeText
|
||||
: QStringLiteral("%1(tag 0x%2)")
|
||||
.arg(TypeText)
|
||||
.arg(RawType, 2, 16, QLatin1Char('0'))
|
||||
.toUpper();
|
||||
}
|
||||
|
||||
QString Lgc_Core_GetPacketSourceText(const Com_Struct_RawPacket& Packet)
|
||||
{
|
||||
return Packet.PortName.isEmpty()
|
||||
? Lgc_Core_GetTransportText(Packet.Source)
|
||||
: QStringLiteral("%1 %2")
|
||||
.arg(Lgc_Core_GetTransportText(Packet.Source))
|
||||
.arg(Packet.PortName);
|
||||
}
|
||||
|
||||
bool Lgc_Core_IsHelloRspValid(
|
||||
const Lgc_Core_Struct_State* /*p_State*/,
|
||||
const Com_Struct_ProtocolHelloRsp& HelloRsp,
|
||||
QString* p_TextError)
|
||||
{
|
||||
auto SetError = [p_TextError](const QString& Text)
|
||||
{
|
||||
if (p_TextError != nullptr)
|
||||
{
|
||||
*p_TextError = Text;
|
||||
}
|
||||
};
|
||||
|
||||
if (p_TextError != nullptr)
|
||||
{
|
||||
p_TextError->clear();
|
||||
}
|
||||
|
||||
if (HelloRsp.ProtocolVersion != kProtocolVersionCurrent)
|
||||
{
|
||||
SetError(
|
||||
QStringLiteral("protocol_version=%1, expected %2")
|
||||
.arg(HelloRsp.ProtocolVersion)
|
||||
.arg(kProtocolVersionCurrent));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Day0 live testing accepts any device identity that speaks the current protocol.
|
||||
// Capability flags stay as runtime gates, but are not a reason to reject HelloRsp itself.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_Core_ConfirmHelloRspSource(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const Com_Struct_RawPacket& Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
if (Packet.Source == Com_Enum_RawPacketSource_UsbCdc)
|
||||
{
|
||||
if (!Dri_Cdc_LockCandidate(&p_State->DriCdcPort, Packet.EndpointId, p_TextStatus))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Dri_Nus_Close(&p_State->DriNusPort);
|
||||
p_State->LastNusRefreshAttemptMs = 0;
|
||||
p_State->IsUsbProtocolReady = true;
|
||||
p_State->IsUsbHelloSent = true;
|
||||
p_State->IsNusProtocolReady = false;
|
||||
p_State->IsNusHelloSent = false;
|
||||
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->PendingNusCommandBits = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Packet.Source == Com_Enum_RawPacketSource_BleNus)
|
||||
{
|
||||
if (!Dri_Nus_LockCandidate(&p_State->DriNusPort, Packet.EndpointId, p_TextStatus))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Dri_Cdc_Close(&p_State->DriCdcPort);
|
||||
p_State->LastCdcRefreshAttemptMs = 0;
|
||||
p_State->IsUsbProtocolReady = false;
|
||||
p_State->IsUsbHelloSent = false;
|
||||
p_State->LastUsbHelloAttemptMs = 0;
|
||||
p_State->PendingUsbCommandBits = 0;
|
||||
p_State->IsNusProtocolReady = true;
|
||||
p_State->IsNusHelloSent = true;
|
||||
p_State->NusHelloRetryCount = 0;
|
||||
p_State->HasLoggedNusWriteAck = false;
|
||||
p_State->HasLoggedNusHelloTimeout = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("HelloRsp arrived from an unknown transport.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Lgc_Core_RejectHelloRsp(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const Com_Struct_RawPacket& Packet,
|
||||
const QString& TextError)
|
||||
{
|
||||
Lgc_Core_ResetProtocolStateForSource(p_State, Packet.Source);
|
||||
|
||||
if (Packet.Source == Com_Enum_RawPacketSource_UsbCdc)
|
||||
{
|
||||
QString TextStatus;
|
||||
if (!Packet.EndpointId.isEmpty())
|
||||
{
|
||||
Dri_Cdc_DiscardCandidate(&p_State->DriCdcPort, Packet.EndpointId, &TextStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dri_Cdc_Close(&p_State->DriCdcPort);
|
||||
}
|
||||
|
||||
if (!p_State->DriCdcPort.IsOpened)
|
||||
{
|
||||
p_State->LastCdcRefreshAttemptMs = 0;
|
||||
}
|
||||
|
||||
p_State->TextFunctionStatus = TextStatus.isEmpty()
|
||||
? QStringLiteral("%1 handshake rejected: %2")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(TextError)
|
||||
: QStringLiteral("%1 handshake rejected: %2, %3")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(TextError)
|
||||
.arg(TextStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Packet.Source == Com_Enum_RawPacketSource_BleNus)
|
||||
{
|
||||
QString TextStatus;
|
||||
if (!Packet.EndpointId.isEmpty())
|
||||
{
|
||||
Dri_Nus_DiscardCandidate(&p_State->DriNusPort, Packet.EndpointId, &TextStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dri_Nus_Close(&p_State->DriNusPort);
|
||||
}
|
||||
|
||||
if (!p_State->DriNusPort.IsOpened)
|
||||
{
|
||||
p_State->LastNusRefreshAttemptMs = 0;
|
||||
}
|
||||
|
||||
p_State->TextFunctionStatus = TextStatus.isEmpty()
|
||||
? QStringLiteral("%1 handshake rejected: %2")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(TextError)
|
||||
: QStringLiteral("%1 handshake rejected: %2, %3")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(TextError)
|
||||
.arg(TextStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("%1 handshake rejected: %2")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(TextError);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Lgc_Core_ClearAllKeyStates(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Lgc_Core_ClearDeviceReportedKeyStates(p_State);
|
||||
p_State->UiPressedUsageSet.clear();
|
||||
}
|
||||
|
||||
void Lgc_Core_ClearDeviceReportedKeyStates(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_State->IsVisibleKeyStateValid = false;
|
||||
p_State->VisibleUsageList.clear();
|
||||
p_State->IsPhysicalKeyStateValid = false;
|
||||
p_State->PhysicalUsageList.clear();
|
||||
p_State->LastPhysicalUsageList.clear();
|
||||
p_State->TestLastFunctionEventBitmap.clear();
|
||||
}
|
||||
|
||||
void Lgc_Core_CloseAllPorts(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dri_Nus_Close(&p_State->DriNusPort);
|
||||
Dri_Cdc_Close(&p_State->DriCdcPort);
|
||||
}
|
||||
|
||||
bool Lgc_Core_SyncSystemState(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool OldNumLock = p_State->IsSystemNumLockOn;
|
||||
const bool OldConnected = p_State->IsConnected;
|
||||
|
||||
p_State->IsSystemNumLockOn = (GetKeyState(VK_NUMLOCK) & 0x0001) != 0;
|
||||
p_State->DeviceReady = p_State->IsUsbProtocolReady || p_State->IsNusProtocolReady;
|
||||
p_State->IsConnected = p_State->DeviceReady;
|
||||
|
||||
if (p_State->IsUsbProtocolReady && !p_State->DriCdcPort.IsOpened)
|
||||
{
|
||||
Lgc_Core_ResetProtocolStateForSource(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_UsbCdc);
|
||||
p_State->LastCdcRefreshAttemptMs = 0;
|
||||
}
|
||||
|
||||
if (p_State->IsNusProtocolReady && !p_State->DriNusPort.IsConnected)
|
||||
{
|
||||
Lgc_Core_ResetProtocolStateForSource(
|
||||
p_State,
|
||||
Com_Enum_RawPacketSource_BleNus);
|
||||
p_State->LastNusRefreshAttemptMs = 0;
|
||||
if (!p_State->DriNusPort.IsOpened)
|
||||
{
|
||||
Dri_Nus_Close(&p_State->DriNusPort);
|
||||
}
|
||||
}
|
||||
|
||||
if (OldConnected && !p_State->IsConnected)
|
||||
{
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("Device disconnected. Trying to reconnect.");
|
||||
}
|
||||
|
||||
return (OldNumLock != p_State->IsSystemNumLockOn) ||
|
||||
(OldConnected != p_State->IsConnected);
|
||||
}
|
||||
|
||||
void Lgc_Core_HandleCdcPacket(Lgc_Core_Struct_State* p_State, const Com_Struct_RawPacket& Packet)
|
||||
{
|
||||
if ((p_State != nullptr) && (Packet.Source == Com_Enum_RawPacketSource_UsbCdc))
|
||||
{
|
||||
Lgc_Core_HandleProtocolPacket(p_State, Packet);
|
||||
}
|
||||
}
|
||||
|
||||
void Lgc_Core_HandleNusPacket(Lgc_Core_Struct_State* p_State, const Com_Struct_RawPacket& Packet)
|
||||
{
|
||||
if ((p_State != nullptr) && (Packet.Source == Com_Enum_RawPacketSource_BleNus))
|
||||
{
|
||||
Lgc_Core_HandleProtocolPacket(p_State, Packet);
|
||||
}
|
||||
}
|
||||
|
||||
void Lgc_Core_HandleProtocolPacket(Lgc_Core_Struct_State* p_State, const Com_Struct_RawPacket& Packet)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Enum_ProtocolType Type = Packet.ProtocolType;
|
||||
if ((Type == Com_Enum_ProtocolType_None) &&
|
||||
!Com_Protocol_DecodeMessageType(Packet.ByteArray, &Type))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Com_Struct_RawPacket LoggedPacket = Packet;
|
||||
LoggedPacket.ProtocolType = Type;
|
||||
Lgc_Core_TestRecordRxPacket(p_State, LoggedPacket);
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case Com_Enum_ProtocolType_HelloRsp:
|
||||
{
|
||||
Com_Struct_ProtocolHelloRsp HelloRsp;
|
||||
if (!Com_Protocol_DecodeHelloRsp(Packet.ByteArray, &HelloRsp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QString TextError;
|
||||
if (!Lgc_Core_IsHelloRspValid(p_State, HelloRsp, &TextError))
|
||||
{
|
||||
Lgc_Core_RejectHelloRsp(p_State, Packet, TextError);
|
||||
return;
|
||||
}
|
||||
|
||||
QString TextStatus;
|
||||
if (!Lgc_Core_ConfirmHelloRspSource(p_State, Packet, &TextStatus))
|
||||
{
|
||||
p_State->TextFunctionStatus = TextStatus.isEmpty()
|
||||
? QStringLiteral("%1 target lock failed.")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
: TextStatus;
|
||||
return;
|
||||
}
|
||||
|
||||
p_State->HelloResponse = HelloRsp;
|
||||
p_State->DeviceReady = true;
|
||||
p_State->IsConnected = true;
|
||||
|
||||
QStringList AutoSentList;
|
||||
if (Lgc_Core_DeviceSupportsPacketType(p_State, Com_Enum_ProtocolType_Bitmap))
|
||||
{
|
||||
p_State->BitmapRetryCount = 0;
|
||||
p_State->BitmapSent = false;
|
||||
Lgc_Core_ScheduleBitmapSend(p_State, kBitmapInitialSendDelayMs);
|
||||
AutoSentList.append(QStringLiteral("Bitmap scheduled"));
|
||||
}
|
||||
if (Lgc_Core_DeviceSupportsPacketType(p_State, Com_Enum_ProtocolType_TimeSync) &&
|
||||
Lgc_Core_SendTimeSync(p_State))
|
||||
{
|
||||
AutoSentList.append(Lgc_Core_GetProtocolTypeText(Com_Enum_ProtocolType_TimeSync));
|
||||
}
|
||||
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("Handshake OK on %1: protocol v%2, FW %3.%4, VID/PID %5, caps 0x%6")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(HelloRsp.ProtocolVersion)
|
||||
.arg(HelloRsp.FirmwareMajor)
|
||||
.arg(HelloRsp.FirmwareMinor)
|
||||
.arg(Lgc_Core_FormatVidPid(HelloRsp.VendorId, HelloRsp.ProductId))
|
||||
.arg(HelloRsp.CapabilityFlags, 0, 16);
|
||||
if (!AutoSentList.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus +=
|
||||
QStringLiteral(" Auto TX: %1.").arg(AutoSentList.join(QStringLiteral(", ")));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case Com_Enum_ProtocolType_FunctionKeyEvent:
|
||||
{
|
||||
Com_Struct_ProtocolFunctionKeyEvent Event;
|
||||
if (!Com_Protocol_DecodeFunctionKeyEvent(Packet.ByteArray, &Event))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray EventBitmap = Event.UsageBitmap;
|
||||
if (!Com_Protocol_IsUsageBitmapValid(EventBitmap))
|
||||
{
|
||||
// 兼容旧固件的 usage + action 单键事件格式,统一还原成位图状态。
|
||||
if (!Event.HasUsageAction)
|
||||
{
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral("%1 FunctionKeyEvent payload format is not supported.")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet)));
|
||||
return;
|
||||
}
|
||||
|
||||
EventBitmap = Com_Protocol_IsUsageBitmapValid(p_State->TestLastFunctionEventBitmap)
|
||||
? p_State->TestLastFunctionEventBitmap
|
||||
: Lgc_Core_BuildUsageBitmapFromList(p_State->PhysicalUsageList);
|
||||
|
||||
if (Event.Action == Com_Enum_ProtocolKeyAction_Press)
|
||||
{
|
||||
(void)Com_Protocol_SetUsageBitmapBit(&EventBitmap, Event.Usage, true);
|
||||
}
|
||||
else if (Event.Action == Com_Enum_ProtocolKeyAction_Release)
|
||||
{
|
||||
(void)Com_Protocol_SetUsageBitmapBit(&EventBitmap, Event.Usage, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral("%1 legacy FunctionKeyEvent has unknown action %2 on key %3.")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(Event.Action)
|
||||
.arg(Lgc_FunctionButton_GetUsageShortText(Event.Usage)));
|
||||
return;
|
||||
}
|
||||
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral("%1 legacy FunctionKeyEvent: %2 %3")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(Lgc_FunctionButton_GetUsageShortText(Event.Usage))
|
||||
.arg(
|
||||
Event.Action == Com_Enum_ProtocolKeyAction_Press
|
||||
? QStringLiteral("PRESS")
|
||||
: QStringLiteral("RELEASE")));
|
||||
}
|
||||
|
||||
const QVector<quint16> UsageList =
|
||||
Com_Protocol_BuildPressedUsageList(EventBitmap);
|
||||
p_State->IsPhysicalKeyStateValid = true;
|
||||
p_State->IsVisibleKeyStateValid = true;
|
||||
p_State->PhysicalUsageList = UsageList;
|
||||
p_State->VisibleUsageList = UsageList;
|
||||
p_State->TestLastFunctionEventBitmap = EventBitmap;
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral("%1 FunctionKeyEvent decoded keys: %2")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(Lgc_Core_FormatUsageListText(UsageList)));
|
||||
return;
|
||||
}
|
||||
|
||||
case Com_Enum_ProtocolType_LedState:
|
||||
{
|
||||
Com_Struct_ProtocolLedState LedState;
|
||||
if (!Com_Protocol_DecodeLedState(Packet.ByteArray, &LedState))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p_State->DeviceLedMask = LedState.LedMask;
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("%1 LED mask 0x%2")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(LedState.LedMask, 8, 16, QLatin1Char('0'))
|
||||
.toUpper();
|
||||
return;
|
||||
}
|
||||
|
||||
case Com_Enum_ProtocolType_Ack:
|
||||
{
|
||||
Com_Struct_ProtocolAck Ack;
|
||||
if (!Com_Protocol_DecodeAckForType(Packet.ByteArray, Type, &Ack))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QString AckedTypeText = Lgc_Core_GetPacketTypeText(Ack.AckedType);
|
||||
const auto AckedType = Lgc_Core_NormalizePacketTypeValue(Ack.AckedType);
|
||||
const bool WasPending =
|
||||
Lgc_Core_ClearPendingCommand(p_State, Packet.Source, AckedType);
|
||||
p_State->LastAckedType =
|
||||
(AckedType == Com_Enum_ProtocolType_None)
|
||||
? Ack.AckedType
|
||||
: static_cast<quint32>(AckedType);
|
||||
if (AckedType == Com_Enum_ProtocolType_Bitmap)
|
||||
{
|
||||
p_State->BitmapRetryCount = 0;
|
||||
if (p_State->KeyboardMaskBitmap != p_State->FunctionMaskBitmap)
|
||||
{
|
||||
p_State->BitmapSent = false;
|
||||
p_State->BitmapDirty = true;
|
||||
p_State->BitmapNextSendMs = QDateTime::currentMSecsSinceEpoch();
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral(
|
||||
"%1 ACK %2 confirmed, but function config changed while the previous Bitmap was in flight. Auto-resending the latest Bitmap.")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(AckedTypeText));
|
||||
(void)Lgc_Core_ProcessBitmapSend(p_State);
|
||||
return;
|
||||
}
|
||||
|
||||
p_State->BitmapSent = true;
|
||||
p_State->BitmapDirty = false;
|
||||
p_State->BitmapNextSendMs = 0;
|
||||
}
|
||||
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("%1 ACK %2%3")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(AckedTypeText)
|
||||
.arg(WasPending ? QStringLiteral(" confirmed.")
|
||||
: QStringLiteral(" (unexpected)."));
|
||||
return;
|
||||
}
|
||||
|
||||
case Com_Enum_ProtocolType_Error:
|
||||
{
|
||||
Com_Struct_ProtocolError Error;
|
||||
if (!Com_Protocol_DecodeErrorForType(Packet.ByteArray, Type, &Error))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QString ErrorTypeText = Lgc_Core_GetPacketTypeText(Error.ErrorType);
|
||||
const auto ErrorType = Lgc_Core_NormalizePacketTypeValue(Error.ErrorType);
|
||||
const bool WasPending =
|
||||
Lgc_Core_ClearPendingCommand(p_State, Packet.Source, ErrorType);
|
||||
p_State->LastErrorType =
|
||||
(ErrorType == Com_Enum_ProtocolType_None)
|
||||
? Error.ErrorType
|
||||
: static_cast<quint32>(ErrorType);
|
||||
p_State->LastErrorCode = Error.ErrorCode;
|
||||
|
||||
if ((ErrorType == Com_Enum_ProtocolType_Bitmap) &&
|
||||
(Error.ErrorCode == Com_Enum_ProtocolErrorCode_NotReady))
|
||||
{
|
||||
p_State->BitmapSent = false;
|
||||
if (p_State->BitmapRetryCount < kBitmapRetryMaxCount)
|
||||
{
|
||||
++p_State->BitmapRetryCount;
|
||||
p_State->BitmapDirty = true;
|
||||
p_State->BitmapNextSendMs =
|
||||
QDateTime::currentMSecsSinceEpoch() + kBitmapRetryDelayMs;
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("%1 ERROR NOT_READY for Bitmap. Retry %2/%3 scheduled.")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(p_State->BitmapRetryCount)
|
||||
.arg(kBitmapRetryMaxCount);
|
||||
return;
|
||||
}
|
||||
|
||||
p_State->BitmapDirty = false;
|
||||
p_State->BitmapNextSendMs = 0;
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("%1 ERROR NOT_READY for Bitmap. Retry limit reached.")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ErrorType == Com_Enum_ProtocolType_Bitmap)
|
||||
{
|
||||
p_State->BitmapSent = false;
|
||||
if (p_State->KeyboardMaskBitmap != p_State->FunctionMaskBitmap)
|
||||
{
|
||||
p_State->BitmapDirty = true;
|
||||
p_State->BitmapNextSendMs = QDateTime::currentMSecsSinceEpoch();
|
||||
Lgc_Core_TestAppendLog(
|
||||
p_State,
|
||||
QStringLiteral(
|
||||
"%1 ERROR %2 for %3. A newer Bitmap config is waiting, so auto-sync will retry with the latest Bitmap.")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(Lgc_Core_GetProtocolErrorText(Error.ErrorCode))
|
||||
.arg(ErrorTypeText));
|
||||
(void)Lgc_Core_ProcessBitmapSend(p_State);
|
||||
return;
|
||||
}
|
||||
|
||||
p_State->BitmapDirty = false;
|
||||
p_State->BitmapNextSendMs = 0;
|
||||
}
|
||||
|
||||
p_State->TextFunctionStatus =
|
||||
QStringLiteral("%1 ERROR %2 for %3%4")
|
||||
.arg(Lgc_Core_GetPacketSourceText(Packet))
|
||||
.arg(Lgc_Core_GetProtocolErrorText(Error.ErrorCode))
|
||||
.arg(ErrorTypeText)
|
||||
.arg(WasPending ? QStringLiteral(".")
|
||||
: QStringLiteral(" (unexpected)."));
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool Lgc_Core_HandleFunctionButtons(Lgc_Core_Struct_State* p_State)
|
||||
{
|
||||
if (p_State == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!p_State->IsPhysicalKeyStateValid)
|
||||
{
|
||||
p_State->LastPhysicalUsageList.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_State->IsFunctionSequenceRecording)
|
||||
{
|
||||
p_State->LastPhysicalUsageList = p_State->PhysicalUsageList;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsChanged = false;
|
||||
for (quint16 Usage : p_State->PhysicalUsageList)
|
||||
{
|
||||
if (p_State->LastPhysicalUsageList.contains(Usage) ||
|
||||
!Lgc_FunctionButton_HasUsageFeature(p_State->FunctionButtonConfig, Usage))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString TextStatus;
|
||||
if (Lgc_FunctionButton_RunBinding(*p_State, Usage, TextStatus) && !TextStatus.isEmpty())
|
||||
{
|
||||
p_State->TextFunctionStatus = TextStatus;
|
||||
IsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
p_State->LastPhysicalUsageList = p_State->PhysicalUsageList;
|
||||
return IsChanged;
|
||||
}
|
||||
54
LOGIC/Lgc_Core_Private.h
Normal file
54
LOGIC/Lgc_Core_Private.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "LOGIC/Lgc_Core.h"
|
||||
|
||||
QString Lgc_Core_GetTransportText(Com_Enum_RawPacketSource Source);
|
||||
QString Lgc_Core_GetProtocolTypeText(Com_Enum_ProtocolType Type);
|
||||
QString Lgc_Core_GetProtocolErrorText(quint32 ErrorCode);
|
||||
QString Lgc_Core_FormatPacketHex(const QByteArray& PacketBody);
|
||||
void Lgc_Core_TestAppendLog(Lgc_Core_Struct_State* p_State, const QString& LineText);
|
||||
void Lgc_Core_TestRecordTxPacket(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource Source,
|
||||
Com_Enum_ProtocolType Type,
|
||||
const QByteArray& PacketBody,
|
||||
const QString& NoteText = QString());
|
||||
void Lgc_Core_TestRecordRxPacket(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
const Com_Struct_RawPacket& Packet);
|
||||
|
||||
void Lgc_Core_ResetProtocolState(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_ResetProtocolStateForSource(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource Source);
|
||||
bool Lgc_Core_DeviceSupportsPacketType(
|
||||
const Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_ProtocolType Type);
|
||||
void Lgc_Core_AddPendingCommand(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource Source,
|
||||
Com_Enum_ProtocolType Type);
|
||||
bool Lgc_Core_ClearPendingCommand(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource Source,
|
||||
Com_Enum_ProtocolType Type);
|
||||
bool Lgc_Core_HasPendingCommand(
|
||||
const Lgc_Core_Struct_State* p_State,
|
||||
Com_Enum_RawPacketSource Source,
|
||||
Com_Enum_ProtocolType Type);
|
||||
|
||||
void Lgc_Core_ClearAllKeyStates(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_ClearDeviceReportedKeyStates(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_CloseAllPorts(Lgc_Core_Struct_State* p_State);
|
||||
void Lgc_Core_FillMaskAllEnabled(QByteArray* p_UsageBitmap);
|
||||
void Lgc_Core_ScheduleBitmapSend(Lgc_Core_Struct_State* p_State, qint64 DelayMs);
|
||||
bool Lgc_Core_ProcessBitmapSend(Lgc_Core_Struct_State* p_State);
|
||||
|
||||
bool Lgc_Core_SendHello(Lgc_Core_Struct_State* p_State);
|
||||
bool Lgc_Core_SyncSystemState(Lgc_Core_Struct_State* p_State);
|
||||
|
||||
void Lgc_Core_HandleCdcPacket(Lgc_Core_Struct_State* p_State, const Com_Struct_RawPacket& Packet);
|
||||
void Lgc_Core_HandleNusPacket(Lgc_Core_Struct_State* p_State, const Com_Struct_RawPacket& Packet);
|
||||
void Lgc_Core_HandleProtocolPacket(Lgc_Core_Struct_State* p_State, const Com_Struct_RawPacket& Packet);
|
||||
|
||||
bool Lgc_Core_HandleFunctionButtons(Lgc_Core_Struct_State* p_State);
|
||||
@@ -1,20 +1,59 @@
|
||||
#include "LOGIC/Lgc_Func_Button.h"
|
||||
|
||||
#include "LOGIC/Lgc_Core.h"
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QVector>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <Windows.h>
|
||||
#include "LOGIC/Lgc_Func_Button_Private.h"
|
||||
#include <QtCore/QStringList>
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QString Lgc_Func_Button_Func_GetUsageShortText(quint16 Usage)
|
||||
QString Lgc_FunctionButton_GetKeyboardUsageText(quint16 Usage)
|
||||
{
|
||||
switch (Usage)
|
||||
{
|
||||
case 0x0053: return QStringLiteral("Num Lock");
|
||||
case 0x0054: return QStringLiteral("小键盘 /");
|
||||
case 0x0055: return QStringLiteral("小键盘 *");
|
||||
case 0x0056: return QStringLiteral("小键盘 -");
|
||||
case 0x0057: return QStringLiteral("小键盘 +");
|
||||
case 0x0058: return QStringLiteral("小键盘 Enter");
|
||||
case 0x0059: return QStringLiteral("小键盘 1");
|
||||
case 0x005A: return QStringLiteral("小键盘 2");
|
||||
case 0x005B: return QStringLiteral("小键盘 3");
|
||||
case 0x005C: return QStringLiteral("小键盘 4");
|
||||
case 0x005D: return QStringLiteral("小键盘 5");
|
||||
case 0x005E: return QStringLiteral("小键盘 6");
|
||||
case 0x005F: return QStringLiteral("小键盘 7");
|
||||
case 0x0060: return QStringLiteral("小键盘 8");
|
||||
case 0x0061: return QStringLiteral("小键盘 9");
|
||||
case 0x0062: return QStringLiteral("小键盘 0");
|
||||
case 0x0063: return QStringLiteral("小键盘 .");
|
||||
case 0x00E0: return QStringLiteral("Left Ctrl");
|
||||
case 0x00E1: return QStringLiteral("Left Shift");
|
||||
case 0x00E2: return QStringLiteral("Left Alt");
|
||||
case 0x00E3: return QStringLiteral("Left GUI");
|
||||
case 0x00E4: return QStringLiteral("Right Ctrl");
|
||||
case 0x00E5: return QStringLiteral("Right Shift");
|
||||
case 0x00E6: return QStringLiteral("Right Alt");
|
||||
case 0x00E7: return QStringLiteral("Right GUI");
|
||||
default:
|
||||
return QStringLiteral("未知按键");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString Lgc_FunctionButton_GetUsageShortText(quint16 Usage)
|
||||
{
|
||||
switch (Usage)
|
||||
{
|
||||
case 0x0053: return QStringLiteral("Num");
|
||||
case 0x0054: return QStringLiteral("/");
|
||||
case 0x0055: return QStringLiteral("*");
|
||||
case 0x0056: return QStringLiteral("-");
|
||||
case 0x0057: return QStringLiteral("+");
|
||||
case 0x0058: return QStringLiteral("Enter");
|
||||
case 0x0059: return QStringLiteral("1");
|
||||
case 0x005A: return QStringLiteral("2");
|
||||
case 0x005B: return QStringLiteral("3");
|
||||
@@ -25,167 +64,244 @@ QString Lgc_Func_Button_Func_GetUsageShortText(quint16 Usage)
|
||||
case 0x0060: return QStringLiteral("8");
|
||||
case 0x0061: return QStringLiteral("9");
|
||||
case 0x0062: return QStringLiteral("0");
|
||||
case 0x0063: return QStringLiteral(".");
|
||||
default:
|
||||
return Mid_Func_GetKeyboardUsageText(Usage);
|
||||
return Lgc_FunctionButton_GetKeyboardUsageText(Usage);
|
||||
}
|
||||
}
|
||||
|
||||
WORD Lgc_Func_Button_Func_GetWindowsVirtualKey(quint16 Usage)
|
||||
QString Lgc_FunctionButton_GetFeatureTypeText(Lgc_FunctionFeature_Type Type)
|
||||
{
|
||||
switch (Usage)
|
||||
switch (Type)
|
||||
{
|
||||
case 0x0056: return VK_SUBTRACT;
|
||||
case 0x0057: return VK_ADD;
|
||||
case 0x0059: return VK_NUMPAD1;
|
||||
case 0x005A: return VK_NUMPAD2;
|
||||
case 0x005B: return VK_NUMPAD3;
|
||||
case 0x005C: return VK_NUMPAD4;
|
||||
case 0x005D: return VK_NUMPAD5;
|
||||
case 0x005E: return VK_NUMPAD6;
|
||||
case 0x005F: return VK_NUMPAD7;
|
||||
case 0x0060: return VK_NUMPAD8;
|
||||
case 0x0061: return VK_NUMPAD9;
|
||||
case 0x0062: return VK_NUMPAD0;
|
||||
case Lgc_FunctionFeature_Type::KeyCombination:
|
||||
return QStringLiteral("Shortcut");
|
||||
case Lgc_FunctionFeature_Type::KeySequence:
|
||||
return QStringLiteral("Shortcut Sequence");
|
||||
case Lgc_FunctionFeature_Type::Website:
|
||||
return QStringLiteral("Open Website");
|
||||
default:
|
||||
return 0;
|
||||
return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
bool Lgc_Func_Button_Func_SendUnicodeText(const QString& Text)
|
||||
QVector<quint16> Lgc_FunctionButton_GetConfigurableUsages()
|
||||
{
|
||||
if (Text.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QVector<INPUT> InputList;
|
||||
InputList.reserve(Text.size() * 2);
|
||||
for (QChar Character : Text)
|
||||
{
|
||||
INPUT InputDown = {};
|
||||
InputDown.type = INPUT_KEYBOARD;
|
||||
InputDown.ki.wScan = Character.unicode();
|
||||
InputDown.ki.dwFlags = KEYEVENTF_UNICODE;
|
||||
InputList.append(InputDown);
|
||||
|
||||
INPUT InputUp = InputDown;
|
||||
InputUp.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
|
||||
InputList.append(InputUp);
|
||||
}
|
||||
|
||||
return SendInput(static_cast<UINT>(InputList.size()), InputList.data(), sizeof(INPUT)) ==
|
||||
static_cast<UINT>(InputList.size());
|
||||
return {
|
||||
0x0053, 0x0054, 0x0055, 0x0056,
|
||||
0x005F, 0x0060, 0x0061, 0x0057,
|
||||
0x005C, 0x005D, 0x005E,
|
||||
0x0059, 0x005A, 0x005B, 0x0058,
|
||||
0x0062, 0x0063
|
||||
};
|
||||
}
|
||||
|
||||
void Lgc_Func_Button_Func_RunMacroText(Lgc_Core_Struct_State* p_State, QString* p_TextStatus)
|
||||
QVector<int> Lgc_FunctionButton_GetFeatureIdList(const Lgc_FunctionButton_Config& Config)
|
||||
{
|
||||
const QString Text = p_State->FunctionButtonConfig.MacroText.trimmed();
|
||||
if (Text.isEmpty())
|
||||
QVector<int> FeatureIdList = Config.FeatureMap.keys().toVector();
|
||||
std::sort(FeatureIdList.begin(), FeatureIdList.end());
|
||||
return FeatureIdList;
|
||||
}
|
||||
|
||||
Lgc_FunctionFeature_Definition Lgc_FunctionButton_GetFeature(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
int FeatureId)
|
||||
{
|
||||
return Config.FeatureMap.value(FeatureId);
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_GetFeatureName(const Lgc_FunctionFeature_Definition& Feature)
|
||||
{
|
||||
if (!Feature.Name.trimmed().isEmpty())
|
||||
{
|
||||
return Feature.Name.trimmed();
|
||||
}
|
||||
|
||||
return Feature.Id > 0
|
||||
? QStringLiteral("Function %1").arg(Feature.Id)
|
||||
: QStringLiteral("Unnamed Function");
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_GetFeatureDescription(const Lgc_FunctionFeature_Definition& Feature)
|
||||
{
|
||||
if (Feature.Id <= 0)
|
||||
{
|
||||
return QStringLiteral("No function selected.");
|
||||
}
|
||||
|
||||
if (!Feature.Description.trimmed().isEmpty())
|
||||
{
|
||||
return Feature.Description.trimmed();
|
||||
}
|
||||
|
||||
switch (Feature.Type)
|
||||
{
|
||||
case Lgc_FunctionFeature_Type::KeyCombination:
|
||||
return Feature.SequenceText.trimmed().isEmpty()
|
||||
? QStringLiteral("Send one shortcut. No shortcut is configured yet.")
|
||||
: QStringLiteral("Send shortcut: %1").arg(Feature.SequenceText.trimmed());
|
||||
case Lgc_FunctionFeature_Type::KeySequence:
|
||||
return Feature.SequenceText.trimmed().isEmpty()
|
||||
? QStringLiteral("Send a sequence of shortcuts. No sequence is configured yet.")
|
||||
: QStringLiteral("Send shortcut sequence: %1").arg(Feature.SequenceText.trimmed());
|
||||
case Lgc_FunctionFeature_Type::Website:
|
||||
return Feature.WebsiteUrl.trimmed().isEmpty()
|
||||
? QStringLiteral("Open a website. No URL is configured yet.")
|
||||
: QStringLiteral("Open website: %1").arg(Feature.WebsiteUrl.trimmed());
|
||||
default:
|
||||
return QStringLiteral("Unknown function.");
|
||||
}
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_GetFeatureDescriptionById(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
int FeatureId)
|
||||
{
|
||||
return Lgc_FunctionButton_GetFeatureDescription(
|
||||
Lgc_FunctionButton_GetFeature(Config, FeatureId));
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_GetFeatureBindingSummary(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
int FeatureId)
|
||||
{
|
||||
if (FeatureId <= 0)
|
||||
{
|
||||
return QStringLiteral("No keys are bound.");
|
||||
}
|
||||
|
||||
QStringList KeyList;
|
||||
for (quint16 Usage : Lgc_FunctionButton_GetConfigurableUsages())
|
||||
{
|
||||
if (Lgc_FunctionButton_GetUsageFeatureId(Config, Usage) == FeatureId)
|
||||
{
|
||||
KeyList.append(Lgc_FunctionButton_GetUsageShortText(Usage));
|
||||
}
|
||||
}
|
||||
|
||||
return KeyList.isEmpty()
|
||||
? QStringLiteral("No keys are bound.")
|
||||
: QStringLiteral("Bound keys: %1").arg(KeyList.join(QStringLiteral(", ")));
|
||||
}
|
||||
|
||||
int Lgc_FunctionButton_AddFeature(Lgc_FunctionButton_Config& Config)
|
||||
{
|
||||
int FeatureId = 1;
|
||||
while (Config.FeatureMap.contains(FeatureId))
|
||||
{
|
||||
++FeatureId;
|
||||
}
|
||||
|
||||
Lgc_FunctionFeature_Definition Feature;
|
||||
Feature.Id = FeatureId;
|
||||
Feature.Name = QStringLiteral("Function %1").arg(FeatureId);
|
||||
Feature.Type = Lgc_FunctionFeature_Type::KeyCombination;
|
||||
Config.FeatureMap.insert(FeatureId, Feature);
|
||||
return FeatureId;
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_RemoveFeature(Lgc_FunctionButton_Config& Config, int FeatureId)
|
||||
{
|
||||
if (FeatureId <= 0)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("功能键 0 未配置输出文本。");
|
||||
return;
|
||||
}
|
||||
|
||||
*p_TextStatus = Lgc_Func_Button_Func_SendUnicodeText(Text)
|
||||
? QStringLiteral("功能键 0 已输出文本:%1").arg(Text)
|
||||
: QStringLiteral("功能键 0 输出文本失败。");
|
||||
Config.FeatureMap.remove(FeatureId);
|
||||
|
||||
auto It = Config.UsageFeatureIdMap.begin();
|
||||
while (It != Config.UsageFeatureIdMap.end())
|
||||
{
|
||||
if (It.value() == FeatureId)
|
||||
{
|
||||
It = Config.UsageFeatureIdMap.erase(It);
|
||||
}
|
||||
else
|
||||
{
|
||||
++It;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lgc_Func_Button_Func_RunSwapKey(Lgc_Core_Struct_State* p_State, QString* p_TextStatus)
|
||||
void Lgc_FunctionButton_SetFeature(
|
||||
Lgc_FunctionButton_Config& Config,
|
||||
const Lgc_FunctionFeature_Definition& Feature)
|
||||
{
|
||||
const quint16 UsageLeft = p_State->FunctionButtonConfig.SwapUsageLeft;
|
||||
const quint16 UsageRight = p_State->FunctionButtonConfig.SwapUsageRight;
|
||||
if ((UsageLeft == 0) || (UsageRight == 0) || (UsageLeft == UsageRight))
|
||||
if (Feature.Id <= 0)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("功能键 1 的交换配置无效。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Lgc_Core_Func_SetSwapMode(p_State, UsageLeft, UsageRight, !p_State->IsSwapModeOn))
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("功能键 1 切换按键交换失败。");
|
||||
return;
|
||||
}
|
||||
|
||||
*p_TextStatus = p_State->IsSwapModeOn
|
||||
? QStringLiteral("功能键 1 已开启按键交换:%1 <-> %2")
|
||||
.arg(Lgc_Func_Button_Func_GetUsageShortText(UsageLeft))
|
||||
.arg(Lgc_Func_Button_Func_GetUsageShortText(UsageRight))
|
||||
: QStringLiteral("功能键 1 已关闭按键交换:%1 <-> %2")
|
||||
.arg(Lgc_Func_Button_Func_GetUsageShortText(UsageLeft))
|
||||
.arg(Lgc_Func_Button_Func_GetUsageShortText(UsageRight));
|
||||
Config.FeatureMap.insert(Feature.Id, Feature);
|
||||
}
|
||||
|
||||
void Lgc_Func_Button_Func_RunOpenWebsite(Lgc_Core_Struct_State* p_State, QString* p_TextStatus)
|
||||
int Lgc_FunctionButton_GetUsageFeatureId(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage)
|
||||
{
|
||||
const QString UrlText = p_State->FunctionButtonConfig.WebsiteUrl.trimmed();
|
||||
const QUrl Url = QUrl::fromUserInput(UrlText);
|
||||
if (UrlText.isEmpty() || !Url.isValid() || Url.isEmpty())
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("功能键 2 的网址配置无效。");
|
||||
return;
|
||||
}
|
||||
|
||||
*p_TextStatus = QDesktopServices::openUrl(Url)
|
||||
? QStringLiteral("功能键 2 已打开网址:%1").arg(Url.toString())
|
||||
: QStringLiteral("功能键 2 打开网址失败。");
|
||||
const int FeatureId = Config.UsageFeatureIdMap.value(Usage, 0);
|
||||
return Config.FeatureMap.contains(FeatureId) ? FeatureId : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Lgc_Func_Button_Func_SendUsageToWindows(quint16 Usage, bool IsPressed)
|
||||
{
|
||||
const WORD VirtualKey = Lgc_Func_Button_Func_GetWindowsVirtualKey(Usage);
|
||||
if (VirtualKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
INPUT InputData = {};
|
||||
InputData.type = INPUT_KEYBOARD;
|
||||
InputData.ki.wVk = VirtualKey;
|
||||
if (!IsPressed)
|
||||
{
|
||||
InputData.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
}
|
||||
|
||||
return SendInput(1, &InputData, sizeof(INPUT)) == 1;
|
||||
}
|
||||
|
||||
bool Lgc_Func_Button_Func_HandlePressedUsage(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
void Lgc_FunctionButton_SetUsageFeatureId(
|
||||
Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage,
|
||||
QString* p_TextStatus)
|
||||
int FeatureId)
|
||||
{
|
||||
p_TextStatus->clear();
|
||||
switch (Usage)
|
||||
if ((FeatureId <= 0) || !Config.FeatureMap.contains(FeatureId))
|
||||
{
|
||||
case 0x0062:
|
||||
Lgc_Func_Button_Func_RunMacroText(p_State, p_TextStatus);
|
||||
return true;
|
||||
Config.UsageFeatureIdMap.remove(Usage);
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x0059:
|
||||
Lgc_Func_Button_Func_RunSwapKey(p_State, p_TextStatus);
|
||||
return true;
|
||||
Config.UsageFeatureIdMap.insert(Usage, FeatureId);
|
||||
}
|
||||
|
||||
case 0x005A:
|
||||
Lgc_Func_Button_Func_RunOpenWebsite(p_State, p_TextStatus);
|
||||
return true;
|
||||
bool Lgc_FunctionButton_HasUsageFeature(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage)
|
||||
{
|
||||
return Lgc_FunctionButton_GetUsageFeatureId(Config, Usage) > 0;
|
||||
}
|
||||
|
||||
case 0x0056:
|
||||
case 0x0057:
|
||||
case 0x005B:
|
||||
case 0x005C:
|
||||
case 0x005D:
|
||||
case 0x005E:
|
||||
case 0x005F:
|
||||
case 0x0060:
|
||||
case 0x0061:
|
||||
*p_TextStatus = QStringLiteral("功能键 %1 还没有绑定具体功能。")
|
||||
.arg(Lgc_Func_Button_Func_GetUsageShortText(Usage));
|
||||
return true;
|
||||
bool Lgc_FunctionButton_SendUsageToWindows(quint16 Usage, bool IsPressed)
|
||||
{
|
||||
return Lgc_FunctionButton_SendWindowsKey(
|
||||
Lgc_FunctionButton_GetWindowsKey(Usage),
|
||||
IsPressed);
|
||||
}
|
||||
|
||||
default:
|
||||
bool Lgc_FunctionButton_RunBinding(
|
||||
Lgc_Core_Struct_State& State,
|
||||
quint16 Usage,
|
||||
QString& TextStatus)
|
||||
{
|
||||
TextStatus.clear();
|
||||
|
||||
const int FeatureId =
|
||||
Lgc_FunctionButton_GetUsageFeatureId(State.FunctionButtonConfig, Usage);
|
||||
if (FeatureId <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const Lgc_FunctionFeature_Definition Feature =
|
||||
Lgc_FunctionButton_GetFeature(State.FunctionButtonConfig, FeatureId);
|
||||
|
||||
switch (Feature.Type)
|
||||
{
|
||||
case Lgc_FunctionFeature_Type::KeyCombination:
|
||||
Lgc_FunctionButton_RunKeyCombination(Feature, Usage, TextStatus);
|
||||
return true;
|
||||
case Lgc_FunctionFeature_Type::KeySequence:
|
||||
Lgc_FunctionButton_RunKeySequence(Feature, Usage, TextStatus);
|
||||
return true;
|
||||
case Lgc_FunctionFeature_Type::Website:
|
||||
Lgc_FunctionButton_RunOpenWebsite(Feature, TextStatus);
|
||||
return true;
|
||||
default:
|
||||
TextStatus = QStringLiteral("Unknown function type: %1")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
struct Lgc_Core_Struct_State;
|
||||
|
||||
struct Lgc_Func_Button_Struct_Config
|
||||
enum class Lgc_FunctionFeature_Type : quint8
|
||||
{
|
||||
QString MacroText = QStringLiteral("HELLO WORLD!");
|
||||
quint16 SwapUsageLeft = 0x005C;
|
||||
quint16 SwapUsageRight = 0x005D;
|
||||
QString WebsiteUrl = QStringLiteral("https://www.deepseek.com/");
|
||||
KeyCombination = 0,
|
||||
KeySequence = 1,
|
||||
Website = 2
|
||||
};
|
||||
|
||||
bool Lgc_Func_Button_Func_SendUsageToWindows(quint16 Usage, bool IsPressed);
|
||||
bool Lgc_Func_Button_Func_HandlePressedUsage(
|
||||
Lgc_Core_Struct_State* p_State,
|
||||
struct Lgc_FunctionFeature_Definition
|
||||
{
|
||||
int Id = 0;
|
||||
QString Name;
|
||||
QString Description;
|
||||
Lgc_FunctionFeature_Type Type = Lgc_FunctionFeature_Type::KeyCombination;
|
||||
QString SequenceText;
|
||||
QString WebsiteUrl;
|
||||
};
|
||||
|
||||
struct Lgc_FunctionButton_Config
|
||||
{
|
||||
QHash<int, Lgc_FunctionFeature_Definition> FeatureMap;
|
||||
QHash<quint16, int> UsageFeatureIdMap;
|
||||
};
|
||||
|
||||
QString Lgc_FunctionButton_GetUsageShortText(quint16 Usage);
|
||||
QString Lgc_FunctionButton_GetFeatureTypeText(Lgc_FunctionFeature_Type Type);
|
||||
QVector<quint16> Lgc_FunctionButton_GetConfigurableUsages();
|
||||
QVector<int> Lgc_FunctionButton_GetFeatureIdList(const Lgc_FunctionButton_Config& Config);
|
||||
Lgc_FunctionFeature_Definition Lgc_FunctionButton_GetFeature(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
int FeatureId);
|
||||
QString Lgc_FunctionButton_GetFeatureName(const Lgc_FunctionFeature_Definition& Feature);
|
||||
QString Lgc_FunctionButton_GetFeatureDescription(const Lgc_FunctionFeature_Definition& Feature);
|
||||
QString Lgc_FunctionButton_GetFeatureDescriptionById(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
int FeatureId);
|
||||
QString Lgc_FunctionButton_GetFeatureBindingSummary(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
int FeatureId);
|
||||
int Lgc_FunctionButton_AddFeature(Lgc_FunctionButton_Config& Config);
|
||||
void Lgc_FunctionButton_RemoveFeature(Lgc_FunctionButton_Config& Config, int FeatureId);
|
||||
void Lgc_FunctionButton_SetFeature(
|
||||
Lgc_FunctionButton_Config& Config,
|
||||
const Lgc_FunctionFeature_Definition& Feature);
|
||||
int Lgc_FunctionButton_GetUsageFeatureId(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage);
|
||||
void Lgc_FunctionButton_SetUsageFeatureId(
|
||||
Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage,
|
||||
QString* p_TextStatus);
|
||||
int FeatureId);
|
||||
bool Lgc_FunctionButton_HasUsageFeature(
|
||||
const Lgc_FunctionButton_Config& Config,
|
||||
quint16 Usage);
|
||||
|
||||
bool Lgc_FunctionButton_SendUsageToWindows(quint16 Usage, bool IsPressed);
|
||||
bool Lgc_FunctionButton_RunBinding(
|
||||
Lgc_Core_Struct_State& State,
|
||||
quint16 Usage,
|
||||
QString& TextStatus);
|
||||
|
||||
278
LOGIC/Lgc_Func_Button_Parse.cpp
Normal file
278
LOGIC/Lgc_Func_Button_Parse.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include "LOGIC/Lgc_Func_Button_Private.h"
|
||||
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct Lgc_FunctionButton_Struct_SequenceKeySpec
|
||||
{
|
||||
const char* Token;
|
||||
WORD VirtualKey;
|
||||
DWORD ExtraFlags;
|
||||
bool IsModifier;
|
||||
const char* Text;
|
||||
};
|
||||
|
||||
bool Lgc_FunctionButton_IsSourceToken(
|
||||
const QString& TrimmedToken,
|
||||
const QString& UpperToken)
|
||||
{
|
||||
Q_UNUSED(TrimmedToken);
|
||||
return (UpperToken == QStringLiteral("SOURCE")) ||
|
||||
(UpperToken == QStringLiteral("SELF"));
|
||||
}
|
||||
|
||||
QStringList Lgc_FunctionButton_SplitCombinationTokens(const QString& Text)
|
||||
{
|
||||
static const QRegularExpression kPlusSeparator(QStringLiteral("\\s*\\+\\s*"));
|
||||
static const QRegularExpression kLooseSeparator(QStringLiteral("[\\s,;|]+"));
|
||||
return Text.contains(QLatin1Char('+'))
|
||||
? Text.split(kPlusSeparator, QString::SkipEmptyParts)
|
||||
: Text.split(kLooseSeparator, QString::SkipEmptyParts);
|
||||
}
|
||||
|
||||
QStringList Lgc_FunctionButton_SplitShortcutSegments(const QString& Text)
|
||||
{
|
||||
static const QRegularExpression kSequenceSeparator(
|
||||
QStringLiteral("\\s*(?:->|=>|\\x{2192})\\s*"));
|
||||
return Text.split(kSequenceSeparator, QString::SkipEmptyParts);
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_ParseTokenList(
|
||||
const QStringList& TokenList,
|
||||
quint16 SourceUsage,
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey>* p_KeyList,
|
||||
QString* p_ErrorText,
|
||||
const QString& ErrorTemplate)
|
||||
{
|
||||
p_KeyList->clear();
|
||||
if (p_ErrorText != nullptr)
|
||||
{
|
||||
p_ErrorText->clear();
|
||||
}
|
||||
|
||||
for (const QString& Token : TokenList)
|
||||
{
|
||||
Lgc_FunctionButton_Struct_SequenceKey KeyItem;
|
||||
if (!Lgc_FunctionButton_TryParseSequenceToken(Token, SourceUsage, &KeyItem))
|
||||
{
|
||||
if (p_ErrorText != nullptr)
|
||||
{
|
||||
*p_ErrorText = ErrorTemplate.arg(Token.trimmed());
|
||||
}
|
||||
p_KeyList->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
p_KeyList->append(KeyItem);
|
||||
}
|
||||
|
||||
return !p_KeyList->isEmpty();
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_TryLookupSequenceKeySpec(
|
||||
const QString& UpperToken,
|
||||
Lgc_FunctionButton_Struct_SequenceKey* p_KeyItem)
|
||||
{
|
||||
static const Lgc_FunctionButton_Struct_SequenceKeySpec kSpecs[] = {
|
||||
{ "CTRL", VK_CONTROL, 0, true, "Ctrl" },
|
||||
{ "CONTROL", VK_CONTROL, 0, true, "Ctrl" },
|
||||
{ "SHIFT", VK_SHIFT, 0, true, "Shift" },
|
||||
{ "ALT", VK_MENU, 0, true, "Alt" },
|
||||
{ "WIN", VK_LWIN, 0, true, "Win" },
|
||||
{ "META", VK_LWIN, 0, true, "Win" },
|
||||
{ "ENTER", VK_RETURN, 0, false, "Enter" },
|
||||
{ "NUMENTER", VK_RETURN, KEYEVENTF_EXTENDEDKEY, false, "NumEnter" },
|
||||
{ "SPACE", VK_SPACE, 0, false, "Space" },
|
||||
{ "TAB", VK_TAB, 0, false, "Tab" },
|
||||
{ "ESC", VK_ESCAPE, 0, false, "Esc" },
|
||||
{ "ESCAPE", VK_ESCAPE, 0, false, "Esc" },
|
||||
{ "BACKSPACE", VK_BACK, 0, false, "Backspace" },
|
||||
{ "DELETE", VK_DELETE, KEYEVENTF_EXTENDEDKEY, false, "Delete" },
|
||||
{ "INSERT", VK_INSERT, KEYEVENTF_EXTENDEDKEY, false, "Insert" },
|
||||
{ "HOME", VK_HOME, KEYEVENTF_EXTENDEDKEY, false, "Home" },
|
||||
{ "END", VK_END, KEYEVENTF_EXTENDEDKEY, false, "End" },
|
||||
{ "PAGEUP", VK_PRIOR, KEYEVENTF_EXTENDEDKEY, false, "PageUp" },
|
||||
{ "PAGEDOWN", VK_NEXT, KEYEVENTF_EXTENDEDKEY, false, "PageDown" },
|
||||
{ "LEFT", VK_LEFT, KEYEVENTF_EXTENDEDKEY, false, "Left" },
|
||||
{ "RIGHT", VK_RIGHT, KEYEVENTF_EXTENDEDKEY, false, "Right" },
|
||||
{ "UP", VK_UP, KEYEVENTF_EXTENDEDKEY, false, "Up" },
|
||||
{ "DOWN", VK_DOWN, KEYEVENTF_EXTENDEDKEY, false, "Down" },
|
||||
{ "CAPSLOCK", VK_CAPITAL, 0, false, "CapsLock" },
|
||||
{ "PRINTSCREEN", VK_SNAPSHOT, KEYEVENTF_EXTENDEDKEY, false, "PrintScreen" },
|
||||
{ "SCROLLLOCK", VK_SCROLL, 0, false, "ScrollLock" },
|
||||
{ "PAUSE", VK_PAUSE, 0, false, "Pause" },
|
||||
{ "NUM0", VK_NUMPAD0, 0, false, "Num0" },
|
||||
{ "NUM1", VK_NUMPAD1, 0, false, "Num1" },
|
||||
{ "NUM2", VK_NUMPAD2, 0, false, "Num2" },
|
||||
{ "NUM3", VK_NUMPAD3, 0, false, "Num3" },
|
||||
{ "NUM4", VK_NUMPAD4, 0, false, "Num4" },
|
||||
{ "NUM5", VK_NUMPAD5, 0, false, "Num5" },
|
||||
{ "NUM6", VK_NUMPAD6, 0, false, "Num6" },
|
||||
{ "NUM7", VK_NUMPAD7, 0, false, "Num7" },
|
||||
{ "NUM8", VK_NUMPAD8, 0, false, "Num8" },
|
||||
{ "NUM9", VK_NUMPAD9, 0, false, "Num9" },
|
||||
{ "NUM/", VK_DIVIDE, KEYEVENTF_EXTENDEDKEY, false, "Num/" },
|
||||
{ "NUM*", VK_MULTIPLY, 0, false, "Num*" },
|
||||
{ "NUM-", VK_SUBTRACT, 0, false, "Num-" },
|
||||
{ "NUM+", VK_ADD, 0, false, "Num+" },
|
||||
{ "NUM.", VK_DECIMAL, 0, false, "Num." },
|
||||
{ "COMMA", VK_OEM_COMMA, 0, false, "Comma" },
|
||||
{ "PERIOD", VK_OEM_PERIOD, 0, false, "Period" },
|
||||
{ "SEMICOLON", VK_OEM_1, 0, false, "Semicolon" },
|
||||
{ "SLASH", VK_OEM_2, 0, false, "Slash" },
|
||||
{ "GRAVE", VK_OEM_3, 0, false, "Grave" },
|
||||
{ "LEFTBRACKET", VK_OEM_4, 0, false, "LeftBracket" },
|
||||
{ "BACKSLASH", VK_OEM_5, 0, false, "Backslash" },
|
||||
{ "RIGHTBRACKET", VK_OEM_6, 0, false, "RightBracket" },
|
||||
{ "QUOTE", VK_OEM_7, 0, false, "Quote" },
|
||||
{ "MINUS", VK_OEM_MINUS, 0, false, "Minus" },
|
||||
{ "EQUAL", VK_OEM_PLUS, 0, false, "Equal" }
|
||||
};
|
||||
|
||||
for (const auto& Spec : kSpecs)
|
||||
{
|
||||
if (UpperToken == QLatin1String(Spec.Token))
|
||||
{
|
||||
p_KeyItem->Key = { Spec.VirtualKey, Spec.ExtraFlags, Spec.IsModifier };
|
||||
p_KeyItem->Text = QString::fromLatin1(Spec.Text);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Lgc_FunctionButton_TryParseSequenceToken(
|
||||
const QString& Token,
|
||||
quint16 SourceUsage,
|
||||
Lgc_FunctionButton_Struct_SequenceKey* p_KeyItem)
|
||||
{
|
||||
const QString TrimmedToken = Token.trimmed();
|
||||
const QString UpperToken = TrimmedToken.toUpper();
|
||||
if (UpperToken.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Lgc_FunctionButton_IsSourceToken(TrimmedToken, UpperToken))
|
||||
{
|
||||
const Lgc_FunctionButton_Struct_WindowsKey SourceKey =
|
||||
Lgc_FunctionButton_GetWindowsKey(SourceUsage);
|
||||
if (SourceKey.VirtualKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
p_KeyItem->Key = SourceKey;
|
||||
p_KeyItem->Text = Lgc_FunctionButton_GetUsageShortText(SourceUsage);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((UpperToken.size() == 1) && UpperToken.at(0).isLetterOrNumber())
|
||||
{
|
||||
p_KeyItem->Key = { static_cast<WORD>(UpperToken.at(0).unicode()), 0, false };
|
||||
p_KeyItem->Text = UpperToken;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Lgc_FunctionButton_TryLookupSequenceKeySpec(UpperToken, p_KeyItem))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static const QRegularExpression kFunctionKeyPattern(QStringLiteral("^F(\\d{1,2})$"));
|
||||
const QRegularExpressionMatch Match = kFunctionKeyPattern.match(UpperToken);
|
||||
if (!Match.hasMatch())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int FunctionIndex = Match.captured(1).toInt();
|
||||
if ((FunctionIndex < 1) || (FunctionIndex > 24))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
p_KeyItem->Key = { static_cast<WORD>(VK_F1 + FunctionIndex - 1), 0, false };
|
||||
p_KeyItem->Text = QStringLiteral("F%1").arg(FunctionIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_IsSameWindowsKey(
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Left,
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Right)
|
||||
{
|
||||
return (Left.VirtualKey == Right.VirtualKey) &&
|
||||
(Left.ExtraFlags == Right.ExtraFlags);
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_ParseKeyCombinationText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey>* p_KeyList,
|
||||
QString* p_ErrorText)
|
||||
{
|
||||
return Lgc_FunctionButton_ParseTokenList(
|
||||
Lgc_FunctionButton_SplitCombinationTokens(Text),
|
||||
SourceUsage,
|
||||
p_KeyList,
|
||||
p_ErrorText,
|
||||
QStringLiteral("Unknown key token: %1"));
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_FormatKeyCombination(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList)
|
||||
{
|
||||
QStringList TextList;
|
||||
for (const auto& KeyItem : KeyList)
|
||||
{
|
||||
TextList.append(KeyItem.Text);
|
||||
}
|
||||
return TextList.join(QStringLiteral("+"));
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_ParseShortcutSequenceText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>* p_CombinationList,
|
||||
QString* p_ErrorText)
|
||||
{
|
||||
p_CombinationList->clear();
|
||||
if (p_ErrorText != nullptr)
|
||||
{
|
||||
p_ErrorText->clear();
|
||||
}
|
||||
|
||||
for (const QString& SegmentText : Lgc_FunctionButton_SplitShortcutSegments(Text))
|
||||
{
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> Combination;
|
||||
if (!Lgc_FunctionButton_ParseKeyCombinationText(
|
||||
SegmentText,
|
||||
SourceUsage,
|
||||
&Combination,
|
||||
p_ErrorText))
|
||||
{
|
||||
p_CombinationList->clear();
|
||||
return false;
|
||||
}
|
||||
p_CombinationList->append(Combination);
|
||||
}
|
||||
|
||||
return !p_CombinationList->isEmpty();
|
||||
}
|
||||
|
||||
QString Lgc_FunctionButton_FormatShortcutSequence(
|
||||
const QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>& CombinationList)
|
||||
{
|
||||
QStringList TextList;
|
||||
for (const auto& Combination : CombinationList)
|
||||
{
|
||||
TextList.append(Lgc_FunctionButton_FormatKeyCombination(Combination));
|
||||
}
|
||||
return TextList.join(QStringLiteral(" -> "));
|
||||
}
|
||||
61
LOGIC/Lgc_Func_Button_Private.h
Normal file
61
LOGIC/Lgc_Func_Button_Private.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "LOGIC/Lgc_Func_Button.h"
|
||||
#include <QtCore/QVector>
|
||||
#include <Windows.h>
|
||||
|
||||
struct Lgc_FunctionButton_Struct_WindowsKey
|
||||
{
|
||||
WORD VirtualKey = 0;
|
||||
DWORD ExtraFlags = 0;
|
||||
bool IsModifier = false;
|
||||
};
|
||||
|
||||
struct Lgc_FunctionButton_Struct_SequenceKey
|
||||
{
|
||||
Lgc_FunctionButton_Struct_WindowsKey Key;
|
||||
QString Text;
|
||||
};
|
||||
|
||||
Lgc_FunctionButton_Struct_WindowsKey Lgc_FunctionButton_GetWindowsKey(quint16 Usage);
|
||||
bool Lgc_FunctionButton_SendWindowsKey(
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Key,
|
||||
bool IsPressed);
|
||||
bool Lgc_FunctionButton_IsSameWindowsKey(
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Left,
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Right);
|
||||
|
||||
bool Lgc_FunctionButton_TryParseSequenceToken(
|
||||
const QString& Token,
|
||||
quint16 SourceUsage,
|
||||
Lgc_FunctionButton_Struct_SequenceKey* p_KeyItem);
|
||||
bool Lgc_FunctionButton_ParseKeyCombinationText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey>* p_KeyList,
|
||||
QString* p_ErrorText);
|
||||
QString Lgc_FunctionButton_FormatKeyCombination(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList);
|
||||
bool Lgc_FunctionButton_ParseShortcutSequenceText(
|
||||
const QString& Text,
|
||||
quint16 SourceUsage,
|
||||
QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>* p_CombinationList,
|
||||
QString* p_ErrorText);
|
||||
QString Lgc_FunctionButton_FormatShortcutSequence(
|
||||
const QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>& CombinationList);
|
||||
|
||||
bool Lgc_FunctionButton_SendKeyCombination(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList);
|
||||
bool Lgc_FunctionButton_SendShortcutSequence(
|
||||
const QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>& CombinationList);
|
||||
void Lgc_FunctionButton_RunKeyCombination(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
quint16 SourceUsage,
|
||||
QString& TextStatus);
|
||||
void Lgc_FunctionButton_RunKeySequence(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
quint16 SourceUsage,
|
||||
QString& TextStatus);
|
||||
void Lgc_FunctionButton_RunOpenWebsite(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
QString& TextStatus);
|
||||
258
LOGIC/Lgc_Func_Button_Run.cpp
Normal file
258
LOGIC/Lgc_Func_Button_Run.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
#include "LOGIC/Lgc_Func_Button_Private.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr DWORD LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS = 12;
|
||||
constexpr DWORD LGC_FUNCTIONBUTTON_SEQUENCE_STEP_DELAY_MS = 60;
|
||||
|
||||
void Lgc_FunctionButton_ReleaseKeys(
|
||||
const QVector<Lgc_FunctionButton_Struct_WindowsKey>& KeyList,
|
||||
int LastIndex)
|
||||
{
|
||||
for (int Index = LastIndex; Index >= 0; --Index)
|
||||
{
|
||||
Lgc_FunctionButton_SendWindowsKey(KeyList.at(Index), false);
|
||||
Sleep(LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Lgc_FunctionButton_Struct_WindowsKey Lgc_FunctionButton_GetWindowsKey(quint16 Usage)
|
||||
{
|
||||
switch (Usage)
|
||||
{
|
||||
case 0x0053: return { VK_NUMLOCK, 0 };
|
||||
case 0x0054: return { VK_DIVIDE, KEYEVENTF_EXTENDEDKEY };
|
||||
case 0x0055: return { VK_MULTIPLY, 0 };
|
||||
case 0x0056: return { VK_SUBTRACT, 0 };
|
||||
case 0x0057: return { VK_ADD, 0 };
|
||||
case 0x0058: return { VK_RETURN, KEYEVENTF_EXTENDEDKEY };
|
||||
case 0x0059: return { VK_NUMPAD1, 0 };
|
||||
case 0x005A: return { VK_NUMPAD2, 0 };
|
||||
case 0x005B: return { VK_NUMPAD3, 0 };
|
||||
case 0x005C: return { VK_NUMPAD4, 0 };
|
||||
case 0x005D: return { VK_NUMPAD5, 0 };
|
||||
case 0x005E: return { VK_NUMPAD6, 0 };
|
||||
case 0x005F: return { VK_NUMPAD7, 0 };
|
||||
case 0x0060: return { VK_NUMPAD8, 0 };
|
||||
case 0x0061: return { VK_NUMPAD9, 0 };
|
||||
case 0x0062: return { VK_NUMPAD0, 0 };
|
||||
case 0x0063: return { VK_DECIMAL, 0 };
|
||||
case 0x00E0: return { VK_CONTROL, 0, true };
|
||||
case 0x00E1: return { VK_SHIFT, 0, true };
|
||||
case 0x00E2: return { VK_MENU, 0, true };
|
||||
case 0x00E3: return { VK_LWIN, 0, true };
|
||||
case 0x00E4: return { VK_CONTROL, KEYEVENTF_EXTENDEDKEY, true };
|
||||
case 0x00E5: return { VK_SHIFT, 0, true };
|
||||
case 0x00E6: return { VK_MENU, KEYEVENTF_EXTENDEDKEY, true };
|
||||
case 0x00E7: return { VK_RWIN, 0, true };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_SendWindowsKey(
|
||||
const Lgc_FunctionButton_Struct_WindowsKey& Key,
|
||||
bool IsPressed)
|
||||
{
|
||||
if (Key.VirtualKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
INPUT InputData = {};
|
||||
InputData.type = INPUT_KEYBOARD;
|
||||
InputData.ki.wVk = Key.VirtualKey;
|
||||
InputData.ki.dwFlags = Key.ExtraFlags;
|
||||
if (!IsPressed)
|
||||
{
|
||||
InputData.ki.dwFlags |= KEYEVENTF_KEYUP;
|
||||
}
|
||||
|
||||
return SendInput(1, &InputData, sizeof(INPUT)) == 1;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_SendKeyCombination(
|
||||
const QVector<Lgc_FunctionButton_Struct_SequenceKey>& KeyList)
|
||||
{
|
||||
QVector<Lgc_FunctionButton_Struct_WindowsKey> ModifierList;
|
||||
QVector<Lgc_FunctionButton_Struct_WindowsKey> NormalKeyList;
|
||||
|
||||
for (const auto& KeyItem : KeyList)
|
||||
{
|
||||
if (KeyItem.Key.VirtualKey == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (KeyItem.Key.IsModifier)
|
||||
{
|
||||
bool IsDuplicate = false;
|
||||
for (const auto& OldModifier : ModifierList)
|
||||
{
|
||||
if (Lgc_FunctionButton_IsSameWindowsKey(OldModifier, KeyItem.Key))
|
||||
{
|
||||
IsDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsDuplicate)
|
||||
{
|
||||
ModifierList.append(KeyItem.Key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
NormalKeyList.append(KeyItem.Key);
|
||||
}
|
||||
|
||||
for (int ModifierIndex = 0; ModifierIndex < ModifierList.size(); ++ModifierIndex)
|
||||
{
|
||||
if (!Lgc_FunctionButton_SendWindowsKey(ModifierList.at(ModifierIndex), true))
|
||||
{
|
||||
Lgc_FunctionButton_ReleaseKeys(ModifierList, ModifierIndex - 1);
|
||||
return false;
|
||||
}
|
||||
Sleep(LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS);
|
||||
}
|
||||
|
||||
if (NormalKeyList.isEmpty())
|
||||
{
|
||||
Lgc_FunctionButton_ReleaseKeys(ModifierList, ModifierList.size() - 1);
|
||||
return !ModifierList.isEmpty();
|
||||
}
|
||||
|
||||
for (const auto& NormalKey : NormalKeyList)
|
||||
{
|
||||
if (!Lgc_FunctionButton_SendWindowsKey(NormalKey, true))
|
||||
{
|
||||
Lgc_FunctionButton_ReleaseKeys(ModifierList, ModifierList.size() - 1);
|
||||
return false;
|
||||
}
|
||||
Sleep(LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS);
|
||||
|
||||
if (!Lgc_FunctionButton_SendWindowsKey(NormalKey, false))
|
||||
{
|
||||
Lgc_FunctionButton_ReleaseKeys(ModifierList, ModifierList.size() - 1);
|
||||
return false;
|
||||
}
|
||||
Sleep(LGC_FUNCTIONBUTTON_KEY_HOLD_DELAY_MS);
|
||||
}
|
||||
|
||||
Lgc_FunctionButton_ReleaseKeys(ModifierList, ModifierList.size() - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lgc_FunctionButton_SendShortcutSequence(
|
||||
const QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>>& CombinationList)
|
||||
{
|
||||
for (int Index = 0; Index < CombinationList.size(); ++Index)
|
||||
{
|
||||
if (!Lgc_FunctionButton_SendKeyCombination(CombinationList.at(Index)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Index + 1 < CombinationList.size())
|
||||
{
|
||||
Sleep(LGC_FUNCTIONBUTTON_SEQUENCE_STEP_DELAY_MS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_RunKeyCombination(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
quint16 SourceUsage,
|
||||
QString& TextStatus)
|
||||
{
|
||||
const QString CombinationText = Feature.SequenceText.trimmed();
|
||||
if (CombinationText.isEmpty())
|
||||
{
|
||||
TextStatus = QStringLiteral("%1 has no shortcut configured.")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<Lgc_FunctionButton_Struct_SequenceKey> KeyList;
|
||||
QString ErrorText;
|
||||
if (!Lgc_FunctionButton_ParseKeyCombinationText(
|
||||
CombinationText,
|
||||
SourceUsage,
|
||||
&KeyList,
|
||||
&ErrorText))
|
||||
{
|
||||
TextStatus = QStringLiteral("%1 has an invalid shortcut: %2")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature), ErrorText);
|
||||
return;
|
||||
}
|
||||
|
||||
TextStatus = Lgc_FunctionButton_SendKeyCombination(KeyList)
|
||||
? QStringLiteral("%1 sent shortcut: %2")
|
||||
.arg(
|
||||
Lgc_FunctionButton_GetFeatureName(Feature),
|
||||
Lgc_FunctionButton_FormatKeyCombination(KeyList))
|
||||
: QStringLiteral("%1 failed to send shortcut.")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_RunKeySequence(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
quint16 SourceUsage,
|
||||
QString& TextStatus)
|
||||
{
|
||||
const QString SequenceText = Feature.SequenceText.trimmed();
|
||||
if (SequenceText.isEmpty())
|
||||
{
|
||||
TextStatus = QStringLiteral("%1 has no shortcut sequence configured.")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<QVector<Lgc_FunctionButton_Struct_SequenceKey>> CombinationList;
|
||||
QString ErrorText;
|
||||
if (!Lgc_FunctionButton_ParseShortcutSequenceText(
|
||||
SequenceText,
|
||||
SourceUsage,
|
||||
&CombinationList,
|
||||
&ErrorText))
|
||||
{
|
||||
TextStatus = QStringLiteral("%1 has an invalid shortcut sequence: %2")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature), ErrorText);
|
||||
return;
|
||||
}
|
||||
|
||||
TextStatus = Lgc_FunctionButton_SendShortcutSequence(CombinationList)
|
||||
? QStringLiteral("%1 sent shortcut sequence: %2")
|
||||
.arg(
|
||||
Lgc_FunctionButton_GetFeatureName(Feature),
|
||||
Lgc_FunctionButton_FormatShortcutSequence(CombinationList))
|
||||
: QStringLiteral("%1 failed to send shortcut sequence.")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
}
|
||||
|
||||
void Lgc_FunctionButton_RunOpenWebsite(
|
||||
const Lgc_FunctionFeature_Definition& Feature,
|
||||
QString& TextStatus)
|
||||
{
|
||||
const QString UrlText = Feature.WebsiteUrl.trimmed();
|
||||
const QUrl Url = QUrl::fromUserInput(UrlText);
|
||||
if (UrlText.isEmpty() || !Url.isValid() || Url.isEmpty())
|
||||
{
|
||||
TextStatus = QStringLiteral("%1 has an invalid URL.")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
return;
|
||||
}
|
||||
|
||||
TextStatus = QDesktopServices::openUrl(Url)
|
||||
? QStringLiteral("%1 opened: %2")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature), Url.toString())
|
||||
: QStringLiteral("%1 failed to open URL.")
|
||||
.arg(Lgc_FunctionButton_GetFeatureName(Feature));
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
#include "LOGIC/Lgc_Nkro.h"
|
||||
|
||||
void Lgc_Nkro_Func_Parse(const QByteArray& ByteArray, Lgc_Nkro_Struct_Result* p_Result)
|
||||
{
|
||||
// 当前调用链内部固定传有效结果对象,这里直接回到默认状态。
|
||||
*p_Result = Lgc_Nkro_Struct_Result();
|
||||
|
||||
// 没有新包时直接返回提示。
|
||||
if (ByteArray.isEmpty())
|
||||
{
|
||||
p_Result->TextExplain = QStringLiteral("NKRO 端口没有收到数据。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 先校验 report id。
|
||||
if (static_cast<quint8>(ByteArray.at(0)) != Mid_Enum_ReportId_Nkro)
|
||||
{
|
||||
p_Result->TextExplain = QStringLiteral("这不是 report id 0x01。");
|
||||
return;
|
||||
}
|
||||
|
||||
p_Result->IsMatch = true;
|
||||
|
||||
// 当前固件里的 0x01 包固定长度是 31 字节。
|
||||
if (ByteArray.size() != MID_CONST_PACKET_SIZE_NKRO)
|
||||
{
|
||||
p_Result->TextExplain = QStringLiteral("0x01 包长度不对。");
|
||||
return;
|
||||
}
|
||||
|
||||
p_Result->IsLengthOk = true;
|
||||
|
||||
// 第 1 字节是 modifier。
|
||||
p_Result->Modifier = static_cast<quint8>(ByteArray.at(1));
|
||||
|
||||
// 后面 29 字节是 usage 位图。
|
||||
p_Result->UsageBitmap = ByteArray.mid(2, MID_CONST_USAGE_BITMAP_SIZE);
|
||||
|
||||
// 逐 bit 扫描位图,得到所有按下的 HID usage。
|
||||
for (quint16 Usage = 0; Usage <= MID_CONST_KEYBOARD_USAGE_MAX; ++Usage)
|
||||
{
|
||||
const int ByteIndex = Usage / 8;
|
||||
const int BitIndex = Usage % 8;
|
||||
const quint8 Value = static_cast<quint8>(p_Result->UsageBitmap.at(ByteIndex));
|
||||
|
||||
if ((Value & static_cast<quint8>(1U << BitIndex)) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
p_Result->UsageList.append(Usage);
|
||||
p_Result->UsageTextList.append(Mid_Func_GetKeyboardUsageText(Usage));
|
||||
}
|
||||
|
||||
// 这里整理的是适合调试窗口直接阅读的教学化文本。
|
||||
QStringList ExplainList;
|
||||
ExplainList.append(QStringLiteral("0x01 NKRO 可见键盘态"));
|
||||
ExplainList.append(QStringLiteral("修饰键:%1").arg(Mid_Func_GetModifierText(p_Result->Modifier)));
|
||||
|
||||
if (p_Result->UsageTextList.isEmpty())
|
||||
{
|
||||
ExplainList.append(QStringLiteral("按下:无"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplainList.append(QStringLiteral("按下:%1").arg(p_Result->UsageTextList.join(QStringLiteral(", "))));
|
||||
}
|
||||
|
||||
p_Result->TextExplain = ExplainList.join(QLatin1Char('\n'));
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
|
||||
/*
|
||||
* 这份文件负责解析 report id 0x01 的 NKRO 包。
|
||||
*
|
||||
* 这里解析的是“Windows 最终可见的键盘态”:
|
||||
* - 会受下位机遮罩影响
|
||||
* - 不是 0x04 那种物理镜像流
|
||||
*
|
||||
* 当前下位机结构固定为:
|
||||
* [report_id(1) | modifier(1) | usage_bitmap(29)]
|
||||
* 总长度 31 字节
|
||||
*/
|
||||
struct Lgc_Nkro_Struct_Result
|
||||
{
|
||||
// 首字节是否匹配 0x01。
|
||||
bool IsMatch = false;
|
||||
|
||||
// 长度是否符合 31 字节。
|
||||
bool IsLengthOk = false;
|
||||
|
||||
// 第 1 字节修饰键位图。
|
||||
quint8 Modifier = 0;
|
||||
|
||||
// 第 2~30 字节 usage 位图原文。
|
||||
QByteArray UsageBitmap;
|
||||
|
||||
// 展开后的 HID usage 列表。
|
||||
QVector<quint16> UsageList;
|
||||
|
||||
// 展开后的按键名称列表。
|
||||
QStringList UsageTextList;
|
||||
|
||||
// 给调试窗口用的简短中文说明。
|
||||
QString TextExplain;
|
||||
};
|
||||
|
||||
void Lgc_Nkro_Func_Parse(const QByteArray& ByteArray, Lgc_Nkro_Struct_Result* p_Result);
|
||||
@@ -1,71 +0,0 @@
|
||||
#include "LOGIC/Lgc_Vendor.h"
|
||||
|
||||
void Lgc_Vendor_Func_Parse(const QByteArray& ByteArray, Lgc_Vendor_Struct_Result* p_Result)
|
||||
{
|
||||
// 当前调用链内部固定传有效结果对象,这里直接回到默认状态。
|
||||
*p_Result = Lgc_Vendor_Struct_Result();
|
||||
|
||||
// 没有包时直接返回提示。
|
||||
if (ByteArray.isEmpty())
|
||||
{
|
||||
p_Result->TextExplain = QStringLiteral("Vendor 端口没有收到数据。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 第 0 字节不是 0x04,就不是当前要解析的 Vendor 镜像包。
|
||||
if (static_cast<quint8>(ByteArray.at(0)) != Mid_Enum_ReportId_Vendor)
|
||||
{
|
||||
p_Result->TextExplain = QStringLiteral("这不是 report id 0x04。");
|
||||
return;
|
||||
}
|
||||
|
||||
p_Result->IsMatch = true;
|
||||
|
||||
// 当前固件里的 0x04 固定长度也是 31 字节。
|
||||
if (ByteArray.size() != MID_CONST_PACKET_SIZE_VENDOR)
|
||||
{
|
||||
p_Result->TextExplain = QStringLiteral("0x04 包长度不对。");
|
||||
return;
|
||||
}
|
||||
|
||||
p_Result->IsLengthOk = true;
|
||||
p_Result->VendorState.IsValid = true;
|
||||
|
||||
// 第 1 字节是物理 modifier。
|
||||
p_Result->VendorState.Modifier = static_cast<quint8>(ByteArray.at(1));
|
||||
|
||||
// 第 2~30 字节是物理 usage 位图。
|
||||
p_Result->VendorState.UsageBitmap = ByteArray.mid(2, MID_CONST_USAGE_BITMAP_SIZE);
|
||||
|
||||
// 展开位图,得到当前物理按下的 usage 列表。
|
||||
for (quint16 Usage = 0; Usage <= MID_CONST_KEYBOARD_USAGE_MAX; ++Usage)
|
||||
{
|
||||
const int ByteIndex = Usage / 8;
|
||||
const int BitIndex = Usage % 8;
|
||||
const quint8 Value = static_cast<quint8>(p_Result->VendorState.UsageBitmap.at(ByteIndex));
|
||||
|
||||
if ((Value & static_cast<quint8>(1U << BitIndex)) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
p_Result->VendorState.UsageList.append(Usage);
|
||||
p_Result->VendorState.UsageTextList.append(Mid_Func_GetKeyboardUsageText(Usage));
|
||||
}
|
||||
|
||||
// 这里整理的是面向教学和调试窗口的中文解释。
|
||||
QStringList ExplainList;
|
||||
ExplainList.append(QStringLiteral("0x04 Vendor 物理镜像态"));
|
||||
ExplainList.append(QStringLiteral("物理修饰键:%1").arg(Mid_Func_GetModifierText(p_Result->VendorState.Modifier)));
|
||||
|
||||
if (p_Result->VendorState.UsageTextList.isEmpty())
|
||||
{
|
||||
ExplainList.append(QStringLiteral("物理按下:无"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplainList.append(QStringLiteral("物理按下:%1").arg(p_Result->VendorState.UsageTextList.join(QStringLiteral(", "))));
|
||||
}
|
||||
|
||||
p_Result->TextExplain = ExplainList.join(QLatin1Char('\n'));
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
|
||||
/*
|
||||
* 这份文件负责解析 report id 0x04 的 Vendor 包。
|
||||
*
|
||||
* 当前 0x04 不是旧 VIA 命令协议,而是“键盘物理状态镜像流”。
|
||||
* 它的结构固定为:
|
||||
* [report_id(1) | modifier(1) | usage_bitmap(29)]
|
||||
*
|
||||
* 也就是说:
|
||||
* - 0x01 代表主机最终可见键盘态
|
||||
* - 0x04 代表下位机物理键盘态
|
||||
*/
|
||||
struct Lgc_Vendor_Struct_Result
|
||||
{
|
||||
// 拆出来的 Vendor 状态。
|
||||
Mid_Struct_VendorState VendorState;
|
||||
|
||||
// 是否匹配到 report id 0x04。
|
||||
bool IsMatch = false;
|
||||
|
||||
// 长度是否符合 31 字节。
|
||||
bool IsLengthOk = false;
|
||||
|
||||
// 给调试窗口看的中文解释。
|
||||
QString TextExplain;
|
||||
};
|
||||
|
||||
void Lgc_Vendor_Func_Parse(const QByteArray& ByteArray, Lgc_Vendor_Struct_Result* p_Result);
|
||||
Reference in New Issue
Block a user