Skip to content

Commit

Permalink
Controller device reservation updates
Browse files Browse the repository at this point in the history
- Reservation type: reserved, preferred, or none
- Menu support for setting type and reserved device
- Test joypad driver extension for controller disconnect
  • Loading branch information
zoltanvb committed Jun 2, 2024
1 parent 247489b commit ac70e1d
Show file tree
Hide file tree
Showing 23 changed files with 643 additions and 59 deletions.
27 changes: 26 additions & 1 deletion configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -3737,6 +3737,10 @@ static bool config_load_file(global_t *global,

strlcpy(buf + _len2, "_analog_dpad_mode", sizeof(buf) - _len2);
CONFIG_GET_INT_BASE(conf, settings, uints.input_analog_dpad_mode[i], buf);

strlcpy(buf + _len2, "_device_reservation_type", sizeof(buf) - _len2);
CONFIG_GET_INT_BASE(conf, settings, uints.input_device_reservation_type[i], buf);

}
}

Expand Down Expand Up @@ -5270,7 +5274,6 @@ bool config_save_file(const char *path)
size_t _len;
char cfg[64];
char formatted_number[4];

formatted_number[0] = '\0';

snprintf(formatted_number, sizeof(formatted_number), "%u", i + 1);
Expand All @@ -5290,6 +5293,9 @@ bool config_save_file(const char *path)

strlcpy(cfg + _len, "_analog_dpad_mode", sizeof(cfg) - _len);
config_set_int(conf, cfg, settings->uints.input_analog_dpad_mode[i]);

strlcpy(cfg + _len, "_device_reservation_type", sizeof(cfg) - _len);
config_set_int(conf, cfg, settings->uints.input_device_reservation_type[i]);
}

/* Boolean settings */
Expand Down Expand Up @@ -5624,6 +5630,25 @@ int8_t config_save_overrides(enum override_type type,
RARCH_DBG("[Overrides]: %s = \"%u\"\n", cfg, overrides->uints.input_analog_dpad_mode[i]);
}

if (settings->uints.input_device_reservation_type[i]
!= overrides->uints.input_device_reservation_type[i])
{
strlcpy(cfg + _len, "_device_reservation_type", sizeof(cfg) - _len);
config_set_int(conf, cfg, overrides->uints.input_device_reservation_type[i]);
RARCH_DBG("[Overrides]: %s = \"%u\"\n", cfg, overrides->uints.input_device_reservation_type[i]);
}

// TODO: is this whole section really necessary? Does the loop above not do this?
if (!string_is_equal(settings->arrays.input_reserved_devices[i], overrides->arrays.input_reserved_devices[i]))
{
strlcpy(cfg + _len, "_device_reservation_type", sizeof(cfg) - _len);

config_set_string(conf, cfg,
overrides->arrays.input_reserved_devices[i]);
RARCH_DBG("[Overrides]: %s = \"%s\"\n",
cfg, overrides->arrays.input_reserved_devices[i]);
}

for (j = 0; j < RARCH_BIND_LIST_END; j++)
{
const struct retro_keybind *override_bind = &input_override_binds[i][j];
Expand Down
1 change: 1 addition & 0 deletions configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ typedef struct settings

unsigned input_libretro_device[MAX_USERS];
unsigned input_analog_dpad_mode[MAX_USERS];
unsigned input_device_reservation_type[MAX_USERS];

unsigned input_remap_ports[MAX_USERS];
unsigned input_remap_ids[MAX_USERS][RARCH_CUSTOM_BIND_LIST_END];
Expand Down
22 changes: 18 additions & 4 deletions input/drivers_joypad/test_joypad.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#endif

#define JOYPAD_TEST_COMMAND_ADD_CONTROLLER 1
#define JOYPAD_TEST_COMMAND_REMOVE_CONTROLLER 2
#define JOYPAD_TEST_COMMAND_BUTTON_PRESS_FIRST 16
#define JOYPAD_TEST_COMMAND_BUTTON_PRESS_LAST 31
#define JOYPAD_TEST_COMMAND_BUTTON_RELEASE_FIRST 32
Expand Down Expand Up @@ -286,17 +287,18 @@ static const char *test_joypad_name(unsigned pad)
if (pad >= MAX_USERS || string_is_empty(test_joypads[pad].name))
return NULL;

return test_joypads[pad].name;
if (strstr(test_joypads[pad].name, ") "))
return strstr(test_joypads[pad].name, ") ") + 2;
else
return test_joypads[pad].name;
}


static void test_joypad_autodetect_add(unsigned autoconf_pad)
{
int vid = 0;
int pid = 0;

sscanf(strstr(test_joypad_name(autoconf_pad), "(") + 1, "%04x:%04x", &vid, &pid);
//sscanf(test_joypad_name(autoconf_pad), "%04x:%04x ", &vid, &pid);
sscanf(strstr(test_joypads[autoconf_pad].name, "(") + 1, "%04x:%04x", &vid, &pid);
RARCH_DBG("[Test input driver]: Autoconf vid/pid %x:%x\n",vid,pid);

input_autoconfigure_connect(
Expand All @@ -309,6 +311,13 @@ static void test_joypad_autodetect_add(unsigned autoconf_pad)
);
}

static void test_joypad_autodetect_remove(unsigned autoconf_pad)
{
RARCH_DBG("[Test input driver]: Autoremove port %d\n", autoconf_pad);

input_autoconfigure_disconnect(autoconf_pad, test_joypad_name(autoconf_pad));
}

static void *test_joypad_init(void *data)
{
settings_t *settings = config_get_ptr();
Expand Down Expand Up @@ -412,6 +421,11 @@ static void test_joypad_poll(void)
test_joypad_autodetect_add(input_test_steps[i].param_num);
input_test_steps[i].handled = true;
}
else if (input_test_steps[i].action == JOYPAD_TEST_COMMAND_REMOVE_CONTROLLER)
{
test_joypad_autodetect_remove(input_test_steps[i].param_num);
input_test_steps[i].handled = true;
}
else if( input_test_steps[i].action >= JOYPAD_TEST_COMMAND_BUTTON_PRESS_FIRST &&
input_test_steps[i].action <= JOYPAD_TEST_COMMAND_BUTTON_PRESS_LAST)
{
Expand Down
8 changes: 8 additions & 0 deletions input/input_defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,14 @@ enum input_turbo_default_button
INPUT_TURBO_DEFAULT_BUTTON_LAST
};

enum input_device_reservation_type
{
INPUT_DEVICE_RESERVATION_NONE = 0,
INPUT_DEVICE_RESERVATION_PREFERRED,
INPUT_DEVICE_RESERVATION_RESERVED,
INPUT_DEVICE_RESERVATION_LAST
};

RETRO_END_DECLS

