OpenDingux: Add rumble support

pull/11586/head
jdgleaver 2020-11-17 15:59:39 +00:00
parent 0a3306a7b4
commit e582cf9f1d
34 changed files with 2901 additions and 19 deletions

View File

@ -1323,6 +1323,24 @@ ifeq ($(HAVE_WINRAWINPUT), 1)
endif
endif
ifeq ($(HAVE_LIBSHAKE), 1)
ifeq ($(OSX), 1)
DEFINES += -DHAVE_LIBSHAKE
INCLUDE_DIRS += -I$(DEPS_DIR)/libShake/include
OBJ += $(DEPS_DIR)/libShake/src/common/error.o \
$(DEPS_DIR)/libShake/src/common/helpers.o \
$(DEPS_DIR)/libShake/src/common/presets.o \
$(DEPS_DIR)/libShake/src/osx/shake.o
else ifeq ($(HAVE_UNIX), 1)
DEFINES += -DHAVE_LIBSHAKE
INCLUDE_DIRS += -I$(DEPS_DIR)/libShake/include
OBJ += $(DEPS_DIR)/libShake/src/common/error.o \
$(DEPS_DIR)/libShake/src/common/helpers.o \
$(DEPS_DIR)/libShake/src/common/presets.o \
$(DEPS_DIR)/libShake/src/linux/shake.o
endif
endif
# Companion UI
ifneq ($(findstring Win32,$(OS)),)

View File

@ -69,6 +69,7 @@ HAVE_ZLIB = 1
HAVE_CONFIGFILE = 1
HAVE_PATCH = 1
HAVE_CHEATS = 1
HAVE_LIBSHAKE = 1
OS = Linux
TARGET = retroarch
@ -149,27 +150,27 @@ SYMBOL_MAP := -Wl,-Map=output.map
$(TARGET): $(RARCH_OBJ)
@$(if $(Q), $(shell echo echo LD $@),)
$(LINK) -o $@ $(RARCH_OBJ) $(LIBS) $(LDFLAGS) $(LIBRARY_DIRS)
$(Q)$(LINK) -o $@ $(RARCH_OBJ) $(LIBS) $(LDFLAGS) $(LIBRARY_DIRS)
$(OBJDIR)/%.o: %.c
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo CC $<),)
$(CC) $(CPPFLAGS) $(CFLAGS) $(DEFINES) -c -o $@ $<
$(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(DEFINES) -c -o $@ $<
$(OBJDIR)/%.o: %.cpp
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo CXX $<),)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFINES) -MMD -c -o $@ $<
$(Q)$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFINES) -MMD -c -o $@ $<
$(OBJDIR)/%.o: %.m
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo OBJC $<),)
$(CXX) $(OBJCFLAGS) $(DEFINES) -MMD -c -o $@ $<
$(Q)$(CXX) $(OBJCFLAGS) $(DEFINES) -MMD -c -o $@ $<
$(OBJDIR)/%.o: %.S $(HEADERS)
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo AS $<),)
$(CC) $(CFLAGS) $(ASFLAGS) $(DEFINES) -c -o $@ $<
$(Q)$(CC) $(CFLAGS) $(ASFLAGS) $(DEFINES) -c -o $@ $<
clean:
rm -rf $(OBJDIR_BASE)

View File

@ -73,6 +73,7 @@ HAVE_CONFIGFILE = 1
HAVE_PATCH = 1
HAVE_CHEATS = 1
HAVE_CHEEVOS = 0
HAVE_LIBSHAKE = 1
OS = Linux
TARGET = retroarch
@ -150,27 +151,27 @@ SYMBOL_MAP := -Wl,-Map=output.map
$(TARGET): $(RARCH_OBJ)
@$(if $(Q), $(shell echo echo LD $@),)
$(LINK) -o $@ $(RARCH_OBJ) $(LIBS) $(LDFLAGS) $(LIBRARY_DIRS)
$(Q)$(LINK) -o $@ $(RARCH_OBJ) $(LIBS) $(LDFLAGS) $(LIBRARY_DIRS)
$(OBJDIR)/%.o: %.c
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo CC $<),)
$(CC) $(CPPFLAGS) $(CFLAGS) $(DEFINES) -c -o $@ $<
$(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(DEFINES) -c -o $@ $<
$(OBJDIR)/%.o: %.cpp
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo CXX $<),)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFINES) -MMD -c -o $@ $<
$(Q)$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFINES) -MMD -c -o $@ $<
$(OBJDIR)/%.o: %.m
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo OBJC $<),)
$(CXX) $(OBJCFLAGS) $(DEFINES) -MMD -c -o $@ $<
$(Q)$(CXX) $(OBJCFLAGS) $(DEFINES) -MMD -c -o $@ $<
$(OBJDIR)/%.o: %.S $(HEADERS)
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo AS $<),)
$(CC) $(CFLAGS) $(ASFLAGS) $(DEFINES) -c -o $@ $<
$(Q)$(CC) $(CFLAGS) $(ASFLAGS) $(DEFINES) -c -o $@ $<
clean:
rm -rf $(OBJDIR_BASE)

View File

@ -1291,6 +1291,12 @@ static const bool sustained_performance_mode = false;
static const bool vibrate_on_keypress = false;
static const bool enable_device_vibration = false;
/* Defines the strength of rumble effects
* on OpenDingux devices */
#if defined(DINGUX) && defined(HAVE_LIBSHAKE)
#define DEFAULT_DINGUX_RUMBLE_GAIN 50
#endif
#ifdef HAVE_VULKAN
#define DEFAULT_VULKAN_GPU_INDEX 0
#endif

View File

