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

Added support for interfacing with joysticks via DirectInput when it is available. #1326

Merged
merged 1 commit into from Feb 10, 2018

Conversation

binary1248
Copy link
Member

Fixes #1251.

Test with this (might be worth making into an official example):

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Graphics.hpp>
#include <sstream>
#include <iomanip>
#include <string>
#include <map>


namespace
{
    typedef std::map<std::string, std::pair<sf::Text, sf::Text>> Texts;
    Texts texts;
    std::ostringstream sstr;

    // Helper to set text entries to a specified value
    template<typename T>
    void set(const char* label, const T& value)
    {
        sstr.str("");
        sstr << value;
        texts[label].second.setString(sstr.str());
    }
}


////////////////////////////////////////////////////////////
/// Entry point of application
///
/// \return Application exit code
///
////////////////////////////////////////////////////////////
int main()
{
    // Create the window of the application
    sf::RenderWindow window(sf::VideoMode(480, 640), "Joystick", sf::Style::Close);
    window.setVerticalSyncEnabled(true);

    // Load the text font
    sf::Font font;
    if (!font.loadFromFile("resources/sansation.ttf"))
        return EXIT_FAILURE;

    // Axes labels in as C strings
    const char* axislabels[] = {"X", "Y", "Z", "R", "U", "V", "PovX", "PovY"};

    // Set up our string conversion parameters
    sstr.precision(2);
    sstr.setf(std::ios::fixed | std::ios::boolalpha);

    // Set up our label-value sf::Text objects
    texts["ID"].first.setPosition(5.f, 5.f);
    texts["ID"].second.setPosition(80.f, 5.f);

    for (unsigned int i = 0; i < sf::Joystick::AxisCount; ++i)
    {
        std::pair<sf::Text, sf::Text>& text = texts[axislabels[i]];

        text.first.setPosition(5.f, 5.f + ((i + 2) * font.getLineSpacing(14)));
        text.first.setString(std::string(axislabels[i]) + ":");

        text.second.setPosition(80.f, 5.f + ((i + 2) * font.getLineSpacing(14)));
    }

    for (unsigned int i = 0; i < sf::Joystick::ButtonCount; ++i)
    {
        sstr.str("");
        sstr << "Button " << i;
        std::pair<sf::Text, sf::Text>& text = texts[sstr.str()];

        text.first.setPosition(5.f, 5.f + ((sf::Joystick::AxisCount + i + 2) * font.getLineSpacing(14)));
        text.first.setString(sstr.str() + ":");

        text.second.setPosition(80.f, 5.f + ((sf::Joystick::AxisCount + i + 2) * font.getLineSpacing(14)));
    }

    for (Texts::iterator i = texts.begin(); i != texts.end(); ++i)
    {
        i->second.first.setFont(font);
        i->second.first.setCharacterSize(14);
        i->second.first.setFillColor(sf::Color::White);

        i->second.second.setFont(font);
        i->second.second.setCharacterSize(14);
        i->second.second.setFillColor(sf::Color::White);
    }

    while (window.isOpen())
    {
        // Handle events
        sf::Event event;
        while (window.pollEvent(event))
        {
            // Window closed or escape key pressed: exit
            if ((event.type == sf::Event::Closed) ||
               ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape)))
            {
                window.close();
                break;
            }
        }

        // Clear the window
        window.clear();

        // Search for a connected joystick
        for (unsigned int i = 0; i < sf::Joystick::Count; ++i)
        {
            if (sf::Joystick::isConnected(i)) {
                // Update the label-value sf::Text objects based on the current joystick state

                // Joystick identification
                sstr.str("");
                sstr << "Joystick " << i << ":";
                texts["ID"].first.setString(sstr.str());
                texts["ID"].second.setString(sf::Joystick::getIdentification(i).name);

                // Joystick axes
                for (unsigned int j = 0; j < sf::Joystick::AxisCount; ++j)
                {
                    if (sf::Joystick::hasAxis(i, static_cast<sf::Joystick::Axis>(j)))
                        set(axislabels[j], sf::Joystick::getAxisPosition(i, static_cast<sf::Joystick::Axis>(j)));
                }

                // Joystick buttons
                for (unsigned int j = 0; j < sf::Joystick::getButtonCount(i); ++j)
                {
                    sstr.str("");
                    sstr << "Button " << j;

                    set(sstr.str().c_str(), sf::Joystick::isButtonPressed(i, j));
                }

                // Only print information for the first connected joystick
                break;
            }
        }

        // Draw the label-value sf::Text objects
        for (Texts::const_iterator i = texts.begin(); i != texts.end(); ++i)
        {
            window.draw(i->second.first);
            window.draw(i->second.second);
        }

        // Display things on screen
        window.display();
    }
}