#endif
28 changes: 28 additions & 0 deletions intl/msg_hash_us.h
Original file line number Diff line number Diff line change
Expand Up @@ -4125,6 +4125,34 @@ MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_DEVICE_INDEX,
"The physical controller as recognized by RetroArch."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_DEVICE_RESERVED_DEVICE_NAME,
"Reserved device for this player"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME,
"This controller will be allocated for this player, according to reservation mode."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_DEVICE_RESERVATION_NONE,
"No reservation"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_DEVICE_RESERVATION_PREFERRED,
"Preferred"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_DEVICE_RESERVATION_RESERVED,
"Reserved"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_DEVICE_RESERVATION_TYPE,
"Device reservation type"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_DEVICE_RESERVATION_TYPE,
"Preferred: if specified device is present, it will be allocated for this player. Reserved: no other controller will be allocated for this player."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_REMAP_PORT,
"Mapped Port"
Expand Down
2 changes: 2 additions & 0 deletions menu/cbs/menu_cbs_deferred_push.c
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ GENERIC_DEFERRED_PUSH_GENERAL(deferred_push_dropdown_box_list_disk_index, PUSH_D
GENERIC_DEFERRED_PUSH_GENERAL(deferred_push_dropdown_box_list_input_device_type, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_INPUT_DEVICE_TYPE)
GENERIC_DEFERRED_PUSH_GENERAL(deferred_push_dropdown_box_list_input_description, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_INPUT_DESCRIPTION)
GENERIC_DEFERRED_PUSH_GENERAL(deferred_push_dropdown_box_list_input_description_kbd, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_INPUT_DESCRIPTION_KBD)
GENERIC_DEFERRED_PUSH_GENERAL(deferred_push_dropdown_box_list_input_select_reserved_device, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_INPUT_SELECT_RESERVED_DEVICE)
#ifdef ANDROID
GENERIC_DEFERRED_PUSH_GENERAL(deferred_push_dropdown_box_list_input_select_physical_keyboard, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_INPUT_SELECT_PHYSICAL_KEYBOARD)
#endif
Expand Down Expand Up @@ -726,6 +727,7 @@ static int menu_cbs_init_bind_deferred_push_compare_label(
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DEVICE_TYPE, deferred_push_dropdown_box_list_input_device_type},
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION, deferred_push_dropdown_box_list_input_description},
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION_KBD, deferred_push_dropdown_box_list_input_description_kbd},
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_SELECT_RESERVED_DEVICE, deferred_push_dropdown_box_list_input_select_reserved_device},
#ifdef ANDROID
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_SELECT_PHYSICAL_KEYBOARD, deferred_push_dropdown_box_list_input_select_physical_keyboard},
#endif
Expand Down
68 changes: 68 additions & 0 deletions menu/cbs/menu_cbs_ok.c
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,8 @@ static enum msg_hash_enums action_ok_dl_to_enum(unsigned lbl)
return MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION;
case ACTION_OK_DL_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION_KBD:
return MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION_KBD;
case ACTION_OK_DL_DROPDOWN_BOX_LIST_INPUT_SELECT_RESERVED_DEVICE:
return MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_SELECT_RESERVED_DEVICE;
#ifdef ANDROID
case ACTION_OK_DL_DROPDOWN_BOX_LIST_INPUT_SELECT_PHYSICAL_KEYBOARD:
return MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_SELECT_PHYSICAL_KEYBOARD;
Expand Down Expand Up @@ -869,6 +871,15 @@ int generic_action_ok_displaylist_push(
info.enum_idx = MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DEVICE_TYPE;
dl_type = DISPLAYLIST_GENERIC;
break;
case ACTION_OK_DL_DROPDOWN_BOX_LIST_INPUT_SELECT_RESERVED_DEVICE:
info.type = type;
info.directory_ptr = idx;
info_path = path;
info_label = msg_hash_to_str(
MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_SELECT_RESERVED_DEVICE);
info.enum_idx = MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_SELECT_RESERVED_DEVICE;
dl_type = DISPLAYLIST_GENERIC;
break;
#ifdef ANDROID
case ACTION_OK_DL_DROPDOWN_BOX_LIST_INPUT_SELECT_PHYSICAL_KEYBOARD:
info.type = type;
Expand Down Expand Up @@ -7318,6 +7329,60 @@ static int action_ok_push_dropdown_item_input_device_type(const char *path,
return action_cancel_pop_default(NULL, NULL, 0, 0);
}

static int action_ok_push_dropdown_item_input_select_reserved_device(const char *path,
const char *label, unsigned type, size_t idx, size_t entry_idx)
{
char* device;
const char *no_device;
const char *reserved_device_name;
enum msg_hash_enums enum_idx;
rarch_setting_t *setting = NULL;
settings_t *settings = config_get_ptr();
const char *menu_path = NULL;
struct menu_state *menu_st = menu_state_get_ptr();
menu_entries_get_last_stack(&menu_path, NULL, NULL, NULL, NULL);
enum_idx = (enum msg_hash_enums)atoi(menu_path);
setting = menu_setting_find_enum(enum_idx);
unsigned user = enum_idx - MENU_ENUM_LABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME;

if (!setting)
return -1;

reserved_device_name = path;
no_device = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NONE);

if (string_is_equal(reserved_device_name, no_device))
settings->arrays.input_reserved_devices[user][0] = '\0';
else
{
int i;
for (i = 0; i < MAX_INPUT_DEVICES; i++)
{
const char* device_name = input_config_get_device_name(i);
if (string_is_equal(device_name, reserved_device_name))
{
uint16_t vendor_id = input_config_get_device_vid(i);
uint16_t product_id = input_config_get_device_pid(i);
snprintf(settings->arrays.input_reserved_devices[user],
sizeof(settings->arrays.input_reserved_devices[user]),
"%04x:%04x %s",
vendor_id, product_id, reserved_device_name);
break;
}
}
}
settings->modified = true;

command_event(CMD_EVENT_REINIT, NULL);

/* Refresh menu */
menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH
| MENU_ST_FLAG_PREVENT_POPULATE;

return action_cancel_pop_default(NULL, NULL, 0, 0);
}