@ -1864,6 +1864,9 @@ static struct config_uint_setting *populate_settings_uint(
SETTING_UINT("input_hotkey_block_delay", &settings->uints.input_hotkey_block_delay, true, DEFAULT_INPUT_HOTKEY_BLOCK_DELAY, false);
#ifdef GEKKO
SETTING_UINT("input_mouse_scale", &settings->uints.input_mouse_scale, true, DEFAULT_MOUSE_SCALE, false);
#endif
#if defined(DINGUX) && defined(HAVE_LIBSHAKE)
SETTING_UINT("input_dingux_rumble_gain", &settings->uints.input_dingux_rumble_gain, true, DEFAULT_DINGUX_RUMBLE_GAIN, false);
#endif
SETTING_UINT("audio_latency", &settings->uints.audio_latency, false, 0 /* TODO */, false);
SETTING_UINT("audio_resampler_quality", &settings->uints.audio_resampler_quality, true, audio_resampler_quality_level, false);

View File

@ -155,6 +155,8 @@ typedef struct settings
unsigned input_menu_toggle_gamepad_combo;
unsigned input_keyboard_gamepad_mapping_type;
unsigned input_poll_type_behavior;
unsigned input_dingux_rumble_gain;
unsigned netplay_port;
unsigned netplay_input_latency_frames_min;
unsigned netplay_input_latency_frames_range;

23
deps/libShake/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2015-2019 Artur Rojek
Copyright (c) 2015-2019 Joe Vargas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

72
deps/libShake/Makefile vendored Normal file
View File

@ -0,0 +1,72 @@
ifeq ($(PLATFORM), gcw0)
CC := /opt/gcw0-toolchain/usr/bin/mipsel-linux-gcc
STRIP := /opt/gcw0-toolchain/usr/bin/mipsel-linux-strip
BACKEND := LINUX
endif
ifndef BACKEND
$(error Please specify BACKEND. Possible values: LINUX, OSX")
endif
LIBNAME := libshake
SOVERSION := 2
SRCDIRS := common
ifeq ($(BACKEND), LINUX)
LIBEXT := .so
SONAME := $(LIBNAME)$(LIBEXT).$(SOVERSION)
PREFIX ?= /usr
LDFLAGS :=-Wl,-soname,$(SONAME)
SRCDIRS += linux
else ifeq ($(BACKEND), OSX)
LIBEXT := .dylib
SONAME := $(LIBNAME).$(SOVERSION)$(LIBEXT)
PREFIX ?= /usr/local
LDFLAGS := -Wl,-framework,Cocoa -framework IOKit -framework CoreFoundation -framework ForceFeedback -install_name $(SONAME)
SRCDIRS += osx
endif
CC ?= gcc
STRIP ?= strip
TARGET ?= $(SONAME)
SYSROOT := $(shell $(CC) --print-sysroot)
MACHINE ?= $(shell $(CC) -dumpmachine)
DESTDIR ?= $(SYSROOT)
CFLAGS := -fPIC
SRCDIR := src
OBJDIR := obj/$(MACHINE)
SRC := $(foreach dir,$(SRCDIRS),$(sort $(wildcard $(addprefix $(SRCDIR)/,$(dir))/*.c)))
OBJ := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRC))
ifdef DEBUG
CFLAGS += -ggdb -Wall -Werror -pedantic -std=c89
else
CFLAGS += -O2
endif
.PHONY: all install-headers install-lib install clean
all: $(TARGET)
$(TARGET): $(OBJ)
$(CC) $(LDFLAGS) -shared $(CFLAGS) $^ -o $@
ifdef DO_STRIP
$(STRIP) $@
endif
$(OBJ): $(OBJDIR)/%.o: $(SRCDIR)/%.c
mkdir -p $(@D)
$(CC) -c $(CFLAGS) $< -o $@ -I include
install-headers:
cp include/*.h $(DESTDIR)$(PREFIX)/include/
install-lib:
cp $(TARGET) $(DESTDIR)$(PREFIX)/lib/
ln -sf $(TARGET) $(DESTDIR)$(PREFIX)/lib/$(LIBNAME)$(LIBEXT)
install: $(TARGET) install-headers install-lib
clean:
rm -Rf $(TARGET) $(OBJDIR)

44
deps/libShake/README.md vendored Normal file
View File

@ -0,0 +1,44 @@
libShake
========
About
-----
libShake is a simple, cross-platform haptic library.
Installation
------------
### Linux
```shell
BACKEND=LINUX make install
```
### OSX
```shell
BACKEND=OSX make install
```
Authors
-------
* Artur Rojek (zear)
* Joe Vargas (jxv)
License
-------
This program is released under the terms of MIT License, also known as Expat License.
See [LICENSE.txt](LICENSE.txt) for more details.
Extras
------
Buildroot support is available for libShake. Package config can be found in [extras/buildroot-package/](extras/buildroot-package/).
IRC channel
-----------
Join our IRC chatroom at the following address:
`#libShake` on `chat.freenode.net`
More information about the Freenode network can be found on [Freenode](https://freenode.net/).

28
deps/libShake/examples/Makefile vendored Normal file
View File

@ -0,0 +1,28 @@
ifeq ($(PLATFORM), gcw0)
CC := /opt/gcw0-toolchain/usr/bin/mipsel-linux-gcc
STRIP := /opt/gcw0-toolchain/usr/bin/mipsel-linux-strip
endif
CC ?= gcc
STRIP ?= strip
TARGET ?= simple listDevices deviceTest
LDFLAGS := -L.. -lshake
CFLAGS := -fPIC -I../include
SRCDIR := .
ifdef DEBUG
CFLAGS += -ggdb -Wall -Werror -pedantic -std=c89
else
CFLAGS += -O2
endif
.PHONY: all clean
all: $(TARGET)
$(TARGET):
$(CC) $(CFLAGS) $(SRCDIR)/$@.c $(LDFLAGS) -o $@
$(STRIP) $@
clean:
rm -Rf $(TARGET)

333
deps/libShake/examples/deviceTest.c vendored Normal file
View File

@ -0,0 +1,333 @@
#include <shake.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
Shake_Device *device;
static int quit;
static void waitForKey(void)
{
printf("Press RETURN to continue");
while(getchar() != '\n');
}
static void listDevices(void)
{
int numDevices;
int i;
numDevices = Shake_NumOfDevices();
printf("Detected devices: %d\n\n", numDevices);
for (i = 0; i < numDevices; ++i)
{
Shake_Device *dev = (i == Shake_DeviceId(dev)) ? dev : Shake_Open(i);
printf("#%2d: %s\n", Shake_DeviceId(dev), Shake_DeviceName(dev));
if (dev != device)
Shake_Close(dev);
}
}
static void deviceInfo(void)
{
printf("\nDevice #%d\n", Shake_DeviceId(device));
printf(" Name: %s\n", Shake_DeviceName(device));
printf(" Adjustable gain: %s\n", Shake_QueryGainSupport(device) ? "yes" : "no");
printf(" Adjustable autocenter: %s\n", Shake_QueryAutocenterSupport(device) ? "yes" : "no");
printf(" Effect capacity: %d\n", Shake_DeviceEffectCapacity(device));
printf(" Supported effects:\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_RUMBLE)) printf(" SHAKE_EFFECT_RUMBLE\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_PERIODIC))
{
printf(" SHAKE_EFFECT_PERIODIC\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_SQUARE)) printf(" * SHAKE_PERIODIC_SQUARE\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_TRIANGLE)) printf(" * SHAKE_PERIODIC_TRIANGLE\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_SINE)) printf(" * SHAKE_PERIODIC_SINE\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_SAW_UP)) printf(" * SHAKE_PERIODIC_SAW_UP\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_SAW_DOWN)) printf(" * SHAKE_PERIODIC_SAW_DOWN\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_CUSTOM)) printf(" * SHAKE_PERIODIC_CUSTOM\n");
}
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_CONSTANT)) printf(" SHAKE_EFFECT_CONSTANT\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_SPRING)) printf(" SHAKE_EFFECT_SPRING\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_FRICTION)) printf(" SHAKE_EFFECT_FRICTION\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_DAMPER)) printf(" SHAKE_EFFECT_DAMPER\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_INERTIA)) printf(" SHAKE_EFFECT_INERTIA\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_RAMP)) printf(" SHAKE_EFFECT_RAMP\n");
printf("\n");
}
static void testCapacity(void)
{
Shake_Effect effect;
int capacity = Shake_DeviceEffectCapacity(device);
int *id;
int i;
id = malloc(capacity * sizeof(int));
if (!id)
{
printf("Unable to allocate memory.\n");
return;
}
printf("-Capacity-\n");
printf("Reported capacity: %d\n", capacity);
for (i = 0; i < capacity; ++i)
{
Shake_SimplePeriodic(&effect, SHAKE_PERIODIC_SINE, 1.0, 0.0, 1.0, 0.0);
id[i] = Shake_UploadEffect(device, &effect);
printf("Uploaded #%d as #%d\n", i, id[i]);
}
printf("-End-\n\n");
for (i = 0; i < capacity; ++i)
{
Shake_EraseEffect(device, id[i]);
}
free(id);
}
static void testEffectPlayback(void)
{
Shake_Effect effect;
int id;
printf("-Effect playback-\n");
Shake_SimplePeriodic(&effect, SHAKE_PERIODIC_SINE, 1.0, 0.0, 2.0, 0.0);
id = Shake_UploadEffect(device, &effect);
Shake_Play(device, id);
printf("Playing (2 sec)\n");
sleep(1);
Shake_Stop(device, id);
printf("Stopping (at 1 sec)\n");
sleep(1);
Shake_Play(device, id);
printf("Replaying (2 sec)\n");
sleep(2);
printf("-End-\n\n");
Shake_EraseEffect(device, id);
}
static void testEffectOrder(void)
{
Shake_Effect effect;
int id[4];
int shuffle[4];
int i;
printf("-Effect order-\n");
Shake_SimplePeriodic(&effect, SHAKE_PERIODIC_SINE, 1.0, 0.0, 1.0, 0.0);
id[0] = Shake_UploadEffect(device, &effect);
Shake_SimplePeriodic(&effect, SHAKE_PERIODIC_SQUARE, 1.0, 0.0, 1.0, 0.0);
id[1] = Shake_UploadEffect(device, &effect);
Shake_SimpleRumble(&effect, 1.0, 1.0, 1.0);
id[2] = Shake_UploadEffect(device, &effect);
Shake_SimpleRumble(&effect, 0.8, 1.0, 1.0);
id[3] = Shake_UploadEffect(device, &effect);
for (i = 0; i < 4; ++i)
{
int r;
while (1)
{
int repeat = 0;
int j;
r = rand() % 4;
for (j = 0; j < i; ++j)
{
if (r == shuffle[j])
{
repeat = 1;
break;
}
}
if (!repeat)
break;
}
shuffle[i] = r;
}
for (i = 0; i < 4; ++i)
{
Shake_Play(device, id[shuffle[i]]);
printf("Effect #%d\n", shuffle[i]);
sleep(1);
}
printf("-End-\n\n");
for (i = 0; i < 4; ++i)
{
Shake_EraseEffect(device, id[i]);
}
}
static void testEffectUpdate(void)
{
Shake_Effect effect;
int id;
printf("-Effect update-\n");
Shake_SimplePeriodic(&effect, SHAKE_PERIODIC_SINE, 1.0, 0.0, 4.0, 0.0);
id = Shake_UploadEffect(device, &effect);
Shake_Play(device, id);
printf("Original\n");
sleep(2);
effect.u.periodic.magnitude = 0x6000;
effect.id = id;
id = Shake_UploadEffect(device, &effect);
printf("Updated\n");
sleep(2);
printf("-End-\n\n");
Shake_EraseEffect(device, id);
}
static void testEffectMixing(void)
{
Shake_Effect effect;
int id[3];
int i;
printf("-Effect mixing-\n");
Shake_SimplePeriodic(&effect, SHAKE_PERIODIC_SINE, 0.6, 0.0, 4.0, 0.0);
id[0] = Shake_UploadEffect(device, &effect);
Shake_SimplePeriodic(&effect, SHAKE_PERIODIC_SQUARE, 0.2, 0.0, 2.0, 0.0);
id[1] = Shake_UploadEffect(device, &effect);
Shake_SimplePeriodic(&effect, SHAKE_PERIODIC_SINE, 0.2, 0.0, 1.0, 0.0);
id[2] = Shake_UploadEffect(device, &effect);
Shake_Play(device, id[0]);
printf("Playing #1 (0.6 mag)\n");
sleep(1);
Shake_Play(device, id[1]);
printf("Adding #2 (+0.2 mag)\n");
sleep(1);
Shake_Play(device, id[2]);
printf("Adding #3 (+0.2 mag)\n");
sleep(1);
printf("Removing #2 and #3 (-0.4 mag)\n");
sleep(1);
printf("-End-\n\n");
for (i = 0; i < 3; ++i)
{
Shake_EraseEffect(device, id[i]);
}
}
static void menu(void)
{
int selection;
printf("Device #%d: %s\n\n", Shake_DeviceId(device), Shake_DeviceName(device));
printf("Select test:\n");
printf("1) Effect capacity\n2) Effect playback\n3) Effect order\n4) Effect update\n5) Effect mixing\n6) Play all above tests\n\nI) Device info\nL) List devices\nQ) Quit\n\n");
printf("> ");
selection = getchar();
switch (selection)
{
case 'q':
case 'Q':
quit = 1;
break;
case 'i':
case 'I':
deviceInfo();
waitForKey();
break;
case 'l':
case 'L':
listDevices();
waitForKey();
break;
case '1':
testCapacity();
waitForKey();
break;
case '2':
testEffectPlayback();
waitForKey();
break;
case '3':
testEffectOrder();
waitForKey();
break;
case '4':
testEffectUpdate();
waitForKey();
break;
case '5':
testEffectMixing();
waitForKey();
break;
case '6':
testCapacity();
sleep(1);
testEffectPlayback();
sleep(1);
testEffectOrder();
sleep(1);
testEffectUpdate();
sleep(1);
testEffectMixing();
waitForKey();
break;
default:
break;
}
while(getchar() != '\n');
printf("------\n");
}
int main(int argc, const char *argv[])
{
int deviceId = 0;
if (argc > 1)
deviceId = atoi(argv[1]);
if (deviceId < 0)
{
printf("Device number must be greater than or equal to 0.\n");
return -1;
}
srand(time(NULL));
Shake_Init();
if (Shake_NumOfDevices() <= deviceId)
{
printf("Device #%d doesn't exist.\n", deviceId);
return -1;
}
device = Shake_Open(deviceId);
while (!quit)
menu();
/* Cleanup. */
Shake_Close(device);
Shake_Quit();
return 0;
}

51
deps/libShake/examples/listDevices.c vendored Normal file
View File

@ -0,0 +1,51 @@
#include <shake.h>
#include <stdio.h>
#include <unistd.h>
void deviceInfo(Shake_Device *device)
{
printf("\nDevice #%d\n", Shake_DeviceId(device));
printf(" Name: %s\n", Shake_DeviceName(device));
printf(" Adjustable gain: %s\n", Shake_QueryGainSupport(device) ? "yes" : "no");
printf(" Adjustable autocenter: %s\n", Shake_QueryAutocenterSupport(device) ? "yes" : "no");
printf(" Effect capacity: %d\n", Shake_DeviceEffectCapacity(device));
printf(" Supported effects:\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_RUMBLE)) printf(" SHAKE_EFFECT_RUMBLE\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_PERIODIC))
{
printf(" SHAKE_EFFECT_PERIODIC\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_SQUARE)) printf(" * SHAKE_PERIODIC_SQUARE\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_TRIANGLE)) printf(" * SHAKE_PERIODIC_TRIANGLE\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_SINE)) printf(" * SHAKE_PERIODIC_SINE\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_SAW_UP)) printf(" * SHAKE_PERIODIC_SAW_UP\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_SAW_DOWN)) printf(" * SHAKE_PERIODIC_SAW_DOWN\n");
if (Shake_QueryWaveformSupport(device, SHAKE_PERIODIC_CUSTOM)) printf(" * SHAKE_PERIODIC_CUSTOM\n");
}
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_CONSTANT)) printf(" SHAKE_EFFECT_CONSTANT\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_SPRING)) printf(" SHAKE_EFFECT_SPRING\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_FRICTION)) printf(" SHAKE_EFFECT_FRICTION\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_DAMPER)) printf(" SHAKE_EFFECT_DAMPER\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_INERTIA)) printf(" SHAKE_EFFECT_INERTIA\n");
if (Shake_QueryEffectSupport(device, SHAKE_EFFECT_RAMP)) printf(" SHAKE_EFFECT_RAMP\n");
}
int main()
{
int numDevices;
int i;
Shake_Init();
numDevices = Shake_NumOfDevices();
printf("Detected devices: %d\n", numDevices);
for (i = 0; i < numDevices; ++i)
{
Shake_Device *device = Shake_Open(i);
deviceInfo(device);
Shake_Close(device);
}
Shake_Quit();
return 0;
}

40
deps/libShake/examples/simple.c vendored Normal file
View File

@ -0,0 +1,40 @@
#include <shake.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
Shake_Device *device;
Shake_Effect effect;
int id;
Shake_Init();
if (Shake_NumOfDevices() > 0)
{
device = Shake_Open(0);
Shake_InitEffect(&effect, SHAKE_EFFECT_PERIODIC);
effect.u.periodic.waveform = SHAKE_PERIODIC_SINE;
effect.u.periodic.period = 0.1*0x100;
effect.u.periodic.magnitude = 0x6000;
effect.u.periodic.envelope.attackLength = 0x100;
effect.u.periodic.envelope.attackLevel = 0;
effect.u.periodic.envelope.fadeLength = 0x100;
effect.u.periodic.envelope.fadeLevel = 0;
effect.direction = 0x4000;
effect.length = 2000;
effect.delay = 0;
id = Shake_UploadEffect(device, &effect);
Shake_Play(device, id);
sleep(2);
Shake_EraseEffect(device, id);
Shake_Close(device);
}
Shake_Quit();
return 0;
}

View File

@ -0,0 +1,6 @@
config BR2_PACKAGE_LIBSHAKE
bool "libshake"
help
A simple, cross-platform haptic library.
https://github.com/zear/libShake

View File

@ -0,0 +1,26 @@
#############################################################
#
# libshake
#
#############################################################
LIBSHAKE_VERSION = master
LIBSHAKE_SITE = $(call github,zear,libShake,$(LIBSHAKE_VERSION))
LIBSHAKE_LICENSE = MIT
LIBSHAKE_LICENSE_FILES = LICENSE.txt
LIBSHAKE_INSTALL_STAGING = YES
LIBSHAKE_MAKE_ENV = CC="$(TARGET_CC)" PREFIX=/usr
define LIBSHAKE_BUILD_CMDS
$(LIBSHAKE_MAKE_ENV) $(MAKE) -C $(@D) BACKEND=LINUX
endef
define LIBSHAKE_INSTALL_STAGING_CMDS
$(LIBSHAKE_MAKE_ENV) DESTDIR="$(STAGING_DIR)" $(MAKE) -C $(@D) BACKEND=LINUX install
endef
define LIBSHAKE_INSTALL_TARGET_CMDS
$(LIBSHAKE_MAKE_ENV) DESTDIR="$(TARGET_DIR)" $(MAKE) -C $(@D) BACKEND=LINUX install-lib
endef
$(eval $(generic-package))

522
deps/libShake/include/shake.h vendored Normal file
View File

