Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/gamepad/cocoa/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 22 kB image not shown  

Quelle  CocoaGamepad.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


// mostly derived from the Allegro source code at:
// http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup

#include "mozilla/dom/GamepadHandle.h"
#include "mozilla/dom/GamepadPlatformService.h"
#include "mozilla/dom/GamepadRemapping.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Tainting.h"
#include "nsComponentManagerUtils.h"
#include "nsITimer.h"
#include "nsThreadUtils.h"
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/hid/IOHIDBase.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <IOKit/hid/IOHIDManager.h>

#include <stdio.h>
#include <vector>

namespace {

using namespace mozilla;
using namespace mozilla::dom;
class DarwinGamepadService;

DarwinGamepadService* gService = nullptr;

struct Button {
  int id;
  bool analog;
  IOHIDElementRef element;
  CFIndex min;
  CFIndex max;
  bool pressed;

  Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax)
      : id(aId),
        analog((aMax - aMin) > 1),
        element(aElement),
        min(aMin),
        max(aMax),
        pressed(false) {}
};

struct Axis {
  int id;
  IOHIDElementRef element;
  uint32_t usagePage;
  uint32_t usage;
  CFIndex min;
  CFIndex max;
  double value;
};

// These values can be found in the USB HID Usage Tables:
// http://www.usb.org/developers/hidpage
const unsigned kDesktopUsagePage = 0x01;
const unsigned kSimUsagePage = 0x02;
const unsigned kAcceleratorUsage = 0xC4;
const unsigned kBrakeUsage = 0xC5;
const unsigned kJoystickUsage = 0x04;
const unsigned kGamepadUsage = 0x05;
const unsigned kAxisUsageMin = 0x30;
const unsigned kAxisUsageMax = 0x35;
const unsigned kDpadUsage = 0x39;
const unsigned kButtonUsagePage = 0x09;
const unsigned kConsumerPage = 0x0C;
const unsigned kHomeUsage = 0x223;
const unsigned kBackUsage = 0x224;

// We poll it periodically,
// 50ms is arbitrarily chosen.
const uint32_t kDarwinGamepadPollInterval = 50;

struct GamepadInputReportContext {
  DarwinGamepadService* service;
  size_t gamepadSlot;
};

class Gamepad {
 private:
  IOHIDDeviceRef mDevice;
  nsTArray<Button> buttons;
  nsTArray<Axis> axes;

 public:
  Gamepad() : mDevice(nullptr) {}

  bool operator==(IOHIDDeviceRef device) const { return mDevice == device; }
  bool empty() const { return mDevice == nullptr; }
  void clear() {
    mDevice = nullptr;
    buttons.Clear();
    axes.Clear();
    mHandle = GamepadHandle{};
  }
  void init(IOHIDDeviceRef device, bool defaultRemapper);
  void ReportChanged(uint8_t* report, CFIndex report_length);
  size_t WriteOutputReport(const std::vector<uint8_t>& aReport) const;

  size_t numButtons() { return buttons.Length(); }
  size_t numAxes() { return axes.Length(); }

  Button* lookupButton(IOHIDElementRef element) {
    for (unsigned i = 0; i < buttons.Length(); i++) {
      if (buttons[i].element == element) return &buttons[i];
    }
    return nullptr;
  }

  Axis* lookupAxis(IOHIDElementRef element) {
    for (unsigned i = 0; i < axes.Length(); i++) {
      if (axes[i].element == element) return &axes[i];
    }
    return nullptr;
  }

  GamepadHandle mHandle;
  RefPtr<GamepadRemapper> mRemapper;
  nsTArray<uint8_t> mInputReport;
  UniquePtr<GamepadInputReportContext> mInputReportContext;
};