#ifdef ANDROID
static int action_ok_push_dropdown_item_input_select_physical_keyboard(const char *path,
const char *label, unsigned type, size_t idx, size_t entry_idx)
Expand Down Expand Up @@ -9249,6 +9314,9 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs,
case MENU_SETTING_DROPDOWN_ITEM_INPUT_DEVICE_TYPE:
BIND_ACTION_OK(cbs, action_ok_push_dropdown_item_input_device_type);
break;
case MENU_SETTING_DROPDOWN_ITEM_INPUT_SELECT_RESERVED_DEVICE:
BIND_ACTION_OK(cbs, action_ok_push_dropdown_item_input_select_reserved_device);
break;
#ifdef ANDROID
case MENU_SETTING_DROPDOWN_ITEM_INPUT_SELECT_PHYSICAL_KEYBOARD:
BIND_ACTION_OK(cbs, action_ok_push_dropdown_item_input_select_physical_keyboard);
Expand Down
10 changes: 10 additions & 0 deletions menu/cbs/menu_cbs_sublabel.c
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,8 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_device_type, ME
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_device_index, MENU_ENUM_SUBLABEL_INPUT_DEVICE_INDEX)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_mouse_index, MENU_ENUM_SUBLABEL_INPUT_MOUSE_INDEX)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_adc_type, MENU_ENUM_SUBLABEL_INPUT_ADC_TYPE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_device_reservation_type, MENU_ENUM_SUBLABEL_INPUT_DEVICE_RESERVATION_TYPE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_device_reserved_device_name, MENU_ENUM_SUBLABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_bind_all, MENU_ENUM_SUBLABEL_INPUT_BIND_ALL)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_save_autoconfig, MENU_ENUM_SUBLABEL_INPUT_SAVE_AUTOCONFIG)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_bind_defaults, MENU_ENUM_SUBLABEL_INPUT_BIND_DEFAULTS)
Expand Down Expand Up @@ -5645,6 +5647,14 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
MENU_ENUM_LABEL_INPUT_DEVICE_INDEX,
NULL
},*/
{
MENU_ENUM_LABEL_INPUT_DEVICE_RESERVATION_TYPE,
action_bind_sublabel_input_device_reservation_type
},
{
MENU_ENUM_LABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME,
action_bind_sublabel_input_device_reserved_device_name
},
{
MENU_ENUM_LABEL_INPUT_MOUSE_INDEX,
action_bind_sublabel_input_mouse_index
Expand Down
11 changes: 11 additions & 0 deletions menu/cbs/menu_cbs_title.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,16 @@ static int action_get_title_dropdown_item(
(enum_idx <= MENU_ENUM_LABEL_INPUT_DEVICE_INDEX_LAST))
enum_idx = MENU_ENUM_LABEL_VALUE_INPUT_DEVICE_INDEX;

/* Device Reservation Type */
if ((enum_idx >= MENU_ENUM_LABEL_INPUT_DEVICE_RESERVATION_TYPE) &&
(enum_idx <= MENU_ENUM_LABEL_INPUT_DEVICE_RESERVATION_TYPE_LAST))
enum_idx = MENU_ENUM_LABEL_VALUE_INPUT_DEVICE_RESERVATION_TYPE;

/* Reserved Device Name */
if ((enum_idx >= MENU_ENUM_LABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME) &&
(enum_idx <= MENU_ENUM_LABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME_LAST))
enum_idx = MENU_ENUM_LABEL_VALUE_INPUT_DEVICE_RESERVED_DEVICE_NAME;

/* Mouse Index */
if ((enum_idx >= MENU_ENUM_LABEL_INPUT_MOUSE_INDEX) &&
(enum_idx <= MENU_ENUM_LABEL_INPUT_MOUSE_INDEX_LAST))
Expand Down Expand Up @@ -1832,6 +1842,7 @@ int menu_cbs_init_bind_title(menu_file_list_cbs_t *cbs,
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DEVICE_TYPE, action_get_title_dropdown_item},
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION, action_get_title_dropdown_input_description},
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION_KBD, action_get_title_dropdown_input_description_kbd},
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_SELECT_RESERVED_DEVICE, action_get_title_dropdown_item},
#ifdef ANDROID
{MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_SELECT_PHYSICAL_KEYBOARD, action_get_title_dropdown_item},
#endif
Expand Down
16 changes: 8 additions & 8 deletions menu/drivers/ozone.c
Original file line number Diff line number Diff line change
Expand Up @@ -2369,22 +2369,22 @@ static uintptr_t ozone_entries_icon_get_texture(
/* account for the additional split joycon option in Input User # Binds */
input_id++;
#endif
if (type == input_id + 1)
if (type >= input_id + 1 && type <= input_id + 3)
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_SETTINGS];
if (type == input_id + 2)
if (type == input_id + 4)
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_MOUSE];
if (type == input_id + 3)
if (type == input_id + 5)
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_BIND_ALL];
if (type == input_id + 4)
if (type == input_id + 6)
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD];
if (type == input_id + 5)
if (type == input_id + 7)
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SAVING];
if ((type > (input_id + 29)) && (type < (input_id + 41)))
if ((type > (input_id + 31)) && (type < (input_id + 43)))
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_LGUN];
if (type == input_id + 41)
if (type == input_id + 43)
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INPUT_TURBO];
/* align to use the same code of Quickmenu controls*/
input_id = input_id + 6;
input_id = input_id + 8;
}
else
{
Expand Down
16 changes: 8 additions & 8 deletions menu/drivers/xmb.c
Original file line number Diff line number Diff line change
Expand Up @@ -3532,22 +3532,22 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb,
option in Input # Binds */
input_id++;
#endif
if (type == input_id + 1)
if (type >= input_id + 1 && type <= input_id + 3)
return xmb->textures.list[XMB_TEXTURE_INPUT_SETTINGS];
if (type == input_id + 2)
if (type == input_id + 4)
return xmb->textures.list[XMB_TEXTURE_INPUT_MOUSE];
if (type == input_id + 3)
if (type == input_id + 5)
return xmb->textures.list[XMB_TEXTURE_INPUT_BIND_ALL];
if (type == input_id + 4)
if (type == input_id + 6)
return xmb->textures.list[XMB_TEXTURE_RELOAD];
if (type == input_id + 5)
if (type == input_id + 7)
return xmb->textures.list[XMB_TEXTURE_SAVING];
if ((type > (input_id + 29)) && (type < (input_id + 41)))
if ((type > (input_id + 31)) && (type < (input_id + 43)))
return xmb->textures.list[XMB_TEXTURE_INPUT_LGUN];
if (type == input_id + 41)
if (type == input_id + 43)
return xmb->textures.list[XMB_TEXTURE_INPUT_TURBO];
/* Align to use the same code of Quickmenu controls */
input_id = input_id + 6;
input_id = input_id + 8;
}
else
{
Expand Down
Loading

0 comments on commit ac70e1d

Please sign in to comment.