@ -0,0 +1,522 @@
/** \file shake.h
\brief libShake public header.
*/
#ifndef _SHAKE_H_
#define _SHAKE_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/** \def SHAKE_MAJOR_VERSION
\brief Version of the program (major).
*/
#define SHAKE_MAJOR_VERSION 0
/** \def SHAKE_MINOR_VERSION
\brief Version of the program (minor).
*/
#define SHAKE_MINOR_VERSION 3
/** \def SHAKE_PATCH_VERSION
\brief Version of the program (patch).
*/
#define SHAKE_PATCH_VERSION 2
/** \def SHAKE_ENVELOPE_ATTACK_LENGTH_MAX
\brief Maximum allowed value of Shake_Envelope attackLength
\sa Shake_Envelope
*/
#define SHAKE_ENVELOPE_ATTACK_LENGTH_MAX 0x7FFF
/** \def SHAKE_ENVELOPE_FADE_LENGTH_MAX
\brief Maximum allowed value of Shake_Envelope fadeLength
\sa Shake_Envelope
*/
#define SHAKE_ENVELOPE_FADE_LENGTH_MAX 0x7FFF
/** \def SHAKE_ENVELOPE_ATTACK_LEVEL_MAX
\brief Maximum allowed value of Shake_Envelope attackLevel
\sa Shake_Envelope
*/
#define SHAKE_ENVELOPE_ATTACK_LEVEL_MAX 0x7FFF
/** \def SHAKE_ENVELOPE_FADE_LEVEL_MAX
\brief Maximum allowed value of Shake_Envelope fadeLevel
\sa Shake_Envelope
*/
#define SHAKE_ENVELOPE_FADE_LEVEL_MAX 0x7FFF
/** \def SHAKE_RUMBLE_STRONG_MAGNITUDE_MAX
\brief Maximum allowed value of Shake_EffectRumble strongMagnitude
\sa Shake_EffectRumble
*/
#define SHAKE_RUMBLE_STRONG_MAGNITUDE_MAX 0x7FFF
/** \def SHAKE_RUMBLE_WEAK_MAGNITUDE_MAX
\brief Maximum allowed value of Shake_EffectRumble weakMagnitude
\sa Shake_EffectRumble
*/
#define SHAKE_RUMBLE_WEAK_MAGNITUDE_MAX 0x7FFF
/** \def SHAKE_PERIODIC_PERIOD_MAX
\brief Maximum allowed value of Shake_EffectPeriodic period
\sa Shake_EffectPeriodic
*/
#define SHAKE_PERIODIC_PERIOD_MAX 0x7FFF
/** \def SHAKE_PERIODIC_MAGNITUDE_MIN
\brief Minimum allowed value of Shake_EffectPeriodic magnitude
\sa Shake_EffectPeriodic
*/
#define SHAKE_PERIODIC_MAGNITUDE_MIN (-0x8000)
/** \def SHAKE_PERIODIC_MAGNITUDE_MAX
\brief Maximum allowed value of Shake_EffectPeriodic magnitude
\sa Shake_EffectPeriodic
*/
#define SHAKE_PERIODIC_MAGNITUDE_MAX 0x7FFF
/** \def SHAKE_PERIODIC_OFFSET_MIN
\brief Minimum allowed value of Shake_EffectPeriodic offset
\sa Shake_EffectPeriodic
*/
#define SHAKE_PERIODIC_OFFSET_MIN (-0x8000)
/** \def SHAKE_PERIODIC_OFFSET_MAX
\brief Maximum allowed value of Shake_EffectPeriodic offset
\sa Shake_EffectPeriodic
*/
#define SHAKE_PERIODIC_OFFSET_MAX 0x7FFF
/** \def SHAKE_PERIODIC_PHASE_MAX
\brief Maximum allowed value of Shake_EffectPeriodic phase
\sa Shake_EffectPeriodic
*/
#define SHAKE_PERIODIC_PHASE_MAX 0x7FFF
/** \def SHAKE_CONSTANT_LEVEL_MIN
\brief Minimum allowed value of Shake_EffectConstant level
\sa Shake_EffectConstant
*/
#define SHAKE_CONSTANT_LEVEL_MIN (-0x8000)
/** \def SHAKE_CONSTANT_LEVEL_MAX
\brief Maximum allowed value of Shake_EffectConstant level
\sa Shake_EffectConstant
*/
#define SHAKE_CONSTANT_LEVEL_MAX 0x7FFF
/** \def SHAKE_RAMP_START_LEVEL_MIN
\brief Minimum allowed value of Shake_EffectRamp startLevel
\sa Shake_EffectRamp
*/
#define SHAKE_RAMP_START_LEVEL_MIN (-0x8000)
/** \def SHAKE_RAMP_START_LEVEL_MAX
\brief Maximum allowed value of Shake_EffectRamp startLevel
\sa Shake_EffectRamp
*/
#define SHAKE_RAMP_START_LEVEL_MAX 0x7FFF
/** \def SHAKE_RAMP_END_LEVEL_MIN
\brief Minimum allowed value of Shake_EffectRamp endLevel
\sa Shake_EffectRamp
*/
#define SHAKE_RAMP_END_LEVEL_MIN (-0x8000)
/** \def SHAKE_RAMP_END_LEVEL_MAX
\brief Maximum allowed value of Shake_EffectRamp endLevel
\sa Shake_EffectRamp
*/
#define SHAKE_RAMP_END_LEVEL_MAX 0x7FFF
/** \def SHAKE_EFFECT_ID_MIN
\brief Minimum allowed value of Shake_Effect id
\sa Shake_Effect
*/
#define SHAKE_EFFECT_ID_MIN (-0x0001)
/** \def SHAKE_EFFECT_DIRECTION_MAX
\brief Maximum allowed value of Shake_Effect direction
\sa Shake_Effect
*/
#define SHAKE_EFFECT_DIRECTION_MAX 0xFFFE
/** \def SHAKE_EFFECT_LENGTH_MAX
\brief Maximum allowed value of Shake_Effect length
\sa Shake_Effect
*/
#define SHAKE_EFFECT_LENGTH_MAX 0x7FFF
/** \def SHAKE_EFFECT_DELAY_MAX
\brief Maximum allowed value of Shake_Effect delay
\sa Shake_Effect
*/
#define SHAKE_EFFECT_DELAY_MAX 0x7FFF
/** \enum Shake_Status
\brief Request status.
*/
typedef enum Shake_Status
{
SHAKE_ERROR = -1, /**< Error. */
SHAKE_OK = 0 /**< Success. */
} Shake_Status;
/** \enum Shake_Bool
\brief Boolean type.
*/
typedef enum Shake_Bool
{
SHAKE_FALSE = 0, /**< False. */
SHAKE_TRUE = 1 /**< True. */
} Shake_Bool;
/** \enum Shake_ErrorCode
\brief Information about the error origin.
*/
typedef enum Shake_ErrorCode
{
SHAKE_EC_UNSET, /**< No error triggered yet. */
SHAKE_EC_SUPPORT, /**< Feature not supported. */
SHAKE_EC_DEVICE, /**< Device related. */
SHAKE_EC_EFFECT, /**< Effect related. */
SHAKE_EC_QUERY, /**< Query related. */
SHAKE_EC_ARG, /**< Invalid argument. */
SHAKE_EC_TRANSFER /**< Device transfer related. */
} Shake_ErrorCode;
/** \struct Shake_Device
\brief Haptic device.
*/
struct Shake_Device;
typedef struct Shake_Device Shake_Device;
/** \enum Shake_PeriodicWaveform
\brief Periodic effect waveform.
*/
typedef enum Shake_PeriodicWaveform
{
SHAKE_PERIODIC_SQUARE = 0, /**< Square waveform. */
SHAKE_PERIODIC_TRIANGLE, /**< Triangle waveform. */
SHAKE_PERIODIC_SINE, /**< Sine waveform. */
SHAKE_PERIODIC_SAW_UP, /**< Saw up waveform. */
SHAKE_PERIODIC_SAW_DOWN, /**< Saw down waveform. */
SHAKE_PERIODIC_CUSTOM, /**< Custom waveform. */
SHAKE_PERIODIC_COUNT
} Shake_PeriodicWaveform;
/** \enum Shake_EffectType
\brief Effect type.
*/
typedef enum Shake_EffectType
{
SHAKE_EFFECT_RUMBLE = 0, /**< Rumble effect. */
SHAKE_EFFECT_PERIODIC, /**< Periodic effect. */
SHAKE_EFFECT_CONSTANT, /**< Constant effect. */
SHAKE_EFFECT_SPRING, /**< Spring effect. <b>NOTE:</b> Currently not supported. */
SHAKE_EFFECT_FRICTION, /**< Friction effect. <b>NOTE:</b> Currently not supported. */
SHAKE_EFFECT_DAMPER, /**< Damper effect. <b>NOTE:</b> Currently not supported. */
SHAKE_EFFECT_INERTIA, /**< Inertia effect. <b>NOTE:</b> Currently not supported. */
SHAKE_EFFECT_RAMP, /**< Ramp effect. */
SHAKE_EFFECT_COUNT
} Shake_EffectType;
/** \struct Shake_Envelope
\brief Effect envelope.
*/
typedef struct Shake_Envelope
{
uint16_t attackLength; /**< Envelope attack duration. */
uint16_t attackLevel; /**< Envelope attack level. */
uint16_t fadeLength; /**< Envelope fade duration. */
uint16_t fadeLevel; /**< Envelope fade level. */
} Shake_Envelope;
/** \struct Shake_EffectRumble
\brief Rumble effect structure.
<b>NOTE:</b> On Linux and OSX backends this effect is internally emulated by averaging the motor values
with a periodic effect and no direct control over individual motors is available.
*/
typedef struct Shake_EffectRumble
{
uint16_t strongMagnitude; /**< Magnitude of the heavy motor. */
uint16_t weakMagnitude; /**< Magnitude of the light motor. */
} Shake_EffectRumble;
/** \struct Shake_EffectPeriodic
\brief Periodic effect structure.
*/
typedef struct Shake_EffectPeriodic
{
Shake_PeriodicWaveform waveform;/**< Effect waveform. */
uint16_t period; /**< Period of the wave (in ms). */
int16_t magnitude; /**< Peak value of the wave. */
int16_t offset; /**< Mean value of the wave. */
uint16_t phase; /**< Horizontal shift of the wave. */
Shake_Envelope envelope; /**< Effect envelope. */
} Shake_EffectPeriodic;
/** \struct Shake_EffectConstant
\brief Constant effect structure.
*/
typedef struct Shake_EffectConstant
{
int16_t level; /**< Magnitude of the effect. */
Shake_Envelope envelope; /**< Effect envelope. */
} Shake_EffectConstant;
/** \struct Shake_EffectRamp
\brief Ramp effect structure.
*/
typedef struct Shake_EffectRamp
{
int16_t startLevel; /**< Starting magnitude of the effect. */
int16_t endLevel; /**< Ending magnitude of the effect. */
Shake_Envelope envelope; /**< Effect envelope. */
} Shake_EffectRamp;
/** \struct Shake_Effect
\brief Effect structure.
*/
typedef struct Shake_Effect
{
Shake_EffectType type; /**< Effect type. */
int16_t id; /**< Effect id. Value of -1 creates a new effect. Value of 0 or greater modifies existing effect in a device. */
uint16_t direction; /**< Direction of the effect. */
uint16_t length; /**< Duration of the effect (in ms). */
uint16_t delay; /**< Delay before the effect starts playing (in ms). */
union
{
Shake_EffectRumble rumble;
Shake_EffectPeriodic periodic;
Shake_EffectConstant constant;
Shake_EffectRamp ramp;
} u; /**< Effect type data container. */
} Shake_Effect;
/** \fn Shake_Status Shake_Init(void)
\brief Initializes libShake.
\return On success, SHAKE_OK is returned.
\return On error, SHAKE_ERROR is returned.
*/
Shake_Status Shake_Init(void);
/** \fn void Shake_Quit(void)
\brief Uninitializes libShake.
*/
void Shake_Quit(void);
/** \fn int Shake_NumOfDevices(void)
\brief Lists the number of haptic devices.
\return On success, number of devices is returned.
\return On error, SHAKE_ERROR is returned.
*/
int Shake_NumOfDevices(void);
/** \fn Shake_Device *Shake_Open(unsigned int id)
\brief Opens a Shake device.
\param id Device id.
\return On success, pointer to Shake device is returned.
\return On error, NULL is returned.
*/
Shake_Device *Shake_Open(unsigned int id);
/** \fn Shake_Status Shake_Close(Shake_Device *dev)
\brief Closes a Shake device.
\param dev Pointer to Shake device.
\return On success, SHAKE_OK is returned.
\return On error, SHAKE_ERROR is returned.
*/
Shake_Status Shake_Close(Shake_Device *dev);
/** \fn int Shake_DeviceId(Shake_Device *dev)
\brief Lists id of a Shake device.
\param dev Pointer to Shake device.
\return On success, id of Shake device is returned.
\return On error, SHAKE_ERROR is returned.
*/
int Shake_DeviceId(Shake_Device *dev);
/** \fn const char *Shake_DeviceName(Shake_Device *dev)
\brief Lists name of a Shake device.
\param dev Pointer to Shake device.
\return On success, name of Shake device is returned.
\return On error, NULL is returned.
*/
const char *Shake_DeviceName(Shake_Device *dev);
/** \fn int Shake_DeviceEffectCapacity(Shake_Device *dev)
\brief Lists effect capacity of a Shake device.
\param dev Pointer to Shake device.
\return On success, capacity of Shake device is returned.
\return On error, SHAKE_ERROR is returned.
*/
int Shake_DeviceEffectCapacity(Shake_Device *dev);
/** \fn Shake_Bool Shake_QueryEffectSupport(Shake_Device *dev, Shake_EffectType type)
\brief Queries effect support of a Shake device.
\param dev Pointer to Shake device.
\param type Effect type to query about.
\return SHAKE_TRUE if effect is supported.
\return SHAKE_FALSE if effect is not supported.
*/
Shake_Bool Shake_QueryEffectSupport(Shake_Device *dev, Shake_EffectType type);
/** \fn Shake_Bool Shake_QueryWaveformSupport(Shake_Device *dev, Shake_PeriodicWaveform waveform)
\brief Queries waveform support of a Shake device.
\param dev Pointer to Shake device.
\param waveform Waveform type to query about.
\return SHAKE_TRUE if waveform is supported.
\return SHAKE_FALSE if waveform is not supported.
*/
Shake_Bool Shake_QueryWaveformSupport(Shake_Device *dev, Shake_PeriodicWaveform waveform);
/** \fn Shake_Bool Shake_QueryGainSupport(Shake_Device *dev)
\brief Queries gain adjustment support of a Shake device.
\param dev Pointer to Shake device.
\return SHAKE_TRUE if gain adjustment is supported.
\return SHAKE_FALSE if gain adjustment is not supported.
*/
Shake_Bool Shake_QueryGainSupport(Shake_Device *dev);
/** \fn Shake_Bool Shake_QueryAutocenterSupport(Shake_Device *dev)
\brief Queries autocenter adjustment support of a Shake device.
\param dev Pointer to Shake device.
\return SHAKE_TRUE if autocenter adjustment is supported.
\return SHAKE_FALSE if autocenter adjustment is not supported.
*/
Shake_Bool Shake_QueryAutocenterSupport(Shake_Device *dev);
/** \fn Shake_Status Shake_SetGain(Shake_Device *dev, int gain)
\brief Sets gain of a Shake device.
\param dev Pointer to Shake device.
\param gain [0-100] Value of a new gain level. Value of 100 means full strength of the device.
\return On success, SHAKE_OK is returned.
\return On error, SHAKE_ERROR is returned.
*/
Shake_Status Shake_SetGain(Shake_Device *dev, int gain);
/** \fn Shake_Status Shake_SetAutocenter(Shake_Device *dev, int autocenter)
\brief Sets autocenter of a Shake device.
\param dev Pointer to Shake device.
\param autocenter [0-100] Value of a new autocenter level. Value of 0 means "autocenter disabled".
\return On success, SHAKE_OK is returned.
\return On error, SHAKE_ERROR is returned.
*/
Shake_Status Shake_SetAutocenter(Shake_Device *dev, int autocenter);
/** \fn Shake_Status Shake_InitEffect(Shake_Effect *effect, Shake_EffectType type)
\brief Initializes an effect.
\param effect Pointer to Effect struct.
\param type Type of the effect.
\return On success, SHAKE_OK is returned.
\return On error, SHAKE_ERROR is returned.
*/
Shake_Status Shake_InitEffect(Shake_Effect *effect, Shake_EffectType type);
/** \fn int Shake_UploadEffect(Shake_Device *dev, Shake_Effect *effect)
\brief Uploads an effect into a Shake device.
\param dev Pointer to Shake device.
\param effect Pointer to Effect struct.
\return On success, id of the uploaded effect returned.
\return On error, SHAKE_ERROR is returned.
*/
int Shake_UploadEffect(Shake_Device *dev, Shake_Effect *effect);
/** \fn Shake_Status Shake_EraseEffect(Shake_Device *dev, int id)
\brief Erases effect from a Shake device.
\param dev Pointer to Shake device.
\param id Id of the effect to erase.
\return On success, SHAKE_OK is returned.
\return On error, SHAKE_ERROR is returned.
*/
Shake_Status Shake_EraseEffect(Shake_Device *dev, int id);
/** \fn Shake_Status Shake_Play(Shake_Device *dev, int id)
\brief Starts playback of an effect.
\param dev Pointer to Shake device.
\param id Id of the effect to play.
\return On success, SHAKE_OK is returned.
\return On error, SHAKE_ERROR is returned.
*/
Shake_Status Shake_Play(Shake_Device *dev, int id);
/** \fn Shake_Status Shake_Stop(Shake_Device *dev, int id)
\brief Stops playback of an effect.
\param dev Pointer to Shake device.
\param id Id of the effect to stop.
\return On success, SHAKE_OK is returned.
\return On error, SHAKE_ERROR is returned.
*/
Shake_Status Shake_Stop(Shake_Device *dev, int id);
/** \fn void Shake_SimpleRumble(Shake_Effect *effect, float strongPercent, float weakPercent, float secs)
\brief Creates a simple Rumble effect.
\param effect Pointer to Effect struct.
\param strongPercent [0.0-1.0] Percentage of the strong motor magnitude.
\param weakPercent [0.0-1.0] Percentage of the weak motor magnitude.
\param secs Duration of the effect (in sec).
\sa Shake_Effect
\sa Shake_EffectRumble
*/
void Shake_SimpleRumble(Shake_Effect *effect, float strongPercent, float weakPercent, float secs);
/** \fn Shake_SimplePeriodic(Shake_Effect *effect, Shake_PeriodicWaveform waveform, float forcePercent, float attackSecs, float sustainSecs, float fadeSecs)
\brief Creates a simple Periodic effect.
\param effect Pointer to Effect struct.
\param waveform Waveform type.
\param forcePercent [0.0-1.0] Percentage of the effect magnitude.
\param attackSecs Duration of the effect attack (in sec).
\param sustainSecs Duration of the effect sustain (in sec).
\param fadeSecs Duration of the effect fade (in sec).
\sa Shake_Effect
\sa Shake_EffectPeriodic
*/
void Shake_SimplePeriodic(Shake_Effect *effect, Shake_PeriodicWaveform waveform, float forcePercent, float attackSecs, float sustainSecs, float fadeSecs);
/** \fn void Shake_SimpleConstant(Shake_Effect *effect, float forcePercent, float attackSecs, float sustainSecs, float fadeSecs)
\brief Creates a simple Constant effect.
\param effect Pointer to Effect struct.
\param forcePercent [0.0-1.0] Percentage of the effect magnitude.
\param attackSecs Duration of the effect attack (in sec).
\param sustainSecs Duration of the effect sustain (in sec).
\param fadeSecs Duration of the effect fade (in sec).
\sa Shake_Effect
\sa Shake_EffectConstant
*/
void Shake_SimpleConstant(Shake_Effect *effect, float forcePercent, float attackSecs, float sustainSecs, float fadeSecs);
/** \fn Shake_SimpleRamp(Shake_Effect *effect, float startForcePercent, float endForcePercent, float attackSecs, float sustainSecs, float fadeSecs)
\brief Creates a simple Ramp effect.
\param effect Pointer to Effect struct.
\param startForcePercent [0.0-1.0] Percentage of the effect magnitude at start.
\param endForcePercent [0.0-1.0] Percentage of the effect magnitude at end.
\param attackSecs Duration of the effect attack (in sec).
\param sustainSecs Duration of the effect sustain (in sec).
\param fadeSecs Duration of the effect fade (in sec).
\sa Shake_Effect
\sa Shake_EffectRamp
*/
void Shake_SimpleRamp(Shake_Effect *effect, float startForcePercent, float endForcePercent, float attackSecs, float sustainSecs, float fadeSecs);
/** \fn Shake_ErrorCode Shake_GetErrorCode(void)
\brief Informs about the error type.
\return Error code reflecting the last error type.
*/
Shake_ErrorCode Shake_GetErrorCode(void);
#ifdef __cplusplus
}
#endif
#endif /* _SHAKE_H_ */