void Gamepad::init(IOHIDDeviceRef aDevice, bool aDefaultRemapper) {
  clear();
  mDevice = aDevice;

  CFArrayRef elements =
      IOHIDDeviceCopyMatchingElements(aDevice, nullptr, kIOHIDOptionsTypeNone);
  CFIndex n = CFArrayGetCount(elements);
  for (CFIndex i = 0; i < n; i++) {
    IOHIDElementRef element =
        (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
    uint32_t usagePage = IOHIDElementGetUsagePage(element);
    uint32_t usage = IOHIDElementGetUsage(element);

    if (usagePage == kDesktopUsagePage && usage >= kAxisUsageMin &&
        usage <= kAxisUsageMax) {
      Axis axis = {aDefaultRemapper ? int(axes.Length())
                                    : static_cast<int>(usage - kAxisUsageMin),
                   element,
                   usagePage,
                   usage,
                   IOHIDElementGetLogicalMin(element),
                   IOHIDElementGetLogicalMax(element)};
      axes.AppendElement(axis);
    } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage &&
               // Don't know how to handle d-pads that return weird values.
               IOHIDElementGetLogicalMax(element) -
                       IOHIDElementGetLogicalMin(element) ==
                   7) {
      Axis axis = {aDefaultRemapper ? int(axes.Length())
                                    : static_cast<int>(usage - kAxisUsageMin),
                   element,
                   usagePage,
                   usage,
                   IOHIDElementGetLogicalMin(element),
                   IOHIDElementGetLogicalMax(element)};
      axes.AppendElement(axis);
    } else if (usagePage == kSimUsagePage &&
               (usage == kAcceleratorUsage || usage == kBrakeUsage)) {
      if (IOHIDElementGetType(element) == kIOHIDElementTypeInput_Button) {
        Button button(int(buttons.Length()), element,
                      IOHIDElementGetLogicalMin(element),
                      IOHIDElementGetLogicalMax(element));
        buttons.AppendElement(button);
      } else {
        Axis axis = {aDefaultRemapper ? int(axes.Length())
                                      : static_cast<int>(usage - kAxisUsageMin),
                     element,
                     usagePage,
                     usage,
                     IOHIDElementGetLogicalMin(element),
                     IOHIDElementGetLogicalMax(element)};
        axes.AppendElement(axis);
      }
    } else if ((usagePage == kButtonUsagePage) ||
               (usagePage == kConsumerPage &&
                (usage == kHomeUsage || usage == kBackUsage))) {
      Button button(int(buttons.Length()), element,
                    IOHIDElementGetLogicalMin(element),
                    IOHIDElementGetLogicalMax(element));
      buttons.AppendElement(button);
    } else {
      // TODO: handle other usage pages
    }
  }
}

// This service is created and destroyed in Background thread while
// operates in a seperate thread(We call it Monitor thread here).
class DarwinGamepadService {
 private:
  IOHIDManagerRef mManager;
  nsTArray<Gamepad> mGamepads;

  nsCOMPtr<nsIThread> mMonitorThread;
  nsCOMPtr<nsIThread> mBackgroundThread;
  nsCOMPtr<nsITimer> mPollingTimer;
  bool mIsRunning;

  static void DeviceAddedCallback(void* data, IOReturn result, void* sender,
                                  IOHIDDeviceRef device);
  static void DeviceRemovedCallback(void* data, IOReturn result, void* sender,
                                    IOHIDDeviceRef device);
  static void InputValueChangedCallback(void* data, IOReturn result,
                                        void* sender, IOHIDValueRef newValue);
  static void EventLoopOnceCallback(nsITimer* aTimer, void* aClosure);

  void DeviceAdded(IOHIDDeviceRef device);
  void DeviceRemoved(IOHIDDeviceRef device);
  void InputValueChanged(IOHIDValueRef value);
  void StartupInternal();
  void RunEventLoopOnce();

 public:
  DarwinGamepadService();
  ~DarwinGamepadService();

  static void ReportChangedCallback(void* context, IOReturn result,
                                    void* sender, IOHIDReportType report_type,
                                    uint32_t report_id, uint8_t* report,
                                    CFIndex report_length);

  void Startup();
  void Shutdown();
  void SetLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
                              const Tainted<uint32_t>& aLightColorIndex,
                              const uint8_t& aRed, const uint8_t& aGreen,
                              const uint8_t& aBlue);
  friend class DarwinGamepadServiceStartupRunnable;
  friend class DarwinGamepadServiceShutdownRunnable;
};

class DarwinGamepadServiceStartupRunnable final : public Runnable {
 private:
  ~DarwinGamepadServiceStartupRunnable() {}
  // This Runnable schedules startup of DarwinGamepadService
  // in a new thread, pointer to DarwinGamepadService is only
  // used by this Runnable within its thread.
  DarwinGamepadService MOZ_NON_OWNING_REF* mService;

 public:
  explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService* service)
      : Runnable("DarwinGamepadServiceStartupRunnable"), mService(service) {}
  NS_IMETHOD Run() override {
    MOZ_ASSERT(mService);
    mService->StartupInternal();
    return NS_OK;
  }
};

class DarwinGamepadServiceShutdownRunnable final : public Runnable {
 private:
  ~DarwinGamepadServiceShutdownRunnable() {}

