From 19eef030a89c77026dc30ddf44b0de140dd94ff1 Mon Sep 17 00:00:00 2001 From: sorgelig Date: Tue, 17 Aug 2021 05:24:32 +0800 Subject: [PATCH] input: support for joycon in combined/split modes, bt_auto_disconnect ini option. --- cfg.cpp | 1 + cfg.h | 1 + input.cpp | 324 +++++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 296 insertions(+), 30 deletions(-) diff --git a/cfg.cpp b/cfg.cpp index 55c601c..6c3beca 100644 --- a/cfg.cpp +++ b/cfg.cpp @@ -84,6 +84,7 @@ static const ini_var_t ini_vars[] = { "AFILTER_DEFAULT", (void*)(&(cfg.afilter_default)), STRING, 0, sizeof(cfg.afilter_default) - 1 }, { "VFILTER_DEFAULT", (void*)(&(cfg.vfilter_default)), STRING, 0, sizeof(cfg.vfilter_default) - 1 }, { "LOG_FILE_ENTRY", (void*)(&(cfg.log_file_entry)), UINT8, 0, 1 }, + { "BT_AUTO_DISCONNECT", (void*)(&(cfg.bt_auto_disconnect)), UINT32, 0, 180 }, }; static const int nvars = (int)(sizeof(ini_vars) / sizeof(ini_var_t)); diff --git a/cfg.h b/cfg.h index 3cd3d7c..7144cfa 100644 --- a/cfg.h +++ b/cfg.h @@ -53,6 +53,7 @@ typedef struct { uint8_t browse_expand; uint8_t logo; uint8_t log_file_entry; + int bt_auto_disconnect; char bootcore[256]; char video_conf[1024]; char video_conf_pal[1024]; diff --git a/input.cpp b/input.cpp index ab41206..3d5ecef 100644 --- a/input.cpp +++ b/input.cpp @@ -856,6 +856,7 @@ enum QUIRK QUIRK_MSSP, QUIRK_TOUCHGUN, QUIRK_VCS, + QUIRK_JOYCON, }; typedef struct @@ -903,6 +904,9 @@ typedef struct int lightgun_req; int lightgun; + int timeout; + char mac[64]; + int bind; char devname[32]; char id[80]; @@ -914,6 +918,12 @@ static devInput input[NUMDEV] = {}; static devInput player_pad[NUMPLAYERS] = {}; static devInput player_pdsp[NUMPLAYERS] = {}; +#define JOYCON_COMBO(dev) (input[(dev)].misc_flags & (1 << 31)) +#define JOYCON_LEFT(dev) (input[(dev)].misc_flags & (1 << 30)) +#define JOYCON_RIGHT(dev) (input[(dev)].misc_flags & (1 << 29)) +#define JOYCON_REQ(dev) ((input[(dev)].misc_flags & 7) == 7) +#define JOYCON_COMBINED(dev) (input[(dev)].quirk == QUIRK_JOYCON && JOYCON_COMBO((dev))) + #define BTN_NUM (sizeof(devInput::map) / sizeof(devInput::map[0])) int mfd = -1; @@ -1172,11 +1182,14 @@ void finish_map_setting(int dismiss) } else { - for (int i = 0; i < NUMDEV; i++) input[i].has_map = 0; + for (int i = 0; i < NUMDEV; i++) + { + input[i].has_map = 0; + input[i].has_mmap = 0; + } if (!dismiss) save_map(get_map_name(mapping_dev, 0), &input[mapping_dev].map, sizeof(input[mapping_dev].map)); if (dismiss == 2) delete_map(get_map_name(mapping_dev, 0)); - if (is_menu()) input[mapping_dev].has_mmap = 0; } } @@ -1733,10 +1746,26 @@ static int set_led(char *base, const char *led, int brightness) return 0; } +static int get_led(char *base, const char *led) +{ + static char path[1024]; + sprintf(path, "%s%s/brightness", base, led); + FILE* f = fopen(path, "r"); + if (f) + { + int res = 0; + fscanf(f, "%d", &res); + fclose(f); + return res; + } + + return 0; +} + static void update_num_hw(int dev, int num) { char *led_path; - if (num > 6) num = 6; + if (num > 7) num = 7; if (input[dev].quirk == QUIRK_DS4 || input[dev].quirk == QUIRK_DS4TOUCH) { @@ -1753,7 +1782,7 @@ static void update_num_hw(int dev, int num) else { //dualshock4 - static const uint8_t color_code[7][3] = + static const uint8_t color_code[8][3] = { { 0x30, 0x30, 0x30 }, // White { 0x00, 0x00, 0x40 }, // Blue @@ -1761,7 +1790,8 @@ static void update_num_hw(int dev, int num) { 0x00, 0x40, 0x00 }, // Green { 0x20, 0x00, 0x20 }, // Pink { 0x40, 0x10, 0x00 }, // Orange - { 0x00, 0x20, 0x20 } // Teal + { 0x00, 0x20, 0x20 }, // Teal + { 0x00, 0x00, 0x00 } // none }; set_led(led_path, ":blue", color_code[num][2]); @@ -1778,7 +1808,7 @@ static void update_num_hw(int dev, int num) set_led(led_path, "::sony1", (num == 0 || num == 1 || num == 5)); set_led(led_path, "::sony2", (num == 0 || num == 2 || num == 6)); set_led(led_path, "::sony3", (num == 0 || num == 3)); - set_led(led_path, "::sony4", (num == 0 || num >= 4)); + set_led(led_path, "::sony4", (num == 0 || num == 4 || num == 5 || num == 6)); } } else if (input[dev].quirk == QUIRK_WIIMOTE) @@ -1789,20 +1819,27 @@ static void update_num_hw(int dev, int num) set_led(led_path, ":blue:p0", (num == 0 || num == 1 || num == 5)); set_led(led_path, ":blue:p1", (num == 0 || num == 2 || num == 6)); set_led(led_path, ":blue:p2", (num == 0 || num == 3)); - set_led(led_path, ":blue:p3", (num == 0 || num >= 4)); + set_led(led_path, ":blue:p3", (num == 0 || num == 4 || num == 5 || num == 6)); } } else if (input[dev].vid == 0x057e && ((input[dev].pid & 0xFF00) == 0x2000)) { // nintendo switch controllers - led_path = get_led_path(dev); - if (led_path) + int repeat = 1; + while (1) { - set_led(led_path, ":home", num ? 1 : 15); - set_led(led_path, ":player1", (num == 0 || num == 1 || num == 5)); - set_led(led_path, ":player2", (num == 0 || num == 2 || num == 6)); - set_led(led_path, ":player3", (num == 0 || num == 3)); - set_led(led_path, ":player4", (num == 0 || num >= 4)); + led_path = get_led_path(dev); + if (led_path) + { + set_led(led_path, ":home", num ? 1 : 15); + set_led(led_path, ":player1", (num == 0 || num == 1 || num == 5)); + set_led(led_path, ":player2", (num == 0 || num == 2 || num == 6)); + set_led(led_path, ":player3", (num == 0 || num == 3)); + set_led(led_path, ":player4", (num == 0 || num == 4 || num == 5 || num == 6)); + } + + if (repeat && JOYCON_COMBINED(dev)) dev = input[dev].bind; else break; + repeat = 0; } } } @@ -1833,7 +1870,7 @@ static void store_player(int num, int dev) static void restore_player(int dev) { // do not restore bound devices - if (dev != input[dev].bind) return; + if (dev != input[dev].bind && !(JOYCON_COMBINED(dev) && JOYCON_LEFT(dev))) return; devInput *player = (input[dev].quirk == QUIRK_PDSP || input[dev].quirk == QUIRK_MSSP) ? player_pdsp : player_pad; for (int k = 1; k < NUMPLAYERS; k++) @@ -1844,6 +1881,11 @@ static void restore_player(int dev) input[dev].num = k; input[dev].map_shown = player[k].map_shown; + if (JOYCON_COMBINED(dev)) + { + input[input[dev].bind].num = k; + input[input[dev].bind].map_shown = player[k].map_shown; + } memcpy(input[dev].jkmap, player[k].jkmap, sizeof(input[dev].jkmap)); input[dev].lightgun = player[k].lightgun; @@ -1881,13 +1923,18 @@ static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int int sub_dev = dev; //check if device is a part of multifunctional device - if (input[dev].bind >= 0) dev = input[dev].bind; + if (!JOYCON_COMBINED(dev) && input[dev].bind >= 0) dev = input[dev].bind; - //mouse - if (ev->type == EV_KEY && ev->code >= BTN_MOUSE && ev->code < BTN_JOYSTICK) + if (ev->type == EV_KEY) { - //skip it. we use /dev/input/mice - return; + if (input[dev].timeout > 0) input[dev].timeout = cfg.bt_auto_disconnect * 10; + + //mouse + if (ev->code >= BTN_MOUSE && ev->code < BTN_JOYSTICK) + { + //skip it. we use /dev/input/mice + return; + } } static int key_mapped = 0; @@ -1977,6 +2024,7 @@ static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int if (!found) { input[dev].num = num; + if (JOYCON_COMBINED(dev)) input[input[dev].bind].num = num; store_player(num, dev); printf("Device %s assigned to player %d\n", input[dev].id, input[dev].num); break; @@ -1988,6 +2036,7 @@ static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int if (!input[dev].map_shown && input[dev].num) { input[dev].map_shown = 1; + if (JOYCON_COMBINED(dev)) input[input[dev].bind].map_shown = 1; store_player(input[dev].num, dev); if (cfg.controller_info) @@ -2877,6 +2926,8 @@ void mergedevs() { strcpy(input[i].id, id); strcpy(input[i].sysfs, sysfs); + strcpy(input[i].mac, uniq); + input[i].timeout = (strlen(uniq) && strstr(sysfs, "bluetooth")) ? (cfg.bt_auto_disconnect * 10) : 0; } } } @@ -3326,12 +3377,168 @@ static int vcs_proc(int dev, input_event *ev) return 1; } +void check_joycon() +{ + while (1) + { + int l = -1, r = -1; + int id_combo = 0; + + for (int i = 0; i < NUMDEV; i++) + { + if (input[i].quirk == QUIRK_JOYCON && !JOYCON_COMBO(i)) + { + if (JOYCON_LEFT(i)) + { + int id = 0; + char *led_path = get_led_path(i); + if (led_path) id = get_led(led_path, ":combo"); + if (id && (!id_combo || id_combo == id)) + { + id_combo = id; + l = i; + } + } + else if (JOYCON_RIGHT(i)) + { + int id = 0; + char *led_path = get_led_path(i); + if (led_path) id = get_led(led_path, ":combo"); + if (id && (!id_combo || id_combo == id)) + { + id_combo = id; + r = i; + } + } + } + } + + if (l >= 0 && r >= 0) + { + printf("** joycon_l = %d, joycon_r = %d, id = %d\n", l, r, id_combo); + + input[l].bind = r; + input[r].bind = l; + input[l].misc_flags |= 1 << 31; + input[r].misc_flags |= 1 << 31; + strcpy(input[l].idstr, "057e_2009"); + strcpy(input[r].idstr, "057e_2009"); + } + else break; + } +} + +int process_joycon(int dev, input_event *ev, input_absinfo *absinfo) +{ + if (ev->type == EV_ABS) + { + if (JOYCON_COMBO(dev)) return 0; + if (ev->code == 4 && JOYCON_RIGHT(dev)) ev->value = -ev->value; + if (ev->code == 0 && JOYCON_LEFT(dev)) ev->value = -ev->value; + return 0; + } + + int mask = 0; + + // simulate DPAD on left joycon + if (JOYCON_COMBO(dev) && (ev->code & ~3) == 0x220) + { + mask = 0x100 << (ev->code & 3); + input[dev].misc_flags = ev->value ? (input[dev].misc_flags | mask) : (input[dev].misc_flags & ~mask); + if (ev->value) + { + ev->value = (ev->code & 1) ? 1 : -1; + } + else + { + mask = (ev->code & 2) ? 0x400 : 0x100; + ev->value = (input[dev].misc_flags & mask) ? -1 : (input[dev].misc_flags & (mask << 1)) ? 1 : 0; + } + + ev->code = (ev->code & 2) ? 16 : 17; + ev->type = EV_ABS; + absinfo->minimum = -1; + absinfo->maximum = 1; + return 0; + } + + //check for request to combine/split joycons + switch (ev->code) + { + case 0x136: case 0x137: mask = 1; break; + case 0x138: case 0x139: mask = 2; break; + case 0x13D: case 0x13E: mask = 4; break; + default: return 0; + } + + input[dev].misc_flags = ev->value ? (input[dev].misc_flags | mask) : (input[dev].misc_flags & ~mask); + + if (JOYCON_REQ(dev)) + { + int uncombo = 0; + int l = -1, r = -1; + for (int n = 0; n < NUMDEV; n++) + { + if (input[n].quirk == QUIRK_JOYCON) + { + if (JOYCON_COMBO(n)) + { + if (JOYCON_REQ(n) && JOYCON_REQ(input[n].bind)) + { + r = n; + l = input[n].bind; + uncombo = 1; + break; + } + } + else if (JOYCON_RIGHT(n) && JOYCON_REQ(n)) r = n; + else if (JOYCON_LEFT(n) && JOYCON_REQ(n)) l = n; + } + } + + if (l >= 0 && r >= 0) + { + uint8_t id = 0; + char *led_path; + + printf(uncombo ? "Joycons request split\n" : "Joycons request combo\n"); + + if (!uncombo) + { + FileLoad("/tmp/combo_id", &id, sizeof(id)); + if (!(++id)) ++id; + FileSave("/tmp/combo_id", &id, sizeof(id)); + } + + led_path = get_led_path(l); if (led_path) set_led(led_path, ":combo", id); + led_path = get_led_path(r); if (led_path) set_led(led_path, ":combo", id); + + printf("Close all devices.\n"); + for (int i = 0; i < NUMDEV; i++) if (pool[i].fd >= 0) + { + ioctl(pool[i].fd, EVIOCGRAB, 0); + close(pool[i].fd); + } + update_num_hw(l, 7); + update_num_hw(r, 7); + usleep(500000); + update_num_hw(l, 0); + update_num_hw(r, 0); + usleep(500000); + return 1; + } + } + + return 0; +} + int input_test(int getchar) { static char cur_leds = 0; static int state = 0; struct input_absinfo absinfo; struct input_event ev; + static uint32_t timeout = 0; if (touch_rel && CheckTimer(touch_rel)) { @@ -3363,6 +3570,7 @@ int input_test(int getchar) if (state == 1) { + timeout = 0; printf("Open up to %d input devices.\n", NUMDEV); for (int i = 0; i < NUMDEV; i++) { @@ -3509,6 +3717,17 @@ int input_test(int getchar) } } + if (input[n].vid == 0x057e && input[n].pid == 0x2006) + { + input[n].misc_flags = 1 << 30; + input[n].quirk = QUIRK_JOYCON; + } + if (input[n].vid == 0x057e && input[n].pid == 0x2007) + { + input[n].misc_flags = 1 << 29; + input[n].quirk = QUIRK_JOYCON; + } + //Ultimarc lightgun if (input[n].vid == 0xd209 && input[n].pid == 0x1601) { @@ -3571,6 +3790,7 @@ int input_test(int getchar) closedir(d); mergedevs(); + check_joycon(); for (int i = 0; i < n; i++) { printf("opened %d(%2d): %s (%04x:%04x) %d \"%s\" \"%s\"\n", i, input[i].bind, input[i].devname, input[i].vid, input[i].pid, input[i].quirk, input[i].id, input[i].name); @@ -3582,6 +3802,34 @@ int input_test(int getchar) state++; } + if (cfg.bt_auto_disconnect) + { + if (!timeout) timeout = GetTimer(6000); + else if (CheckTimer(timeout)) + { + timeout = GetTimer(6000); + for (int i = 0; i < NUMDEV; i++) + { + if (pool[i].fd >= 0 && input[i].timeout > 0) + { + if (!(JOYCON_COMBINED(i) && JOYCON_LEFT(i)) && input[i].bind != i) continue; + input[i].timeout--; + if (!input[i].timeout) + { + static char cmd[128]; + sprintf(cmd, "btctl disconnect %s", input[i].mac); + system(cmd); + if (JOYCON_COMBINED(i)) + { + sprintf(cmd, "btctl disconnect %s", input[input[i].bind].mac); + system(cmd); + } + } + } + } + } + } + if (state == 2) { int timeout = 0; @@ -3610,8 +3858,9 @@ int input_test(int getchar) return 0; } - for (int i = 0; i < NUMDEV; i++) + for (int pos = 0; pos < NUMDEV; pos++) { + int i = pos; if ((pool[i].fd >= 0) && (pool[i].revents & POLLIN)) { if (!input[i].mouse) @@ -3629,7 +3878,7 @@ int input_test(int getchar) else if (ev.type) { int dev = i; - if (input[dev].bind >= 0) dev = input[dev].bind; + if (!JOYCON_COMBINED(i) && input[dev].bind >= 0) dev = input[dev].bind; int noabs = 0; @@ -3700,8 +3949,8 @@ int input_test(int getchar) } else if (ev.code == 0) { - absinfo.minimum = 200; - absinfo.maximum = 1720; + absinfo.minimum = 200; + absinfo.maximum = 1720; } else continue; } @@ -3795,6 +4044,15 @@ int input_test(int getchar) } } + if (input[i].quirk == QUIRK_JOYCON) + { + if (process_joycon(i, &ev, &absinfo)) + { + state = 1; + return 0; + } + } + //Menu combo on 8BitDo receiver in PSC mode if (input[dev].vid == 0x054c && input[dev].pid == 0x0cda && ev.type == EV_KEY) { @@ -3848,7 +4106,7 @@ int input_test(int getchar) { //keyboard, buttons case EV_KEY: - printf("Input event: type=EV_KEY, code=%d(0x%x), value=%d, jnum=%d, ID:%04x:%04x:%02d\n", ev.code, ev.code, ev.value, input[dev].num, input[dev].vid, input[dev].pid, i); + printf("%04x:%04x:%02d P%d Input event: type=EV_KEY, code=%d(0x%x), value=%d\n", input[dev].vid, input[dev].pid, i, input[dev].num, ev.code, ev.code, ev.value); break; case EV_REL: @@ -3858,7 +4116,7 @@ int input_test(int getchar) if (!timeout || CheckTimer(timeout)) { timeout = GetTimer(20); - printf("Input event: type=EV_REL, Axis=%d, Offset=%d, jnum=%d, ID:%04x:%04x:%02d\n", ev.code, ev.value, input[dev].num, input[dev].vid, input[dev].pid, i); + printf("%04x:%04x:%02d P%d Input event: type=EV_REL, Axis=%d, Offset=%d\n", input[dev].vid, input[dev].pid, i, input[dev].num, ev.code, ev.value); } } break; @@ -3888,9 +4146,8 @@ int input_test(int getchar) if (ev.code == 2) break; } - printf("Input event: type=EV_ABS, Axis=%d, Offset=%d, jnum=%d, ID:%04x:%04x:%02d,", ev.code, ev.value, input[dev].num, input[dev].vid, input[dev].pid, i); - printf(" abs_min = %d, abs_max = %d", absinfo.minimum, absinfo.maximum); - if (absinfo.fuzz) printf(", fuzz = %d", absinfo.fuzz); + printf("%04x:%04x:%02d P%d Input event: type=EV_ABS, Axis=%d [%d...%d], Offset=%d", input[dev].vid, input[dev].pid, i, input[dev].num, ev.code, absinfo.minimum, absinfo.maximum, ev.value); + //if (absinfo.fuzz) printf(", fuzz = %d", absinfo.fuzz); if (absinfo.resolution) printf(", res = %d", absinfo.resolution); printf("\n"); } @@ -3898,7 +4155,7 @@ int input_test(int getchar) break; default: - printf("Input event: type=%d, code=%d(0x%x), value=%d(0x%x), jnum=%d, ID:%04x:%04x:%02d\n", ev.type, ev.code, ev.code, ev.value, ev.value, input[dev].num, input[dev].vid, input[dev].pid, i); + printf("%04x:%04x:%02d P%d Input event: type=%d, code=%d(0x%x), value=%d(0x%x)\n", input[dev].vid, input[dev].pid, i, input[dev].num, ev.type, ev.code, ev.code, ev.value, ev.value); } } @@ -3938,6 +4195,13 @@ int input_test(int getchar) } } + // redirect further actions to left joycon in combined mode + if (JOYCON_COMBINED(i)) + { + if (JOYCON_RIGHT(i)) i = input[i].bind; + dev = i; + } + if (!noabs) input_cb(&ev, &absinfo, i); //sumulate digital directions from analog