summaryrefslogtreecommitdiff
path: root/indra/llwindow/llgamecontrol.h
blob: a4b47cc47fa152667df7305c3e7a133618f65457 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
/**
 * @file llgamecontrol.h
 * @brief GameController detection and management
 *
 * $LicenseInfo:firstyear=2023&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2023, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#pragma once

#include <vector>


#include "llerror.h"
#include "llsingleton.h"
#include "stdtypes.h"
#include "SDL2/SDL_events.h"

// For reference, here are the RAW indices of the various input channels
// of a standard XBox controller.  Button (N) is numbered in parentheses,
// whereas axisN has N+ and N- labels.
//
//                 leftpaddle                          rightpaddle
//                 _______                               _______
//                /   4+  '-.                         .-'  5+   \.
// leftshoulder _(9)_________'-.____           ____.-'_________(10) rightshoulder
//             /  _________         \_________/                   \.
//            /  /    1-   \                               (3)     \.
//            | |           |     (4)   (5)   (6)           Y      |
//            | |0-  (7)  0+|               _________  (2)X   B(1) |
//            | |           |              /    3-   \      A      |
//            | |     1+    |             |           |    (0)     |
//            |  \_________/              |2-  (8)  2+|            |
//            |  leftstick     (11)       |           |            |
//            |             (13)  (14)    |     3+    |            |
//            |                (12)        \_________/             |
//            |               d-pad         rightstick             |
//            |                ____________________                |
//            |              /                      \              |
//            |             /                        \             |
//            |            /                          \            |
//             \__________/                            \__________/
//
// Note: the analog joysticks provide NEGATIVE X,Y values for LEFT,FORWARD
// whereas those directions are actually POSITIVE in SL's local right-handed
// reference frame.  This is why we implicitly negate those axes the moment
// they are extracted from SDL, before being used anywhere.  See the
// implementation in LLGameControllerManager::onAxis().

// LLGameControl is a singleton with pure static public interface
class LLGameControl : public LLSingleton<LLGameControl>
{
    LLSINGLETON_EMPTY_CTOR(LLGameControl);
    virtual ~LLGameControl();
    LOG_CLASS(LLGameControl);

public:
    enum AgentControlMode
    {
        CONTROL_MODE_AVATAR,
        CONTROL_MODE_FLYCAM,
        CONTROL_MODE_NONE
    };

    enum ActionNameType
    {
        ACTION_NAME_UNKNOWN,
        ACTION_NAME_ANALOG,     // E.g., "push"
        ACTION_NAME_ANALOG_POS, // E.g., "push+"
        ACTION_NAME_ANALOG_NEG, // E.g., "push-"
        ACTION_NAME_BINARY,     // E.g., "stop"
        ACTION_NAME_FLYCAM      // E.g., "zoom"
    };

    enum KeyboardAxis : U8
    {
        AXIS_LEFTX,
        AXIS_LEFTY,
        AXIS_RIGHTX,
        AXIS_RIGHTY,
        AXIS_TRIGGERLEFT,
        AXIS_TRIGGERRIGHT,
        NUM_AXES
    };

    enum Button
    {
        BUTTON_A,
        BUTTON_B,
        BUTTON_X,
        BUTTON_Y,
        BUTTON_BACK,
        BUTTON_GUIDE,
        BUTTON_START,
        BUTTON_LEFTSTICK,
        BUTTON_RIGHTSTICK,
        BUTTON_LEFTSHOULDER,
        BUTTON_RIGHTSHOULDER, // 10
        BUTTON_DPAD_UP,
        BUTTON_DPAD_DOWN,
        BUTTON_DPAD_LEFT,
        BUTTON_DPAD_RIGHT,
        BUTTON_MISC1,
        BUTTON_PADDLE1,
        BUTTON_PADDLE2,
        BUTTON_PADDLE3,
        BUTTON_PADDLE4,
        BUTTON_TOUCHPAD, // 20
        BUTTON_21,
        BUTTON_22,
        BUTTON_23,
        BUTTON_24,
        BUTTON_25,
        BUTTON_26,
        BUTTON_27,
        BUTTON_28,
        BUTTON_29,
        BUTTON_30,
        BUTTON_31,
        NUM_BUTTONS
    };

    static const U16 MAX_AXIS_DEAD_ZONE = 16384;
    static const U16 MAX_AXIS_OFFSET = 16384;

    class InputChannel
    {
    public:
        enum Type
        {
            TYPE_NONE,
            TYPE_AXIS,
            TYPE_BUTTON
        };

        InputChannel() {}
        InputChannel(Type type, U8 index) : mType(type), mIndex(index) {}
        InputChannel(Type type, U8 index, S32 sign) : mType(type), mSign(sign), mIndex(index) {}

        // these methods for readability
        bool isNone() const { return mType == TYPE_NONE; }
        bool isAxis() const { return mType == TYPE_AXIS; }
        bool isButton() const { return mType == TYPE_BUTTON; }

        bool isEqual(const InputChannel& other)
        {
            return mType == other.mType && mSign == other.mSign && mIndex == other.mIndex;
        }

        std::string getLocalName() const; // AXIS_0-, AXIS_0+, BUTTON_0, NONE etc.
        std::string getRemoteName() const; // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc

        Type mType { TYPE_NONE };
        S32 mSign { 0 };
        U8 mIndex { 255 };
    };

    // Options is a data structure for storing device-specific settings
    class Options
    {
    public:
        struct AxisOptions
        {
            S32 mMultiplier = 1;
            U16 mDeadZone { 0 };
            S16 mOffset { 0 };

            void resetToDefaults()
            {
                mMultiplier = 1;
                mDeadZone = 0;
                mOffset = 0;
            }

            S16 computeModifiedValue(S16 raw_value) const
            {
                S32 new_value = ((S32)raw_value + S32(mOffset)) * mMultiplier;
                return (S16)(std::clamp(new_value, -32768, 32767));
            }

            std::string saveToString() const;
            void loadFromString(std::string options);
        };

        Options();

        void resetToDefaults();

        U8 mapAxis(U8 axis) const;
        U8 mapButton(U8 button) const;

        S16 fixAxisValue(U8 axis, S16 value) const;

        std::string saveToString(const std::string& name, bool force_empty = false) const;
        bool loadFromString(std::string& name, std::string options);
        bool loadFromString(std::string options);

        const std::vector<AxisOptions>& getAxisOptions() const { return mAxisOptions; }
        std::vector<AxisOptions>& getAxisOptions() { return mAxisOptions; }
        const std::vector<U8>& getAxisMap() const { return mAxisMap; }
        std::vector<U8>& getAxisMap() { return mAxisMap; }
        const std::vector<U8>& getButtonMap() const { return mButtonMap; }
        std::vector<U8>& getButtonMap() { return mButtonMap; }

    private:
        std::vector<AxisOptions> mAxisOptions;
        std::vector<U8> mAxisMap;
        std::vector<U8> mButtonMap;
    };

    // State is a minimal class for storing axes and buttons values
    class State
    {
    public:
        State();
        void clear();
        bool onButton(U8 button, bool pressed);
        std::vector<S16> mAxes; // [ -32768, 32767 ]
        std::vector<S16> mPrevAxes; // value in last outgoing packet
        U32 mButtons;
    };

    // Device is a data structure for describing any detected controller
    class Device
    {
        const int mJoystickID { -1 };
        const std::string mGUID;
        const std::string mName;
        Options mOptions;
        State mState;

    public:
        Device(int joystickID, const std::string& guid, const std::string& name);
        int getJoystickID() const { return mJoystickID; }
        std::string getGUID() const { return mGUID; }
        std::string getName() const { return mName; }
        const Options& getOptions() const { return mOptions; }
        const State& getState() const { return mState; }

        void resetOptionsToDefaults() { mOptions.resetToDefaults(); }
        std::string saveOptionsToString(bool force_empty = false) const { return mOptions.saveToString(mName, force_empty); }
        void loadOptionsFromString(const std::string& options) { mOptions.loadFromString(options); }

        friend class LLGameControllerManager;
    };

    static bool isEnabled();
    static void setEnabled(bool enabled);

    static bool isInitialized();
    static void init(const std::string& gamecontrollerdb_path,
        std::function<bool(const std::string&)> loadBoolean,
        std::function<void(const std::string&, bool)> saveBoolean,
        std::function<std::string(const std::string&)> loadString,
        std::function<void(const std::string&, const std::string&)> saveString,
        std::function<LLSD(const std::string&)> loadObject,
        std::function<void(const std::string&, const LLSD&)> saveObject,
        std::function<void()> updateUI);
    static void terminate();

    static const std::list<LLGameControl::Device>& getDevices();
    static const std::map<std::string, std::string>& getDeviceOptions();

    // returns 'true' if GameControlInput message needs to go out,
    // which will be the case for new data or resend. Call this right
    // before deciding to put a GameControlInput packet on the wire
    // or not.
    static bool computeFinalStateAndCheckForChanges();

    static void clearAllStates();

    static void processEvents(bool app_has_focus = true);
    static void handleEvent(const SDL_Event& event, bool app_has_focus);
    static const State& getState();
    static InputChannel getActiveInputChannel();
    static void getFlycamInputs(std::vector<F32>& inputs_out);

    // these methods for accepting input from keyboard
    static void setSendToServer(bool enable);
    static void setControlAgent(bool enable);
    static void setTranslateAgentActions(bool enable);
    static void setAgentControlMode(AgentControlMode mode);

    static bool getSendToServer();
    static bool getControlAgent();
    static bool getTranslateAgentActions();
    static AgentControlMode getAgentControlMode();
    static ActionNameType getActionNameType(const std::string& action);

    static bool willControlAvatar();

    // Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel
    // If the axis name lacks the +/- postfix it assumes '+' postfix.
    static LLGameControl::InputChannel getChannelByName(const std::string& name);

    // action_name = push+, strafe-, etc
    static LLGameControl::InputChannel getChannelByAction(const std::string& action);

    static bool updateActionMap(const std::string& action_name,  LLGameControl::InputChannel channel);

    // Keyboard presses produce action_flags which can be translated into State
    // and game_control devices produce State which can be translated into action_flags.
    // These methods help exchange such translations.
    static U32 computeInternalActionFlags();
    static void setExternalInput(U32 action_flags, U32 buttons_from_keys);

    // call this after putting a GameControlInput packet on the wire
    static void updateResendPeriod();

    using getChannel_t = std::function<LLGameControl::InputChannel(const std::string& action)>;
    static std::string stringifyAnalogMappings(getChannel_t getChannel);
    static std::string stringifyBinaryMappings(getChannel_t getChannel);
    static std::string stringifyFlycamMappings(getChannel_t getChannel);
    static void getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings);

    static bool parseDeviceOptions(const std::string& options, std::string& name,
        std::vector<LLGameControl::Options::AxisOptions>& axis_options,
        std::vector<U8>& axis_map, std::vector<U8>& button_map);
    static std::string stringifyDeviceOptions(const std::string& name,
        const std::vector<LLGameControl::Options::AxisOptions>& axis_options,
        const std::vector<U8>& axis_map, const std::vector<U8>& button_map,
        bool force_empty = false);

    static void initByDefault();
    static void loadFromSettings();
    static void saveToSettings();
    static void setDeviceOptions(const std::string& guid, const Options& options);
};