Unreviewed, rolling out r170244.
[WebKit-https.git] / Source / WebCore / platform / gtk / GamepadsGtk.cpp
1 /*
2  * Copyright (C) 2012 Zan Dobersek <zandobersek@gmail.com>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
23  * DAMAGE.
24  */
25
26 #include "config.h"
27 #include "Gamepads.h"
28
29 #if ENABLE(GAMEPAD)
30
31 #include "GamepadDeviceLinux.h"
32 #include "GamepadList.h"
33 #include "Logging.h"
34 #include <gio/gunixinputstream.h>
35 #include <gudev/gudev.h>
36 #include <wtf/HashMap.h>
37 #include <wtf/PassOwnPtr.h>
38 #include <wtf/gobject/GRefPtr.h>
39 #include <wtf/gobject/GUniquePtr.h>
40 #include <wtf/text/CString.h>
41 #include <wtf/text/StringHash.h>
42
43 namespace WebCore {
44
45 class GamepadDeviceGtk : public GamepadDeviceLinux {
46 public:
47     static PassOwnPtr<GamepadDeviceGtk> create(String deviceFile)
48     {
49         return adoptPtr(new GamepadDeviceGtk(deviceFile));
50     }
51     ~GamepadDeviceGtk();
52
53 private:
54     GamepadDeviceGtk(String deviceFile);
55
56     static gboolean readCallback(GObject* pollableStream, gpointer data);
57     GRefPtr<GInputStream> m_inputStream;
58     GRefPtr<GSource> m_source;
59 };
60
61 GamepadDeviceGtk::GamepadDeviceGtk(String deviceFile)
62     : GamepadDeviceLinux(deviceFile)
63 {
64     if (m_fileDescriptor == -1)
65         return;
66
67     m_inputStream = adoptGRef(g_unix_input_stream_new(m_fileDescriptor, FALSE));
68     m_source = adoptGRef(g_pollable_input_stream_create_source(G_POLLABLE_INPUT_STREAM(m_inputStream.get()), 0));
69     g_source_set_callback(m_source.get(), reinterpret_cast<GSourceFunc>(readCallback), this, 0);
70     g_source_attach(m_source.get(), 0);
71 }
72
73 GamepadDeviceGtk::~GamepadDeviceGtk()
74 {
75     if (m_source)
76         g_source_destroy(m_source.get());
77 }
78
79 gboolean GamepadDeviceGtk::readCallback(GObject* pollableStream, gpointer data)
80 {
81     GamepadDeviceGtk* gamepadDevice = reinterpret_cast<GamepadDeviceGtk*>(data);
82     GUniqueOutPtr<GError> error;
83     struct js_event event;
84
85     gssize len = g_pollable_input_stream_read_nonblocking(G_POLLABLE_INPUT_STREAM(pollableStream),
86                                                           &event, sizeof(event), 0, &error.outPtr());
87
88     // FIXME: Properly log the error.
89     // In the case of G_IO_ERROR_WOULD_BLOCK error return TRUE to wait until
90     // the source becomes readable again and FALSE otherwise.
91     if (error)
92         return g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
93
94     ASSERT_UNUSED(len, len == sizeof(event));
95     gamepadDevice->updateForEvent(event);
96     return TRUE;
97 }
98
99 class GamepadsGtk {
100 public:
101     GamepadsGtk(unsigned length);
102
103     void registerDevice(String deviceFile);
104     void unregisterDevice(String deviceFile);
105
106     void updateGamepadList(GamepadList*);
107
108 private:
109     ~GamepadsGtk();
110     static void onUEventCallback(GUdevClient*, gchar* action, GUdevDevice*, gpointer data);
111     static gboolean isGamepadDevice(GUdevDevice*);
112
113     Vector<OwnPtr<GamepadDeviceGtk> > m_slots;
114     HashMap<String, GamepadDeviceGtk*> m_deviceMap;
115
116     GRefPtr<GUdevClient> m_gudevClient;
117 };
118
119 GamepadsGtk::GamepadsGtk(unsigned length)
120     : m_slots(length)
121 {
122     static const char* subsystems[] = { "input", 0 };
123     m_gudevClient = adoptGRef(g_udev_client_new(subsystems));
124     g_signal_connect(m_gudevClient.get(), "uevent", G_CALLBACK(onUEventCallback), this);
125
126     GUniquePtr<GList> devicesList(g_udev_client_query_by_subsystem(m_gudevClient.get(), subsystems[0]));
127     for (GList* listItem = devicesList.get(); listItem; listItem = g_list_next(listItem)) {
128         GUdevDevice* device = G_UDEV_DEVICE(listItem->data);
129         String deviceFile = String::fromUTF8(g_udev_device_get_device_file(device));
130         if (isGamepadDevice(device))
131             registerDevice(deviceFile);
132         g_object_unref(device);
133     }
134 }
135
136 GamepadsGtk::~GamepadsGtk()
137 {
138     g_signal_handlers_disconnect_matched(m_gudevClient.get(), G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, this);
139 }
140
141 void GamepadsGtk::registerDevice(String deviceFile)
142 {
143     LOG(Gamepad, "GamepadsGtk::registerDevice %s", deviceFile.ascii().data());
144     ASSERT(!m_deviceMap.contains(deviceFile));
145
146     for (unsigned index = 0; index < m_slots.size(); index++) {
147         if (!m_slots[index]) {
148             m_slots[index] = GamepadDeviceGtk::create(deviceFile);
149             m_deviceMap.add(deviceFile, m_slots[index].get());
150             break;
151         }
152     }
153 }
154
155 void GamepadsGtk::unregisterDevice(String deviceFile)
156 {
157     LOG(Gamepad, "GamepadsGtk::unregisterDevice %s", deviceFile.ascii().data());
158     ASSERT(m_deviceMap.contains(deviceFile));
159
160     GamepadDeviceGtk* gamepadDevice = m_deviceMap.take(deviceFile);
161     size_t index = m_slots.find(gamepadDevice);
162     ASSERT(index != notFound);
163
164     m_slots[index].clear();
165 }
166
167 void GamepadsGtk::updateGamepadList(GamepadList* into)
168 {
169     ASSERT(m_slots.size() == into->length());
170
171     for (unsigned i = 0; i < m_slots.size(); i++) {
172         if (m_slots[i].get() && m_slots[i]->connected()) {
173             GamepadDeviceGtk* gamepadDevice = m_slots[i].get();
174             RefPtr<Gamepad> gamepad = into->item(i);
175             if (!gamepad)
176                 gamepad = Gamepad::create();
177
178             gamepad->index(i);
179             gamepad->id(gamepadDevice->id());
180             gamepad->timestamp(gamepadDevice->timestamp());
181             gamepad->axes(gamepadDevice->axesCount(), gamepadDevice->axesData());
182             gamepad->buttons(gamepadDevice->buttonsCount(), gamepadDevice->buttonsData());
183
184             into->set(i, gamepad);
185         } else
186             into->set(i, 0);
187     }
188 }
189
190 void GamepadsGtk::onUEventCallback(GUdevClient*, gchar* action, GUdevDevice* device, gpointer data)
191 {
192     if (!isGamepadDevice(device))
193         return;
194
195     GamepadsGtk* gamepadsGtk = reinterpret_cast<GamepadsGtk*>(data);
196     String deviceFile = String::fromUTF8(g_udev_device_get_device_file(device));
197
198     if (!g_strcmp0(action, "add"))
199         gamepadsGtk->registerDevice(deviceFile);
200     else if (!g_strcmp0(action, "remove"))
201         gamepadsGtk->unregisterDevice(deviceFile);
202 }
203
204 gboolean GamepadsGtk::isGamepadDevice(GUdevDevice* device)
205 {
206     const gchar* deviceFile = g_udev_device_get_device_file(device);
207     const gchar* sysfsPath = g_udev_device_get_sysfs_path(device);
208     if (!deviceFile || !sysfsPath)
209         return FALSE;
210
211     if (!g_udev_device_has_property(device, "ID_INPUT") || !g_udev_device_has_property(device, "ID_INPUT_JOYSTICK"))
212         return FALSE;
213
214     return g_str_has_prefix(deviceFile, "/dev/input/js");
215 }
216
217 void sampleGamepads(GamepadList* into)
218 {
219     DEPRECATED_DEFINE_STATIC_LOCAL(GamepadsGtk, gamepadsGtk, (into->length()));
220     gamepadsGtk.updateGamepadList(into);
221 }
222
223 } // namespace WebCore
224
225 #endif // ENABLE(GAMEPAD)