From e582cf9f1da07e16e1258ec63656d04dfcb072f2 Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Tue, 17 Nov 2020 15:59:39 +0000 Subject: [PATCH] OpenDingux: Add rumble support --- Makefile.common | 18 + Makefile.dingux | 11 +- Makefile.rg350 | 11 +- config.def.h | 6 + configuration.c | 3 + configuration.h | 2 + deps/libShake/LICENSE.txt | 23 + deps/libShake/Makefile | 72 ++ deps/libShake/README.md | 44 ++ deps/libShake/examples/Makefile | 28 + deps/libShake/examples/deviceTest.c | 333 ++++++++ deps/libShake/examples/listDevices.c | 51 ++ deps/libShake/examples/simple.c | 40 + .../buildroot-package/libshake/Config.in | 6 + .../buildroot-package/libshake/libshake.mk | 26 + deps/libShake/include/shake.h | 522 +++++++++++++ deps/libShake/src/common/error.c | 14 + deps/libShake/src/common/error.h | 9 + deps/libShake/src/common/helpers.c | 93 +++ deps/libShake/src/common/helpers.h | 16 + deps/libShake/src/common/presets.c | 50 ++ deps/libShake/src/linux/shake.c | 422 ++++++++++ deps/libShake/src/linux/shake_private.h | 30 + deps/libShake/src/osx/shake.c | 719 ++++++++++++++++++ deps/libShake/src/osx/shake_private.h | 43 ++ griffin/griffin.c | 14 + input/drivers_joypad/sdl_dingux_joypad.c | 235 +++++- intl/msg_hash_lbl.h | 6 + intl/msg_hash_us.h | 10 + menu/cbs/menu_cbs_sublabel.c | 8 + menu/menu_displaylist.c | 33 +- menu/menu_setting.c | 16 + msg_hash.h | 4 + qb/config.params.sh | 2 + 34 files changed, 2901 insertions(+), 19 deletions(-) create mode 100644 deps/libShake/LICENSE.txt create mode 100644 deps/libShake/Makefile create mode 100644 deps/libShake/README.md create mode 100644 deps/libShake/examples/Makefile create mode 100644 deps/libShake/examples/deviceTest.c create mode 100644 deps/libShake/examples/listDevices.c create mode 100644 deps/libShake/examples/simple.c create mode 100644 deps/libShake/extras/buildroot-package/libshake/Config.in create mode 100644 deps/libShake/extras/buildroot-package/libshake/libshake.mk create mode 100644 deps/libShake/include/shake.h create mode 100644 deps/libShake/src/common/error.c create mode 100644 deps/libShake/src/common/error.h create mode 100644 deps/libShake/src/common/helpers.c create mode 100644 deps/libShake/src/common/helpers.h create mode 100644 deps/libShake/src/common/presets.c create mode 100644 deps/libShake/src/linux/shake.c create mode 100644 deps/libShake/src/linux/shake_private.h create mode 100644 deps/libShake/src/osx/shake.c create mode 100644 deps/libShake/src/osx/shake_private.h diff --git a/Makefile.common b/Makefile.common index 235ee2a893..79ba7e16fd 100644 --- a/Makefile.common +++ b/Makefile.common @@ -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)),) diff --git a/Makefile.dingux b/Makefile.dingux index 8e7f877391..67449f47bc 100644 --- a/Makefile.dingux +++ b/Makefile.dingux @@ -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) diff --git a/Makefile.rg350 b/Makefile.rg350 index 7923a46e85..41a6693324 100644 --- a/Makefile.rg350 +++ b/Makefile.rg350 @@ -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) diff --git a/config.def.h b/config.def.h index 6a6d7fc36c..7cfdd1b7c5 100644 --- a/config.def.h +++ b/config.def.h @@ -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 diff --git a/configuration.c b/configuration.c index f31c5101f1..4e723cea76 100644 --- a/configuration.c +++ b/configuration.c @@ -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); diff --git a/configuration.h b/configuration.h index 7206d32862..3b9e627e75 100644 --- a/configuration.h +++ b/configuration.h @@ -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; diff --git a/deps/libShake/LICENSE.txt b/deps/libShake/LICENSE.txt new file mode 100644 index 0000000000..c368c8e31d --- /dev/null +++ b/deps/libShake/LICENSE.txt @@ -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. + diff --git a/deps/libShake/Makefile b/deps/libShake/Makefile new file mode 100644 index 0000000000..d7906c9eb1 --- /dev/null +++ b/deps/libShake/Makefile @@ -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) diff --git a/deps/libShake/README.md b/deps/libShake/README.md new file mode 100644 index 0000000000..3792f179d8 --- /dev/null +++ b/deps/libShake/README.md @@ -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/). diff --git a/deps/libShake/examples/Makefile b/deps/libShake/examples/Makefile new file mode 100644 index 0000000000..5d7abea5eb --- /dev/null +++ b/deps/libShake/examples/Makefile @@ -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) diff --git a/deps/libShake/examples/deviceTest.c b/deps/libShake/examples/deviceTest.c new file mode 100644 index 0000000000..c7e0358d58 --- /dev/null +++ b/deps/libShake/examples/deviceTest.c @@ -0,0 +1,333 @@ +#include +#include +#include +#include +#include + +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; +} diff --git a/deps/libShake/examples/listDevices.c b/deps/libShake/examples/listDevices.c new file mode 100644 index 0000000000..67d8215a4a --- /dev/null +++ b/deps/libShake/examples/listDevices.c @@ -0,0 +1,51 @@ +#include +#include +#include + +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; +} diff --git a/deps/libShake/examples/simple.c b/deps/libShake/examples/simple.c new file mode 100644 index 0000000000..29151eca70 --- /dev/null +++ b/deps/libShake/examples/simple.c @@ -0,0 +1,40 @@ +#include +#include +#include + +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; +} diff --git a/deps/libShake/extras/buildroot-package/libshake/Config.in b/deps/libShake/extras/buildroot-package/libshake/Config.in new file mode 100644 index 0000000000..ad1a25d8f2 --- /dev/null +++ b/deps/libShake/extras/buildroot-package/libshake/Config.in @@ -0,0 +1,6 @@ +config BR2_PACKAGE_LIBSHAKE + bool "libshake" + help + A simple, cross-platform haptic library. + + https://github.com/zear/libShake diff --git a/deps/libShake/extras/buildroot-package/libshake/libshake.mk b/deps/libShake/extras/buildroot-package/libshake/libshake.mk new file mode 100644 index 0000000000..ab70bd9f1e --- /dev/null +++ b/deps/libShake/extras/buildroot-package/libshake/libshake.mk @@ -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)) diff --git a/deps/libShake/include/shake.h b/deps/libShake/include/shake.h new file mode 100644 index 0000000000..ff095e4afa --- /dev/null +++ b/deps/libShake/include/shake.h @@ -0,0 +1,522 @@ +/** \file shake.h + \brief libShake public header. +*/ + +#ifndef _SHAKE_H_ +#define _SHAKE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** \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. NOTE: Currently not supported. */ + SHAKE_EFFECT_FRICTION, /**< Friction effect. NOTE: Currently not supported. */ + SHAKE_EFFECT_DAMPER, /**< Damper effect. NOTE: Currently not supported. */ + SHAKE_EFFECT_INERTIA, /**< Inertia effect. NOTE: 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. + + NOTE: 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_ */ diff --git a/deps/libShake/src/common/error.c b/deps/libShake/src/common/error.c new file mode 100644 index 0000000000..3b9d801b53 --- /dev/null +++ b/deps/libShake/src/common/error.c @@ -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; +} diff --git a/deps/libShake/src/common/error.h b/deps/libShake/src/common/error.h new file mode 100644 index 0000000000..6f2baf38ef --- /dev/null +++ b/deps/libShake/src/common/error.h @@ -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 diff --git a/deps/libShake/src/common/helpers.c b/deps/libShake/src/common/helpers.c new file mode 100644 index 0000000000..50a3f0738a --- /dev/null +++ b/deps/libShake/src/common/helpers.c @@ -0,0 +1,93 @@ +#include "helpers.h" +#include + +/* 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; +} diff --git a/deps/libShake/src/common/helpers.h b/deps/libShake/src/common/helpers.h new file mode 100644 index 0000000000..ed4d2557fe --- /dev/null +++ b/deps/libShake/src/common/helpers.h @@ -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_ */ diff --git a/deps/libShake/src/common/presets.c b/deps/libShake/src/common/presets.c new file mode 100644 index 0000000000..80370d998b --- /dev/null +++ b/deps/libShake/src/common/presets.c @@ -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; +} diff --git a/deps/libShake/src/linux/shake.c b/deps/libShake/src/linux/shake.c new file mode 100644 index 0000000000..e480287108 --- /dev/null +++ b/deps/libShake/src/linux/shake.c @@ -0,0 +1,422 @@ +/* libShake - a basic haptic library */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/deps/libShake/src/linux/shake_private.h b/deps/libShake/src/linux/shake_private.h new file mode 100644 index 0000000000..656fdc4647 --- /dev/null +++ b/deps/libShake/src/linux/shake_private.h @@ -0,0 +1,30 @@ +#ifndef _SHAKE_PRIVATE_H_ +#define _SHAKE_PRIVATE_H_ + +#include +#include +#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(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_ */ diff --git a/deps/libShake/src/osx/shake.c b/deps/libShake/src/osx/shake.c new file mode 100644 index 0000000000..175afd8b67 --- /dev/null +++ b/deps/libShake/src/osx/shake.c @@ -0,0 +1,719 @@ +/* libShake - a basic haptic library */ + +#include +#include +#include +#include +#include + +#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; +} diff --git a/deps/libShake/src/osx/shake_private.h b/deps/libShake/src/osx/shake_private.h new file mode 100644 index 0000000000..e96dec88ef --- /dev/null +++ b/deps/libShake/src/osx/shake_private.h @@ -0,0 +1,43 @@ +#ifndef _SHAKE_PRIVATE_H_ +#define _SHAKE_PRIVATE_H_ + +#include +#include +#include "../common/helpers.h" + +#define BITS_PER_LONG (sizeof(long) * 8) +#define OFF(x) ((x)%BITS_PER_LONG) +#define BIT(x) (1UL<> 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_ */ diff --git a/griffin/griffin.c b/griffin/griffin.c index c2369c79ac..2f3e2013f2 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -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) ============================================================ */ diff --git a/input/drivers_joypad/sdl_dingux_joypad.c b/input/drivers_joypad/sdl_dingux_joypad.c index 72e29c07f8..e268d77d43 100644 --- a/input/drivers_joypad/sdl_dingux_joypad.c +++ b/input/drivers_joypad/sdl_dingux_joypad.c @@ -26,15 +26,50 @@ #include "../../tasks/tasks_internal.h" #include "../../verbosity.h" +#if defined(HAVE_LIBSHAKE) +#include +#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", }; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index a4a975a302..3faa8004f2 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -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" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index eda846e4f2..f6c3304b43 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -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 */ diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 49f3dd97c2..14b3057a7d 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -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; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index f69004c1d7..9e42415025 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -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: { diff --git a/menu/menu_setting.c b/menu/menu_setting.c index abac18514a..5e7d04d6cf 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -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, diff --git a/msg_hash.h b/msg_hash.h index cc73bdc535..26f7a65e62 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -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), diff --git a/qb/config.params.sh b/qb/config.params.sh index 00cd97ef39..33a3e03244 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -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