mirror of https://github.com/nirenjan/libx52.git
feat: Add action name support
This change adds an optional name at the end of each profile action, this can be used in the future to update the MFD.clutch-profile-support
parent
541f8f739c
commit
b556d34778
|
|
@ -5,6 +5,7 @@
|
||||||
# Button.X = key KEY_Y [KEY_Z ...] (single key or combo, down on press/up on release)
|
# Button.X = key KEY_Y [KEY_Z ...] (single key or combo, down on press/up on release)
|
||||||
# Button.X = macro KEY_A KEY_B (sequence: A then B)
|
# Button.X = macro KEY_A KEY_B (sequence: A then B)
|
||||||
# Button.X = macro KEY_LEFTCTRL KEY_C | KEY_A (steps separated by |: combo Ctrl+C then key A)
|
# Button.X = macro KEY_LEFTCTRL KEY_C | KEY_A (steps separated by |: combo Ctrl+C then key A)
|
||||||
|
# Optional " name Display Name" or name \"Quoted Name\" for UI/documentation (e.g. "Yaw Left")
|
||||||
# Fallback: ModeN.Shift -> ModeN; Mode2 -> Mode1; Mode3 -> Mode2 -> Mode1
|
# Fallback: ModeN.Shift -> ModeN; Mode2 -> Mode1; Mode3 -> Mode2 -> Mode1
|
||||||
|
|
||||||
[Profile]
|
[Profile]
|
||||||
|
|
@ -12,10 +13,10 @@ Name=Example profile
|
||||||
ShiftButton=BTN_PINKY
|
ShiftButton=BTN_PINKY
|
||||||
|
|
||||||
[Mode1]
|
[Mode1]
|
||||||
# Single key: Fire -> E
|
# Single key: Fire -> E (optional name for UI)
|
||||||
Button.BTN_FIRE = key KEY_E
|
Button.BTN_FIRE = key KEY_E name "Primary Fire"
|
||||||
# Key combo: A -> Ctrl+X (modifiers first, then key)
|
# Key combo: A -> Ctrl+X (modifiers first, then key)
|
||||||
Button.BTN_A = key KEY_LEFTCTRL KEY_X
|
Button.BTN_A = key KEY_LEFTCTRL KEY_X name "Cut"
|
||||||
# Alt+Y
|
# Alt+Y
|
||||||
Button.BTN_B = key KEY_LEFTALT KEY_Y
|
Button.BTN_B = key KEY_LEFTALT KEY_Y
|
||||||
# Macro: T1 up -> Ctrl+Shift+T (on button down only)
|
# Macro: T1 up -> Ctrl+Shift+T (on button down only)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
#define DEFAULT_SHIFT_BUTTON "BTN_PINKY"
|
#define DEFAULT_SHIFT_BUTTON "BTN_PINKY"
|
||||||
#define PROFILE_NAME_LEN 128
|
#define PROFILE_NAME_LEN 128
|
||||||
#define SHIFT_BUTTON_STR_LEN 64
|
#define SHIFT_BUTTON_STR_LEN 64
|
||||||
|
#define ACTION_NAME_LEN 128
|
||||||
#define BUTTON_PREFIX_LEN (sizeof(BUTTON_PREFIX) - 1)
|
#define BUTTON_PREFIX_LEN (sizeof(BUTTON_PREFIX) - 1)
|
||||||
#define KEY_PREFIX "key"
|
#define KEY_PREFIX "key"
|
||||||
#define MACRO_PREFIX "macro"
|
#define MACRO_PREFIX "macro"
|
||||||
|
|
@ -59,6 +60,7 @@ typedef struct {
|
||||||
size_t macro_step_count;
|
size_t macro_step_count;
|
||||||
size_t *macro_step_len; /* length of each step */
|
size_t *macro_step_len; /* length of each step */
|
||||||
uint16_t *macro_keys; /* flat key codes, may be NULL */
|
uint16_t *macro_keys; /* flat key codes, may be NULL */
|
||||||
|
char action_name[ACTION_NAME_LEN]; /* optional display name (e.g. "Yaw Left") */
|
||||||
} profile_action_t;
|
} profile_action_t;
|
||||||
|
|
||||||
static profile_action_t layers[NUM_LAYERS][LIBX52IO_BUTTON_MAX];
|
static profile_action_t layers[NUM_LAYERS][LIBX52IO_BUTTON_MAX];
|
||||||
|
|
@ -96,8 +98,45 @@ static int key_name_to_code(const char *name)
|
||||||
return (code >= 0) ? code : -1;
|
return (code >= 0) ? code : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse optional " name ..." or " name \"...\"" from remainder at p into out->action_name.
|
||||||
|
*/
|
||||||
|
static void parse_optional_action_name(char *p, profile_action_t *out)
|
||||||
|
{
|
||||||
|
char *dst;
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
out->action_name[0] = '\0';
|
||||||
|
while (*p == ' ') p++;
|
||||||
|
if (strncasecmp(p, "name", 4) != 0 || (p[4] != ' ' && p[4] != '\0')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p += 4;
|
||||||
|
while (*p == ' ') p++;
|
||||||
|
dst = out->action_name;
|
||||||
|
n = ACTION_NAME_LEN - 1;
|
||||||
|
if (*p == '"') {
|
||||||
|
p++;
|
||||||
|
while (n > 0 && *p != '\0' && *p != '"') {
|
||||||
|
*dst++ = *p++;
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (n > 0 && *p != '\0' && *p != '\n' && *p != '\r') {
|
||||||
|
*dst++ = *p++;
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*dst = '\0';
|
||||||
|
/* Trim trailing space */
|
||||||
|
while (dst > out->action_name && (dst[-1] == ' ' || dst[-1] == '\t')) {
|
||||||
|
*--dst = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse value into a single key code (action KEY) or macro (action MACRO).
|
* Parse value into a single key code (action KEY) or macro (action MACRO).
|
||||||
|
* Optional trailing " name Display Name" or " name \"Quoted Name\"".
|
||||||
* Returns 0 on success, -1 on parse error.
|
* Returns 0 on success, -1 on parse error.
|
||||||
*/
|
*/
|
||||||
static int parse_action_value(const char *value, profile_action_t *out)
|
static int parse_action_value(const char *value, profile_action_t *out)
|
||||||
|
|
@ -138,6 +177,10 @@ static int parse_action_value(const char *value, profile_action_t *out)
|
||||||
if (*p == '\0') break;
|
if (*p == '\0') break;
|
||||||
tok = (char *)p;
|
tok = (char *)p;
|
||||||
while (*p != '\0' && *p != ' ') p++;
|
while (*p != '\0' && *p != ' ') p++;
|
||||||
|
if (n > 0 && (size_t)(p - tok) == 4 && strncasecmp(tok, "name", 4) == 0) {
|
||||||
|
p = tok;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (*p != '\0') { *p = '\0'; p++; }
|
if (*p != '\0') { *p = '\0'; p++; }
|
||||||
code = key_name_to_code(tok);
|
code = key_name_to_code(tok);
|
||||||
if (code < 0) return -1;
|
if (code < 0) return -1;
|
||||||
|
|
@ -149,6 +192,7 @@ static int parse_action_value(const char *value, profile_action_t *out)
|
||||||
memcpy(out->key_codes, keys, n * sizeof(uint16_t));
|
memcpy(out->key_codes, keys, n * sizeof(uint16_t));
|
||||||
out->key_len = n;
|
out->key_len = n;
|
||||||
out->type = ACTION_KEY;
|
out->type = ACTION_KEY;
|
||||||
|
parse_optional_action_name(p, out);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,6 +217,10 @@ static int parse_action_value(const char *value, profile_action_t *out)
|
||||||
if (*p == '\0') break;
|
if (*p == '\0') break;
|
||||||
tok = (char *)p;
|
tok = (char *)p;
|
||||||
while (*p != '\0' && *p != ' ') p++;
|
while (*p != '\0' && *p != ' ') p++;
|
||||||
|
if (n > 0 && (size_t)(p - tok) == 4 && strncasecmp(tok, "name", 4) == 0) {
|
||||||
|
p = tok;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (*p != '\0') *p++ = '\0';
|
if (*p != '\0') *p++ = '\0';
|
||||||
code = key_name_to_code(tok);
|
code = key_name_to_code(tok);
|
||||||
if (code < 0) return -1;
|
if (code < 0) return -1;
|
||||||
|
|
@ -233,6 +281,8 @@ static int parse_action_value(const char *value, profile_action_t *out)
|
||||||
out->macro_step_len = step_len_alloc;
|
out->macro_step_len = step_len_alloc;
|
||||||
out->macro_step_count = step_count;
|
out->macro_step_count = step_count;
|
||||||
out->type = ACTION_MACRO;
|
out->type = ACTION_MACRO;
|
||||||
|
/* p points past last segment; parse optional name from remainder */
|
||||||
|
parse_optional_action_name(p, out);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,6 +322,7 @@ static void free_action(profile_action_t *a)
|
||||||
a->macro_len = 0;
|
a->macro_len = 0;
|
||||||
a->macro_step_count = 0;
|
a->macro_step_count = 0;
|
||||||
}
|
}
|
||||||
|
a->action_name[0] = '\0';
|
||||||
a->type = ACTION_NONE;
|
a->type = ACTION_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -648,6 +699,31 @@ void x52d_profile_macro_wait_drained(void)
|
||||||
pthread_mutex_unlock(¯o_queue.mutex);
|
pthread_mutex_unlock(¯o_queue.mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *x52d_profile_get_action_name(const libx52io_report *report,
|
||||||
|
libx52io_button btn)
|
||||||
|
{
|
||||||
|
unsigned int chain_index;
|
||||||
|
int layer;
|
||||||
|
const profile_action_t *a;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!profile_loaded || report == NULL || btn >= LIBX52IO_BUTTON_MAX) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
chain_index = get_layer_index(report);
|
||||||
|
for (i = 0; i < MAX_FALLBACK; i++) {
|
||||||
|
layer = fallback_chain[chain_index][i];
|
||||||
|
if (layer < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
a = &layers[layer][btn];
|
||||||
|
if (a->type != ACTION_NONE) {
|
||||||
|
return (a->action_name[0] != '\0') ? a->action_name : NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void emit_macro(const profile_action_t *a)
|
static void emit_macro(const profile_action_t *a)
|
||||||
{
|
{
|
||||||
if (a->type != ACTION_MACRO || a->macro_keys == NULL || a->macro_step_len == NULL ||
|
if (a->type != ACTION_MACRO || a->macro_keys == NULL || a->macro_step_len == NULL ||
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,20 @@ void x52d_profile_apply(const libx52io_report *report,
|
||||||
*/
|
*/
|
||||||
void x52d_profile_macro_wait_drained(void);
|
void x52d_profile_macro_wait_drained(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the optional display name for the action bound to a button.
|
||||||
|
*
|
||||||
|
* Uses the same (mode, shift) layer and fallback as x52d_profile_apply().
|
||||||
|
* The name is set in the profile with e.g. "key KEY_A name Yaw Left".
|
||||||
|
*
|
||||||
|
* @param report Current joystick report (for mode and shift).
|
||||||
|
* @param btn Button index.
|
||||||
|
* @return The action name, or NULL if no mapping or no name set.
|
||||||
|
* Valid until x52d_profile_exit() or next x52d_profile_init().
|
||||||
|
*/
|
||||||
|
const char *x52d_profile_get_action_name(const libx52io_report *report,
|
||||||
|
libx52io_button btn);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static inline void x52d_profile_init(void) { (void)0; }
|
static inline void x52d_profile_init(void) { (void)0; }
|
||||||
|
|
@ -74,6 +88,12 @@ static inline void x52d_profile_apply(const libx52io_report *report,
|
||||||
(void)prev;
|
(void)prev;
|
||||||
}
|
}
|
||||||
static inline void x52d_profile_macro_wait_drained(void) { (void)0; }
|
static inline void x52d_profile_macro_wait_drained(void) { (void)0; }
|
||||||
|
static inline const char *x52d_profile_get_action_name(const libx52io_report *report,
|
||||||
|
libx52io_button btn) {
|
||||||
|
(void)report;
|
||||||
|
(void)btn;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* HAVE_EVDEV */
|
#endif /* HAVE_EVDEV */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -362,6 +362,172 @@ static void test_profile_get_name(void **state)
|
||||||
rmdir(tmpdir);
|
rmdir(tmpdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_profile_action_name_key(void **state)
|
||||||
|
{
|
||||||
|
char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX";
|
||||||
|
char path[256];
|
||||||
|
libx52io_report report, prev;
|
||||||
|
const char *action_name;
|
||||||
|
|
||||||
|
(void)state;
|
||||||
|
assert_non_null(mkdtemp(tmpdir));
|
||||||
|
|
||||||
|
snprintf(path, sizeof(path), "%s/test.conf", tmpdir);
|
||||||
|
write_profile_file(path,
|
||||||
|
"[Mode1]\n"
|
||||||
|
"Button.BTN_FIRE = key KEY_E name \"Primary Fire\"\n");
|
||||||
|
|
||||||
|
x52d_config_set("Profiles", "Directory", tmpdir);
|
||||||
|
x52d_config_set("Profiles", "Profile", "test");
|
||||||
|
x52d_profile_init();
|
||||||
|
|
||||||
|
memset(&report, 0, sizeof(report));
|
||||||
|
memset(&prev, 0, sizeof(prev));
|
||||||
|
report.mode = 1;
|
||||||
|
action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_FIRE);
|
||||||
|
assert_non_null(action_name);
|
||||||
|
assert_string_equal(action_name, "Primary Fire");
|
||||||
|
|
||||||
|
report.button[LIBX52IO_BTN_FIRE] = true;
|
||||||
|
key_record_reset();
|
||||||
|
x52d_profile_apply(&report, &prev);
|
||||||
|
assert_int_equal(key_record_n, 1);
|
||||||
|
assert_int_equal(key_record[0].code, key_code_from_name("KEY_E"));
|
||||||
|
|
||||||
|
x52d_profile_exit();
|
||||||
|
unlink(path);
|
||||||
|
rmdir(tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_profile_action_name_unquoted(void **state)
|
||||||
|
{
|
||||||
|
char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX";
|
||||||
|
char path[256];
|
||||||
|
libx52io_report report;
|
||||||
|
const char *action_name;
|
||||||
|
|
||||||
|
(void)state;
|
||||||
|
assert_non_null(mkdtemp(tmpdir));
|
||||||
|
|
||||||
|
snprintf(path, sizeof(path), "%s/test.conf", tmpdir);
|
||||||
|
write_profile_file(path,
|
||||||
|
"[Mode1]\n"
|
||||||
|
"Button.BTN_A = key KEY_X name YawLeft\n");
|
||||||
|
|
||||||
|
x52d_config_set("Profiles", "Directory", tmpdir);
|
||||||
|
x52d_config_set("Profiles", "Profile", "test");
|
||||||
|
x52d_profile_init();
|
||||||
|
|
||||||
|
memset(&report, 0, sizeof(report));
|
||||||
|
report.mode = 1;
|
||||||
|
action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_A);
|
||||||
|
assert_non_null(action_name);
|
||||||
|
assert_string_equal(action_name, "YawLeft");
|
||||||
|
|
||||||
|
x52d_profile_exit();
|
||||||
|
unlink(path);
|
||||||
|
rmdir(tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_profile_action_name_none(void **state)
|
||||||
|
{
|
||||||
|
char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX";
|
||||||
|
char path[256];
|
||||||
|
libx52io_report report;
|
||||||
|
const char *action_name;
|
||||||
|
|
||||||
|
(void)state;
|
||||||
|
assert_non_null(mkdtemp(tmpdir));
|
||||||
|
|
||||||
|
snprintf(path, sizeof(path), "%s/test.conf", tmpdir);
|
||||||
|
write_profile_file(path,
|
||||||
|
"[Mode1]\n"
|
||||||
|
"Button.BTN_FIRE = key KEY_E\n");
|
||||||
|
|
||||||
|
x52d_config_set("Profiles", "Directory", tmpdir);
|
||||||
|
x52d_config_set("Profiles", "Profile", "test");
|
||||||
|
x52d_profile_init();
|
||||||
|
|
||||||
|
memset(&report, 0, sizeof(report));
|
||||||
|
report.mode = 1;
|
||||||
|
action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_FIRE);
|
||||||
|
assert_null(action_name);
|
||||||
|
|
||||||
|
x52d_profile_exit();
|
||||||
|
unlink(path);
|
||||||
|
rmdir(tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_profile_action_name_shift_layer(void **state)
|
||||||
|
{
|
||||||
|
char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX";
|
||||||
|
char path[256];
|
||||||
|
libx52io_report report;
|
||||||
|
const char *action_name;
|
||||||
|
|
||||||
|
(void)state;
|
||||||
|
assert_non_null(mkdtemp(tmpdir));
|
||||||
|
|
||||||
|
snprintf(path, sizeof(path), "%s/test.conf", tmpdir);
|
||||||
|
write_profile_file(path,
|
||||||
|
"[Profile]\n"
|
||||||
|
"ShiftButton=BTN_PINKY\n"
|
||||||
|
"[Mode1]\n"
|
||||||
|
"Button.BTN_FIRE = key KEY_E name Unshifted\n"
|
||||||
|
"[Mode1.Shift]\n"
|
||||||
|
"Button.BTN_FIRE = key KEY_F name Shifted\n");
|
||||||
|
|
||||||
|
x52d_config_set("Profiles", "Directory", tmpdir);
|
||||||
|
x52d_config_set("Profiles", "Profile", "test");
|
||||||
|
x52d_profile_init();
|
||||||
|
|
||||||
|
memset(&report, 0, sizeof(report));
|
||||||
|
report.mode = 1;
|
||||||
|
report.button[LIBX52IO_BTN_PINKY] = false;
|
||||||
|
action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_FIRE);
|
||||||
|
assert_non_null(action_name);
|
||||||
|
assert_string_equal(action_name, "Unshifted");
|
||||||
|
|
||||||
|
report.button[LIBX52IO_BTN_PINKY] = true;
|
||||||
|
action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_FIRE);
|
||||||
|
assert_non_null(action_name);
|
||||||
|
assert_string_equal(action_name, "Shifted");
|
||||||
|
|
||||||
|
x52d_profile_exit();
|
||||||
|
unlink(path);
|
||||||
|
rmdir(tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_profile_action_name_macro(void **state)
|
||||||
|
{
|
||||||
|
char tmpdir[] = "/tmp/x52d_profile_test_XXXXXX";
|
||||||
|
char path[256];
|
||||||
|
libx52io_report report;
|
||||||
|
const char *action_name;
|
||||||
|
|
||||||
|
(void)state;
|
||||||
|
assert_non_null(mkdtemp(tmpdir));
|
||||||
|
|
||||||
|
snprintf(path, sizeof(path), "%s/test.conf", tmpdir);
|
||||||
|
write_profile_file(path,
|
||||||
|
"[Mode1]\n"
|
||||||
|
"Button.BTN_T1_UP = macro KEY_A KEY_B name \"Simple macro\"\n");
|
||||||
|
|
||||||
|
x52d_config_set("Profiles", "Directory", tmpdir);
|
||||||
|
x52d_config_set("Profiles", "Profile", "test");
|
||||||
|
x52d_profile_init();
|
||||||
|
|
||||||
|
memset(&report, 0, sizeof(report));
|
||||||
|
report.mode = 1;
|
||||||
|
action_name = x52d_profile_get_action_name(&report, LIBX52IO_BTN_T1_UP);
|
||||||
|
assert_non_null(action_name);
|
||||||
|
assert_string_equal(action_name, "Simple macro");
|
||||||
|
|
||||||
|
x52d_profile_exit();
|
||||||
|
unlink(path);
|
||||||
|
rmdir(tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
const struct CMUnitTest tests[] = {
|
const struct CMUnitTest tests[] = {
|
||||||
|
|
@ -372,6 +538,11 @@ int main(void)
|
||||||
cmocka_unit_test(test_profile_fallback_mode2_to_mode1),
|
cmocka_unit_test(test_profile_fallback_mode2_to_mode1),
|
||||||
cmocka_unit_test(test_profile_shift_layer),
|
cmocka_unit_test(test_profile_shift_layer),
|
||||||
cmocka_unit_test(test_profile_get_name),
|
cmocka_unit_test(test_profile_get_name),
|
||||||
|
cmocka_unit_test(test_profile_action_name_key),
|
||||||
|
cmocka_unit_test(test_profile_action_name_unquoted),
|
||||||
|
cmocka_unit_test(test_profile_action_name_none),
|
||||||
|
cmocka_unit_test(test_profile_action_name_shift_layer),
|
||||||
|
cmocka_unit_test(test_profile_action_name_macro),
|
||||||
};
|
};
|
||||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue