Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linux/GTK3: add gamepad hotplug support #855

Merged
merged 6 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions desmume/src/frontend/posix/gtk/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2088,21 +2088,21 @@ static gboolean JoyKeyAcceptTimerFunc(gpointer data)
switch(event.type)
{
case SDL_JOYBUTTONDOWN:
key = ((event.jbutton.which & 15) << 12) | JOY_BUTTON << 8 | (event.jbutton.button & 255);
key = ((get_joystick_number_by_id(event.jbutton.which) & 15) << 12) | JOY_BUTTON << 8 | (event.jbutton.button & 255);
done = TRUE;
break;
case SDL_JOYAXISMOTION:
if( ((u32)abs(event.jaxis.value) >> 14) != 0 )
{
key = ((event.jaxis.which & 15) << 12) | JOY_AXIS << 8 | ((event.jaxis.axis & 127) << 1);
key = ((get_joystick_number_by_id(event.jaxis.which) & 15) << 12) | JOY_AXIS << 8 | ((event.jaxis.axis & 127) << 1);
if (event.jaxis.value > 0)
key |= 1;
done = TRUE;
}
break;
case SDL_JOYHATMOTION:
if (event.jhat.value != SDL_HAT_CENTERED) {
key = ((event.jhat.which & 15) << 12) | JOY_HAT << 8 | ((event.jhat.hat & 63) << 2);
key = ((get_joystick_number_by_id(event.jhat.which) & 15) << 12) | JOY_HAT << 8 | ((event.jhat.hat & 63) << 2);
if ((event.jhat.value & SDL_HAT_UP) != 0)
key |= JOY_HAT_UP;
else if ((event.jhat.value & SDL_HAT_RIGHT) != 0)
Expand All @@ -2114,6 +2114,9 @@ static gboolean JoyKeyAcceptTimerFunc(gpointer data)
done = TRUE;
}
break;
default:
do_process_joystick_device_events(&event);
break;
}
}

Expand Down Expand Up @@ -3320,6 +3323,13 @@ static gboolean timeout_exit_cb(gpointer data)
return FALSE;
}

static gboolean OutOfLoopJoyDeviceCheckTimerFunc(gpointer data)
{
if(!regMainLoop && !in_joy_config_mode)
process_joystick_device_events();
return !regMainLoop;
}

