blob: 7a29744f41e9579fde809a0bb50a4be572fb046d [file] [log] [blame]
[email protected]cf786002014-02-11 02:05:541// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
danakj89f47082020-09-02 17:53:435#include "content/web_test/renderer/gamepad_controller.h"
[email protected]cf786002014-02-11 02:05:546
Henrique Ferreirof464d9f12019-11-04 20:47:177#include <string>
8#include <utility>
avi5dd91f82015-12-25 22:30:469
Sebastien Marchandf8cbfab2019-01-25 16:02:3010#include "base/bind.h"
avi5dd91f82015-12-25 22:30:4611#include "base/macros.h"
Robbie McElrathf7b2d1172018-06-11 22:35:4512#include "content/public/common/service_names.mojom.h"
Oksana Zhuravlova11651852018-07-16 20:07:5013#include "content/public/renderer/render_frame.h"
[email protected]cf786002014-02-11 02:05:5414#include "gin/arguments.h"
15#include "gin/handle.h"
16#include "gin/object_template_builder.h"
17#include "gin/wrappable.h"
Alexandr Ilin1ce671502018-07-19 20:59:3518#include "mojo/public/cpp/system/platform_handle.h"
Miyoung Shinc9f4dac2019-09-26 15:14:1019#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
Blink Reformata30d4232018-04-07 15:31:0620#include "third_party/blink/public/web/blink.h"
21#include "third_party/blink/public/web/web_local_frame.h"
[email protected]cf786002014-02-11 02:05:5422#include "v8/include/v8.h"
23
juncai2f298a82017-04-18 03:51:3924using device::Gamepad;
25using device::Gamepads;
[email protected]cf786002014-02-11 02:05:5426
danakj741848a2020-04-07 22:48:0627namespace content {
[email protected]cf786002014-02-11 02:05:5428
Matt Reynolds99e2f5ef2019-01-18 23:40:5529namespace {
30
31// Set button.pressed if the button value is above a threshold. The threshold is
32// chosen to match XInput's trigger deadzone.
33constexpr float kButtonPressedThreshold = 30.f / 255.f;
34
35int64_t CurrentTimeInMicroseconds() {
36 return base::TimeTicks::Now().since_origin().InMicroseconds();
37}
38
39} // namespace
40
[email protected]cf786002014-02-11 02:05:5441class GamepadControllerBindings
42 : public gin::Wrappable<GamepadControllerBindings> {
43 public:
44 static gin::WrapperInfo kWrapperInfo;
45
46 static void Install(base::WeakPtr<GamepadController> controller,
lukaszadf18ba762017-06-09 22:24:3047 blink::WebLocalFrame* frame);
[email protected]cf786002014-02-11 02:05:5448
49 private:
50 explicit GamepadControllerBindings(
51 base::WeakPtr<GamepadController> controller);
dchenge933b3e2014-10-21 11:44:0952 ~GamepadControllerBindings() override;
[email protected]cf786002014-02-11 02:05:5453
54 // gin::Wrappable.
dchenge933b3e2014-10-21 11:44:0955 gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
anand.ratn449f39a42014-10-06 13:45:5756 v8::Isolate* isolate) override;
[email protected]cf786002014-02-11 02:05:5457
58 void Connect(int index);
[email protected]85603cbb2014-03-25 02:20:0159 void DispatchConnected(int index);
[email protected]cf786002014-02-11 02:05:5460 void Disconnect(int index);
61 void SetId(int index, const std::string& src);
62 void SetButtonCount(int index, int buttons);
63 void SetButtonData(int index, int button, double data);
64 void SetAxisCount(int index, int axes);
65 void SetAxisData(int index, int axis, double data);
Matt Reynolds6e9187e2017-10-23 18:32:0166 void SetDualRumbleVibrationActuator(int index, bool enabled);
[email protected]cf786002014-02-11 02:05:5467
68 base::WeakPtr<GamepadController> controller_;
69
70 DISALLOW_COPY_AND_ASSIGN(GamepadControllerBindings);
71};
72
73gin::WrapperInfo GamepadControllerBindings::kWrapperInfo = {
74 gin::kEmbedderNativeGin};
75
76// static
77void GamepadControllerBindings::Install(
78 base::WeakPtr<GamepadController> controller,
lukaszadf18ba762017-06-09 22:24:3079 blink::WebLocalFrame* frame) {
Blink Reformat1c4d759e2017-04-09 16:34:5480 v8::Isolate* isolate = blink::MainThreadIsolate();
[email protected]cf786002014-02-11 02:05:5481 v8::HandleScope handle_scope(isolate);
Blink Reformat1c4d759e2017-04-09 16:34:5482 v8::Local<v8::Context> context = frame->MainWorldScriptContext();
[email protected]cf786002014-02-11 02:05:5483 if (context.IsEmpty())
84 return;
85
86 v8::Context::Scope context_scope(context);
87
88 gin::Handle<GamepadControllerBindings> bindings =
89 gin::CreateHandle(isolate, new GamepadControllerBindings(controller));
[email protected]ad4d2032014-04-28 13:50:5990 if (bindings.IsEmpty())
91 return;
deepak.s750d68f2015-04-30 07:32:4192 v8::Local<v8::Object> global = context->Global();
Dan Elphicka83be512019-02-05 15:57:2393 global
94 ->Set(context, gin::StringToV8(isolate, "gamepadController"),
95 bindings.ToV8())
96 .Check();
[email protected]cf786002014-02-11 02:05:5497}
98
99GamepadControllerBindings::GamepadControllerBindings(
100 base::WeakPtr<GamepadController> controller)
101 : controller_(controller) {}
102
103GamepadControllerBindings::~GamepadControllerBindings() {}
104
105gin::ObjectTemplateBuilder GamepadControllerBindings::GetObjectTemplateBuilder(
106 v8::Isolate* isolate) {
107 return gin::Wrappable<GamepadControllerBindings>::GetObjectTemplateBuilder(
108 isolate)
109 .SetMethod("connect", &GamepadControllerBindings::Connect)
jochen73e711c2015-06-03 10:01:46110 .SetMethod("dispatchConnected",
111 &GamepadControllerBindings::DispatchConnected)
[email protected]cf786002014-02-11 02:05:54112 .SetMethod("disconnect", &GamepadControllerBindings::Disconnect)
113 .SetMethod("setId", &GamepadControllerBindings::SetId)
114 .SetMethod("setButtonCount", &GamepadControllerBindings::SetButtonCount)
115 .SetMethod("setButtonData", &GamepadControllerBindings::SetButtonData)
116 .SetMethod("setAxisCount", &GamepadControllerBindings::SetAxisCount)
Matt Reynolds6e9187e2017-10-23 18:32:01117 .SetMethod("setAxisData", &GamepadControllerBindings::SetAxisData)
118 .SetMethod("setDualRumbleVibrationActuator",
119 &GamepadControllerBindings::SetDualRumbleVibrationActuator);
[email protected]cf786002014-02-11 02:05:54120}
121
122void GamepadControllerBindings::Connect(int index) {
123 if (controller_)
124 controller_->Connect(index);
125}
126
[email protected]85603cbb2014-03-25 02:20:01127void GamepadControllerBindings::DispatchConnected(int index) {
128 if (controller_)
129 controller_->DispatchConnected(index);
130}
131
[email protected]cf786002014-02-11 02:05:54132void GamepadControllerBindings::Disconnect(int index) {
133 if (controller_)
134 controller_->Disconnect(index);
135}
136
137void GamepadControllerBindings::SetId(int index, const std::string& src) {
138 if (controller_)
139 controller_->SetId(index, src);
140}
141
142void GamepadControllerBindings::SetButtonCount(int index, int buttons) {
143 if (controller_)
144 controller_->SetButtonCount(index, buttons);
145}
146
147void GamepadControllerBindings::SetButtonData(int index,
148 int button,
149 double data) {
150 if (controller_)
151 controller_->SetButtonData(index, button, data);
152}
153
154void GamepadControllerBindings::SetAxisCount(int index, int axes) {
155 if (controller_)
156 controller_->SetAxisCount(index, axes);
157}
158
159void GamepadControllerBindings::SetAxisData(int index, int axis, double data) {
160 if (controller_)
161 controller_->SetAxisData(index, axis, data);
162}
163
Matt Reynolds6e9187e2017-10-23 18:32:01164void GamepadControllerBindings::SetDualRumbleVibrationActuator(int index,
165 bool enabled) {
166 if (controller_)
167 controller_->SetDualRumbleVibrationActuator(index, enabled);
168}
169
Matt Reynolds093c89f2019-05-03 00:58:59170GamepadController::MonitorImpl::MonitorImpl(
171 GamepadController* controller,
Gyuyoung Kim7959faf2019-08-15 12:10:20172 mojo::PendingReceiver<device::mojom::GamepadMonitor> receiver)
173 : controller_(controller) {
174 receiver_.Bind(std::move(receiver));
Matt Reynolds093c89f2019-05-03 00:58:59175}
176
177GamepadController::MonitorImpl::~MonitorImpl() = default;
178
179bool GamepadController::MonitorImpl::HasPendingConnect(int index) {
180 return missed_dispatches_.test(index);
181}
182
183void GamepadController::MonitorImpl::GamepadStartPolling(
184 GamepadStartPollingCallback callback) {
185 std::move(callback).Run(controller_->GetSharedMemoryRegion());
186}
187
188void GamepadController::MonitorImpl::GamepadStopPolling(
189 GamepadStopPollingCallback callback) {
190 std::move(callback).Run();
191}
192
193void GamepadController::MonitorImpl::SetObserver(
Gyuyoung Kim7959faf2019-08-15 12:10:20194 mojo::PendingRemote<device::mojom::GamepadObserver> observer) {
195 observer_remote_.Bind(std::move(observer));
196 observer_remote_.set_disconnect_handler(
Matt Reynolds093c89f2019-05-03 00:58:59197 base::BindOnce(&GamepadController::OnConnectionError,
198 base::Unretained(controller_), base::Unretained(this)));
199
200 // Notify the new observer of any GamepadConnected RPCs that it missed because
201 // the SetObserver RPC wasn't processed in time. This happens during layout
202 // tests because SetObserver is async, so the test can continue to the
203 // DispatchConnected call before the SetObserver RPC was processed. This isn't
204 // an issue in the real implementation because the 'gamepadconnected' event
205 // doesn't fire until user input is detected, so even if a GamepadConnected
206 // event is missed, another will be picked up after the next user input.
207 controller_->NotifyForMissedDispatches(this);
208 missed_dispatches_.reset();
209}
210
211void GamepadController::MonitorImpl::DispatchConnected(
212 int index,
213 const device::Gamepad& pad) {
Gyuyoung Kim7959faf2019-08-15 12:10:20214 if (observer_remote_) {
215 observer_remote_->GamepadConnected(index, pad);
Matt Reynolds093c89f2019-05-03 00:58:59216 } else {
217 // Record that there wasn't an observer to get the GamepadConnected RPC so
218 // we can send it when SetObserver gets called.
219 missed_dispatches_.set(index);
220 }
221}
222
223void GamepadController::MonitorImpl::DispatchDisconnected(
224 int index,
225 const device::Gamepad& pad) {
Gyuyoung Kim7959faf2019-08-15 12:10:20226 if (observer_remote_)
227 observer_remote_->GamepadDisconnected(index, pad);
Matt Reynolds093c89f2019-05-03 00:58:59228}
229
230void GamepadController::MonitorImpl::Reset() {
231 missed_dispatches_.reset();
232}
233
234GamepadController::GamepadController() {
Robbie McElrathf7b2d1172018-06-11 22:35:45235 size_t buffer_size = sizeof(device::GamepadHardwareBuffer);
Alexandr Ilin1ce671502018-07-19 20:59:35236 // Use mojo to delegate the creation of shared memory to the broker process.
237 mojo::ScopedSharedBufferHandle mojo_buffer =
238 mojo::SharedBufferHandle::Create(buffer_size);
239 base::WritableSharedMemoryRegion writable_region =
240 mojo::UnwrapWritableSharedMemoryRegion(std::move(mojo_buffer));
241 shared_memory_mapping_ = writable_region.Map();
242 shared_memory_region_ = base::WritableSharedMemoryRegion::ConvertToReadOnly(
243 std::move(writable_region));
danakja0c51ca2020-08-26 20:54:59244 if (!shared_memory_region_.IsValid()) {
245 // Log an error instead of crashing, as this can flakily happen in
246 // clusterfuzz. If it happened in the wild, the test would be retried.
247 LOG(ERROR) << "GamepadController shared memory region is not valid";
248 } else if (!shared_memory_mapping_.IsValid()) {
249 // Log an error instead of crashing, as this can flakily happen in
250 // clusterfuzz. If it happened in the wild, the test would be retried.
251 LOG(ERROR) << "GamepadController shared memory mapping is not valid";
252 } else {
253 gamepads_ =
254 new (shared_memory_mapping_.memory()) device::GamepadHardwareBuffer();
255 }
Robbie McElrathf7b2d1172018-06-11 22:35:45256
[email protected]cf786002014-02-11 02:05:54257 Reset();
258}
259
danakjc989ac72020-04-15 20:31:23260GamepadController::~GamepadController() = default;
[email protected]cf786002014-02-11 02:05:54261
262void GamepadController::Reset() {
danakja0c51ca2020-08-26 20:54:59263 if (!gamepads_)
264 return; // Shared memory failed.
265
Robbie McElrathf7b2d1172018-06-11 22:35:45266 memset(gamepads_, 0, sizeof(*gamepads_));
Matt Reynolds093c89f2019-05-03 00:58:59267 for (auto& monitor : monitors_)
268 monitor->Reset();
[email protected]cf786002014-02-11 02:05:54269}
270
danakja0c51ca2020-08-26 20:54:59271void GamepadController::Install(RenderFrame* frame) {
272 if (!gamepads_)
273 return; // Shared memory failed.
Oksana Zhuravlova11651852018-07-16 20:07:50274
danakja0c51ca2020-08-26 20:54:59275 frame->GetBrowserInterfaceBroker()->SetBinderForTesting(
Robbie McElrathf7b2d1172018-06-11 22:35:45276 device::mojom::GamepadMonitor::Name_,
277 base::BindRepeating(&GamepadController::OnInterfaceRequest,
278 base::Unretained(this)));
danakja0c51ca2020-08-26 20:54:59279 GamepadControllerBindings::Install(weak_factory_.GetWeakPtr(),
280 frame->GetWebFrame());
[email protected]cf786002014-02-11 02:05:54281}
282
Robbie McElrathf7b2d1172018-06-11 22:35:45283void GamepadController::OnInterfaceRequest(
284 mojo::ScopedMessagePipeHandle handle) {
Matt Reynolds093c89f2019-05-03 00:58:59285 monitors_.insert(std::make_unique<MonitorImpl>(
Miyoung Shinc9f4dac2019-09-26 15:14:10286 this,
287 mojo::PendingReceiver<device::mojom::GamepadMonitor>(std::move(handle))));
[email protected]078780b2014-06-20 16:57:06288}
289
Matt Reynolds093c89f2019-05-03 00:58:59290base::ReadOnlySharedMemoryRegion GamepadController::GetSharedMemoryRegion()
291 const {
292 return shared_memory_region_.Duplicate();
Robbie McElrathf7b2d1172018-06-11 22:35:45293}
294
Matt Reynolds093c89f2019-05-03 00:58:59295void GamepadController::OnConnectionError(
296 GamepadController::MonitorImpl* monitor) {
297 monitors_.erase(monitors_.find(monitor));
Robbie McElrathf7b2d1172018-06-11 22:35:45298}
299
Matt Reynolds093c89f2019-05-03 00:58:59300void GamepadController::NotifyForMissedDispatches(
301 GamepadController::MonitorImpl* monitor) {
Robbie McElrathf7b2d1172018-06-11 22:35:45302 gamepads_->seqlock.WriteBegin();
303 for (size_t index = 0; index < Gamepads::kItemsLengthCap; index++) {
Matt Reynolds093c89f2019-05-03 00:58:59304 if (monitor->HasPendingConnect(index))
305 monitor->DispatchConnected(index, gamepads_->data.items[index]);
Robbie McElrathf7b2d1172018-06-11 22:35:45306 }
Robbie McElrathf7b2d1172018-06-11 22:35:45307 gamepads_->seqlock.WriteEnd();
[email protected]cf786002014-02-11 02:05:54308}
309
310void GamepadController::Connect(int index) {
juncai2f298a82017-04-18 03:51:39311 if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
[email protected]cf786002014-02-11 02:05:54312 return;
Matt Reynolds99e2f5ef2019-01-18 23:40:55313 const int64_t now = CurrentTimeInMicroseconds();
Robbie McElrathf7b2d1172018-06-11 22:35:45314 gamepads_->seqlock.WriteBegin();
Matt Reynolds99e2f5ef2019-01-18 23:40:55315 Gamepad& pad = gamepads_->data.items[index];
316 pad.connected = true;
317 pad.timestamp = now;
Robbie McElrathf7b2d1172018-06-11 22:35:45318 gamepads_->seqlock.WriteEnd();
[email protected]cf786002014-02-11 02:05:54319}
320
[email protected]85603cbb2014-03-25 02:20:01321void GamepadController::DispatchConnected(int index) {
Matt Reynolds093c89f2019-05-03 00:58:59322 if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
323 return;
324 const Gamepad& pad = gamepads_->data.items[index];
325 if (!pad.connected)
[email protected]85603cbb2014-03-25 02:20:01326 return;
Robbie McElrathf7b2d1172018-06-11 22:35:45327 gamepads_->seqlock.WriteBegin();
Matt Reynolds093c89f2019-05-03 00:58:59328 for (auto& monitor : monitors_)
329 monitor->DispatchConnected(index, pad);
Robbie McElrathf7b2d1172018-06-11 22:35:45330 gamepads_->seqlock.WriteEnd();
[email protected]85603cbb2014-03-25 02:20:01331}
332
[email protected]cf786002014-02-11 02:05:54333void GamepadController::Disconnect(int index) {
juncai2f298a82017-04-18 03:51:39334 if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
[email protected]cf786002014-02-11 02:05:54335 return;
Matt Reynolds99e2f5ef2019-01-18 23:40:55336 const int64_t now = CurrentTimeInMicroseconds();
Robbie McElrathf7b2d1172018-06-11 22:35:45337 gamepads_->seqlock.WriteBegin();
338 Gamepad& pad = gamepads_->data.items[index];
[email protected]85603cbb2014-03-25 02:20:01339 pad.connected = false;
Matt Reynolds99e2f5ef2019-01-18 23:40:55340 pad.timestamp = now;
Matt Reynolds093c89f2019-05-03 00:58:59341 for (auto& monitor : monitors_)
342 monitor->DispatchDisconnected(index, pad);
Robbie McElrathf7b2d1172018-06-11 22:35:45343 gamepads_->seqlock.WriteEnd();
[email protected]cf786002014-02-11 02:05:54344}
345
346void GamepadController::SetId(int index, const std::string& src) {
juncai2f298a82017-04-18 03:51:39347 if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
[email protected]cf786002014-02-11 02:05:54348 return;
349 const char* p = src.c_str();
Matt Reynolds99e2f5ef2019-01-18 23:40:55350 const int64_t now = CurrentTimeInMicroseconds();
Robbie McElrathf7b2d1172018-06-11 22:35:45351 gamepads_->seqlock.WriteBegin();
Matt Reynolds99e2f5ef2019-01-18 23:40:55352 Gamepad& pad = gamepads_->data.items[index];
353 memset(pad.id, 0, sizeof(pad.id));
juncai2f298a82017-04-18 03:51:39354 for (unsigned i = 0; *p && i < Gamepad::kIdLengthCap - 1; ++i)
Matt Reynolds99e2f5ef2019-01-18 23:40:55355 pad.id[i] = *p++;
356 pad.timestamp = now;
Robbie McElrathf7b2d1172018-06-11 22:35:45357 gamepads_->seqlock.WriteEnd();
[email protected]cf786002014-02-11 02:05:54358}
359
360void GamepadController::SetButtonCount(int index, int buttons) {
juncai2f298a82017-04-18 03:51:39361 if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
[email protected]cf786002014-02-11 02:05:54362 return;
juncai2f298a82017-04-18 03:51:39363 if (buttons < 0 || buttons >= static_cast<int>(Gamepad::kButtonsLengthCap))
[email protected]cf786002014-02-11 02:05:54364 return;
Matt Reynolds99e2f5ef2019-01-18 23:40:55365 const int64_t now = CurrentTimeInMicroseconds();
Robbie McElrathf7b2d1172018-06-11 22:35:45366 gamepads_->seqlock.WriteBegin();
Matt Reynolds99e2f5ef2019-01-18 23:40:55367 Gamepad& pad = gamepads_->data.items[index];
368 pad.buttons_length = buttons;
369 pad.timestamp = now;
Robbie McElrathf7b2d1172018-06-11 22:35:45370 gamepads_->seqlock.WriteEnd();
[email protected]cf786002014-02-11 02:05:54371}
372
373void GamepadController::SetButtonData(int index, int button, double data) {
juncai2f298a82017-04-18 03:51:39374 if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
[email protected]cf786002014-02-11 02:05:54375 return;
juncai2f298a82017-04-18 03:51:39376 if (button < 0 || button >= static_cast<int>(Gamepad::kButtonsLengthCap))
[email protected]cf786002014-02-11 02:05:54377 return;
Matt Reynolds99e2f5ef2019-01-18 23:40:55378 const int64_t now = CurrentTimeInMicroseconds();
Robbie McElrathf7b2d1172018-06-11 22:35:45379 gamepads_->seqlock.WriteBegin();
Matt Reynolds99e2f5ef2019-01-18 23:40:55380 Gamepad& pad = gamepads_->data.items[index];
381 pad.buttons[button].value = data;
382 pad.buttons[button].pressed = data > kButtonPressedThreshold;
383 pad.timestamp = now;
Robbie McElrathf7b2d1172018-06-11 22:35:45384 gamepads_->seqlock.WriteEnd();
[email protected]cf786002014-02-11 02:05:54385}
386
387void GamepadController::SetAxisCount(int index, int axes) {
juncai2f298a82017-04-18 03:51:39388 if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
[email protected]cf786002014-02-11 02:05:54389 return;
juncai2f298a82017-04-18 03:51:39390 if (axes < 0 || axes >= static_cast<int>(Gamepad::kAxesLengthCap))
[email protected]cf786002014-02-11 02:05:54391 return;
Matt Reynolds99e2f5ef2019-01-18 23:40:55392 const int64_t now = CurrentTimeInMicroseconds();
Robbie McElrathf7b2d1172018-06-11 22:35:45393 gamepads_->seqlock.WriteBegin();
Matt Reynolds99e2f5ef2019-01-18 23:40:55394 Gamepad& pad = gamepads_->data.items[index];
395 pad.axes_length = axes;
396 pad.timestamp = now;
Robbie McElrathf7b2d1172018-06-11 22:35:45397 gamepads_->seqlock.WriteEnd();
[email protected]cf786002014-02-11 02:05:54398}
399
400void GamepadController::SetAxisData(int index, int axis, double data) {
juncai2f298a82017-04-18 03:51:39401 if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
[email protected]cf786002014-02-11 02:05:54402 return;
juncai2f298a82017-04-18 03:51:39403 if (axis < 0 || axis >= static_cast<int>(Gamepad::kAxesLengthCap))
[email protected]cf786002014-02-11 02:05:54404 return;
Matt Reynolds99e2f5ef2019-01-18 23:40:55405 const int64_t now = CurrentTimeInMicroseconds();
Robbie McElrathf7b2d1172018-06-11 22:35:45406 gamepads_->seqlock.WriteBegin();
Matt Reynolds99e2f5ef2019-01-18 23:40:55407 Gamepad& pad = gamepads_->data.items[index];
408 pad.axes[axis] = data;
409 pad.timestamp = now;
Robbie McElrathf7b2d1172018-06-11 22:35:45410 gamepads_->seqlock.WriteEnd();
[email protected]cf786002014-02-11 02:05:54411}
412
Matt Reynolds6e9187e2017-10-23 18:32:01413void GamepadController::SetDualRumbleVibrationActuator(int index,
414 bool enabled) {
415 if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
416 return;
Matt Reynolds99e2f5ef2019-01-18 23:40:55417 const int64_t now = CurrentTimeInMicroseconds();
Robbie McElrathf7b2d1172018-06-11 22:35:45418 gamepads_->seqlock.WriteBegin();
Matt Reynolds99e2f5ef2019-01-18 23:40:55419 Gamepad& pad = gamepads_->data.items[index];
420 pad.vibration_actuator.type = device::GamepadHapticActuatorType::kDualRumble;
421 pad.vibration_actuator.not_null = enabled;
422 pad.timestamp = now;
Robbie McElrathf7b2d1172018-06-11 22:35:45423 gamepads_->seqlock.WriteEnd();
Matt Reynolds6e9187e2017-10-23 18:32:01424}
425
danakj741848a2020-04-07 22:48:06426} // namespace content