518 lines
12 KiB
C
518 lines
12 KiB
C
// clang-format off
|
|
// Don't format because the order is important here
|
|
#include <windows.h>
|
|
#include <commctrl.h>
|
|
#include <wchar.h>
|
|
#include <windowsx.h>
|
|
// clang-format on
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "config/bind-light.h"
|
|
#include "config/resource.h"
|
|
#include "config/schema.h"
|
|
#include "config/usages.h"
|
|
|
|
#include "geninput/input-config.h"
|
|
#include "geninput/mapper.h"
|
|
|
|
#include "util/array.h"
|
|
#include "util/defs.h"
|
|
#include "util/mem.h"
|
|
|
|
#define PULSE_DELTA 0.03f
|
|
|
|
struct lights_ui {
|
|
const struct schema *schema;
|
|
struct array devices;
|
|
struct hid_stub *hid;
|
|
struct hid_light *lights;
|
|
size_t nlights;
|
|
bool pulse_active;
|
|
bool pulse_up;
|
|
size_t pulse_light_no;
|
|
float pulse_coeff;
|
|
};
|
|
|
|
static INT_PTR CALLBACK
|
|
lights_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
|
|
static INT_PTR lights_handle_init(HWND hwnd, const PROPSHEETPAGE *psp);
|
|
static void lights_handle_init_devices(HWND hwnd);
|
|
static void lights_handle_init_device(HWND hwnd, struct hid_stub *hid);
|
|
static void lights_handle_init_lights(HWND hwnd);
|
|
static INT_PTR lights_handle_change_dev(HWND hwnd);
|
|
static INT_PTR lights_handle_highlight_light(HWND hwnd, const NMLISTVIEW *n);
|
|
static INT_PTR lights_handle_bind_light(HWND hwnd);
|
|
static INT_PTR lights_handle_pulse_tick(HWND hwnd);
|
|
static void lights_update_bindings(HWND hwnd);
|
|
static void lights_pulse_start(HWND hwnd, size_t light_no);
|
|
static void lights_pulse_stop(HWND hwnd);
|
|
static INT_PTR lights_handle_fini(HWND hwnd);
|
|
|
|
HPROPSHEETPAGE
|
|
lights_tab_create(HINSTANCE inst, const struct schema *schema)
|
|
{
|
|
PROPSHEETPAGE psp;
|
|
|
|
memset(&psp, 0, sizeof(psp));
|
|
psp.dwSize = sizeof(psp);
|
|
psp.dwFlags = PSP_DEFAULT;
|
|
psp.hInstance = inst;
|
|
psp.pszTemplate = MAKEINTRESOURCE(IDD_TAB_LIGHTS);
|
|
psp.pfnDlgProc = lights_dlg_proc;
|
|
psp.lParam = (LPARAM) schema;
|
|
|
|
return CreatePropertySheetPage(&psp);
|
|
}
|
|
|
|
static INT_PTR CALLBACK
|
|
lights_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
NMHDR *n;
|
|
|
|
switch (msg) {
|
|
case WM_INITDIALOG:
|
|
return lights_handle_init(hwnd, (const PROPSHEETPAGE *) lparam);
|
|
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wparam)) {
|
|
case IDC_DEVICE:
|
|
switch (HIWORD(wparam)) {
|
|
case CBN_SELCHANGE:
|
|
return lights_handle_change_dev(hwnd);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
case WM_NOTIFY:
|
|
n = (NMHDR *) lparam;
|
|
|
|
switch (n->idFrom) {
|
|
case IDC_LIGHT:
|
|
switch (n->code) {
|
|
case LVN_ITEMCHANGED:
|
|
return lights_handle_highlight_light(
|
|
hwnd, (NMLISTVIEW *) n);
|
|
|
|
case NM_DBLCLK:
|
|
return lights_handle_bind_light(hwnd);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
case WM_TIMER:
|
|
return lights_handle_pulse_tick(hwnd);
|
|
|
|
case WM_DESTROY:
|
|
return lights_handle_fini(hwnd);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static INT_PTR lights_handle_init(HWND hwnd, const PROPSHEETPAGE *psp)
|
|
{
|
|
struct lights_ui *ui;
|
|
|
|
ui = xmalloc(sizeof(*ui));
|
|
ui->schema = (const struct schema *) psp->lParam;
|
|
ui->hid = NULL;
|
|
ui->lights = NULL;
|
|
array_init(&ui->devices);
|
|
|
|
SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR) ui);
|
|
|
|
lights_handle_init_devices(hwnd);
|
|
lights_handle_init_lights(hwnd);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void lights_handle_init_devices(HWND hwnd)
|
|
{
|
|
struct hid_stub *hid;
|
|
|
|
hid_mgr_lock();
|
|
|
|
for (hid = hid_mgr_get_first_stub(); hid != NULL;
|
|
hid = hid_mgr_get_next_stub(hid)) {
|
|
lights_handle_init_device(hwnd, hid);
|
|
}
|
|
|
|
hid_mgr_unlock();
|
|
}
|
|
|
|
static void lights_handle_init_device(HWND hwnd, struct hid_stub *hid)
|
|
{
|
|
wchar_t *chars;
|
|
size_t nchars;
|
|
size_t nlights;
|
|
struct lights_ui *ui;
|
|
HWND devs;
|
|
|
|
if (!hid_stub_get_lights(hid, NULL, &nlights) || nlights == 0) {
|
|
goto lights_fail;
|
|
}
|
|
|
|
if (!hid_stub_get_name(hid, NULL, &nchars)) {
|
|
goto name_sz_fail;
|
|
}
|
|
|
|
chars = xmalloc(sizeof(*chars) * nchars);
|
|
|
|
if (!hid_stub_get_name(hid, chars, &nchars)) {
|
|
goto name_fail;
|
|
}
|
|
|
|
ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
devs = GetDlgItem(hwnd, IDC_DEVICE);
|
|
|
|
*array_append(struct hid_stub *, &ui->devices) = hid;
|
|
ComboBox_AddString(devs, chars);
|
|
|
|
return;
|
|
|
|
name_fail:
|
|
free(chars);
|
|
|
|
name_sz_fail:
|
|
lights_fail:
|
|
return;
|
|
}
|
|
|
|
static void lights_handle_init_lights(HWND hwnd)
|
|
{
|
|
wchar_t str[128];
|
|
HINSTANCE inst;
|
|
LVCOLUMN col;
|
|
HWND lights;
|
|
|
|
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
|
|
lights = GetDlgItem(hwnd, IDC_LIGHT);
|
|
|
|
ListView_SetExtendedListViewStyle(
|
|
lights, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
|
|
|
|
LoadString(inst, IDS_COL_LIGHT_HID, str, lengthof(str));
|
|
|
|
col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
|
|
col.fmt = LVCFMT_LEFT;
|
|
col.cx = 120;
|
|
col.pszText = str;
|
|
|
|
ListView_InsertColumn(lights, 0, &col);
|
|
|
|
LoadString(inst, IDS_COL_LIGHT_GAME, str, lengthof(str));
|
|
|
|
col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
|
|
col.fmt = LVCFMT_LEFT;
|
|
col.cx = 120;
|
|
col.pszText = str;
|
|
|
|
ListView_InsertColumn(lights, 1, &col);
|
|
}
|
|
|
|
static INT_PTR lights_handle_change_dev(HWND hwnd)
|
|
{
|
|
char chars[256];
|
|
wchar_t wchars[256];
|
|
struct lights_ui *ui;
|
|
struct hid_stub *hid;
|
|
size_t i;
|
|
int dev_idx;
|
|
LVITEM item;
|
|
HWND devs_ctl;
|
|
HWND lights_ctl;
|
|
|
|
lights_pulse_stop(hwnd);
|
|
|
|
ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
devs_ctl = GetDlgItem(hwnd, IDC_DEVICE);
|
|
lights_ctl = GetDlgItem(hwnd, IDC_LIGHT);
|
|
dev_idx = ComboBox_GetCurSel(devs_ctl);
|
|
|
|
if (dev_idx >= (int) ui->devices.nitems) {
|
|
goto unlocked_fail;
|
|
}
|
|
|
|
ListView_DeleteAllItems(lights_ctl);
|
|
|
|
hid = *array_item(struct hid_stub *, &ui->devices, dev_idx);
|
|
|
|
hid_mgr_lock();
|
|
|
|
if (!hid_stub_get_lights(hid, NULL, &ui->nlights)) {
|
|
goto locked_fail;
|
|
}
|
|
|
|
ui->lights = xrealloc(ui->lights, sizeof(*ui->lights) * ui->nlights);
|
|
|
|
if (!hid_stub_get_lights(hid, ui->lights, &ui->nlights)) {
|
|
free(ui->lights);
|
|
ui->lights = NULL;
|
|
|
|
goto locked_fail;
|
|
}
|
|
|
|
for (i = 0; i < ui->nlights; i++) {
|
|
if (ui->lights[i].name[0] != L'\0') {
|
|
item.pszText = ui->lights[i].name;
|
|
} else {
|
|
wchars[0] = L'\0';
|
|
|
|
usages_get(chars, lengthof(chars), ui->lights[i].usage);
|
|
MultiByteToWideChar(
|
|
CP_UTF8, 0, chars, lengthof(chars), wchars, lengthof(wchars));
|
|
|
|
item.pszText = wchars;
|
|
}
|
|
|
|
item.mask = LVIF_TEXT;
|
|
item.iItem = (int) i;
|
|
item.iSubItem = 0;
|
|
|
|
ListView_InsertItem(lights_ctl, &item);
|
|
}
|
|
|
|
ui->hid = hid;
|
|
lights_update_bindings(hwnd);
|
|
|
|
locked_fail:
|
|
hid_mgr_unlock();
|
|
|
|
unlocked_fail:
|
|
return TRUE;
|
|
}
|
|
|
|
static INT_PTR lights_handle_highlight_light(HWND hwnd, const NMLISTVIEW *n)
|
|
{
|
|
size_t light_no;
|
|
|
|
light_no = n->iItem;
|
|
|
|
if (n->uNewState & LVIS_SELECTED) {
|
|
lights_pulse_start(hwnd, light_no);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static INT_PTR lights_handle_bind_light(HWND hwnd)
|
|
{
|
|
struct lights_ui *ui;
|
|
struct mapped_light ml;
|
|
uint8_t game_light;
|
|
bool bound;
|
|
HINSTANCE inst;
|
|
HWND lights_ctl;
|
|
int pos;
|
|
|
|
ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
|
|
lights_ctl = GetDlgItem(hwnd, IDC_LIGHT);
|
|
|
|
pos = ListView_GetSelectionMark(lights_ctl);
|
|
|
|
if (pos < 0) {
|
|
return TRUE;
|
|
}
|
|
|
|
ml.hid = ui->hid;
|
|
ml.light_no = pos;
|
|
|
|
if (bind_light(inst, hwnd, ui->schema, &ml, &game_light, &bound)) {
|
|
if (bound) {
|
|
mapper_set_light_map(&ml, game_light);
|
|
} else {
|
|
mapper_clear_light_map(&ml);
|
|
}
|
|
|
|
lights_update_bindings(hwnd);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static INT_PTR lights_handle_pulse_tick(HWND hwnd)
|
|
{
|
|
struct lights_ui *ui;
|
|
size_t light_no;
|
|
float intensity;
|
|
float bias;
|
|
float scale;
|
|
float tmp;
|
|
|
|
ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
|
|
if (!ui->pulse_active) {
|
|
return TRUE;
|
|
}
|
|
|
|
light_no = ui->pulse_light_no;
|
|
|
|
if (ui->hid == NULL || light_no >= ui->nlights) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (ui->pulse_up) {
|
|
tmp = ui->pulse_coeff + PULSE_DELTA;
|
|
|
|
if (tmp > 1.0f) {
|
|
ui->pulse_coeff = 1.0f;
|
|
ui->pulse_up = false;
|
|
} else {
|
|
ui->pulse_coeff = tmp;
|
|
}
|
|
} else {
|
|
tmp = ui->pulse_coeff - PULSE_DELTA;
|
|
|
|
if (tmp < 0.0f) {
|
|
ui->pulse_coeff = 0.0f;
|
|
ui->pulse_up = true;
|
|
} else {
|
|
ui->pulse_coeff = tmp;
|
|
}
|
|
}
|
|
|
|
bias = (float) (ui->lights[light_no].value_min);
|
|
scale = (float) (ui->lights[light_no].value_max -
|
|
ui->lights[light_no].value_min);
|
|
|
|
/* Intensity perception is non-linear. Pulse quadratically. */
|
|
|
|
intensity = bias + scale * ui->pulse_coeff * ui->pulse_coeff + 0.5f;
|
|
|
|
hid_mgr_lock();
|
|
hid_stub_set_light(ui->hid, light_no, (int32_t) intensity);
|
|
hid_mgr_unlock();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void lights_update_bindings(HWND hwnd)
|
|
{
|
|
light_iter_t pos;
|
|
struct mapped_light ml;
|
|
uint8_t game_light;
|
|
size_t i;
|
|
wchar_t wchars[256];
|
|
struct lights_ui *ui;
|
|
HINSTANCE inst;
|
|
HWND lights_ctl;
|
|
|
|
ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
|
|
lights_ctl = GetDlgItem(hwnd, IDC_LIGHT);
|
|
|
|
for (i = 0; i < ui->nlights; i++) {
|
|
ListView_SetItemText(lights_ctl, i, 1, NULL);
|
|
}
|
|
|
|
for (pos = mapper_iterate_lights(); light_iter_is_valid(pos);
|
|
light_iter_next(pos)) {
|
|
light_iter_get_mapping(pos, &ml);
|
|
|
|
if (ml.hid != ui->hid) {
|
|
continue;
|
|
}
|
|
|
|
if (ml.light_no >= ui->nlights) {
|
|
/* Shouldn't happen unless the HID report descriptor has changed
|
|
since we last configured this device */
|
|
|
|
continue;
|
|
}
|
|
|
|
game_light = light_iter_get_game_light(pos);
|
|
|
|
for (i = 0; i < ui->schema->nlights; i++) {
|
|
if (ui->schema->lights[i].bit == game_light) {
|
|
LoadString(
|
|
inst,
|
|
ui->schema->lights[i].name_rsrc,
|
|
wchars,
|
|
lengthof(wchars));
|
|
ListView_SetItemText(lights_ctl, ml.light_no, 1, wchars);
|
|
}
|
|
}
|
|
}
|
|
|
|
light_iter_free(pos);
|
|
}
|
|
|
|
static void lights_pulse_start(HWND hwnd, size_t light_no)
|
|
{
|
|
struct lights_ui *ui;
|
|
int32_t intensity;
|
|
|
|
ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
|
|
if (ui->pulse_active) {
|
|
lights_pulse_stop(hwnd);
|
|
}
|
|
|
|
/* Start light at max intensity */
|
|
|
|
intensity = ui->lights[light_no].value_max;
|
|
|
|
hid_mgr_lock();
|
|
hid_stub_set_light(ui->hid, light_no, intensity);
|
|
hid_mgr_unlock();
|
|
|
|
ui->pulse_active = true;
|
|
ui->pulse_up = false;
|
|
ui->pulse_light_no = light_no;
|
|
ui->pulse_coeff = 1.0f;
|
|
|
|
SetTimer(hwnd, 1, 17, NULL);
|
|
}
|
|
|
|
static void lights_pulse_stop(HWND hwnd)
|
|
{
|
|
struct lights_ui *ui;
|
|
size_t light_no;
|
|
int32_t intensity;
|
|
|
|
ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
|
|
if (!ui->pulse_active) {
|
|
return;
|
|
}
|
|
|
|
light_no = ui->pulse_light_no;
|
|
|
|
if (ui->hid != NULL && light_no < ui->nlights) {
|
|
intensity = ui->lights[light_no].value_min;
|
|
|
|
hid_mgr_lock();
|
|
hid_stub_set_light(ui->hid, light_no, intensity);
|
|
hid_mgr_unlock();
|
|
}
|
|
|
|
ui->pulse_active = false;
|
|
|
|
KillTimer(hwnd, 1);
|
|
}
|
|
|
|
static INT_PTR lights_handle_fini(HWND hwnd)
|
|
{
|
|
struct lights_ui *ui;
|
|
|
|
lights_pulse_stop(hwnd);
|
|
|
|
ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
|
|
array_fini(&ui->devices);
|
|
free(ui->lights);
|
|
free(ui);
|
|
|
|
return TRUE;
|
|
}
|