14
deps/libShake/src/common/error.c vendored Normal file
View File

@ -0,0 +1,14 @@
#include "error.h"
Shake_ErrorCode errorCode = SHAKE_EC_UNSET;
Shake_Status Shake_EmitErrorCode(Shake_ErrorCode ec)
{
errorCode = ec;
return SHAKE_ERROR;
}
Shake_ErrorCode Shake_GetErrorCode(void)
{
return errorCode;
}

9
deps/libShake/src/common/error.h vendored Normal file
View File

@ -0,0 +1,9 @@
#ifndef _SHAKE_ERROR_H_
#define _SHAKE_ERROR_H_
#include "shake.h"
Shake_Status Shake_EmitErrorCode(Shake_ErrorCode ec);
Shake_ErrorCode Shake_GetErrorCode(void);
#endif

93
deps/libShake/src/common/helpers.c vendored Normal file
View File

@ -0,0 +1,93 @@
#include "helpers.h"
#include <stdlib.h>
/* Linked list */
ListElement *listElementPrepend(ListElement *head)
{
ListElement *newNode = malloc(sizeof(ListElement));
if(!newNode)
{
return head;
}
newNode->next = head;
return newNode;
}
ListElement *listElementDelete(ListElement *head, ListElement *toDelNode, void(*itemDel)(void *item))
{
ListElement *prevNode = NULL;
ListElement *curNode = head;
while(curNode)
{
if(curNode == toDelNode)
{
if(!prevNode)
{
head = curNode->next;
}
else
{
prevNode->next = curNode->next;
}
itemDel(curNode->item);
free(curNode);
return head;
}
prevNode = curNode;
curNode = curNode->next;
}
return head;
}
ListElement *listElementDeleteAll(ListElement *head, void(*itemDel)(void *item))
{
ListElement *curNode = head;
while(curNode)
{
ListElement *toDelNode = curNode;
curNode = curNode->next;
itemDel(toDelNode->item);
free(toDelNode);
}
return NULL;
}
ListElement *listElementGet(ListElement *head, unsigned int id)
{
ListElement *curNode = head;
int i = 0;
while(curNode)
{
if (i == id)
return curNode;
curNode = curNode->next;
++i;
}
return NULL;
}
unsigned int listLength(ListElement *head)
{
ListElement *curNode = head;
unsigned int count = 0;
while (curNode)
{
curNode = curNode->next;
++count;
}
return count;
}

16
deps/libShake/src/common/helpers.h vendored Normal file
View File

@ -0,0 +1,16 @@
#ifndef _HELPERS_H_
#define _HELPERS_H_
typedef struct ListElement
{
struct ListElement *next;
void *item;
} ListElement;
ListElement *listElementPrepend(ListElement *head);
ListElement *listElementDelete(ListElement *head, ListElement *toDelNode, void(*itemDel)());
ListElement *listElementDeleteAll(ListElement *head, void(*itemDel)(void *item));
ListElement *listElementGet(ListElement *head, unsigned int id);
unsigned int listLength(ListElement *head);
#endif /* _HELPERS_H_ */

50
deps/libShake/src/common/presets.c vendored Normal file
View File