static void
common_gtk_main(GApplication *app, gpointer user_data)
{
Expand Down Expand Up @@ -4118,6 +4128,7 @@ common_gtk_main(GApplication *app, gpointer user_data)
video->SetFilterParameteri(VF_PARAM_SCANLINE_D, _scanline_filter_d);

RedrawScreen();
g_timeout_add(200, OutOfLoopJoyDeviceCheckTimerFunc, 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd guess that between plugging in a joystick and moving the mouse to the controls menu, at least a second passes, so polling less often would make sense

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on second thought, why poll at all ? sdl sends an event when a device is plugged in, so we could simply notify the frontend about that, or am i missing something ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must read the event from the queue as far as I understand, but when emulation is not started, events are not processed in the loop (EmuLoop function). When EmuLoop starts, this timer fucntion exits.

}

static void Teardown() {
Expand Down
141 changes: 106 additions & 35 deletions desmume/src/frontend/posix/shared/ctrlssdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include "NDSSystem.h"
#include "frontend/modules/osd/agg/agg_osd.h"
#include "driver.h"
#include <list>
#include <unordered_map>

#ifdef FAKE_MIC
#include "mic.h"
Expand All @@ -39,7 +41,8 @@ u16 nbr_joy;
mouse_status mouse;
static int fullscreen;

static SDL_Joystick **open_joysticks = NULL;
//List of currently connected joysticks (key - instance id, value - SDL_Joystick structure, joystick number)
static std::unordered_map<SDL_JoystickID, std::pair<SDL_Joystick *, SDL_JoystickID> > open_joysticks;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like overkill, currently we support a max of 16 joysticks, which means the joystick id we use fits into one byte... and the average gamer certainly has max 4 joysticks connected so everything would fit into one cache line using a dumb loop over an array. i'm pretty certain the overhead involved in the use of a map for such tiny amount of data is way slower

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which means the joystick id we use fits into one byte

actually, this could be derived from the array index, so it's for free. we only need to make sure to fix the ordering of the array on insertion/removal of a device

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I probably just use static array of 16 elements?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, sounds like the best option. in the unlikely case that more than 16 joys are connected, we just have to discard those > 15.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redone with static array. Note that there's nbr_joy variable declared as extern in ctrlssdl.h that is now only keeps number of joysticks detected at the start, not the actual number of currently connected joysticks, because joysticks can be removed from any element of the array upon disconnect, so frontends should not rely on that variable (though as far as I can tell, they don't).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added int get_number_of_joysticks()

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, please proceed and use it in the interface frontend and remove nbr_joys. (just grep posix subdir for nbr_joy to find the file).
with that out of the way i can test it as soon as the gtk2 conversion is done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I also make corresponding changes to gtk2 frontend in my branch?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, please

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


/* Keypad key names */
const char *key_names[NB_KEYS] =
Expand Down Expand Up @@ -120,23 +123,23 @@ BOOL init_joy( void) {

if ( nbr_joy > 0) {
printf("Found %d joysticks\n", nbr_joy);
open_joysticks =
(SDL_Joystick**)calloc( sizeof ( SDL_Joystick *), nbr_joy);

if ( open_joysticks != NULL) {
for (i = 0; i < nbr_joy; i++)
{
SDL_Joystick * joy = SDL_JoystickOpen(i);
printf("Joystick %d %s\n", i, SDL_JoystickNameForIndex(i));
printf("Axes: %d\n", SDL_JoystickNumAxes(joy));
printf("Buttons: %d\n", SDL_JoystickNumButtons(joy));
printf("Trackballs: %d\n", SDL_JoystickNumBalls(joy));
printf("Hats: %d\n\n", SDL_JoystickNumHats(joy));
}
}
else {
joy_init_good = FALSE;

for (i = 0; i < nbr_joy; i++) {
SDL_Joystick * joy = SDL_JoystickOpen(i);
if(joy) {
printf("Joystick %d %s\n", i, SDL_JoystickNameForIndex(i));
printf("Axes: %d\n", SDL_JoystickNumAxes(joy));
printf("Buttons: %d\n", SDL_JoystickNumButtons(joy));
printf("Trackballs: %d\n", SDL_JoystickNumBalls(joy));
printf("Hats: %d\n\n", SDL_JoystickNumHats(joy));
open_joysticks.insert(std::make_pair(SDL_JoystickInstanceID(joy), std::make_pair(joy, i)));
}
else {
fprintf(stderr, "Failed to open joystick %d: %s\n", i, SDL_GetError());
joy_init_good = FALSE;
}
}
nbr_joy = open_joysticks.size();
}

return joy_init_good;
Expand All @@ -145,17 +148,13 @@ BOOL init_joy( void) {
/* Unload joysticks */
void uninit_joy( void)
{
int i;

if ( open_joysticks != NULL) {
for (i = 0; i < SDL_NumJoysticks(); i++) {
SDL_JoystickClose( open_joysticks[i]);
}

free( open_joysticks);
for (auto & it: open_joysticks) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd really cherish if we could stick to C++03 features for the time being, following the overall style of the existing code and not gratuitously breaking the build for people with older compilers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open_joysticks is now static array and is iterated with int index.

if(it.second.first)
SDL_JoystickClose(it.second.first);
}

open_joysticks = NULL;

open_joysticks.clear();
nbr_joy = 0;
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}

Expand Down Expand Up @@ -203,14 +202,14 @@ u16 get_joy_key(int index) {
{
case SDL_JOYBUTTONDOWN:
printf( "Device: %d; Button: %d\n", event.jbutton.which, event.jbutton.button );
key = ((event.jbutton.which & 15) << 12) | JOY_BUTTON << 8 | (event.jbutton.button & 255);
key = ((get_joystick_number_by_id(event.jbutton.which) & 15) << 12) | JOY_BUTTON << 8 | (event.jbutton.button & 255);
done = TRUE;
break;
case SDL_JOYAXISMOTION:
/* Dead zone of 50% */
if( ((u32)abs(event.jaxis.value) >> 14) != 0 )
{
key = ((event.jaxis.which & 15) << 12) | JOY_AXIS << 8 | ((event.jaxis.axis & 127) << 1);
key = ((get_joystick_number_by_id(event.jaxis.which) & 15) << 12) | JOY_AXIS << 8 | ((event.jaxis.axis & 127) << 1);
if (event.jaxis.value > 0) {
printf( "Device: %d; Axis: %d (+)\n", event.jaxis.which, event.jaxis.axis );
key |= 1;
Expand All @@ -224,7 +223,7 @@ u16 get_joy_key(int index) {
/* Diagonal positions will be treated as two separate keys being activated, rather than a single diagonal key. */
/* JOY_HAT_* are sequential integers, rather than a bitmask */
if (event.jhat.value != SDL_HAT_CENTERED) {
key = ((event.jhat.which & 15) << 12) | JOY_HAT << 8 | ((event.jhat.hat & 63) << 2);
key = ((get_joystick_number_by_id(event.jhat.which) & 15) << 12) | JOY_HAT << 8 | ((event.jhat.hat & 63) << 2);
/* Can't just use a switch here because SDL_HAT_* make up a bitmask. We only want one of these when assigning keys. */
if ((event.jhat.value & SDL_HAT_UP) != 0) {
key |= JOY_HAT_UP;
Expand Down Expand Up @@ -379,7 +378,7 @@ do_process_joystick_events( u16 *keypad, SDL_Event *event) {
/* Joystick axis motion
Note: button constants have a 1bit offset. */
case SDL_JOYAXISMOTION:
key_code = ((event->jaxis.which & 15) << 12) | JOY_AXIS << 8 | ((event->jaxis.axis & 127) << 1);
key_code = ((get_joystick_number_by_id(event->jaxis.which) & 15) << 12) | JOY_AXIS << 8 | ((event->jaxis.axis & 127) << 1);
if( ((u32)abs(event->jaxis.value) >> 14) != 0 )
{
if (event->jaxis.value > 0)
Expand All @@ -406,7 +405,7 @@ do_process_joystick_events( u16 *keypad, SDL_Event *event) {
case SDL_JOYHATMOTION:
/* Diagonal positions will be treated as two separate keys being activated, rather than a single diagonal key. */
/* JOY_HAT_* are sequential integers, rather than a bitmask */
key_code = ((event->jhat.which & 15) << 12) | JOY_HAT << 8 | ((event->jhat.hat & 63) << 2);
key_code = ((get_joystick_number_by_id(event->jhat.which) & 15) << 12) | JOY_HAT << 8 | ((event->jhat.hat & 63) << 2);
key_u = lookup_joy_key( key_code | JOY_HAT_UP );
key_r = lookup_joy_key( key_code | JOY_HAT_RIGHT );
key_d = lookup_joy_key( key_code | JOY_HAT_DOWN );
Expand All @@ -432,15 +431,15 @@ do_process_joystick_events( u16 *keypad, SDL_Event *event) {
/* Joystick button pressed */
/* FIXME: Add support for BOOST */
case SDL_JOYBUTTONDOWN:
key_code = ((event->jbutton.which & 15) << 12) | JOY_BUTTON << 8 | (event->jbutton.button & 255);
key_code = ((get_joystick_number_by_id(event->jbutton.which) & 15) << 12) | JOY_BUTTON << 8 | (event->jbutton.button & 255);
key = lookup_joy_key( key_code );
if (key != 0)
ADD_KEY( *keypad, key );
break;

/* Joystick button released */
case SDL_JOYBUTTONUP:
key_code = ((event->jbutton.which & 15) << 12) | JOY_BUTTON << 8 | (event->jbutton.button & 255);
key_code = ((get_joystick_number_by_id(event->jbutton.which) & 15) << 12) | JOY_BUTTON << 8 | (event->jbutton.button & 255);
key = lookup_joy_key( key_code );
if (key != 0)
RM_KEY( *keypad, key );
Expand All @@ -454,6 +453,54 @@ do_process_joystick_events( u16 *keypad, SDL_Event *event) {
return processed;
}

/*
* The real joystick connect/disconnect processing function
* Not static because we need it in some frontends during gamepad button configuration
*/
int
do_process_joystick_device_events(SDL_Event* event) {
int processed = 1;
switch(event->type) {
/* Joystick disconnected */
case SDL_JOYDEVICEREMOVED:
{
auto it = open_joysticks.find(event->jdevice.which);
if(it != open_joysticks.cend()) {
printf("Joystick with instance %d disconnected\n", event->jdevice.which);
SDL_JoystickClose(it->second.first);
open_joysticks.erase(it);
}
nbr_joy = open_joysticks.size();
}
break;

/* Joystick connected */
case SDL_JOYDEVICEADDED:
{
if(get_joystick_number_by_id(SDL_JoystickGetDeviceInstanceID(event->jdevice.which))==-1) {
SDL_Joystick* joy = SDL_JoystickOpen(event->jdevice.which);
if(joy) {
printf("Joystick %d %s\n", event->jdevice.which, SDL_JoystickNameForIndex(event->jdevice.which));
printf("Axes: %d\n", SDL_JoystickNumAxes(joy));
printf("Buttons: %d\n", SDL_JoystickNumButtons(joy));
printf("Trackballs: %d\n", SDL_JoystickNumBalls(joy));
printf("Hats: %d\n\n", SDL_JoystickNumHats(joy));
open_joysticks.insert(std::make_pair(SDL_JoystickInstanceID(joy), std::make_pair(joy, event->jdevice.which)));
nbr_joy = open_joysticks.size();
}
else
fprintf(stderr, "Failed to open joystick %d: %s\n", event->jdevice.which, SDL_GetError());
}
}
break;

default:
processed = 0;
break;
}
return processed;
}

/*
* Process only the joystick events
*/
Expand All @@ -468,10 +515,34 @@ process_joystick_events( u16 *keypad) {
/* There's an event waiting to be processed? */
while (SDL_PollEvent(&event))
{
do_process_joystick_events( keypad, &event);
if(!do_process_joystick_events( keypad, &event))
do_process_joystick_device_events(&event);
}
}

/*
* Process only joystick connect/disconnect events
*/
void process_joystick_device_events() {
SDL_Event event;

/* IMPORTANT: Reenable joystick events if needed. */
if(SDL_JoystickEventState(SDL_QUERY) == SDL_IGNORE)
SDL_JoystickEventState(SDL_ENABLE);

/* There's an event waiting to be processed? */
while (SDL_PollEvent(&event))
do_process_joystick_device_events(&event);
}

int get_joystick_number_by_id(SDL_JoystickID id)
{
auto it = open_joysticks.find(id);
if(it != open_joysticks.cend())
return it->second.second;
return -1;
}

static u16 shift_pressed;

void
Expand Down
6 changes: 6 additions & 0 deletions desmume/src/frontend/posix/shared/ctrlssdl.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,11 @@ process_ctrls_event( SDL_Event& event,

void
process_joystick_events( u16 *keypad);
int
do_process_joystick_device_events(SDL_Event* event);
void
process_joystick_device_events();

int get_joystick_number_by_id(SDL_JoystickID id);

#endif /* CTRLSSDL_H */