mirror of
https://github.com/djhackersdev/bemanitools.git
synced 2026-09-15 03:45:09 -05:00
Added format protection block to avoid re-ordering of header files according to formatting rules.
433 lines
11 KiB
C
433 lines
11 KiB
C
// clang-format off
|
|
// Don't format because the order is important here
|
|
#include <windows.h>
|
|
#include <commctrl.h>
|
|
#include <wchar.h>
|
|
// clang-format on
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "config/bind-adv.h"
|
|
#include "config/bind.h"
|
|
#include "config/resource.h"
|
|
#include "config/schema.h"
|
|
#include "config/usages.h"
|
|
|
|
#include "geninput/input-config.h"
|
|
#include "geninput/mapper.h"
|
|
|
|
#include "util/defs.h"
|
|
#include "util/log.h"
|
|
#include "util/mem.h"
|
|
#include "util/winres.h"
|
|
|
|
static INT_PTR CALLBACK
|
|
buttons_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
|
|
static bool buttons_get_control_name(
|
|
const struct mapped_action *ma, wchar_t *str, size_t nchars);
|
|
static INT_PTR buttons_handle_init(HWND hwnd, const PROPSHEETPAGE *psp);
|
|
static INT_PTR buttons_handle_binding_adv(HWND hwnd);
|
|
static INT_PTR buttons_handle_binding_clear(HWND hwnd);
|
|
static INT_PTR buttons_handle_binding_edit(HWND hwnd, BOOL bind_many);
|
|
static INT_PTR buttons_handle_switch_page(HWND hwnd, NMUPDOWN *n);
|
|
static INT_PTR buttons_handle_fini(HWND hwnd);
|
|
static void buttons_update(HWND hwnd);
|
|
static void buttons_update_bindings(HWND hwnd);
|
|
static void buttons_update_pager(HWND hwnd);
|
|
|
|
struct buttons_tab {
|
|
const struct schema *schema;
|
|
uint8_t page_no;
|
|
uint8_t npages;
|
|
};
|
|
|
|
HPROPSHEETPAGE
|
|
buttons_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_BUTTONS);
|
|
psp.pfnDlgProc = buttons_dlg_proc;
|
|
psp.lParam = (LPARAM) schema;
|
|
|
|
return CreatePropertySheetPage(&psp);
|
|
}
|
|
|
|
static INT_PTR CALLBACK
|
|
buttons_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
NMHDR *n;
|
|
|
|
switch (msg) {
|
|
case WM_INITDIALOG:
|
|
return buttons_handle_init(hwnd, (const PROPSHEETPAGE *) lparam);
|
|
|
|
case WM_NOTIFY:
|
|
n = (NMHDR *) lparam;
|
|
|
|
switch (n->idFrom) {
|
|
case IDC_PAGE:
|
|
switch (n->code) {
|
|
case UDN_DELTAPOS:
|
|
return buttons_handle_switch_page(
|
|
hwnd, (NMUPDOWN *) n);
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
case IDC_BINDINGS:
|
|
switch (n->code) {
|
|
case NM_DBLCLK:
|
|
return buttons_handle_binding_edit(hwnd, FALSE);
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wparam)) {
|
|
case IDC_BINDING_ADV:
|
|
return buttons_handle_binding_adv(hwnd);
|
|
|
|
case IDC_BINDING_CLEAR:
|
|
return buttons_handle_binding_clear(hwnd);
|
|
|
|
case IDC_BINDING_EDIT:
|
|
return buttons_handle_binding_edit(hwnd, FALSE);
|
|
|
|
case IDC_BINDING_EDIT_MANY:
|
|
return buttons_handle_binding_edit(hwnd, TRUE);
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
case WM_DESTROY:
|
|
return buttons_handle_fini(hwnd);
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static INT_PTR buttons_handle_init(HWND hwnd, const PROPSHEETPAGE *psp)
|
|
{
|
|
struct buttons_tab *self;
|
|
HWND bindings_ctl;
|
|
HINSTANCE inst;
|
|
LVCOLUMN col;
|
|
LVITEM item;
|
|
wchar_t str[128];
|
|
size_t i;
|
|
|
|
self = xmalloc(sizeof(*self));
|
|
self->schema = (const struct schema *) psp->lParam;
|
|
self->page_no = 0;
|
|
|
|
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) self);
|
|
|
|
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
|
|
bindings_ctl = GetDlgItem(hwnd, IDC_BINDINGS);
|
|
|
|
ListView_SetExtendedListViewStyle(
|
|
bindings_ctl, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
|
|
|
|
LoadString(inst, IDS_COL_ACTION, str, lengthof(str));
|
|
|
|
col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
|
|
col.fmt = LVCFMT_LEFT;
|
|
col.cx = 120;
|
|
col.pszText = str;
|
|
|
|
ListView_InsertColumn(bindings_ctl, 0, &col);
|
|
|
|
LoadString(inst, IDS_COL_BUTTON, str, lengthof(str));
|
|
|
|
col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
|
|
col.fmt = LVCFMT_LEFT;
|
|
col.cx = 120;
|
|
col.pszText = str;
|
|
|
|
ListView_InsertColumn(bindings_ctl, 1, &col);
|
|
|
|
for (i = 0; i < self->schema->nactions; i++) {
|
|
memset(&item, 0, sizeof(item));
|
|
|
|
LoadString(
|
|
inst, self->schema->actions[i].name_rsrc, str, lengthof(str));
|
|
|
|
item.mask = LVIF_TEXT;
|
|
item.iItem = (int) i;
|
|
item.iSubItem = 0;
|
|
item.pszText = str;
|
|
|
|
ListView_InsertItem(bindings_ctl, &item);
|
|
}
|
|
|
|
buttons_update(hwnd);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void buttons_update(HWND hwnd)
|
|
{
|
|
buttons_update_pager(hwnd);
|
|
buttons_update_bindings(hwnd);
|
|
|
|
/* Flickery, but seems to be the only way to avoid label garbling when
|
|
the page label changes. Need to do better. */
|
|
InvalidateRect(hwnd, NULL, TRUE);
|
|
}
|
|
|
|
static void buttons_update_pager(HWND hwnd)
|
|
{
|
|
struct buttons_tab *self;
|
|
HINSTANCE inst;
|
|
wchar_t str[128];
|
|
|
|
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
|
|
self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
|
|
self->npages = mapper_get_npages() + 1;
|
|
|
|
rswprintf(
|
|
str, lengthof(str), inst, IDS_PAGE, self->page_no + 1, self->npages);
|
|
SetWindowText(GetDlgItem(hwnd, IDC_PAGE_TEXT), str);
|
|
}
|
|
|
|
static void buttons_update_bindings(HWND hwnd)
|
|
{
|
|
action_iter_t pos;
|
|
struct mapped_action ma;
|
|
struct buttons_tab *self;
|
|
HINSTANCE inst;
|
|
HWND bindings_ctl;
|
|
LVITEM item;
|
|
wchar_t str[128];
|
|
size_t i;
|
|
|
|
self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
|
|
bindings_ctl = GetDlgItem(hwnd, IDC_BINDINGS);
|
|
|
|
/* Clear binding cell text */
|
|
|
|
for (i = 0; i < self->schema->nactions; i++) {
|
|
memset(&item, 0, sizeof(item));
|
|
|
|
item.mask = LVIF_TEXT;
|
|
item.iItem = (int) i;
|
|
item.iSubItem = 1;
|
|
item.pszText = L"";
|
|
|
|
ListView_SetItem(bindings_ctl, &item);
|
|
}
|
|
|
|
/* Repopulate bindings */
|
|
|
|
hid_mgr_lock();
|
|
|
|
for (pos = mapper_iterate_actions(); action_iter_is_valid(pos);
|
|
action_iter_next(pos)) {
|
|
if (action_iter_get_page(pos) != self->page_no) {
|
|
continue;
|
|
}
|
|
|
|
action_iter_get_mapping(pos, &ma);
|
|
|
|
if (!buttons_get_control_name(&ma, str, lengthof(str))) {
|
|
/* Can't read device metadata, device is probably unplugged */
|
|
LoadString(inst, IDS_NOT_PRESENT, str, lengthof(str));
|
|
}
|
|
|
|
memset(&item, 0, sizeof(item));
|
|
|
|
item.mask = LVIF_TEXT;
|
|
item.iItem = (int) action_iter_get_action(pos);
|
|
item.iSubItem = 1;
|
|
item.pszText = str;
|
|
|
|
ListView_SetItem(bindings_ctl, &item);
|
|
}
|
|
|
|
action_iter_free(pos);
|
|
hid_mgr_unlock();
|
|
}
|
|
|
|
static bool buttons_get_control_name(
|
|
const struct mapped_action *ma, wchar_t *str, size_t nchars)
|
|
{
|
|
char chars[128];
|
|
struct hid_control *controls;
|
|
size_t ncontrols;
|
|
|
|
if (!hid_stub_get_controls(ma->hid, NULL, &ncontrols)) {
|
|
goto count_fail;
|
|
}
|
|
|
|
controls = xmalloc(ncontrols * sizeof(*controls));
|
|
|
|
if (!hid_stub_get_controls(ma->hid, controls, &ncontrols)) {
|
|
goto data_fail;
|
|
}
|
|
|
|
if (ma->control_no >= ncontrols) {
|
|
goto bounds_fail;
|
|
}
|
|
|
|
usages_get(chars, lengthof(chars), controls[ma->control_no].usage);
|
|
MultiByteToWideChar(CP_UTF8, 0, chars, lengthof(chars), str, nchars);
|
|
|
|
free(controls);
|
|
|
|
return true;
|
|
|
|
bounds_fail:
|
|
data_fail:
|
|
free(controls);
|
|
|
|
count_fail:
|
|
return false;
|
|
}
|
|
|
|
static INT_PTR buttons_handle_binding_adv(HWND hwnd)
|
|
{
|
|
HINSTANCE inst;
|
|
struct mapped_action ma;
|
|
struct buttons_tab *self;
|
|
int action_no;
|
|
uint8_t bit;
|
|
bool is_valid;
|
|
|
|
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
|
|
self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
|
|
action_no = ListView_GetSelectionMark(GetDlgItem(hwnd, IDC_BINDINGS));
|
|
|
|
if (action_no < 0 || action_no >= (int) self->schema->nactions) {
|
|
return TRUE;
|
|
}
|
|
|
|
bit = self->schema->actions[action_no].bit;
|
|
|
|
is_valid = mapper_get_action_map((uint8_t) action_no, self->page_no, &ma);
|
|
|
|
if (bind_adv(inst, hwnd, &ma, is_valid)) {
|
|
mapper_set_action_map((uint8_t) action_no, self->page_no, bit, &ma);
|
|
buttons_update(hwnd);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static INT_PTR buttons_handle_binding_clear(HWND hwnd)
|
|
{
|
|
struct buttons_tab *self;
|
|
int action_no;
|
|
|
|
self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
action_no = ListView_GetSelectionMark(GetDlgItem(hwnd, IDC_BINDINGS));
|
|
|
|
if (action_no < 0 || action_no >= (int) self->schema->nactions) {
|
|
return TRUE;
|
|
}
|
|
|
|
mapper_clear_action_map((uint8_t) action_no, self->page_no);
|
|
buttons_update(hwnd);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static INT_PTR buttons_handle_binding_edit(HWND hwnd, BOOL bind_many)
|
|
{
|
|
HINSTANCE inst;
|
|
HWND listview;
|
|
struct buttons_tab *self;
|
|
struct mapped_action ma;
|
|
int action_start;
|
|
int action_no;
|
|
uint8_t bit;
|
|
|
|
inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
|
|
self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
listview = GetDlgItem(hwnd, IDC_BINDINGS);
|
|
|
|
action_start = ListView_GetSelectionMark(listview);
|
|
|
|
if (action_start < 0 || action_start >= (int) self->schema->nactions) {
|
|
/* wtf... */
|
|
return TRUE;
|
|
}
|
|
|
|
for (action_no = action_start; action_no < self->schema->nactions;
|
|
action_no++) {
|
|
// Make sure the next listview item is visible
|
|
ListView_SetItemState(
|
|
listview, action_no, LVIS_FOCUSED | LVIS_SELECTED, 0x000F);
|
|
ListView_EnsureVisible(listview, action_no, FALSE);
|
|
|
|
bit = self->schema->actions[action_no].bit;
|
|
|
|
if (bind_control(inst, hwnd, &self->schema->actions[action_no], &ma)) {
|
|
mapper_set_action_map((uint8_t) action_no, self->page_no, bit, &ma);
|
|
buttons_update(hwnd);
|
|
|
|
log_misc(
|
|
"Bind act %d -> dev %p ctl %u range [%d, %d]",
|
|
self->schema->actions[action_no].bit,
|
|
ma.hid,
|
|
(unsigned int) ma.control_no,
|
|
ma.value_min,
|
|
ma.value_max);
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
if (!bind_many) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static INT_PTR buttons_handle_switch_page(HWND hwnd, NMUPDOWN *n)
|
|
{
|
|
struct buttons_tab *self;
|
|
|
|
self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
|
|
/* The buttons on a horizontal spinner are backwards -.- */
|
|
|
|
if (n->iDelta > 0 && self->page_no > 0) {
|
|
self->page_no--;
|
|
buttons_update(hwnd);
|
|
} else if (n->iDelta < 0 && self->page_no < self->npages - 1) {
|
|
self->page_no++;
|
|
buttons_update(hwnd);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static INT_PTR buttons_handle_fini(HWND hwnd)
|
|
{
|
|
struct buttons_tab *self;
|
|
|
|
self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
free(self);
|
|
|
|
return TRUE;
|
|
}
|