@ -0,0 +1,50 @@
#include "shake.h"
#include "helpers.h"
void Shake_SimpleRumble(Shake_Effect *effect, float strongPercent, float weakPercent, float secs)
{
Shake_InitEffect(effect, SHAKE_EFFECT_RUMBLE);
effect->u.rumble.strongMagnitude = SHAKE_RUMBLE_STRONG_MAGNITUDE_MAX * strongPercent;
effect->u.rumble.weakMagnitude = SHAKE_RUMBLE_WEAK_MAGNITUDE_MAX * weakPercent;
effect->length = 1000 * secs;
effect->delay = 0;
}
void Shake_SimplePeriodic(Shake_Effect *effect, Shake_PeriodicWaveform waveform, float forcePercent, float attackSecs, float sustainSecs, float fadeSecs)
{
Shake_InitEffect(effect, SHAKE_EFFECT_PERIODIC);
effect->u.periodic.waveform = waveform;
effect->u.periodic.period = 0.1*0x100;
effect->u.periodic.magnitude = SHAKE_PERIODIC_MAGNITUDE_MAX * forcePercent;
effect->u.periodic.envelope.attackLength = 1000 * attackSecs;
effect->u.periodic.envelope.attackLevel = 0;
effect->u.periodic.envelope.fadeLength = 1000 * fadeSecs;
effect->u.periodic.envelope.fadeLevel = 0;
effect->length = 1000 * (sustainSecs + attackSecs + fadeSecs);
effect->delay = 0;
}
void Shake_SimpleConstant(Shake_Effect *effect, float forcePercent, float attackSecs, float sustainSecs, float fadeSecs)
{
Shake_InitEffect(effect, SHAKE_EFFECT_CONSTANT);
effect->u.constant.level = SHAKE_CONSTANT_LEVEL_MAX * forcePercent;
effect->u.constant.envelope.attackLength = 1000 * attackSecs;
effect->u.constant.envelope.attackLevel = 0;
effect->u.constant.envelope.fadeLength = 1000 * fadeSecs;
effect->u.constant.envelope.fadeLevel = 0;
effect->length = 1000 * (sustainSecs + attackSecs + fadeSecs);
effect->delay = 0;
}
void Shake_SimpleRamp(Shake_Effect *effect, float startForcePercent, float endForcePercent, float attackSecs, float sustainSecs, float fadeSecs)
{
Shake_InitEffect(effect, SHAKE_EFFECT_RAMP);
effect->u.ramp.startLevel = SHAKE_RAMP_START_LEVEL_MAX * startForcePercent;
effect->u.ramp.endLevel = SHAKE_RAMP_END_LEVEL_MAX * endForcePercent;
effect->u.ramp.envelope.attackLength = 1000 * attackSecs;
effect->u.ramp.envelope.attackLevel = 0;
effect->u.ramp.envelope.fadeLength = 1000 * fadeSecs;
effect->u.ramp.envelope.fadeLevel = 0;
effect->length = 1000 * (sustainSecs + attackSecs + fadeSecs);
effect->delay = 0;
}

422
deps/libShake/src/linux/shake.c vendored Normal file
View File