@LaurentGomila
Copy link
Member

LaurentGomila commented Dec 22, 2017

Would it be possible to split the implementations (JoystickImplDInput / JoystickImplWinMM -- or at least 2 distinct sets of functions within JoystickImpl) for better readability and maintainability?

What are those macros in _mingw_dxhelper.h used for?

@MarioLiebisch
Copy link
Member

Oh, didn't notice those new header files from MinGW before. IMO neither of them should be included since they're part of the compiler toolset/platform SDK.

@binary1248
Copy link
Member Author

@LaurentGomila @MarioLiebisch Those headers are meant for compatibility with some (older?) versions of MinGW that for some reason don't come with dinput.h out of the box.

See https://ci.sfml-dev.org/builders/windows-gcc-492-tdm-32/builds/404

If you check, GLFW does the same for probably similar reasons.

@MarioLiebisch
Copy link
Member

Really confusing. According to this bug report it seems like the headers should be there.

@binary1248
Copy link
Member Author

They are there in the 64-bit distribution: https://ci.sfml-dev.org/builders/windows-gcc-492-tdm-64/builds/401

However, they are missing in the 32-bit distribution. I just double checked on the CI server.

@binary1248
Copy link
Member Author

I split the DirectInput implementation into its own functions further down in JoystickImpl.cpp.


IDirectInput8W* directInput = NULL;
HMODULE dinput8dll = NULL;
HRESULT(WINAPI *DirectInput8CreateFunc)(HINSTANCE, DWORD, REFIID, LPVOID*, LPUNKNOWN);
Copy link
Member

Choose a reason for hiding this comment

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

DirectInput8CreateFunc doesn't follow our naming conventions (starts with capital letter). There's no need for it to be a global, it is only used in initializeDInput.

The DirectInput-related variables could be stuffed into a structure (like DirectInputContext), to limit the number of globals and make things clearer.

objectDataFormat is also used locally in a single place, but since its address is taken, it may have to live as long as DInput is used; do you know more about this stuff?

@binary1248
Copy link
Member Author

Fixed.

@MarioLiebisch
Copy link
Member

MarioLiebisch commented Dec 23, 2017

However, they are missing in the 32-bit distribution. I just double checked on the CI server.

That sounds like a bug or issue. Why would that be the case?

@binary1248
Copy link
Member Author

https://sourceforge.net/projects/tdm-gcc/files/TDM-GCC%20Installer/Previous/1.1309.0/tdm-gcc-4.9.2.exe/download

You can have a look yourself.

There was an issue: https://sourceforge.net/p/mingw-w64/discussion/723797/thread/0176cb85/
The files got added in r312: https://sourceforge.net/p/mingw-w64/code/312/

Don't ask me why the distributions took so long to react. Fact is that this is only an issue for 4.9.2, meaning it will most probably never be fixed for that version since newer versions don't have this problem any more. Either we decide to drop support for 4.9.2 32-bit just because of the missing header or we bundle it ourselves.

@dabonetn
Copy link

dabonetn commented Jan 7, 2018

Is there any guide for getting a mxe setup with a different branch like yours?
I'd love to see if this fixes the crash error in attractmode under windows when wireless controllers disconnect.

@anthnich
Copy link
Contributor