 public:
  // This Runnable schedules shutdown of DarwinGamepadService
  // in background thread.
  explicit DarwinGamepadServiceShutdownRunnable()
      : Runnable("DarwinGamepadServiceStartupRunnable") {}
  NS_IMETHOD Run() override {
    MOZ_ASSERT(gService);
    MOZ_ASSERT(NS_GetCurrentThread() == gService->mBackgroundThread);

    IOHIDManagerRef manager = (IOHIDManagerRef)gService->mManager;

    if (manager) {
      IOHIDManagerClose(manager, 0);
      CFRelease(manager);
      gService->mManager = nullptr;
    }
    gService->mMonitorThread->Shutdown();
    delete gService;
    gService = nullptr;
    return NS_OK;
  }
};

void DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device) {
  RefPtr<GamepadPlatformService> service =
      GamepadPlatformService::GetParentService();
  if (!service) {
    return;
  }

  size_t slot = size_t(-1);
  for (size_t i = 0; i < mGamepads.Length(); i++) {
    if (mGamepads[i] == device) return;
    if (slot == size_t(-1) && mGamepads[i].empty()) slot = i;
  }

  if (slot == size_t(-1)) {
    slot = mGamepads.Length();
    mGamepads.AppendElement(Gamepad());
  }

  // Gather some identifying information
  CFNumberRef vendorIdRef =
      (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
  CFNumberRef productIdRef =
      (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
  CFStringRef productRef =
      (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
  int vendorId, productId;
  CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId);
  CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
  char product_name[128];
  CFStringGetCString(productRef, product_name, sizeof(product_name),
                     kCFStringEncodingASCII);
  char buffer[256];
  SprintfLiteral(buffer, "%x-%x-%s", vendorId, productId, product_name);

  bool defaultRemapper = false;
  RefPtr<GamepadRemapper> remapper =
      GetGamepadRemapper(vendorId, productId, defaultRemapper);
  MOZ_ASSERT(remapper);
  mGamepads[slot].init(device, defaultRemapper);

  remapper->SetAxisCount(mGamepads[slot].numAxes());
  remapper->SetButtonCount(mGamepads[slot].numButtons());

  GamepadHandle handle = service->AddGamepad(
      buffer, remapper->GetMappingType(), mozilla::dom::GamepadHand::_empty,
      remapper->GetButtonCount(), remapper->GetAxisCount(),
      0,  // TODO: Bug 680289, implement gamepad haptics for cocoa.
      remapper->GetLightIndicatorCount(), remapper->GetTouchEventCount());

  nsTArray<GamepadLightIndicatorType> lightTypes;
  remapper->GetLightIndicators(lightTypes);
  for (uint32_t i = 0; i < lightTypes.Length(); ++i) {
    if (lightTypes[i] != GamepadLightIndicator::DefaultType()) {
      service->NewLightIndicatorTypeEvent(handle, i, lightTypes[i]);
    }
  }

  mGamepads[slot].mHandle = handle;
  mGamepads[slot].mInputReport.SetLength(remapper->GetMaxInputReportLength());
  mGamepads[slot].mInputReportContext = UniquePtr<GamepadInputReportContext>(
      new GamepadInputReportContext{this, slot});
  mGamepads[slot].mRemapper = remapper.forget();

  IOHIDDeviceRegisterInputReportCallback(
      device, mGamepads[slot].mInputReport.Elements(),
      mGamepads[slot].mInputReport.Length(), ReportChangedCallback,
      mGamepads[slot].mInputReportContext.get());
}

void DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device) {
  RefPtr<GamepadPlatformService> service =
      GamepadPlatformService::GetParentService();
  if (!service) {
    return;
  }
  for (Gamepad& gamepad : mGamepads) {
    if (gamepad == device) {
      IOHIDDeviceRegisterInputReportCallback(
          device, gamepad.mInputReport.Elements(), 0, NULL,
          gamepad.mInputReportContext.get());

      gamepad.mInputReportContext.reset();

      service->RemoveGamepad(gamepad.mHandle);
      gamepad.clear();
      return;
    }
  }
}

// Replace context to be Gamepad.
void DarwinGamepadService::ReportChangedCallback(
    void* context, IOReturn result, void* sender, IOHIDReportType report_type,
    uint32_t report_id, uint8_t* report, CFIndex report_length) {
  if (context && report_type == kIOHIDReportTypeInput && report_length) {
    auto reportContext = static_cast<GamepadInputReportContext*>(context);
    DarwinGamepadService* service = reportContext->service;
    service->mGamepads[reportContext->gamepadSlot].ReportChanged(report,
                                                                 report_length);
  }
}

void Gamepad::ReportChanged(uint8_t* report, CFIndex report_len) {
  MOZ_RELEASE_ASSERT(report_len <= mRemapper->GetMaxInputReportLength());
  mRemapper->ProcessTouchData(mHandle, report);
}

size_t Gamepad::WriteOutputReport(const std::vector<uint8_t>& aReport) const {
  IOReturn success =
      IOHIDDeviceSetReport(mDevice, kIOHIDReportTypeOutput, aReport[0],
                           aReport.data(), aReport.size());

  MOZ_ASSERT(success == kIOReturnSuccess);
  return (success == kIOReturnSuccess) ? aReport.size() : 0;
}

void DarwinGamepadService::InputValueChanged(IOHIDValueRef value) {
  RefPtr<GamepadPlatformService> service =
      GamepadPlatformService::GetParentService();
  if (!service) {
    return;
  }

  uint32_t value_length = IOHIDValueGetLength(value);
  if (value_length > 4) {
    // Workaround for bizarre issue with PS3 controllers that try to return
    // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
    return;
  }
  IOHIDElementRef element = IOHIDValueGetElement(value);
  IOHIDDeviceRef device = IOHIDElementGetDevice(element);

  for (Gamepad& gamepad : mGamepads) {
    if (gamepad == device) {
      // Axis elements represent axes and d-pads.
      if (Axis* axis = gamepad.lookupAxis(element)) {
        const double d = IOHIDValueGetIntegerValue(value);
        const double v =
            2.0f * (d - axis->min) / (double)(axis->max - axis->min) - 1.0f;
        if (axis->value != v) {
          gamepad.mRemapper->RemapAxisMoveEvent(gamepad.mHandle, axis->id, v);
          axis->value = v;
        }
      } else if (Button* button = gamepad.lookupButton(element)) {
        const int iv = IOHIDValueGetIntegerValue(value);
        const bool pressed = iv != 0;
        if (button->pressed != pressed) {
          gamepad.mRemapper->RemapButtonEvent(gamepad.mHandle, button->id,
                                              pressed);
          button->pressed = pressed;
        }
      }
      return;
    }
  }
}

void DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
                                               void* sender,
                                               IOHIDDeviceRef device) {
  DarwinGamepadService* service = (DarwinGamepadService*)data;
  service->DeviceAdded(device);
}

void DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result,
                                                 void* sender,
                                                 IOHIDDeviceRef device) {
  DarwinGamepadService* service = (DarwinGamepadService*)data;
  service->DeviceRemoved(device);
}

void DarwinGamepadService::InputValueChangedCallback(void* data,
                                                     IOReturn result,
                                                     void* sender,
                                                     IOHIDValueRef newValue) {
  DarwinGamepadService* service = (DarwinGamepadService*)data;
  service->InputValueChanged(newValue);
}

void DarwinGamepadService::EventLoopOnceCallback(nsITimer* aTimer,
                                                 void* aClosure) {
  DarwinGamepadService* service = static_cast<DarwinGamepadService*>(aClosure);
  service->RunEventLoopOnce();
}

static CFMutableDictionaryRef MatchingDictionary(UInt32 inUsagePage,
                                                 UInt32 inUsage) {
  CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
      kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
      &kCFTypeDictionaryValueCallBacks);
  if (!dict) return nullptr;
  CFNumberRef number =
      CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsagePage);
  if (!number) {
    CFRelease(dict);
    return nullptr;
  }
  CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
  CFRelease(number);

  number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
  if (!number) {
    CFRelease(dict);
    return nullptr;
  }
  CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
  CFRelease(number);

  return dict;
}

DarwinGamepadService::DarwinGamepadService()
    : mManager(nullptr), mIsRunning(false) {}

DarwinGamepadService::~DarwinGamepadService() {
  if (mManager != nullptr) CFRelease(mManager);
  mMonitorThread = nullptr;
  mBackgroundThread = nullptr;
  if (mPollingTimer) {
    mPollingTimer->Cancel();
    mPollingTimer = nullptr;
  }
}

void DarwinGamepadService::RunEventLoopOnce() {
  MOZ_ASSERT(NS_GetCurrentThread() == mMonitorThread);
  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);

  // This timer must be created in monitor thread
  if (!mPollingTimer) {
    mPollingTimer = NS_NewTimer();
  }
  mPollingTimer->Cancel();
  if (mIsRunning) {
    mPollingTimer->InitWithNamedFuncCallback(
        EventLoopOnceCallback, this, kDarwinGamepadPollInterval,
        nsITimer::TYPE_ONE_SHOT, "EventLoopOnceCallback");
  } else {
    // We schedule a task shutdown and cleaning up resources to Background
    // thread here to make sure no runloop is running to prevent potential race
    // condition.
    RefPtr<Runnable> shutdownTask = new DarwinGamepadServiceShutdownRunnable();
    mBackgroundThread->Dispatch(shutdownTask.forget(), NS_DISPATCH_NORMAL);
  }
}

