1/*
2 * Copyright (C) 2012 INdT - Instituto Nokia de Tecnologia
3 * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "Gamepads.h"
29
30#include "GamepadDeviceLinux.h"
31#include "GamepadList.h"
32
33#include <QObject>
34#include <QSocketNotifier>
35
36extern "C" {
37#include <libudev.h>
38}
39
40#include <unistd.h>
41#include <wtf/HashMap.h>
42#include <wtf/PassOwnPtr.h>
43#include <wtf/text/CString.h>
44#include <wtf/text/StringHash.h>
45
46namespace WebCore {
47
48class GamepadDeviceLinuxQt : public QObject, public GamepadDeviceLinux {
49    Q_OBJECT
50public:
51    static PassOwnPtr<GamepadDeviceLinuxQt> create(const String& deviceFile)
52    {
53        return adoptPtr(new GamepadDeviceLinuxQt(deviceFile));
54    }
55    ~GamepadDeviceLinuxQt();
56
57private:
58    GamepadDeviceLinuxQt(const String&);
59    QSocketNotifier* m_notifier;
60
61private Q_SLOTS:
62    bool readCallback();
63};
64
65GamepadDeviceLinuxQt::GamepadDeviceLinuxQt(const String& deviceFile)
66    : QObject()
67    , GamepadDeviceLinux(deviceFile)
68{
69    if (m_fileDescriptor == -1)
70        return;
71
72    m_notifier = new QSocketNotifier(m_fileDescriptor, QSocketNotifier::Read, this);
73    connect(m_notifier, SIGNAL(activated(int)), this, SLOT(readCallback()));
74}
75
76GamepadDeviceLinuxQt::~GamepadDeviceLinuxQt()
77{
78}
79
80bool GamepadDeviceLinuxQt::readCallback()
81{
82    js_event event;
83    int len = read(m_fileDescriptor, &event, sizeof(js_event));
84    if (len != sizeof(event))
85        return false;
86    updateForEvent(event);
87    return true;
88}
89
90class GamepadsQt : public QObject {
91    Q_OBJECT
92public:
93    GamepadsQt(unsigned);
94
95    void registerDevice(const String&);
96    void unregisterDevice(const String&);
97
98    void updateGamepadList(GamepadList*);
99
100private Q_SLOTS:
101    void onGamePadChange();
102
103private:
104    ~GamepadsQt();
105    bool isGamepadDevice(struct udev_device*);
106
107    Vector<OwnPtr<GamepadDeviceLinuxQt> > m_slots;
108    HashMap<String, GamepadDeviceLinuxQt*> m_deviceMap;
109
110    struct udev* m_udev;
111    struct udev_monitor* m_gamepadsMonitor;
112    QSocketNotifier* m_gamepadsNotifier;
113};
114
115GamepadsQt::GamepadsQt(unsigned length)
116    : QObject()
117    , m_slots(length)
118{
119    m_udev = udev_new();
120    m_gamepadsMonitor = udev_monitor_new_from_netlink(m_udev, "udev");
121    udev_monitor_enable_receiving(m_gamepadsMonitor);
122    udev_monitor_filter_add_match_subsystem_devtype(m_gamepadsMonitor, "input", 0);
123    m_gamepadsNotifier = new QSocketNotifier(udev_monitor_get_fd(m_gamepadsMonitor), QSocketNotifier::Read, this);
124    connect(m_gamepadsNotifier, SIGNAL(activated(int)), this, SLOT(onGamePadChange()));
125
126    struct udev_enumerate* enumerate = udev_enumerate_new(m_udev);
127    udev_enumerate_add_match_subsystem(enumerate, "input");
128    udev_enumerate_add_match_property(enumerate, "ID_INPUT_JOYSTICK", "1");
129    udev_enumerate_scan_devices(enumerate);
130    struct udev_list_entry* cur;
131    struct udev_list_entry* devs = udev_enumerate_get_list_entry(enumerate);
132    udev_list_entry_foreach(cur, devs)
133    {
134        const char* devname = udev_list_entry_get_name(cur);
135        struct udev_device* device = udev_device_new_from_syspath(m_udev, devname);
136        if (isGamepadDevice(device))
137            registerDevice(String::fromUTF8(udev_device_get_devnode(device)));
138        udev_device_unref(device);
139    }
140    udev_enumerate_unref(enumerate);
141}
142
143GamepadsQt::~GamepadsQt()
144{
145    udev_unref(m_udev);
146    udev_monitor_unref(m_gamepadsMonitor);
147}
148
149bool GamepadsQt::isGamepadDevice(struct udev_device* device)
150{
151    const char* deviceFile = udev_device_get_devnode(device);
152    const char* sysfsPath = udev_device_get_syspath(device);
153    if (!deviceFile || !sysfsPath)
154        return false;
155    if (!udev_device_get_property_value(device, "ID_INPUT") || !udev_device_get_property_value(device, "ID_INPUT_JOYSTICK"))
156        return false;
157    return QByteArray(deviceFile).startsWith("/dev/input/js");
158}
159
160void GamepadsQt::onGamePadChange()
161{
162    struct udev_device* device = udev_monitor_receive_device(m_gamepadsMonitor);
163    if (!isGamepadDevice(device))
164        return;
165    QByteArray action(udev_device_get_action(device));
166    if (action == "add")
167        registerDevice(udev_device_get_devnode(device));
168    else if (action == "remove")
169        unregisterDevice(udev_device_get_devnode(device));
170}
171
172void GamepadsQt::registerDevice(const String& deviceFile)
173{
174    ASSERT(!m_deviceMap.contains(deviceFile));
175
176    for (unsigned index = 0; index < m_slots.size(); index++) {
177        if (!m_slots[index]) {
178            m_slots[index] = GamepadDeviceLinuxQt::create(deviceFile);
179            m_deviceMap.add(deviceFile, m_slots[index].get());
180            break;
181        }
182    }
183}
184
185void GamepadsQt::unregisterDevice(const String& deviceFile)
186{
187    ASSERT(m_deviceMap.contains(deviceFile));
188
189    GamepadDeviceLinuxQt* gamepadDevice = m_deviceMap.take(deviceFile);
190    unsigned index = m_slots.find(gamepadDevice);
191
192    m_slots[index].clear();
193}
194
195void GamepadsQt::updateGamepadList(GamepadList* into)
196{
197    ASSERT(m_slots.size() == into->length());
198
199    for (unsigned i = 0; i < m_slots.size(); i++) {
200        if (m_slots[i] && m_slots[i]->connected()) {
201            GamepadDeviceLinuxQt* gamepadDevice = m_slots[i].get();
202            RefPtr<Gamepad> gamepad = into->item(i);
203            if (!gamepad)
204                gamepad = Gamepad::create();
205
206            gamepad->index(i);
207            gamepad->id(gamepadDevice->id());
208            gamepad->timestamp(gamepadDevice->timestamp());
209            gamepad->axes(gamepadDevice->axesCount(), gamepadDevice->axesData());
210            gamepad->buttons(gamepadDevice->buttonsCount(), gamepadDevice->buttonsData());
211
212            into->set(i, gamepad);
213        } else
214            into->set(i, 0);
215    }
216}
217
218void sampleGamepads(GamepadList* into)
219{
220    DEFINE_STATIC_LOCAL(GamepadsQt, gamepadsQt, (into->length()));
221    gamepadsQt.updateGamepadList(into);
222}
223
224#include "GamepadsQt.moc"
225
226} // namespace WebCore
227