Tested this. No crashing when unplugging. Controller works as expected, didn't notice any issues. Ran a performance profile for 60 secs, nothing out of the ordinary.

@MarioLiebisch
Copy link
Member

Just wanted to try this, still having MinGW-W64 (I think?) GCC 5.3.0 installed. While it got the Direct3D headers and others, it's missing the Direct Input one for whatever reason. Updated to 6.3.0 and the header still isn't there.

I'd just suggest dropping those headers, letting CMake look for them, and then print a notice about them missing. If they're missing, compile the legacy API version only? What do you think?

Copy link
Member

@MarioLiebisch MarioLiebisch left a comment

Choose a reason for hiding this comment

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

Okay, yet another attempt with GCC 7.2.0 x64 finally worked – as does the code.

But as mentioned, I'm still not 100% sure about handling legacy compilers.

I'd suggest letting CMake look for dinput.h and other necessary files. If they're found, use Direct Input exclusively (move the fallback to a separate file); if they aren't found, spit out a warning and use the legacy/Win16 API only. If Direct Input is broken, the old API shouldn't work either (on any modern Win32 platform).

What do you think?

* Copyright (C) the Wine project
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Copy link
Member

Choose a reason for hiding this comment

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

I assume LGPL headers are fine to be shipped, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

We are only working around a toolchain "bug". Either you use the one you already have or you use the one we provide. In both cases you are #includeing an LGPL header.

initializeDInput();

if (!directInput)
err() << "DirectInput not available, falling back to Windows joystick API" << std::endl;
Copy link
Member

Choose a reason for hiding this comment

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

If someone doesn't want to use a joystick and doesn't have direct input available will this still output the error?

Copy link
Member Author

Choose a reason for hiding this comment

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

People have already "complained" a few times about not being able to disable joystick polling, so yes... this error will still be output as soon as window events are pumped. Realistically, these can only be Windows 98 or older systems, so if they get the rest of SFML running on those kinds of systems they deserve a medal.

@eXpl0it3r
Copy link
Member

I think, shipping our own header is fine. Will make our life easier as we won't have to deal with broke compilers.

@binary1248
Copy link
Member Author

The workaround is trivial anyway, so I don't see any problems with it. AFAIK GLFW decided to go down the same route.

@eXpl0it3r eXpl0it3r moved this from Review & Testing to Ready in SFML 2.5.0 Jan 25, 2018
@eXpl0it3r eXpl0it3r merged commit 22f1b85 into master Feb 10, 2018
SFML 2.5.0 automation moved this from Ready to Merged / Superseded Feb 10, 2018
@eXpl0it3r eXpl0it3r deleted the feature/directinput_joystick branch February 10, 2018 13:48
@dabonetn
Copy link

What are the chances of getting this back ported to 2.4.2?

@eXpl0it3r
Copy link
Member

Zero, as SFML 2.5 is around the corner.

@Foaly
Copy link
Contributor

Foaly commented Mar 22, 2018

Pretty much zero.
What would be the use case for that? SFML 2.5 will be release soonish™ and you can already get nightly builds of the master branch from the build servers.

edit: aaah... eXpl0it3r was faster again 😄

@dabonetn
Copy link

Because I have no idea how to get the 2.5 sfml into a mxe environment so I can compile attract mode.

see here under cross compile.

http://attractmode.org/docs/Compile.html

@MarioLiebisch
Copy link
Member

I've never used mxe before (nor have I heard of it), but since it's obviously to cross-compile Win32 binaries, you'll just have to compile those yourself and replace those mxe uses. You'll probably have more luck in their community/communities though.

@eXpl0it3r
Copy link
Member

Because I have no idea how to get the 2.5 sfml into a mxe environment so I can compile attract mode.

The same way you'd get a new "backported" build in there. If you can do that, you can also do it with a new SFML version.

Besides, backporting for minor versions is not a thing we do, especially not for features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
SFML 2.5.0
  
Merged / Superseded
Development

Successfully merging this pull request may close these issues.

Unplugging gamepad crashes
7 participants