@ -0,0 +1,422 @@
/* libShake - a basic haptic library */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <linux/input.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include "shake.h"
#include "shake_private.h"
#include "../common/helpers.h"
#include "../common/error.h"
#define SHAKE_TEST(x) ((x) ? SHAKE_TRUE : SHAKE_FALSE)
static ListElement *listHead;
static unsigned int numOfDevices;
static int nameFilter(const struct dirent *entry)
{
const char filter[] = "event";
return !strncmp(filter, entry->d_name, strlen(filter));
}
static void itemDelete(void *item)
{
Shake_Device *dev = (Shake_Device *)item;
if (!dev)
return;
Shake_Close(dev);
free(dev->node);
free(dev);
}
static Shake_Status query(Shake_Device *dev)
{
int size = sizeof(dev->features)/sizeof(unsigned long);
int i;
if(!dev)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
if (ioctl(dev->fd, EVIOCGBIT(EV_FF, sizeof(dev->features)), dev->features) == -1)
return Shake_EmitErrorCode(SHAKE_EC_QUERY);
for (i = 0; i < size; ++i)
{
if (dev->features[i])
break;
}
if (i >= size) /* Device doesn't support any force feedback effects. Ignore it. */
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
if (ioctl(dev->fd, EVIOCGEFFECTS, &dev->capacity) == -1)
return Shake_EmitErrorCode(SHAKE_EC_QUERY);
if (dev->capacity <= 0) /* Device doesn't support uploading effects. Ignore it. */
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
if (ioctl(dev->fd, EVIOCGNAME(sizeof(dev->name)), dev->name) == -1) /* Get device name */
{
strncpy(dev->name, "Unknown", sizeof(dev->name));
}
return SHAKE_OK;
}
static Shake_Status probe(Shake_Device *dev)
{
int isHaptic;
if(!dev)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
if (!dev->node)
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
dev->fd = open(dev->node, O_RDWR);
if (!dev->fd)
return SHAKE_ERROR;
isHaptic = !query(dev);
dev->fd = close(dev->fd);
return isHaptic ? SHAKE_OK : SHAKE_ERROR;
}
/* API implementation. */
Shake_Status Shake_Init(void)
{
struct dirent **nameList;
int numOfEntries;
numOfDevices = 0;
numOfEntries = scandir(SHAKE_DIR_NODES, &nameList, nameFilter, alphasort);
if (numOfEntries < 0)
{
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
}
else
{
int i;
for (i = 0; i < numOfEntries; ++i)
{
Shake_Device dev;
dev.node = malloc(strlen(SHAKE_DIR_NODES) + strlen("/") + strlen(nameList[i]->d_name) + 1);
if (dev.node == NULL)
{
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
}
strcpy(dev.node, SHAKE_DIR_NODES);
strcat(dev.node, "/");
strcat(dev.node, nameList[i]->d_name);
if (probe(&dev) == SHAKE_OK)
{
dev.id = numOfDevices;
listHead = listElementPrepend(listHead);
listHead->item = malloc(sizeof(Shake_Device));
memcpy(listHead->item, &dev, sizeof(Shake_Device));
++numOfDevices;
}
else
{
free(dev.node);
}
free(nameList[i]);
}
free(nameList);
}
return SHAKE_OK;
}
void Shake_Quit(void)
{
if (listHead != NULL)
{
listElementDeleteAll(listHead, itemDelete);
}
listHead = NULL;
numOfDevices = 0;
}
int Shake_NumOfDevices(void)
{
return numOfDevices;
}
Shake_Device *Shake_Open(unsigned int id)
{
Shake_Device *dev;
ListElement *element;
if (id >= numOfDevices)
{
Shake_EmitErrorCode(SHAKE_EC_ARG);
return NULL;
}
element = listElementGet(listHead, numOfDevices - 1 - id);
dev = (Shake_Device *)element->item;
if(!dev || !dev->node)
{
Shake_EmitErrorCode(SHAKE_EC_DEVICE);
return NULL;
}
dev->fd = open(dev->node, O_RDWR);
if (!dev->fd)
Shake_EmitErrorCode(SHAKE_EC_DEVICE);
return dev->fd ? dev : NULL;
}
int Shake_DeviceId(Shake_Device *dev)
{
if (!dev)
Shake_EmitErrorCode(SHAKE_EC_ARG);
return dev ? dev->id : SHAKE_ERROR;
}
const char *Shake_DeviceName(Shake_Device *dev)
{
if (!dev)
Shake_EmitErrorCode(SHAKE_EC_ARG);
return dev ? dev->name : NULL;
}
int Shake_DeviceEffectCapacity(Shake_Device *dev)
{
if (!dev)
Shake_EmitErrorCode(SHAKE_EC_ARG);
return dev ? dev->capacity : SHAKE_ERROR;
}
Shake_Bool Shake_QueryEffectSupport(Shake_Device *dev, Shake_EffectType type)
{
/* Starts at a magic, non-zero number, FF_RUMBLE.
Increments respectively to EffectType. */
return SHAKE_TEST(test_bit(FF_RUMBLE + type, dev->features));
}
Shake_Bool Shake_QueryWaveformSupport(Shake_Device *dev, Shake_PeriodicWaveform waveform)
{
/* Starts at a magic, non-zero number, FF_SQUARE.
Increments respectively to PeriodicWaveform. */
return SHAKE_TEST(test_bit(FF_SQUARE + waveform, dev->features));
}
Shake_Bool Shake_QueryGainSupport(Shake_Device *dev)
{
return SHAKE_TEST(test_bit(FF_GAIN, dev->features));
}
Shake_Bool Shake_QueryAutocenterSupport(Shake_Device *dev)
{
return SHAKE_TEST(test_bit(FF_AUTOCENTER, dev->features));
}
Shake_Status Shake_SetGain(Shake_Device *dev, int gain)
{
struct input_event ie;
if (!dev)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
if (gain < 0)
gain = 0;
if (gain > 100)
gain = 100;
ie.type = EV_FF;
ie.code = FF_GAIN;
ie.value = 0xFFFFUL * gain / 100;
if (write(dev->fd, &ie, sizeof(ie)) == -1)
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
return SHAKE_OK;
}
Shake_Status Shake_SetAutocenter(Shake_Device *dev, int autocenter)
{
struct input_event ie;
if (!dev)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
if (autocenter < 0)
autocenter = 0;
if (autocenter > 100)
autocenter = 100;
ie.type = EV_FF;
ie.code = FF_AUTOCENTER;
ie.value = 0xFFFFUL * autocenter / 100;
if (write(dev->fd, &ie, sizeof(ie)) == -1)
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
return SHAKE_OK;
}
Shake_Status Shake_InitEffect(Shake_Effect *effect, Shake_EffectType type)
{
if (!effect || type >= SHAKE_EFFECT_COUNT)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
memset(effect, 0, sizeof(*effect));
effect->type = type;
effect->id = -1;
return SHAKE_OK;
}
int Shake_UploadEffect(Shake_Device *dev, Shake_Effect *effect)
{
struct ff_effect e;
if (!dev || !effect || effect->id < -1)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
/* Common parameters. */
e.id = effect->id;
e.direction = effect->direction;
e.trigger.button = 0;
e.trigger.interval = 0;
e.replay.delay = effect->delay;
e.replay.length = effect->length;
/* Effect type specific parameters. */
if(effect->type == SHAKE_EFFECT_RUMBLE)
{
e.type = FF_RUMBLE;
e.u.rumble.strong_magnitude = effect->u.rumble.strongMagnitude;
e.u.rumble.weak_magnitude = effect->u.rumble.weakMagnitude;
}
else if(effect->type == SHAKE_EFFECT_PERIODIC)
{
e.type = FF_PERIODIC;
e.u.periodic.waveform = FF_SQUARE + effect->u.periodic.waveform;
e.u.periodic.period = effect->u.periodic.period;
e.u.periodic.magnitude = effect->u.periodic.magnitude;
e.u.periodic.offset = effect->u.periodic.offset;
e.u.periodic.phase = effect->u.periodic.phase;
e.u.periodic.envelope.attack_length = effect->u.periodic.envelope.attackLength;
e.u.periodic.envelope.attack_level = effect->u.periodic.envelope.attackLevel;
e.u.periodic.envelope.fade_length = effect->u.periodic.envelope.fadeLength;
e.u.periodic.envelope.fade_level = effect->u.periodic.envelope.fadeLevel;
}
else if(effect->type == SHAKE_EFFECT_CONSTANT)
{
e.type = FF_CONSTANT;
e.u.constant.level = effect->u.constant.level;
e.u.constant.envelope.attack_length = effect->u.constant.envelope.attackLength;
e.u.constant.envelope.attack_level = effect->u.constant.envelope.attackLevel;
e.u.constant.envelope.fade_length = effect->u.constant.envelope.fadeLength;
e.u.constant.envelope.fade_level = effect->u.constant.envelope.fadeLevel;
}
else if(effect->type == SHAKE_EFFECT_RAMP)
{
e.type = FF_RAMP;
e.u.ramp.start_level = effect->u.ramp.startLevel;
e.u.ramp.end_level = effect->u.ramp.endLevel;
e.u.ramp.envelope.attack_length = effect->u.ramp.envelope.attackLength;
e.u.ramp.envelope.attack_level = effect->u.ramp.envelope.attackLevel;
e.u.ramp.envelope.fade_length = effect->u.ramp.envelope.fadeLength;
e.u.ramp.envelope.fade_level = effect->u.ramp.envelope.fadeLevel;
}
else
{
return (effect->type >= SHAKE_EFFECT_COUNT ? Shake_EmitErrorCode(SHAKE_EC_ARG) : Shake_EmitErrorCode(SHAKE_EC_SUPPORT));
}
if (ioctl(dev->fd, EVIOCSFF, &e) == -1)
{
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
return e.id;
}
Shake_Status Shake_EraseEffect(Shake_Device *dev, int id)
{
if (!dev || id < 0)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
if (ioctl(dev->fd, EVIOCRMFF, id) == -1)
{
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
return SHAKE_OK;
}
Shake_Status Shake_Play(Shake_Device *dev, int id)
{
struct input_event play;
if (!dev || id < 0)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
play.type = EV_FF;
play.code = id; /* the id we got when uploading the effect */
play.value = FF_STATUS_PLAYING; /* play: FF_STATUS_PLAYING, stop: FF_STATUS_STOPPED */
if (write(dev->fd, (const void*) &play, sizeof(play)) == -1)
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
return SHAKE_OK;
}
Shake_Status Shake_Stop(Shake_Device *dev, int id)
{
struct input_event stop;
if (!dev || id < 0)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
stop.type = EV_FF;
stop.code = id; /* the id we got when uploading the effect */
stop.value = FF_STATUS_STOPPED;
if (write(dev->fd, (const void*) &stop, sizeof(stop)) == -1)
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
return SHAKE_OK;
}
Shake_Status Shake_Close(Shake_Device *dev)
{
if (!dev)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
close(dev->fd);
return SHAKE_OK;
}

30
deps/libShake/src/linux/shake_private.h vendored Normal file
View File

@ -0,0 +1,30 @@
#ifndef _SHAKE_PRIVATE_H_
#define _SHAKE_PRIVATE_H_
#include <dirent.h>
#include <linux/input.h>
#include "../common/helpers.h"
#define SHAKE_DIR_NODES "/dev/input"
#define BITS_PER_LONG (sizeof(long) * 8)
#define OFF(x) ((x)%BITS_PER_LONG)
#define BIT(x) (1UL<<OFF(x))
#define LONG(x) ((x)/BITS_PER_LONG)
#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
#define BITS_TO_LONGS(x) \
(((x) + 8 * sizeof (unsigned long) - 1) / (8 * sizeof (unsigned long)))
struct Shake_Device
{
char name[128];
int id;
int capacity; /* Number of effects the device can play at the same time */
/* Platform dependent section */
int fd;
char *node;
unsigned long features[BITS_TO_LONGS(FF_CNT)];
};
#endif /* _SHAKE_PRIVATE_H_ */

719
deps/libShake/src/osx/shake.c vendored Normal file
View File

@ -0,0 +1,719 @@
/* libShake - a basic haptic library */
#include <ForceFeedback/ForceFeedback.h>
#include <ForceFeedback/ForceFeedbackConstants.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDKeys.h>
#include "shake.h"
#include "./shake_private.h"
#include "../common/helpers.h"
#include "../common/error.h"
static ListElement *listHead;
static unsigned int numOfDevices;
static int convertMagnitude(int magnitude)
{
return ((float)magnitude/0x7FFF) * FF_FFNOMINALMAX;
}
static void devItemDelete(void *item)
{
Shake_Device *dev = (Shake_Device *)item;
if (!dev)
return;
Shake_Close(dev);
if (dev->service)
IOObjectRelease(dev->service);
free(dev);
}
static void effectItemDelete(void *item)
{
EffectContainer *effect = (EffectContainer *)item;
free(effect);
}
static Shake_Status query(Shake_Device *dev)
{
HRESULT result;
io_name_t deviceName;
if(!dev)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
if (!dev->service)
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
result = FFCreateDevice(dev->service, &dev->device);
if (result != FF_OK)
{
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
}
result = FFDeviceGetForceFeedbackCapabilities(dev->device, &dev->features);
if (result != FF_OK)
{
return Shake_EmitErrorCode(SHAKE_EC_QUERY);
}
if (!dev->features.supportedEffects) /* Device doesn't support any force feedback effects. Ignore it. */
{
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
}
dev->capacity = dev->features.storageCapacity;
if (dev->capacity <= 0) /* Device doesn't support uploading effects. Ignore it. */
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
IORegistryEntryGetName(dev->service, deviceName); /* Get device name */
if (strlen((char *)deviceName))
{
strncpy(dev->name, (char *)deviceName, sizeof(dev->name));
}
else
{
strncpy(dev->name, "Unknown", sizeof(dev->name));
}
if (FFReleaseDevice(dev->device) == FF_OK)
{
dev->device = 0;
}
return SHAKE_OK;
}
static Shake_Status probe(Shake_Device *dev)
{
if ((FFIsForceFeedback(dev->service)) != FF_OK)
{
return SHAKE_ERROR;
}
if (query(dev))
{
return SHAKE_ERROR;
}
return SHAKE_OK;
}
/* API implementation. */
Shake_Status Shake_Init(void)
{
IOReturn ret;
io_iterator_t iter;
CFDictionaryRef match;
io_service_t device;
match = IOServiceMatching(kIOHIDDeviceKey);
if (!match)
{
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
}
ret = IOServiceGetMatchingServices(kIOMasterPortDefault, match, &iter);
if (ret != kIOReturnSuccess)
{
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
}
if (!IOIteratorIsValid(iter))
{
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
}
while ((device = IOIteratorNext(iter)) != IO_OBJECT_NULL)
{
Shake_Device dev;
dev.service = device;
dev.effectList = NULL;
if (probe(&dev) == SHAKE_OK)
{
dev.id = numOfDevices;
listHead = listElementPrepend(listHead);
listHead->item = malloc(sizeof(Shake_Device));
memcpy(listHead->item, &dev, sizeof(Shake_Device));
++numOfDevices;
}
else
{
IOObjectRelease(device);
}
}
IOObjectRelease(iter);
return SHAKE_OK;
}
void Shake_Quit(void)
{
if (listHead != NULL)
{
listElementDeleteAll(listHead, devItemDelete);
}
}
int Shake_NumOfDevices(void)
{
return numOfDevices;
}
Shake_Device *Shake_Open(unsigned int id)
{
HRESULT result;
Shake_Device *dev;
ListElement *element;
if (id >= numOfDevices)
{
Shake_EmitErrorCode(SHAKE_EC_ARG);
return NULL;
}
element = listElementGet(listHead, numOfDevices - 1 - id);
dev = (Shake_Device *)element->item;
if(!dev || !dev->service)
{
Shake_EmitErrorCode(SHAKE_EC_DEVICE);
return NULL;
}
result = FFCreateDevice(dev->service, &dev->device);
if (result != FF_OK)
{
Shake_EmitErrorCode(SHAKE_EC_DEVICE);
return NULL;
}
return dev;
}
int Shake_DeviceId(Shake_Device *dev)
{
if (!dev)
Shake_EmitErrorCode(SHAKE_EC_ARG);
return dev ? dev->id : SHAKE_ERROR;
}
const char *Shake_DeviceName(Shake_Device *dev)
{
if (!dev)
Shake_EmitErrorCode(SHAKE_EC_ARG);
return dev ? dev->name : NULL;
}
int Shake_DeviceEffectCapacity(Shake_Device *dev)
{
if (!dev)
Shake_EmitErrorCode(SHAKE_EC_ARG);
return dev ? dev->capacity : SHAKE_ERROR;
}
Shake_Bool Shake_QueryEffectSupport(Shake_Device *dev, Shake_EffectType type)
{
FFCapabilitiesEffectType query;
switch (type)
{
case SHAKE_EFFECT_RUMBLE:
/* Emulate EFFECT_RUMBLE with EFFECT_PERIODIC. */
return Shake_QueryWaveformSupport(dev, SHAKE_PERIODIC_SINE) ? SHAKE_TRUE : SHAKE_FALSE;
break;
case SHAKE_EFFECT_PERIODIC:
{
Shake_PeriodicWaveform waveform;
for (waveform = SHAKE_PERIODIC_SQUARE; waveform < SHAKE_PERIODIC_COUNT; ++waveform)
{
if (Shake_QueryWaveformSupport(dev, waveform))
return SHAKE_TRUE;
}
return SHAKE_FALSE;
}
break;
case SHAKE_EFFECT_CONSTANT:
query = FFCAP_ET_CONSTANTFORCE;
break;
case SHAKE_EFFECT_SPRING:
query = FFCAP_ET_SPRING;
break;
case SHAKE_EFFECT_FRICTION:
query = FFCAP_ET_FRICTION;
break;
case SHAKE_EFFECT_DAMPER:
query = FFCAP_ET_DAMPER;
break;
case SHAKE_EFFECT_INERTIA:
query = FFCAP_ET_INERTIA;
break;
case SHAKE_EFFECT_RAMP:
query = FFCAP_ET_RAMPFORCE;
break;
default:
return SHAKE_FALSE;
}
return test_bit(query, dev->features.supportedEffects) ? SHAKE_TRUE : SHAKE_FALSE;
}
Shake_Bool Shake_QueryWaveformSupport(Shake_Device *dev, Shake_PeriodicWaveform waveform)
{
FFCapabilitiesEffectType query;
switch (waveform)
{
case SHAKE_PERIODIC_SQUARE:
query = FFCAP_ET_SQUARE;
break;
case SHAKE_PERIODIC_TRIANGLE:
query = FFCAP_ET_TRIANGLE;
break;
case SHAKE_PERIODIC_SINE:
query = FFCAP_ET_SINE;
break;
case SHAKE_PERIODIC_SAW_UP:
query = FFCAP_ET_SAWTOOTHUP;
break;
case SHAKE_PERIODIC_SAW_DOWN:
query = FFCAP_ET_SAWTOOTHDOWN;
break;
case SHAKE_PERIODIC_CUSTOM:
query = FFCAP_ET_CUSTOMFORCE;
break;
default:
return SHAKE_FALSE;
}
return test_bit(query, dev->features.supportedEffects) ? SHAKE_TRUE : SHAKE_FALSE;
}
Shake_Bool Shake_QueryGainSupport(Shake_Device *dev)
{
HRESULT result;
int value = 0; /* Unused for now. */
result = FFDeviceGetForceFeedbackProperty(dev->device, FFPROP_FFGAIN, &value, sizeof(value));
if (result == FF_OK)
{
return SHAKE_TRUE;
}
else if (result != FFERR_UNSUPPORTED)
{
Shake_EmitErrorCode(SHAKE_EC_QUERY);
}
return SHAKE_FALSE;
}
Shake_Bool Shake_QueryAutocenterSupport(Shake_Device *dev)
{
HRESULT result;
int value = 0; /* Unused for now. */
result = FFDeviceGetForceFeedbackProperty(dev->device, FFPROP_AUTOCENTER, &value, sizeof(value));
if (result == FF_OK)
{
return SHAKE_TRUE;
}
else if (result != FFERR_UNSUPPORTED)
{
Shake_EmitErrorCode(SHAKE_EC_QUERY);
}
return SHAKE_FALSE;
}
Shake_Status Shake_SetGain(Shake_Device *dev, int gain)
{
if (!dev)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
if (gain < 0)
gain = 0;
if (gain > 100)
gain = 100;
gain = ((float)gain/100) * FF_FFNOMINALMAX;
if (FFDeviceSetForceFeedbackProperty(dev->device, FFPROP_FFGAIN, &gain) != FF_OK)
{
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
return SHAKE_OK;
}
Shake_Status Shake_SetAutocenter(Shake_Device *dev, int autocenter)
{
if (!dev)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
if (autocenter) /* OSX supports only OFF and ON values */
{
autocenter = 1;
}
if (FFDeviceSetForceFeedbackProperty(dev->device, FFPROP_AUTOCENTER, &autocenter) != FF_OK)
{
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
return SHAKE_OK;
}
Shake_Status Shake_InitEffect(Shake_Effect *effect, Shake_EffectType type)
{
if (!effect || type >= SHAKE_EFFECT_COUNT)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
memset(effect, 0, sizeof(*effect));
effect->type = type;
effect->id = -1;
return SHAKE_OK;
}
int Shake_UploadEffect(Shake_Device *dev, Shake_Effect *effect)
{
HRESULT result;
FFEFFECT e;
CFUUIDRef effectType;
EffectContainer *container = NULL;
FFENVELOPE envelope;
TypeSpecificParams typeParams;
DWORD rgdwAxes[2];
LONG rglDirection[2];
if (!dev || !effect || effect->id < SHAKE_EFFECT_ID_MIN)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
rgdwAxes[0] = FFJOFS_X;
rgdwAxes[1] = FFJOFS_Y;
rglDirection[0] = effect->direction;
rglDirection[1] = 0;
memset(&envelope, 0, sizeof(FFENVELOPE));
/* Common parameters. */
memset(&e, 0, sizeof(FFEFFECT));
e.dwSize = sizeof(FFEFFECT);
e.dwFlags = FFEFF_POLAR | FFEFF_OBJECTOFFSETS;
e.dwDuration = effect->length * 1000;
e.dwSamplePeriod = 0;
e.cAxes = 2;
e.rgdwAxes = rgdwAxes;
e.rglDirection = rglDirection;
e.dwStartDelay = effect->delay;
e.dwTriggerButton = FFEB_NOTRIGGER;
e.dwTriggerRepeatInterval = 0;
e.lpEnvelope = &envelope;
e.lpEnvelope->dwSize = sizeof(FFENVELOPE);
e.dwGain = FF_FFNOMINALMAX;
/* Effect type specific parameters. */
if(effect->type == SHAKE_EFFECT_RUMBLE)
{
/* Emulate EFFECT_RUMBLE with EFFECT_PERIODIC. */
int magnitude;
/*
* The magnitude is calculated as average of
* 2/3 of strongMagnitude and 1/3 of weakMagnitude.
* This follows the same ratios as in the Linux kernel.
*/
magnitude = effect->u.rumble.strongMagnitude/3 + effect->u.rumble.weakMagnitude/6;
if (magnitude > SHAKE_PERIODIC_MAGNITUDE_MAX)
{
magnitude = SHAKE_PERIODIC_MAGNITUDE_MAX;
}
typeParams.pf.dwMagnitude = convertMagnitude(magnitude);
typeParams.pf.lOffset = 0;
typeParams.pf.dwPhase = 0;
typeParams.pf.dwPeriod = 50 * 1000; /* Magic number from the Linux kernel implementation. */
effectType = kFFEffectType_Sine_ID;
e.lpEnvelope->dwAttackTime = 0;
e.lpEnvelope->dwAttackLevel = 0;
e.lpEnvelope->dwFadeTime = 0;
e.lpEnvelope->dwFadeLevel = 0;
e.cbTypeSpecificParams = sizeof(FFPERIODIC);
e.lpvTypeSpecificParams = &typeParams.pf;
}
else if(effect->type == SHAKE_EFFECT_PERIODIC)
{
switch (effect->u.periodic.waveform)
{
case SHAKE_PERIODIC_SQUARE:
effectType = kFFEffectType_Square_ID;
break;
case SHAKE_PERIODIC_TRIANGLE:
effectType = kFFEffectType_Triangle_ID;
break;
case SHAKE_PERIODIC_SINE:
effectType = kFFEffectType_Sine_ID;
break;
case SHAKE_PERIODIC_SAW_UP:
effectType = kFFEffectType_SawtoothUp_ID;
break;
case SHAKE_PERIODIC_SAW_DOWN:
effectType = kFFEffectType_SawtoothDown_ID;
break;
case SHAKE_PERIODIC_CUSTOM:
effectType = kFFEffectType_CustomForce_ID;
break;
default:
return Shake_EmitErrorCode(SHAKE_EC_SUPPORT);
}
typeParams.pf.dwMagnitude = convertMagnitude(effect->u.periodic.magnitude);
typeParams.pf.lOffset = convertMagnitude(effect->u.periodic.offset);
typeParams.pf.dwPhase = ((float)effect->u.periodic.phase/SHAKE_PERIODIC_PHASE_MAX) * OSX_PERIODIC_PHASE_MAX;
typeParams.pf.dwPeriod = effect->u.periodic.period * 1000;
e.lpEnvelope->dwAttackTime = effect->u.periodic.envelope.attackLength * 1000;
e.lpEnvelope->dwAttackLevel = effect->u.periodic.envelope.attackLevel;
e.lpEnvelope->dwFadeTime = effect->u.periodic.envelope.fadeLength * 1000;
e.lpEnvelope->dwFadeLevel = effect->u.periodic.envelope.fadeLevel;
e.cbTypeSpecificParams = sizeof(FFPERIODIC);
e.lpvTypeSpecificParams = &typeParams.pf;
}
else if(effect->type == SHAKE_EFFECT_CONSTANT)
{
typeParams.cf.lMagnitude = convertMagnitude(effect->u.constant.level);
effectType = kFFEffectType_ConstantForce_ID;
e.lpEnvelope->dwAttackTime = effect->u.constant.envelope.attackLength * 1000;
e.lpEnvelope->dwAttackLevel = effect->u.constant.envelope.attackLevel;
e.lpEnvelope->dwFadeTime = effect->u.constant.envelope.fadeLength * 1000;
e.lpEnvelope->dwFadeLevel = effect->u.constant.envelope.fadeLevel;
e.cbTypeSpecificParams = sizeof(FFCONSTANTFORCE);
e.lpvTypeSpecificParams = &typeParams.cf;
}
else if(effect->type == SHAKE_EFFECT_RAMP)
{
typeParams.rf.lStart = ((float)effect->u.ramp.startLevel/SHAKE_RAMP_START_LEVEL_MAX) * FF_FFNOMINALMAX;
typeParams.rf.lEnd = ((float)effect->u.ramp.endLevel/SHAKE_RAMP_END_LEVEL_MAX) * FF_FFNOMINALMAX;
effectType = kFFEffectType_RampForce_ID;
e.lpEnvelope->dwAttackTime = effect->u.ramp.envelope.attackLength * 1000;
e.lpEnvelope->dwAttackLevel = effect->u.ramp.envelope.attackLevel;
e.lpEnvelope->dwFadeTime = effect->u.ramp.envelope.fadeLength * 1000;
e.lpEnvelope->dwFadeLevel = effect->u.ramp.envelope.fadeLevel;
e.cbTypeSpecificParams = sizeof(FFRAMPFORCE);
e.lpvTypeSpecificParams = &typeParams.rf;
}
else
{
return (effect->type >= SHAKE_EFFECT_COUNT ? Shake_EmitErrorCode(SHAKE_EC_ARG) : Shake_EmitErrorCode(SHAKE_EC_SUPPORT));
}
if (effect->id == SHAKE_EFFECT_ID_MIN) /* Create a new effect. */
{
dev->effectList = listElementPrepend(dev->effectList);
dev->effectList->item = malloc(sizeof(EffectContainer));
container = dev->effectList->item;
container->id = listLength(dev->effectList) - 1;
container->effect = 0;
result = FFDeviceCreateEffect(dev->device, effectType, &e, &container->effect);
if ((unsigned int)result != FF_OK)
{
dev->effectList = listElementDelete(dev->effectList, dev->effectList, effectItemDelete);
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
}
else /* Update existing effect. */
{
ListElement *node = dev->effectList;
EffectContainer *item;
while (node)
{
item = (EffectContainer *)node->item;
if (item->id == effect->id)
{
container = item;
break;
}
node = node->next;
}
if (container)
{
int flags = FFEP_AXES | FFEP_DIRECTION | FFEP_DURATION | FFEP_ENVELOPE | FFEP_GAIN | FFEP_SAMPLEPERIOD | FFEP_STARTDELAY | FFEP_TRIGGERBUTTON | FFEP_TYPESPECIFICPARAMS;
result = FFEffectSetParameters(container->effect, &e, flags);
if ((unsigned int)result != FF_OK)
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
}
return container ? container->id : Shake_EmitErrorCode(SHAKE_EC_EFFECT);
}
Shake_Status Shake_EraseEffect(Shake_Device *dev, int id)
{
ListElement *node;
EffectContainer *effect = NULL;
if(!dev || id < 0)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
node = dev->effectList;
while (node)
{
effect = (EffectContainer *)node->item;
if (effect->id == id)
{
break;
}
node = node->next;
}
if (!node || !effect)
{
return Shake_EmitErrorCode(SHAKE_EC_EFFECT);
}
if (FFDeviceReleaseEffect(dev->device, effect->effect))
{
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
dev->effectList = listElementDelete(dev->effectList, node, effectItemDelete);
return SHAKE_OK;
}
Shake_Status Shake_Play(Shake_Device *dev, int id)
{
ListElement *node;
EffectContainer *effect = NULL;
if(!dev || id < 0)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
node = dev->effectList;
while (node)
{
effect = (EffectContainer *)node->item;
if (effect->id == id)
{
break;
}
node = node->next;
}
if (!node || !effect)
{
return Shake_EmitErrorCode(SHAKE_EC_EFFECT);
}
if (FFEffectStart(effect->effect, 1, 0) != FF_OK)
{
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
return SHAKE_OK;
}
Shake_Status Shake_Stop(Shake_Device *dev, int id)
{
ListElement *node;
EffectContainer *effect = NULL;
if(!dev || id < 0)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
node = dev->effectList;
while (node)
{
effect = (EffectContainer *)node->item;
if (effect->id == id)
{
break;
}
node = node->next;
}
if (!node || !effect)
{
return Shake_EmitErrorCode(SHAKE_EC_EFFECT);
}
if (FFEffectStop(effect->effect))
{
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
return SHAKE_OK;
}
Shake_Status Shake_Close(Shake_Device *dev)
{
int effectLen;
int i;
if (!dev)
return Shake_EmitErrorCode(SHAKE_EC_ARG);
if (!dev->device)
return Shake_EmitErrorCode(SHAKE_EC_DEVICE);
effectLen = listLength(dev->effectList);
for (i = 0; i < effectLen; ++i)
{
EffectContainer *effect = (EffectContainer *)listElementGet(dev->effectList, i);
if (FFDeviceReleaseEffect(dev->device, effect->effect))
{
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
}
dev->effectList = listElementDeleteAll(dev->effectList, effectItemDelete);
if (FFReleaseDevice(dev->device) != FF_OK)
{
return Shake_EmitErrorCode(SHAKE_EC_TRANSFER);
}
dev->device = 0;
return SHAKE_OK;
}

43
deps/libShake/src/osx/shake_private.h vendored Normal file
View File

@ -0,0 +1,43 @@
#ifndef _SHAKE_PRIVATE_H_
#define _SHAKE_PRIVATE_H_
#include <ForceFeedback/ForceFeedback.h>
#include <IOKit/IOKitLib.h>
#include "../common/helpers.h"
#define BITS_PER_LONG (sizeof(long) * 8)
#define OFF(x) ((x)%BITS_PER_LONG)
#define BIT(x) (1UL<<OFF(x))
#define LONG(x) ((x)/BITS_PER_LONG)
#define test_bit(bit, array) ((array >> OFF(bit)) & 1)
#define BITS_TO_LONGS(x) \
(((x) + 8 * sizeof (unsigned long) - 1) / (8 * sizeof (unsigned long)))
#define OSX_PERIODIC_PHASE_MAX 0x8C9F
typedef union TypeSpecificParams
{
FFPERIODIC pf;
FFCONSTANTFORCE cf;
FFRAMPFORCE rf;
} TypeSpecificParams;
typedef struct EffectContainer
{
int id;
FFEffectObjectReference effect;
} EffectContainer;
struct Shake_Device
{
char name[128];
int id;
int capacity; /* Number of effects the device can play at the same time */
/* Platform dependent section */
io_service_t service;
FFDeviceObjectReference device;
ListElement *effectList;
FFCAPABILITIES features;
};
#endif /* _SHAKE_PRIVATE_H_ */

View File

@ -778,6 +778,20 @@ INPUT
#include "../input/drivers_joypad/udev_joypad.c"
#endif
#if defined(HAVE_LIBSHAKE)
#if TARGET_OS_OSX
#include "../deps/libShake/src/common/error.c"
#include "../deps/libShake/src/common/helpers.c"
#include "../deps/libShake/src/common/presets.c"
#include "../deps/libShake/src/osx/shake.c"
#elif defined(__linux__) || (defined(BSD) && !defined(__MACH__))
#include "../deps/libShake/src/common/error.c"
#include "../deps/libShake/src/common/helpers.c"
#include "../deps/libShake/src/common/presets.c"
#include "../deps/libShake/src/linux/shake.c"
#endif
#endif
/*============================================================
INPUT (HID)
============================================================ */

View File

@ -26,15 +26,50 @@
#include "../../tasks/tasks_internal.h"
#include "../../verbosity.h"
#if defined(HAVE_LIBSHAKE)
#include <shake.h>
#include "../../configuration.h"
#endif
/* Simple joypad driver designed to rationalise
* the bizarre keyboard/gamepad hybrid setup
* of OpenDingux devices */
#define SDL_DINGUX_JOYPAD_NAME "Dingux Gamepad"
#if defined(HAVE_LIBSHAKE)
/* 5 ms period == 200 Hz
* > Meissner's Corpuscle registers this
* as 'fast' motion */
#define SDL_DINGUX_RUMBLE_WEAK_PERIOD 5
/* 142 ms period ~= 7 Hz
* > Merkel's Cells and Ruffini Ending register
* this as 'slow' motion */
#define SDL_DINGUX_RUMBLE_STRONG_PERIOD 142
typedef struct
{
int id;
uint16_t strength;
Shake_Effect effect;
bool active;
} dingux_joypad_rumble_effect_t;
typedef struct
{
Shake_Device *device;
dingux_joypad_rumble_effect_t weak;
dingux_joypad_rumble_effect_t strong;
} dingux_joypad_rumble_t;
#endif
typedef struct
{
SDL_Joystick *device;
#if defined(HAVE_LIBSHAKE)
dingux_joypad_rumble_t rumble;
#endif
uint16_t pad_state;
int16_t analog_state[2][2];
unsigned num_axes;
@ -47,6 +82,169 @@ extern uint64_t lifecycle_state;
static dingux_joypad_t dingux_joypad;
#if defined(HAVE_LIBSHAKE)
static bool sdl_dingux_rumble_init(dingux_joypad_rumble_t *rumble)
{
settings_t *settings = config_get_ptr();
unsigned rumble_gain = settings ? settings->uints.input_dingux_rumble_gain : 0;
bool weak_uploaded = false;
bool strong_uploaded = false;
/* If gain is zero, rumble is disabled
* > No need to initialise device */
if (rumble_gain == 0)
goto error;
if (Shake_NumOfDevices() < 1)
goto error;
/* Open shake device */
rumble->device = Shake_Open(0);
if (!rumble->device)
goto error;
/* Check whether shake device has the required
* feature set */
if (!Shake_QueryEffectSupport(rumble->device, SHAKE_EFFECT_PERIODIC) ||
!Shake_QueryWaveformSupport(rumble->device, SHAKE_PERIODIC_SINE))
goto error;
/* In most cases it is recommended to use SHAKE_EFFECT_PERIODIC
* instead of SHAKE_EFFECT_RUMBLE. All devices that support
* SHAKE_EFFECT_RUMBLE support SHAKE_EFFECT_PERIODIC (square,
* triangle, sine) and vice versa */
/* Initialise weak rumble effect */
if (Shake_InitEffect(&rumble->weak.effect, SHAKE_EFFECT_PERIODIC) != SHAKE_OK)
goto error;
rumble->weak.effect.u.periodic.waveform = SHAKE_PERIODIC_SINE;
rumble->weak.effect.u.periodic.period = SDL_DINGUX_RUMBLE_WEAK_PERIOD;
rumble->weak.effect.u.periodic.magnitude = 0;
rumble->weak.id = Shake_UploadEffect(rumble->device, &rumble->weak.effect);
if (rumble->weak.id == SHAKE_ERROR)
goto error;
weak_uploaded = true;
/* Initialise strong rumble effect */
if (Shake_InitEffect(&rumble->strong.effect, SHAKE_EFFECT_PERIODIC) != SHAKE_OK)
goto error;
rumble->strong.effect.u.periodic.waveform = SHAKE_PERIODIC_SINE;
rumble->strong.effect.u.periodic.period = SDL_DINGUX_RUMBLE_STRONG_PERIOD;
rumble->strong.effect.u.periodic.magnitude = 0;
rumble->strong.id = Shake_UploadEffect(rumble->device, &rumble->strong.effect);
if (rumble->weak.id == SHAKE_ERROR)
goto error;
strong_uploaded = true;
/* Set gain, if supported */
if (Shake_QueryGainSupport(rumble->device))
if (Shake_SetGain(rumble->device, (int)rumble_gain) != SHAKE_OK)
goto error;
return true;
error:
if (rumble_gain != 0)
RARCH_WARN("[libShake]: Input device does not support rumble effects.\n");
if (rumble->device)
{
if (weak_uploaded)
Shake_EraseEffect(rumble->device, rumble->weak.id);
if (strong_uploaded)
Shake_EraseEffect(rumble->device, rumble->strong.id);
Shake_Close(rumble->device);
rumble->device = NULL;
}
return false;
}
static bool sdl_dingux_rumble_update(Shake_Device *device,
dingux_joypad_rumble_effect_t *effect,
uint16_t strength, uint16_t max_strength)
{
int id;
/* If strength is zero, halt rumble effect */
if (strength == 0)
{
if (effect->active)
{
if (Shake_Stop(device, effect->id) == SHAKE_OK)
{
effect->active = false;
effect->strength = 0;
return true;
}
else
return false;
}
return true;
}
/* If strength is unchanged, do nothing */
if (strength == effect->strength)
return true;
/* Strength has changed - update effect */
effect->effect.id = effect->id;
effect->effect.u.periodic.magnitude = (max_strength * strength) / 0xFFFF;
id = Shake_UploadEffect(device, &effect->effect);
if (id == SHAKE_ERROR)
return false;
effect->id = id;
if (!effect->active)
{
if (Shake_Play(device, effect->id) == SHAKE_OK)
{
effect->active = true;
return true;
}
else
return false;
}
return true;
}
static bool sdl_dingux_joypad_set_rumble(unsigned pad,
enum retro_rumble_effect effect, uint16_t strength)
{
dingux_joypad_t *joypad = (dingux_joypad_t*)&dingux_joypad;
if (!joypad->rumble.device)
return false;
switch (effect)
{
case RETRO_RUMBLE_STRONG:
return sdl_dingux_rumble_update(joypad->rumble.device,
&joypad->rumble.strong, strength,
SHAKE_RUMBLE_STRONG_MAGNITUDE_MAX);
case RETRO_RUMBLE_WEAK:
return sdl_dingux_rumble_update(joypad->rumble.device,
&joypad->rumble.weak, strength,
SHAKE_RUMBLE_WEAK_MAGNITUDE_MAX);
default:
break;
}
return false;
}
#endif
static const char *sdl_dingux_joypad_name(unsigned port)
{
const char *joypad_name = NULL;
@ -69,6 +267,11 @@ static void sdl_dingux_joypad_connect(void)
if (joypad->device)
joypad->num_axes = SDL_JoystickNumAxes(joypad->device);
#if defined(HAVE_LIBSHAKE)
/* Configure rumble interface */
sdl_dingux_rumble_init(&joypad->rumble);
#endif
/* 'Register' joypad connection via
* autoconfig task */
input_autoconfigure_connect(
@ -92,6 +295,22 @@ static void sdl_dingux_joypad_disconnect(void)
if (joypad->connected)
input_autoconfigure_disconnect(0, sdl_dingux_joypad.ident);
#if defined(HAVE_LIBSHAKE)
if (joypad->rumble.device)
{
if (joypad->rumble.weak.active)
Shake_Stop(joypad->rumble.device, joypad->rumble.weak.id);
if (joypad->rumble.strong.active)
Shake_Stop(joypad->rumble.device, joypad->rumble.strong.id);
Shake_EraseEffect(joypad->rumble.device, joypad->rumble.weak.id);
Shake_EraseEffect(joypad->rumble.device, joypad->rumble.strong.id);
Shake_Close(joypad->rumble.device);
}
#endif
memset(joypad, 0, sizeof(dingux_joypad_t));
}
@ -108,6 +327,11 @@ static void sdl_dingux_joypad_destroy(void)
/* Flush out all pending events */
while (SDL_PollEvent(&event));
#if defined(HAVE_LIBSHAKE)
/* De-initialise rumble interface */
Shake_Quit();
#endif
BIT64_CLEAR(lifecycle_state, RARCH_MENU_TOGGLE);
}
@ -127,6 +351,11 @@ static void *sdl_dingux_joypad_init(void *data)
else if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
return NULL;
#if defined(HAVE_LIBSHAKE)
/* Initialise rumble interface */
Shake_Init();
#endif
/* Connect joypad */
sdl_dingux_joypad_connect();
@ -457,7 +686,11 @@ input_device_driver_t sdl_dingux_joypad = {
sdl_dingux_joypad_get_buttons,
sdl_dingux_joypad_axis,
sdl_dingux_joypad_poll,
NULL, /* set_rumble */
#if defined(HAVE_LIBSHAKE)
sdl_dingux_joypad_set_rumble,
#else
NULL,
#endif
sdl_dingux_joypad_name,
"sdl_dingux",
};

View File

@ -4780,6 +4780,12 @@ MSG_HASH(
MENU_ENUM_LABEL_INPUT_HAPTIC_FEEDBACK_SETTINGS,
"input_haptic_feedback_settings"
)
#if defined(DINGUX) && defined(HAVE_LIBSHAKE)
MSG_HASH(
MENU_ENUM_LABEL_INPUT_DINGUX_RUMBLE_GAIN,
"input_dingux_rumble_gain"
)
#endif
MSG_HASH(
MENU_ENUM_LABEL_INPUT_TURBO_MODE,
"input_turbo_mode"

View File

@ -2173,6 +2173,16 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_ENABLE_DEVICE_VIBRATION,
"Enable Device Vibration (For Supported Cores)"
)
#if defined(DINGUX) && defined(HAVE_LIBSHAKE)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_DINGUX_RUMBLE_GAIN,
"Vibration Strength (Restart)"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_DINGUX_RUMBLE_GAIN,
"Specifies the magnitude of haptic feedback effects."
)
#endif
/* Settings > Input > Menu Controls */

View File

@ -331,6 +331,9 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_turbo_period, MENU_
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_duty_cycle, MENU_ENUM_SUBLABEL_INPUT_DUTY_CYCLE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_turbo_mode, MENU_ENUM_SUBLABEL_INPUT_TURBO_MODE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_turbo_default_button, MENU_ENUM_SUBLABEL_INPUT_TURBO_DEFAULT_BUTTON)
#if defined(DINGUX) && defined(HAVE_LIBSHAKE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_dingux_rumble_gain, MENU_ENUM_SUBLABEL_INPUT_DINGUX_RUMBLE_GAIN)
#endif
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_vertical_sync, MENU_ENUM_SUBLABEL_VIDEO_VSYNC)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_adaptive_vsync, MENU_ENUM_SUBLABEL_VIDEO_ADAPTIVE_VSYNC)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_allow_rotate, MENU_ENUM_SUBLABEL_VIDEO_ALLOW_ROTATE)
@ -3301,6 +3304,11 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
case MENU_ENUM_LABEL_INPUT_TURBO_DEFAULT_BUTTON:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_turbo_default_button);
break;
#if defined(DINGUX) && defined(HAVE_LIBSHAKE)
case MENU_ENUM_LABEL_INPUT_DINGUX_RUMBLE_GAIN:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_dingux_rumble_gain);
break;
#endif
case MENU_ENUM_LABEL_INPUT_BIND_TIMEOUT:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_bind_timeout);
break;

View File

@ -5202,14 +5202,31 @@ unsigned menu_displaylist_build_list(
count++;
break;
case DISPLAYLIST_INPUT_HAPTIC_FEEDBACK_SETTINGS_LIST:
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIBRATE_ON_KEYPRESS,
PARSE_ONLY_BOOL, false) == 0)
count++;
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_ENABLE_DEVICE_VIBRATION,
PARSE_ONLY_BOOL, false) == 0)
count++;
{
settings_t *settings = config_get_ptr();
const char *input_driver_id = settings->arrays.input_driver;
const char *joypad_driver_id = settings->arrays.input_joypad_driver;
if (string_is_equal(input_driver_id, "android"))
{
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_VIBRATE_ON_KEYPRESS,
PARSE_ONLY_BOOL, false) == 0)
count++;
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_ENABLE_DEVICE_VIBRATION,
PARSE_ONLY_BOOL, false) == 0)
count++;
}
#if defined(DINGUX) && defined(HAVE_LIBSHAKE)
if (string_is_equal(joypad_driver_id, "sdl_dingux"))
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_INPUT_DINGUX_RUMBLE_GAIN,
PARSE_ONLY_UINT, false) == 0)
count++;
#endif
}
break;
case DISPLAYLIST_INPUT_HOTKEY_BINDS_LIST:
{

View File

@ -11794,6 +11794,22 @@ static bool setting_append_list(
SD_FLAG_NONE
);
#if defined(DINGUX) && defined(HAVE_LIBSHAKE)
CONFIG_UINT(
list, list_info,
&settings->uints.input_dingux_rumble_gain,
MENU_ENUM_LABEL_INPUT_DINGUX_RUMBLE_GAIN,
MENU_ENUM_LABEL_VALUE_INPUT_DINGUX_RUMBLE_GAIN,
DEFAULT_DINGUX_RUMBLE_GAIN,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler);
(*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_COMBOBOX;
(*list)[list_info->index - 1].action_ok = &setting_action_ok_uint;
menu_settings_list_current_add_range(list, list_info, 0, 100, 5, true, true);
#endif
CONFIG_UINT(
list, list_info,
&settings->uints.input_poll_type_behavior,

View File

@ -960,6 +960,10 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_QUIT_ON_CLOSE_CONTENT_ENABLED,
MENU_ENUM_LABEL_VALUE_QUIT_ON_CLOSE_CONTENT_CLI,
#if defined(DINGUX) && defined(HAVE_LIBSHAKE)
MENU_LABEL(INPUT_DINGUX_RUMBLE_GAIN),
#endif
/* Video */
MENU_LABEL(CRT_SWITCH_RESOLUTION),
MENU_LABEL(CRT_SWITCH_RESOLUTION_SUPER),

View File

@ -83,6 +83,7 @@ HAVE_OPENGL1=yes # OpenGL 1.1 support
HAVE_MALI_FBDEV=no # Mali fbdev context support
HAVE_VIVANTE_FBDEV=no # Vivante fbdev context support
HAVE_OPENDINGUX_FBDEV=no # Opendingux fbdev context support
HAVE_SDL_DINGUX=no # Opendingux SDL input/gfx driver support
HAVE_OPENGLES=no # Use GLESv2 instead of desktop GL
HAVE_OPENGLES3=no # OpenGLES3 support
HAVE_OPENGLES3_1=no # OpenGLES3.1 support
@ -189,4 +190,5 @@ C89_METAL=no
HAVE_NETWORK_VIDEO=no
HAVE_STEAM=no # Enable Steam build
HAVE_ODROIDGO2=no # ODROID-GO Advance rotation support (requires librga)
HAVE_LIBSHAKE=no # libShake haptic feedback support
HAVE_GIT_VERSION=yes # Git version support