364 lines
12 KiB
C
364 lines
12 KiB
C
/* Benjamin DELPY `gentilkiwi`
|
|
http://blog.gentilkiwi.com
|
|
benjamin@gentilkiwi.com
|
|
Licence : https://creativecommons.org/licenses/by/4.0/
|
|
*/
|
|
#include "kull_m_busylight.h"
|
|
|
|
const BUSYLIGHT_DEVICE_ID KULL_M_BUSYLIGHT_CATALOG[] = {
|
|
{0x27bb, 0x3bca, BUSYLIGHT_CAP_LIGHT | BUSYLIGHT_CAP_SOUND | BUSYLIGHT_CAP_JINGLE_CLIPS, L"Busylight Lync model (with bootloader)"},
|
|
{0x27bb, 0x3bcb, BUSYLIGHT_CAP_LIGHT | BUSYLIGHT_CAP_SOUND, L"Busylight UC model"},
|
|
{0x27bb, 0x3bcc, BUSYLIGHT_CAP_INPUTEVENT, L"kuandoBOX"},
|
|
{0x27bb, 0x3bcd, BUSYLIGHT_CAP_LIGHT | BUSYLIGHT_CAP_SOUND | BUSYLIGHT_CAP_JINGLE_CLIPS, L"Busylight Omega model"},
|
|
{0x04d8, 0xf848, BUSYLIGHT_CAP_LIGHT | BUSYLIGHT_CAP_SOUND, L"Busylight Lync model (Microchip Id)"},
|
|
{0x0bf8, 0x1020, BUSYLIGHT_CAP_LIGHT, L"Fujitsu MMM2"},
|
|
};
|
|
|
|
const BUSYLIGHT_COLOR
|
|
BUSYLIGHT_COLOR_OFF = {0, 0, 0},
|
|
BUSYLIGHT_COLOR_RED = {100, 0, 0},
|
|
BUSYLIGHT_COLOR_ORANGE = {100, 50, 0},
|
|
BUSYLIGHT_COLOR_YELLOW = {100, 100, 0},
|
|
BUSYLIGHT_COLOR_CHARTREUSE_GREEN = {50, 100, 0},
|
|
BUSYLIGHT_COLOR_GREEN = {0, 100, 0},
|
|
BUSYLIGHT_COLOR_SPRING_GREEN = {0, 100, 50},
|
|
BUSYLIGHT_COLOR_CYAN = {0, 100, 100},
|
|
BUSYLIGHT_COLOR_AZURE = {0, 50, 100},
|
|
BUSYLIGHT_COLOR_BLUE = {0, 0, 100},
|
|
BUSYLIGHT_COLOR_VIOLET = {50, 0, 100},
|
|
BUSYLIGHT_COLOR_MAGENTA = {100, 0, 100},
|
|
BUSYLIGHT_COLOR_ROSE = {100, 0, 50},
|
|
BUSYLIGHT_COLOR_WHITE = {100, 100, 100}
|
|
;
|
|
|
|
PCBUSYLIGHT_DEVICE_ID kull_m_busylight_devices_getIdFromAttributes(PHIDD_ATTRIBUTES attributes)
|
|
{
|
|
DWORD i;
|
|
if(attributes)
|
|
for(i = 0; i < ARRAYSIZE(KULL_M_BUSYLIGHT_CATALOG); i++)
|
|
if((KULL_M_BUSYLIGHT_CATALOG[i].Vid == attributes->VendorID) && (KULL_M_BUSYLIGHT_CATALOG[i].Pid == attributes->ProductID))
|
|
return &KULL_M_BUSYLIGHT_CATALOG[i];
|
|
return NULL;
|
|
}
|
|
|
|
BOOL kull_m_busylight_devices_get(PBUSYLIGHT_DEVICE *devices, DWORD *count, DWORD mask, BOOL bAutoThread)
|
|
{
|
|
PBUSYLIGHT_DEVICE *next = devices;
|
|
GUID guidHid;
|
|
HDEVINFO hDevInfo;
|
|
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
|
|
BOOL enumStatus;
|
|
DWORD enumIndex, dwRequired, id = 0;
|
|
PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData;
|
|
HANDLE deviceHandle;
|
|
HIDD_ATTRIBUTES attributes;
|
|
PCBUSYLIGHT_DEVICE_ID deviceId;
|
|
PHIDP_PREPARSED_DATA PreparsedData;
|
|
NTSTATUS status;
|
|
|
|
*next = NULL;
|
|
HidD_GetHidGuid(&guidHid);
|
|
hDevInfo = SetupDiGetClassDevs(&guidHid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
|
|
if(hDevInfo != INVALID_HANDLE_VALUE)
|
|
{
|
|
for(enumIndex = 0, enumStatus = TRUE; enumStatus; enumIndex++)
|
|
{
|
|
DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
|
if(enumStatus = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &guidHid, enumIndex, &DeviceInterfaceData))
|
|
{
|
|
dwRequired = 0;
|
|
if(!SetupDiGetDeviceInterfaceDetail(hDevInfo, &DeviceInterfaceData, NULL, 0, &dwRequired, NULL) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER))
|
|
{
|
|
if(DeviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) LocalAlloc(LPTR, dwRequired))
|
|
{
|
|
DeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
|
if(SetupDiGetDeviceInterfaceDetail(hDevInfo, &DeviceInterfaceData, DeviceInterfaceDetailData, dwRequired, &dwRequired, NULL))
|
|
{
|
|
deviceHandle = CreateFile(DeviceInterfaceDetailData->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
|
if(deviceHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
attributes.Size = sizeof(HIDD_ATTRIBUTES);
|
|
if(HidD_GetAttributes(deviceHandle, &attributes))
|
|
{
|
|
if(deviceId = kull_m_busylight_devices_getIdFromAttributes(&attributes))
|
|
{
|
|
if((deviceId->Capabilities & mask) == mask)
|
|
{
|
|
if(*next = (PBUSYLIGHT_DEVICE) LocalAlloc(LPTR, sizeof(BUSYLIGHT_DEVICE)))
|
|
{
|
|
if(HidD_GetPreparsedData(deviceHandle, &PreparsedData))
|
|
{
|
|
status = HidP_GetCaps(PreparsedData, &(*next)->hidCaps);
|
|
if(!NT_SUCCESS(status))
|
|
PRINT_ERROR(L"HidP_GetCaps (%08x)\n", status);
|
|
HidD_FreePreparsedData(PreparsedData);
|
|
}
|
|
(*next)->DevicePath = _wcsdup(DeviceInterfaceDetailData->DevicePath);
|
|
(*next)->hidAttributes = attributes;
|
|
(*next)->deviceId = deviceId;
|
|
(*next)->dpi.box_sensivity = 4;
|
|
(*next)->dpi.box_timeout = 4;
|
|
(*next)->dpi.box_triggertime = 85;
|
|
(*next)->id = id;
|
|
(*next)->hBusy = CreateFile(DeviceInterfaceDetailData->DevicePath, FILE_READ_DATA | FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
|
|
|
if((*next)->hBusy && ((*next)->hBusy != INVALID_HANDLE_VALUE))
|
|
{
|
|
if(bAutoThread)
|
|
{
|
|
(*next)->dKeepAliveThread = 5000;
|
|
if((*next)->hKeepAliveThread = CreateThread(NULL, 0, kull_m_busylight_keepAliveThread, *next, 0, NULL))
|
|
{
|
|
next = &(*next)->next;
|
|
id++;
|
|
}
|
|
else
|
|
{
|
|
PRINT_ERROR_AUTO(L"CreateThread (hKeepAliveThread)");
|
|
CloseHandle((*next)->hBusy);
|
|
LocalFree(*next);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
next = &(*next)->next;
|
|
id++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PRINT_ERROR_AUTO(L"CreateFile (hBusy)");
|
|
LocalFree(*next);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CloseHandle(deviceHandle);
|
|
}
|
|
else PRINT_ERROR_AUTO(L"CreateFile (deviceHandle)");
|
|
}
|
|
LocalFree(DeviceInterfaceDetailData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SetupDiDestroyDeviceInfoList(hDevInfo);
|
|
}
|
|
else PRINT_ERROR_AUTO(L"SetupDiGetClassDevs");
|
|
|
|
if(count)
|
|
*count = id;
|
|
return (id > 0);
|
|
}
|
|
|
|
void kull_m_busylight_devices_free(PBUSYLIGHT_DEVICE devices, BOOL instantOff)
|
|
{
|
|
PBUSYLIGHT_DEVICE tmp;
|
|
while(devices)
|
|
{
|
|
if(devices->hBusy)
|
|
{
|
|
if(instantOff && (devices->hBusy != INVALID_HANDLE_VALUE))
|
|
kull_m_busylight_request_send_off(devices, FALSE);
|
|
CloseHandle(devices->hBusy);
|
|
devices->hBusy = NULL;
|
|
}
|
|
devices->dKeepAliveThread = 0;
|
|
if(devices->hKeepAliveThread)
|
|
{
|
|
TerminateThread(devices->hKeepAliveThread, ERROR_SUCCESS);
|
|
devices->hKeepAliveThread = NULL;
|
|
}
|
|
devices->dWorkerThread = 0;
|
|
if(devices->hWorkerThread)
|
|
{
|
|
TerminateThread(devices->hWorkerThread, ERROR_SUCCESS);
|
|
devices->hWorkerThread = NULL;
|
|
}
|
|
if(devices->DevicePath)
|
|
free(devices->DevicePath);
|
|
tmp = devices->next;
|
|
LocalFree(devices);
|
|
devices = tmp;
|
|
}
|
|
}
|
|
|
|
BOOL kull_m_busylight_request_create(PCBUSYLIGHT_COMMAND_STEP commands, DWORD count, PBYTE *data, DWORD *size)
|
|
{
|
|
BOOL status = FALSE;
|
|
DWORD i;
|
|
USHORT sum;
|
|
|
|
*size = BUSYLIGHT_OUTPUT_REPORT_SIZE;
|
|
if(*data = (PBYTE) LocalAlloc(LPTR, *size))
|
|
{
|
|
if(count >=7)
|
|
PRINT_ERROR(L"count=%u (max is 7)\n", count);
|
|
for(i = 0; i < min(count, 7); i++)
|
|
{
|
|
(*data)[i * 8 + 1] = (commands[i].NextStep & 0xf0) ? commands[i].NextStep : (commands[i].NextStep | 0x10);
|
|
(*data)[i * 8 + 2] = commands[i].RepeatInterval;
|
|
// TODO avoid color (or not ?)
|
|
(*data)[i * 8 + 3] = commands[i].color.red;
|
|
(*data)[i * 8 + 4] = commands[i].color.green;
|
|
(*data)[i * 8 + 5] = commands[i].color.blue;
|
|
|
|
(*data)[i * 8 + 6] = commands[i].OnTimeSteps;
|
|
(*data)[i * 8 + 7] = commands[i].OffTimeSteps;
|
|
(*data)[i * 8 + 8] = commands[i].AudioByte;
|
|
}
|
|
(*data)[57] = 4;
|
|
(*data)[58] = 4;
|
|
(*data)[59] = 85;
|
|
|
|
(*data)[60] = (*data)[61] = (*data)[62] = 0xff;
|
|
|
|
for(i = 1, sum = 0; i < (*size - 2); i++)
|
|
sum += (*data)[i];
|
|
(*data)[63] = (BYTE) (sum / 256);
|
|
(*data)[64] = (BYTE) (sum % 256);
|
|
|
|
status = TRUE; // TODO add checks
|
|
if(!status)
|
|
{
|
|
*data = (PBYTE) LocalFree(*data);
|
|
*size = 0;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
BOOL kull_m_busylight_device_send_raw(PBUSYLIGHT_DEVICE device, LPCVOID request, DWORD size)
|
|
{
|
|
BOOL status = FALSE;
|
|
DWORD writed;
|
|
if(device && device->hBusy && (device->hBusy != INVALID_HANDLE_VALUE))
|
|
{
|
|
if(size <= device->hidCaps.OutputReportByteLength)
|
|
{
|
|
status = WriteFile(device->hBusy, request, size, &writed, NULL);
|
|
if(!status)
|
|
PRINT_ERROR(L"[device %u] WriteFile (0x%08x)\n", device->id, GetLastError());
|
|
}
|
|
else PRINT_ERROR(L"[device %u] Size is not valide (siz = %u, max = %u)\n", device->id, size, device->hidCaps.OutputReportByteLength);
|
|
}
|
|
else PRINT_ERROR(L"[device %u] Invalid Device/Busy Handle\n", device->id);
|
|
return status;
|
|
}
|
|
|
|
BOOL kull_m_busylight_device_read_raw(PBUSYLIGHT_DEVICE device, LPVOID *data, DWORD *size)
|
|
{
|
|
BOOL status = FALSE;
|
|
DWORD toRead;
|
|
if(device && device->hBusy && (device->hBusy != INVALID_HANDLE_VALUE))
|
|
{
|
|
toRead = device->hidCaps.InputReportByteLength;
|
|
if(*data = LocalAlloc(LPTR, toRead))
|
|
{
|
|
status = ReadFile(device->hBusy, *data, toRead, size, NULL);
|
|
if(!status || (status && (*size != toRead)))
|
|
{
|
|
if(!status)
|
|
PRINT_ERROR(L"[device %u] ReadFile (0x%08x)\n", device->id, GetLastError());
|
|
else
|
|
PRINT_ERROR(L"[device %u] %u byte(s) readed, %u wanted\n", *size, toRead);
|
|
*data = LocalFree(*data);
|
|
*size = 0;
|
|
}
|
|
}
|
|
}
|
|
else PRINT_ERROR(L"[device %u] Invalid Device/Busy Handle\n", device->id);
|
|
return status;
|
|
}
|
|
|
|
DWORD WINAPI kull_m_busylight_keepAliveThread(LPVOID lpThreadParameter)
|
|
{
|
|
PBUSYLIGHT_DEVICE device = (PBUSYLIGHT_DEVICE) lpThreadParameter;
|
|
while(device && device->hKeepAliveThread && device->dKeepAliveThread && device->hBusy)
|
|
{
|
|
if(kull_m_busylight_request_send_keepalive(device, FALSE))
|
|
Sleep(device->dKeepAliveThread);
|
|
else
|
|
{
|
|
CloseHandle(device->hBusy);
|
|
device->hBusy = NULL;
|
|
device->dKeepAliveThread = 0;
|
|
device->hKeepAliveThread = NULL;
|
|
break;
|
|
}
|
|
};
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
BOOL kull_m_busylight_device_read_infos(PBUSYLIGHT_DEVICE device, BUSYLIGHT_INFO *info)
|
|
{
|
|
BOOL status = FALSE;
|
|
LPBYTE data;
|
|
DWORD size;
|
|
if(kull_m_busylight_request_send_keepalive(device, FALSE))
|
|
{
|
|
if(kull_m_busylight_device_read_raw(device, (LPVOID *) &data, &size))
|
|
{
|
|
RtlZeroMemory(info, sizeof(BUSYLIGHT_INFO));
|
|
if(status = !data[0])
|
|
{
|
|
info->status = data[1];
|
|
RtlCopyMemory(info->ProductId, data + 2, 3);
|
|
RtlCopyMemory(info->CostumerId, data + 5, 8);
|
|
RtlCopyMemory(info->Model, data + 13, 4);
|
|
RtlCopyMemory(info->Serial, data + 17, 8);
|
|
RtlCopyMemory(info->Mfg_ID, data + 25, 8);
|
|
RtlCopyMemory(info->Mfg_Date, data + 33, 8);
|
|
RtlCopyMemory(info->swrelease, data + 41, 6);
|
|
}
|
|
else PRINT_ERROR(L"[device %u] data[0] is not NULL (0x%02x)\n", data[0]);
|
|
LocalFree(data);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
BOOL kull_m_busylight_request_send(PBUSYLIGHT_DEVICE device, PCBUSYLIGHT_COMMAND_STEP commands, DWORD count, BOOL all)
|
|
{
|
|
BOOL status = FALSE;
|
|
PBUSYLIGHT_DEVICE cur;
|
|
LPBYTE data;
|
|
DWORD size;
|
|
if(status = kull_m_busylight_request_create(commands, count, &data, &size))
|
|
{
|
|
for(cur = device; cur; cur = all ? cur->next : NULL)
|
|
status &= kull_m_busylight_device_send_raw(cur, data, size);
|
|
LocalFree(data);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
BOOL kull_m_busylight_request_send_keepalive(PBUSYLIGHT_DEVICE device, BOOL all)
|
|
{
|
|
BUSYLIGHT_COMMAND_STEP mdl = {0x8f, 0, {0, 0, 0}, 0, 0, 0};
|
|
return kull_m_busylight_request_send(device, &mdl, 1, all);
|
|
}
|
|
|
|
BOOL kull_m_busylight_request_send_off(PBUSYLIGHT_DEVICE device, BOOL all)
|
|
{
|
|
PBUSYLIGHT_DEVICE cur;
|
|
BUSYLIGHT_COMMAND_STEP mdl = {0, 0, {0, 0, 0}, 0, 0, BUSYLIGHT_MEDIA_MUTE};
|
|
|
|
for(cur = device; cur; cur = all ? cur->next : NULL)
|
|
{
|
|
cur->dWorkerThread = 0;
|
|
if(cur->hWorkerThread)
|
|
{
|
|
TerminateThread(cur->hWorkerThread, ERROR_SUCCESS);
|
|
cur->hWorkerThread = NULL;
|
|
}
|
|
}
|
|
return kull_m_busylight_request_send(device, &mdl, 1, all);
|
|
}
|
|
|
|
BOOL kull_m_busylight_request_single_send(PBUSYLIGHT_DEVICE device, const BUSYLIGHT_COLOR * color, BYTE sound, BYTE volume, BOOL all)
|
|
{
|
|
BUSYLIGHT_COMMAND_STEP mdl = {0, 0, {color->red, color->green, color->blue}, 0, 0, BUSYLIGHT_MEDIA(sound, volume)};
|
|
return kull_m_busylight_request_send(device, &mdl, 1, all);
|
|
} |