// clang-format off // Don't format because the order is important here #include #include #include #include // clang-format on #include #include #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; }