void DarwinGamepadService::StartupInternal() {
  if (mManager != nullptr) return;

  IOHIDManagerRef manager =
      IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);

  CFMutableDictionaryRef criteria_arr[2];
  criteria_arr[0] = MatchingDictionary(kDesktopUsagePage, kJoystickUsage);
  if (!criteria_arr[0]) {
    CFRelease(manager);
    return;
  }

  criteria_arr[1] = MatchingDictionary(kDesktopUsagePage, kGamepadUsage);
  if (!criteria_arr[1]) {
    CFRelease(criteria_arr[0]);
    CFRelease(manager);
    return;
  }

  CFArrayRef criteria = CFArrayCreate(kCFAllocatorDefault,
                                      (const void**)criteria_arr, 2, nullptr);
  if (!criteria) {
    CFRelease(criteria_arr[1]);
    CFRelease(criteria_arr[0]);
    CFRelease(manager);
    return;
  }

  IOHIDManagerSetDeviceMatchingMultiple(manager, criteria);
  CFRelease(criteria);
  CFRelease(criteria_arr[1]);
  CFRelease(criteria_arr[0]);

  IOHIDManagerRegisterDeviceMatchingCallback(manager, DeviceAddedCallback,
                                             this);
  IOHIDManagerRegisterDeviceRemovalCallback(manager, DeviceRemovedCallback,
                                            this);
  IOHIDManagerRegisterInputValueCallback(manager, InputValueChangedCallback,
                                         this);
  IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(),
                                  kCFRunLoopDefaultMode);
  IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
  if (rv != kIOReturnSuccess) {
    CFRelease(manager);
    return;
  }

  mManager = manager;

  mIsRunning = true;
  RunEventLoopOnce();
}

void DarwinGamepadService::Startup() {
  mBackgroundThread = NS_GetCurrentThread();
  Unused << NS_NewNamedThread("Gamepad", getter_AddRefs(mMonitorThread),
                              new DarwinGamepadServiceStartupRunnable(this));
}

void DarwinGamepadService::Shutdown() {
  // Flipping this flag will stop the eventloop in Monitor thread
  // and dispatch a task destroying and cleaning up resources in
  // Background thread
  mIsRunning = false;
}

void DarwinGamepadService::SetLightIndicatorColor(
    const Tainted<GamepadHandle>& aGamepadHandle,
    const Tainted<uint32_t>& aLightColorIndex, const uint8_t& aRed,
    const uint8_t& aGreen, const uint8_t& aBlue) {
  // We get aControllerIdx from GamepadPlatformService::AddGamepad(),
  // It begins from 1 and is stored at Gamepad.id.
  const Gamepad* gamepad = MOZ_FIND_AND_VALIDATE(
      aGamepadHandle, list_item.mHandle == aGamepadHandle, mGamepads);
  if (!gamepad) {
    MOZ_ASSERT(false);
    return;
  }

  RefPtr<GamepadRemapper> remapper = gamepad->mRemapper;
  if (!remapper ||
      MOZ_IS_VALID(aLightColorIndex,
                   remapper->GetLightIndicatorCount() <= aLightColorIndex)) {
    MOZ_ASSERT(false);
    return;
  }

  std::vector<uint8_t> report;
  remapper->GetLightColorReport(aRed, aGreen, aBlue, report);
  gamepad->WriteOutputReport(report);
}

}  // namespace

namespace mozilla {
namespace dom {

void StartGamepadMonitoring() {
  ::mozilla::ipc::AssertIsOnBackgroundThread();
  if (gService) {
    return;
  }

  gService = new DarwinGamepadService();
  gService->Startup();
}

void StopGamepadMonitoring() {
  ::mozilla::ipc::AssertIsOnBackgroundThread();
  if (!gService) {
    return;
  }

  // Calling Shutdown() will delete gService as well
  gService->Shutdown();
}

void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
                                   const Tainted<uint32_t>& aLightColorIndex,
                                   const uint8_t& aRed, const uint8_t& aGreen,
                                   const uint8_t& aBlue) {
  MOZ_ASSERT(gService);
  if (!gService) {
    return;
  }
  gService->SetLightIndicatorColor(aGamepadHandle, aLightColorIndex, aRed,
                                   aGreen, aBlue);
}

}  // namespace dom
}  // namespace mozilla

Messung V0.5
C=90 H=94 G=91

¤ Dauer der Verarbeitung: 0.15 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.