diff --git a/.github/actions/cmake/build/action.yml b/.github/actions/cmake/build/action.yml new file mode 100644 index 00000000..6d3b4dac --- /dev/null +++ b/.github/actions/cmake/build/action.yml @@ -0,0 +1,27 @@ +--- +runs: + using: composite + steps: + - name: Create `cmake` symbolic link + run: | + if ! command -v cmake &> /dev/null && command -v cmake3 &> /dev/null; then + ln --symbolic cmake3 /usr/bin/cmake + fi + shell: bash --noprofile --norc -euxo pipefail {0} + + - name: Prepare `build` directory + run: | + cmake -S . -B build \ + -LA \ + -DCMAKE_BUILD_TYPE=${BUILD_TYPE:-Debug} \ + -DFORCE_INIT_SYSTEM=systemd \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX:-/usr} \ + -DSAFE_INSTALL=ON \ + -DSAFE_UNINSTALL=ON + shell: bash --noprofile --norc -euxo pipefail {0} + + - name: Build + run: | + export CMAKE_BUILD_PARALLEL_LEVEL=${BUILD_PARALLEL_LEVEL:-$(nproc)} + cmake --build build + shell: bash --noprofile --norc -euxo pipefail {0} diff --git a/.github/actions/cmake/install/action.yml b/.github/actions/cmake/install/action.yml new file mode 100644 index 00000000..284caf9a --- /dev/null +++ b/.github/actions/cmake/install/action.yml @@ -0,0 +1,7 @@ +--- +runs: + using: composite + steps: + - name: Install + run: cmake --build build --target install + shell: bash --noprofile --norc -euxo pipefail {0} diff --git a/.github/actions/cmake/package/action.yml b/.github/actions/cmake/package/action.yml new file mode 100644 index 00000000..b390a5ea --- /dev/null +++ b/.github/actions/cmake/package/action.yml @@ -0,0 +1,31 @@ +--- +runs: + using: composite + steps: + - name: Create `cpack` symbolic link + run: | + if ! command -v cpack &> /dev/null && command -v cpack3 &> /dev/null; then + ln --symbolic cpack3 /usr/bin/cpack + fi + shell: bash --noprofile --norc -euxo pipefail {0} + + - name: Use `TGZ` generator + run: | + echo "CPACK_OPTIONS=-G TGZ" >> ${GITHUB_ENV} + shell: bash --noprofile --norc -euxo pipefail {0} + if: startsWith(matrix.image, 'archlinux:') + + - name: Use `RPM` generator + run: | + echo "CPACK_OPTIONS=-G RPM" >> ${GITHUB_ENV} + shell: bash --noprofile --norc -euxo pipefail {0} + if: | + startsWith(matrix.image, 'almalinux:') || + startsWith(matrix.image, 'amazonlinux:') || + startsWith(matrix.image, 'fedora:') || + startsWith(matrix.image, 'rockylinux:') + + - name: Package + run: cpack --config CPackConfig.cmake ${CPACK_OPTIONS:-} + shell: bash --noprofile --norc -euxo pipefail {0} + working-directory: build diff --git a/.github/actions/dependencies/install/action.yml b/.github/actions/dependencies/install/action.yml new file mode 100644 index 00000000..36ab26cf --- /dev/null +++ b/.github/actions/dependencies/install/action.yml @@ -0,0 +1,155 @@ +--- +inputs: + archlinux-build-dependencies: + default: >- + cmake + git + libdbusmenu-qt5 + libpulse + libsystemd + libxcb + make + pkgconf + qt5-base + qt5-tools + qt5-x11extras + quazip + xcb-util-wm + zlib + + debian-build-dependencies: + default: >- + cmake + git + libdbusmenu-qt5-dev + libpulse-dev + libqt5x11extras5-dev + libquazip5-dev + libudev-dev + libxcb-ewmh-dev + libxcb-screensaver0-dev + libxcb1-dev + qtbase5-dev + qttools5-dev + zlib1g-dev + + debian-package-dependencies: + default: >- + dpkg-dev + file + + fedora-build-dependencies: + default: >- + cmake + dbusmenu-qt5-devel + git + glibc + libxcb-devel + make + pulseaudio-libs-devel + qt5-linguist + qt5-qtbase-devel + qt5-qtx11extras-devel + quazip-qt5-devel + systemd-devel + xcb-util-devel + xcb-util-wm-devel + zlib-devel + + fedora-package-dependencies: + default: >- + rpm-build + + rhel-build-dependencies: + default: >- + cmake3 + dbusmenu-qt5-devel + git + glibc + libxcb-devel + make + pulseaudio-libs-devel + qt5-linguist + qt5-qtbase-devel + qt5-qtx11extras-devel + quazip-qt5-devel + systemd-devel + xcb-util-devel + xcb-util-wm-devel + zlib-devel + + rhel-package-dependencies: + default: >- + rpm-build + + ubuntu-build-dependencies: + default: >- + cmake + git + libdbusmenu-qt5-dev + libpulse-dev + libqt5x11extras5-dev + libquazip5-dev + libudev-dev + libxcb-ewmh-dev + libxcb-screensaver0-dev + libxcb1-dev + qtbase5-dev + qttools5-dev + zlib1g-dev + + ubuntu-package-dependencies: + default: >- + dpkg-dev + file + +runs: + using: composite + steps: + - name: Install Dependencies (AlmaLinux/Amazon Linux/Rocky Linux) + uses: ./.github/actions/dependencies/install/yum + with: + dependencies: epel-release + packages: >- + ${{ inputs.rhel-build-dependencies }} + ${{ inputs.rhel-package-dependencies }} + ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc gcc-c++' }} + if: | + startsWith(matrix.image, 'almalinux:') || + startsWith(matrix.image, 'amazonlinux:') || + startsWith(matrix.image, 'rockylinux:') + + - name: Install Dependencies (Arch Linux) + uses: ./.github/actions/dependencies/install/pacman + with: + packages: >- + ${{ inputs.archlinux-build-dependencies }} + ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc' }} + if: startsWith(matrix.image, 'archlinux:') + + - name: Install Dependencies (Debian) + uses: ./.github/actions/dependencies/install/apt-get + with: + packages: >- + ${{ inputs.debian-build-dependencies }} + ${{ inputs.debian-package-dependencies }} + ${{ matrix.compiler == 'LLVM' && 'clang' || 'g++ gcc' }} + if: startsWith(matrix.image, 'debian:') + + - name: Install Dependencies (Fedora) + uses: ./.github/actions/dependencies/install/yum + with: + packages: >- + ${{ inputs.fedora-build-dependencies }} + ${{ inputs.fedora-package-dependencies }} + ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc gcc-c++' }} + if: startsWith(matrix.image, 'fedora:') + + - name: Install Dependencies (Ubuntu) + uses: ./.github/actions/dependencies/install/apt-get + with: + packages: >- + ${{ inputs.ubuntu-build-dependencies }} + ${{ inputs.ubuntu-package-dependencies }} + ${{ matrix.compiler == 'LLVM' && 'clang' || 'g++ gcc' }} + if: startsWith(matrix.image, 'ubuntu:') diff --git a/.github/actions/dependencies/install/apt-get/action.yml b/.github/actions/dependencies/install/apt-get/action.yml new file mode 100644 index 00000000..a0c8d223 --- /dev/null +++ b/.github/actions/dependencies/install/apt-get/action.yml @@ -0,0 +1,18 @@ +--- +inputs: + packages: + description: List of package(s) to install + required: true + +runs: + using: composite + steps: + - name: Update package information + run: apt-get --yes update + shell: bash --noprofile --norc -euxo pipefail {0} + + - name: Install package(s) + env: + DEBIAN_FRONTEND: noninteractive + run: apt-get --yes install ${{ inputs.packages }} + shell: bash --noprofile --norc -euxo pipefail {0} diff --git a/.github/actions/dependencies/install/pacman/action.yml b/.github/actions/dependencies/install/pacman/action.yml new file mode 100644 index 00000000..17a0cb68 --- /dev/null +++ b/.github/actions/dependencies/install/pacman/action.yml @@ -0,0 +1,12 @@ +--- +inputs: + packages: + description: List of package(s) to install + required: true + +runs: + using: composite + steps: + - name: Install package(s) + run: pacman --sync --refresh --sysupgrade --noconfirm ${{ inputs.packages }} + shell: bash --noprofile --norc -euxo pipefail {0} diff --git a/.github/actions/dependencies/install/yum/action.yml b/.github/actions/dependencies/install/yum/action.yml new file mode 100644 index 00000000..dcb4e83c --- /dev/null +++ b/.github/actions/dependencies/install/yum/action.yml @@ -0,0 +1,44 @@ +--- +inputs: + dependencies: + description: List of package(s) to pre-install + required: false + groups: + description: List of group(s) to install + required: false + packages: + description: List of package(s) to install + required: true + +runs: + using: composite + steps: + - name: Enable PowerTools repository (AlmaLinux 8/Rocky Linux 8) + run: | + dnf --assumeyes --skip-broken install "dnf-command(config-manager)" + dnf config-manager --set-enabled powertools + shell: bash --noprofile --norc -euxo pipefail {0} + if: matrix.image == 'almalinux:8' || matrix.image == 'rockylinux:8' + + - name: Enable EPEL repository (Amazon Linux 2) + run: amazon-linux-extras install epel -y + shell: bash --noprofile --norc -euxo pipefail {0} + if: matrix.image == 'amazonlinux:2' && contains(inputs.dependencies, 'epel-release') + + - name: Install dependency package(s) + run: | + if [ -n "${{ inputs.dependencies }}" ]; then + yum --assumeyes install ${{ inputs.dependencies }} + fi + shell: bash --noprofile --norc -euxo pipefail {0} + + - name: Install group(s) + run: | + if [ -n '${{ inputs.groups }}' ]; then + yum --assumeyes groups install ${{ inputs.groups }} + fi + shell: bash --noprofile --norc -euxo pipefail {0} + + - name: Install package(s) + run: yum --assumeyes --skip-broken install ${{ inputs.packages }} + shell: bash --noprofile --norc -euxo pipefail {0} diff --git a/.github/actions/upload-package/action.yml b/.github/actions/upload-package/action.yml new file mode 100644 index 00000000..c76a297f --- /dev/null +++ b/.github/actions/upload-package/action.yml @@ -0,0 +1,30 @@ +--- +inputs: + new_package_name: + default: ckb-next.${{ matrix.image }}.${{ matrix.compiler || 'GNU' }}.${{ matrix.build_type || 'Debug' }} + description: Name of package file (without suffix) + required: false + + old_package_name: + default: ckb-next + description: Name of package file (without suffix) + required: false + +runs: + using: composite + steps: + - name: Rename package + run: | + mkdir -p /tmp/package + for OLD_PACKAGE_FILE in ${{ inputs.old_package_name }}.*; do + NEW_PACKAGE_FILE=$(echo ${OLD_PACKAGE_FILE} | sed -e 's/${{ inputs.old_package_name }}/${{ inputs.new_package_name }}/' -e 's/:/-/') + mv ${OLD_PACKAGE_FILE} /tmp/package/${NEW_PACKAGE_FILE} + done + shell: bash --noprofile --norc -euxo pipefail {0} + working-directory: build + + - name: Upload package + uses: actions/upload-artifact@v3 + with: + name: Packages + path: /tmp/package/* diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..6f2560af --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,68 @@ +--- +name: Build + +on: + - push + +jobs: + Linux: + name: ${{ matrix.image }} (${{ matrix.compiler }}) + runs-on: ubuntu-latest + strategy: + matrix: + image: + - 'amazonlinux:2' + - 'archlinux:latest' + - 'almalinux:8' + - 'rockylinux:8' + - 'debian:10' + - 'debian:11' + - 'fedora:35' + - 'fedora:36' + - 'ubuntu:20.04' + - 'ubuntu:22.04' + build_type: + - Release + compiler: + - GNU + fail-fast: false + container: + env: + BUILD_TYPE: ${{ matrix.build_type || 'Debug' }} + CC: ${{ matrix.compiler == 'LLVM' && 'clang' || 'gcc' }} + CXX: ${{ matrix.compiler == 'LLVM' && 'clang++' || 'g++' }} + image: ${{ matrix.image }} + steps: + - name: Install `git` + run: | + if command -v apt-get &> /dev/null; then + apt-get --quiet --quiet update && apt-get --quiet --quiet install git + elif command -v pacman &> /dev/null; then + pacman --sync --refresh --sysupgrade --noconfirm git + elif command -v yum &> /dev/null; then + yum --assumeyes install git + fi + + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install dependencies + uses: ./.github/actions/dependencies/install + + - name: Build `ckb-next` + uses: ./.github/actions/cmake/build + + - name: Install `ckb-next` + uses: ./.github/actions/cmake/install + + - name: Run `ckb-next-daemon --help` + run: | + ckb-next-daemon --help + + - name: Package `ckb-next` + uses: ./.github/actions/cmake/package + + - name: Upload `ckb-next` package to workflow artifacts + uses: ./.github/actions/upload-package diff --git a/.gitignore b/.gitignore index f24e5625..0d3573ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ CMakeLists.txt.user bin/ build/ +!.github/actions/cmake/build *.app/ cmake-build*/ *.pkg diff --git a/cmake/modules/FindPulseAudio.cmake b/cmake/modules/FindPulseAudio.cmake index b31a2462..39062634 100644 --- a/cmake/modules/FindPulseAudio.cmake +++ b/cmake/modules/FindPulseAudio.cmake @@ -24,58 +24,44 @@ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. - -# - Try to find PulseAudioSimple # # Once done this will define: -# PULSEAUDIO_FOUND - system has pulseaudio simple -# PULSEAUDIO_INCLUDE_DIRS - the pulseaudio simple include directory -# PULSEAUDIO_LIBRARIES - Link these to use pulseaudio simple -# PULSEAUDIOSIMPLE_FOUND - system has pulseaudio simple -# PULSEAUDIOSIMPLE_INCLUDE_DIRS - the pulseaudio simple include directory -# PULSEAUDIOSIMPLE_LIBRARIES - Link these to use pulseaudio simple +# PULSEAUDIO_FOUND - system has pulseaudio +# PULSEAUDIO_INCLUDE_DIRS - the pulseaudio include directory +# PULSEAUDIO_LIBRARIES - Link these to use pulseaudio -foreach (pulsevariablename PULSEAUDIOSIMPLE PULSEAUDIO) - find_path(${pulsevariablename}_INCLUDE_DIR - NAMES - pulseaudio.h - PATH_SUFFIXES pulse - PATHS - /usr/include - /usr/local/include - /opt/local/include - /sw/include - ) +find_path(PULSEAUDIO_INCLUDE_DIR + NAMES + pulseaudio.h + PATH_SUFFIXES pulse + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) - set(pulselibname pulse) - set(pulsepackagename PulseAudio) - if (${pulsevariablename} STREQUAL "PULSEAUDIOSIMPLE") - set(pulselibname pulse-simple) - set(pulsepackagename PulseAudioSimple) - endif () +find_library(PULSEAUDIO_LIBRARY + NAMES + pulse + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) - find_library(${pulsevariablename}_LIBRARY - NAMES - ${pulselibname} - PATHS - /usr/lib - /usr/local/lib - /opt/local/lib - /sw/lib - ) +set(PULSEAUDIO_INCLUDE_DIRS + ${PULSEAUDIO_DIR} + ) +set(PULSEAUDIO_LIBRARIES + ${PULSEAUDIO_LIBRARY} + ) - set(${pulsevariablename}_INCLUDE_DIRS - ${${pulsevariablename}_DIR} - ) - set(${pulsevariablename}_LIBRARIES - ${${pulsevariablename}_LIBRARY} - ) +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set PULSEAUDIO_FOUND to TRUE if +# all listed variables are TRUE +find_package_handle_standard_args(PulseAudio DEFAULT_MSG PULSEAUDIO_LIBRARY PULSEAUDIO_INCLUDE_DIR) - include(FindPackageHandleStandardArgs) - # handle the QUIETLY and REQUIRED arguments and set ${pulsevariablename}_FOUND to TRUE if - # all listed variables are TRUE - find_package_handle_standard_args(${pulsepackagename} DEFAULT_MSG ${pulsevariablename}_LIBRARY ${pulsevariablename}_INCLUDE_DIR) - - # show the ${pulsevariablename}_INCLUDE_DIRS and ${pulsevariablename}_LIBRARIES variables only in the advanced view - mark_as_advanced(${pulsevariablename}_INCLUDE_DIRS ${pulsevariablename}_LIBRARIES) -endforeach (pulsevariablename) +# show the PULSEAUDIO_INCLUDE_DIRS and PULSEAUDIO_LIBRARIES variables only in the advanced view +mark_as_advanced(PULSEAUDIO_INCLUDE_DIRS PULSEAUDIO_LIBRARIES) diff --git a/src/animations/mviz/CMakeLists.txt b/src/animations/mviz/CMakeLists.txt index 628d0e59..5f2f06ca 100644 --- a/src/animations/mviz/CMakeLists.txt +++ b/src/animations/mviz/CMakeLists.txt @@ -27,8 +27,8 @@ if (NOT USE_PORTAUDIO) find_package(PulseAudio) - if (NOT ${PULSEAUDIOSIMPLE_FOUND}) - message(WARNING "Missing PulseAudio Simple API. Not building mviz.") + if (NOT ${PULSEAUDIO_FOUND}) + message(WARNING "Missing PulseAudio API. Not building mviz.") return() endif () endif () @@ -61,9 +61,9 @@ else () target_include_directories( mviz PRIVATE - "${PULSEAUDIOSIMPLE_INCLUDE_DIRS}") + "${PULSEAUDIO_INCLUDE_DIRS}") - set(AUDIO_LIBRARIES "${PULSEAUDIOSIMPLE_LIBRARIES}") + set(AUDIO_LIBRARIES "${PULSEAUDIO_LIBRARIES}") endif () if (MACOS) diff --git a/src/animations/mviz/main.c b/src/animations/mviz/main.c index 1daf2bd3..7a6507bb 100644 --- a/src/animations/mviz/main.c +++ b/src/animations/mviz/main.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2017 Devon Richards - * Copyright (C) 2019 Tasos Sahanidis + * Copyright (C) 2019 - 2022 Tasos Sahanidis * mviz is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or @@ -17,36 +17,62 @@ #include #include #include +#include +#include +#include -#ifdef USE_PORTAUDIO -#include -PaStream *stream = NULL; -long int devid = 0; -#else -#include -pa_simple *stream = NULL; -#endif +#include +static pa_mainloop* pa_ml; +static pa_context* pa_ctx; +static pa_stream* pa_str; +static pa_context_state_t pa_state = PA_CONTEXT_UNCONNECTED; +typedef struct { + char* dev; + bool ok; +} server_info_cb_data; #include #include -#define MAX_RANGE 1024 -int translated_frame[MAX_RANGE] = { 0 }; -//#define range 768 // ~15KHz -long int range = 512; -double div_fact = 2000000.0; +#define APPEND(a, b) a ## b +#define F_CONST(num) APPEND(num, f) +#define M_PI_F F_CONST(M_PI) + +#define FFT_SIZE 1024 +static float translated_frame[FFT_SIZE/4] = { 0.f }; + +#define MAX_LATENCY 16666 +static float powers[FFT_SIZE/2] = { 0.f }; +static float hann_res[FFT_SIZE] = { 0.f }; +static kiss_fft_cfg config; +static ckb_gradient color = { 0 }; +static unsigned char bg_a, bg_r, bg_g, bg_b; + +static double div_fact = 100.0; // Normalisation factor * 100 -double norm_f = 0.06; -long int v_offset = 0; +static double norm_f = 0.06; +static long int v_offset = 0; +static size_t actual_fft_size = 0; +static unsigned int* key_lit = NULL; -// int linear = 0; +static int single_colour = 0; -int single_colour = 0; +#define ENUMVALSTR_(x) #x +#define ENUMVALSTR(x) ENUMVALSTR_(x) +typedef enum { + CAPTURE_DEV_SERVER, + CAPTURE_DEV_MONITOR, + CAPTURE_DEV_SOURCE, + CAPTURE_DEV_MAX, +} default_device_t; +// We have to use long int here as this is the only int the functions can understand +// Default value is CAPTURE_DEV_SERVER to not break older profiles +static long int default_device = CAPTURE_DEV_SERVER, old_default_device = CAPTURE_DEV_SERVER; void ckb_info(){ CKB_NAME("Music Visualization"); CKB_VERSION("1.0"); - CKB_COPYRIGHT("2017-2019", "RULER501 & TasosSah"); + CKB_COPYRIGHT("2017-2022", "RULER501 & TasosSah"); CKB_LICENSE("GPLv2+"); CKB_GUID("{097D69F0-70B2-48B8-AFE2-25CA1DB0D92C}"); CKB_DESCRIPTION("A collection of music visualization effects.
Set-up instructions"); @@ -56,149 +82,297 @@ void ckb_info(){ CKB_PARAM_ARGB("bgcolour", "Background:", "", 0xFF, 0x00, 0x00, 0x00); CKB_PARAM_DOUBLE("norm_f", "Normalisation:", "", 7.5, 0.1, 20.0); CKB_PARAM_DOUBLE("div_fact", "Sensitivity:", "", 70.0, 1.0, 100.0); -#ifdef USE_PORTAUDIO - CKB_PARAM_LONG("devid", "Device:\n(See About)", "", 0, 0, 100); -#endif CKB_PARAM_LONG("v_offset", "Vertical Offset:", "", 0, 0, 100); - CKB_PARAM_LONG("range", "Range:", "", 512, 1, MAX_RANGE); - -// CKB_PARAM_BOOL("linear", "Use linear scale", "0"); CKB_PARAM_BOOL("singlecol", "Single Colour", 0); + CKB_PARAM_LONG("default_device", "Audio device type:", "", CAPTURE_DEV_MONITOR, CAPTURE_DEV_SERVER, CAPTURE_DEV_MAX-1); // Timing/input parameters CKB_KPMODE(CKB_KP_NONE); - CKB_TIMEMODE(CKB_TIME_ABSOLUTE); + CKB_TIMEMODE(CKB_TIME_DURATION); CKB_REPEAT(FALSE); CKB_LIVEPARAMS(TRUE); // Presets - CKB_PRESET_START("Default"); - CKB_PRESET_PARAM("trigger", "0"); - CKB_PRESET_PARAM("kptrigger", "1"); +#define MON_STR " (Monitor)" +#define SRC_STR " (Source)" +#define CST_SRC " (Custom)" + char enumstr[2]; + // Spectrum + CKB_PRESET_START("Spectrum" MON_STR); + CKB_PRESET_PARAM("trigger", "1"); + CKB_PRESET_PARAM("kptrigger", "0"); CKB_PRESET_PARAM("norm_f", "7.5"); CKB_PRESET_PARAM("v_offset", "0"); CKB_PRESET_PARAM("singlecol", "0"); - -// CKB_PRESET_PARAM("linear", "0"); - + snprintf(enumstr, sizeof(enumstr), "%1d", (int)CAPTURE_DEV_MONITOR); + CKB_PRESET_PARAM("default_device", enumstr); CKB_PRESET_END; + CKB_PRESET_START("Spectrum" SRC_STR); + CKB_PRESET_PARAM("trigger", "1"); + CKB_PRESET_PARAM("kptrigger", "0"); + CKB_PRESET_PARAM("norm_f", "7.5"); + CKB_PRESET_PARAM("v_offset", "0"); + CKB_PRESET_PARAM("singlecol", "0"); + snprintf(enumstr, sizeof(enumstr), "%1d", (int)CAPTURE_DEV_SOURCE); + CKB_PRESET_PARAM("default_device", enumstr); + CKB_PRESET_END; - CKB_PRESET_START("Single Colour"); - CKB_PRESET_PARAM("trigger", "0"); - CKB_PRESET_PARAM("kptrigger", "1"); + CKB_PRESET_START("Spectrum" CST_SRC); + CKB_PRESET_PARAM("trigger", "1"); + CKB_PRESET_PARAM("kptrigger", "0"); + CKB_PRESET_PARAM("norm_f", "7.5"); + CKB_PRESET_PARAM("v_offset", "0"); + CKB_PRESET_PARAM("singlecol", "0"); + snprintf(enumstr, sizeof(enumstr), "%1d", (int)CAPTURE_DEV_SERVER); + CKB_PRESET_PARAM("default_device", enumstr); + CKB_PRESET_END; + + // Single Colour + CKB_PRESET_START("Single Colour" MON_STR); + CKB_PRESET_PARAM("trigger", "1"); + CKB_PRESET_PARAM("kptrigger", "0"); CKB_PRESET_PARAM("norm_f", "7.5"); CKB_PRESET_PARAM("v_offset", "0"); CKB_PRESET_PARAM("singlecol", "1"); + snprintf(enumstr, sizeof(enumstr), "%1d", (int)CAPTURE_DEV_MONITOR); + CKB_PRESET_PARAM("default_device", enumstr); CKB_PRESET_END; + CKB_PRESET_START("Single Colour" SRC_STR); + CKB_PRESET_PARAM("trigger", "1"); + CKB_PRESET_PARAM("kptrigger", "0"); + CKB_PRESET_PARAM("norm_f", "7.5"); + CKB_PRESET_PARAM("v_offset", "0"); + CKB_PRESET_PARAM("singlecol", "1"); + snprintf(enumstr, sizeof(enumstr), "%1d", (int)CAPTURE_DEV_SOURCE); + CKB_PRESET_PARAM("default_device", enumstr); + CKB_PRESET_END; + + CKB_PRESET_START("Single Colour" CST_SRC); + CKB_PRESET_PARAM("trigger", "1"); + CKB_PRESET_PARAM("kptrigger", "0"); + CKB_PRESET_PARAM("norm_f", "7.5"); + CKB_PRESET_PARAM("v_offset", "0"); + CKB_PRESET_PARAM("singlecol", "1"); + snprintf(enumstr, sizeof(enumstr), "%1d", (int)CAPTURE_DEV_SERVER); + CKB_PRESET_PARAM("default_device", enumstr); + CKB_PRESET_END; } -double powers[1024] = { 0.0 }; -double hann_res[2048] = { 0.0 }; -kiss_fft_cfg config; -kiss_fft_cpx inbuf[2048] = { 0 }; -kiss_fft_cpx outbuf[2048] = { 0 }; -ckb_gradient color = { 0 }; -unsigned char bg_a, bg_r, bg_g, bg_b; - -void destroyCapture(){ - if(stream == NULL) - return; -#ifdef USE_PORTAUDIO - Pa_CloseStream(stream); -#else - pa_simple_free(stream); +#if 0 +static void mviz_destroy_capture(){ + if(pa_str){ + pa_stream_disconnect(pa_str); + pa_stream_unref(pa_str); + } + if(pa_ctx){ + pa_context_disconnect(pa_ctx); + pa_context_unref(pa_ctx); + } + if(pa_ml) + pa_mainloop_free(pa_ml); + pa_str = pa_ctx = pa_ml = NULL; +} #endif - stream = NULL; + +static void pa_state_cb(pa_context* ctx, void* userdata) { + pa_context_state_t* pa_state_p = userdata; + *pa_state_p = pa_context_get_state(ctx); } -void initCapture(){ - if(stream != NULL) - return; -#ifdef USE_PORTAUDIO - PaStreamParameters inputParams; - PaError err; +static void pa_read_cb(pa_stream* p, size_t nbytes, void* userdata){ + size_t datasz = 0; + size_t* effective_data = userdata; + const float* dataptr; - if(devid > -1 && devid < Pa_GetDeviceCount()) - inputParams.device = devid; - else - inputParams.device = Pa_GetDefaultInputDevice(); + pa_stream_peek(p, (const void**)&dataptr, &datasz); + if(dataptr && datasz){ + kiss_fft_cpx inbuf[FFT_SIZE] = { 0 }; + kiss_fft_cpx outbuf[FFT_SIZE]; - const PaDeviceInfo* inputInfo = Pa_GetDeviceInfo(inputParams.device); - fprintf(stderr, "Recording from %s", inputInfo->name); + // If we get too many samples, ignore the extra ones + const size_t samples = datasz / sizeof(*dataptr) > FFT_SIZE ? FFT_SIZE : datasz / sizeof(*dataptr); - inputParams.channelCount = 1; - inputParams.sampleFormat = paInt16; - inputParams.suggestedLatency = inputInfo->defaultLowInputLatency; - inputParams.hostApiSpecificStreamInfo = NULL; - - err = Pa_OpenStream(&stream, &inputParams, NULL, 44100, 2048, paNoFlag, NULL, NULL); - if(err != paNoError) - fprintf(stderr, "Error opening PortAudio stream %d\n", err); - - err = Pa_StartStream(stream); - if(err != paNoError) - fprintf(stderr, "Error starting PortAudio stream %d\n", err); + for(size_t i = 0; i < samples; i++){ + inbuf[i].r = dataptr[i] * hann_res[i]; + //inbuf[i].i = 0; + } + + kiss_fft(config, inbuf, outbuf); + + // FFT is symmetric because there are no imaginary elements + *effective_data = samples / 2; + for(size_t i = 0; i < *effective_data; i++) + powers[i] = sqrtf(outbuf[i].r * outbuf[i].r + outbuf[i].i * outbuf[i].i); + + } else { + *effective_data = 0; + } + pa_stream_drop(p); +} + +static void server_info_cb(pa_context* c, const pa_server_info* i, void* userdata){ + server_info_cb_data* cb_data = userdata; + int major, minor, patch; + // We have to check for PipeWire since it currently doesn't know what to do with "@DEFAULT_MONITOR@" + // When these old PW versions become irrelevant, this entire callback can be removed, and the switch in the else block + // can be moved to the init capture function. + // FIXME: Add the version of PW the bug was fixed in +#if 0 +#define PIPEWIRE_BUG_FIX_VER (major == 0 && minor <= 3 && patch < 57) #else - static const pa_sample_spec ss ={ - .format = PA_SAMPLE_S16LE, - .rate = 44100, - .channels = 1 - }; - stream = pa_simple_new(NULL, "CKB Music Viz", PA_STREAM_RECORD, NULL, "CKB Music Viz", &ss, NULL, NULL, NULL); +#define PIPEWIRE_BUG_FIX_VER 1 #endif + if(sscanf(i->server_name, "PulseAudio (on PipeWire %d.%d.%d)", &major, &minor, &patch) == 3 && PIPEWIRE_BUG_FIX_VER){ + DBG("Applying workaround for PipeWire %d.%d.%d", major, minor, patch); + switch(default_device){ + case CAPTURE_DEV_MONITOR: + cb_data->dev = malloc(strlen(i->default_sink_name) + strlen(".monitor") + 1); + sprintf(cb_data->dev, "%s.monitor", i->default_sink_name); + break; + case CAPTURE_DEV_SOURCE: + cb_data->dev = strdup(i->default_source_name); + break; + case CAPTURE_DEV_SERVER: + break; + default: + abort(); + } + } else { + const char* dev; + switch(default_device){ + case CAPTURE_DEV_MONITOR: + dev = "@DEFAULT_MONITOR@"; + break; + case CAPTURE_DEV_SOURCE: + dev = "@DEFAULT_SOURCE@"; + break; + case CAPTURE_DEV_SERVER: + dev = NULL; + break; + default: + abort(); + } + if(dev) + cb_data->dev = strdup(dev); + } + cb_data->ok = true; +} + +static bool mviz_init_capture(){ + if(!pa_ml) + pa_ml = pa_mainloop_new(); + + if(!pa_ctx){ + pa_ctx = pa_context_new(pa_mainloop_get_api(pa_ml), "ckb-next music visualizer"); + pa_context_connect(pa_ctx, NULL, 0, NULL); + + pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_state); + while(pa_state != PA_CONTEXT_FAILED && pa_state != PA_CONTEXT_TERMINATED && pa_state != PA_CONTEXT_READY) + pa_mainloop_iterate(pa_ml, 1, NULL); + + if (pa_state != PA_CONTEXT_READY){ + pa_context_disconnect(pa_ctx); + pa_context_unref(pa_ctx); + pa_ctx = NULL; + pa_state = PA_CONTEXT_UNCONNECTED; + return true; + } + } + + if(!pa_str){ + static_assert(sizeof(float) == 4, "Expected 32 bit floats"); + static const pa_sample_spec ss = { + .format = PA_SAMPLE_FLOAT32LE, + .rate = 48000, + .channels = 1 + }; + + pa_proplist* pl = pa_proplist_new(); + + pa_proplist_sets(pl, PA_PROP_APPLICATION_ICON_NAME, "ckb-next"); + pa_proplist_sets(pl, PA_PROP_APPLICATION_NAME, "ckb-next"); + + DBG("New PA capture stream"); + pa_str = pa_stream_new_with_proplist(pa_ctx, "capture", &ss, NULL, pl); + + pa_proplist_free(pl); + + pa_buffer_attr bufattr = { + .maxlength = (uint32_t)-1, + }; + bufattr.fragsize = pa_usec_to_bytes(MAX_LATENCY, &ss); + + server_info_cb_data cb_data = { + .dev = NULL, + .ok = false + }; + pa_context_get_server_info(pa_ctx, server_info_cb, &cb_data); + while(!cb_data.ok) + pa_mainloop_iterate(pa_ml, 1, NULL); + + DBG("Capture dev: %s", cb_data.dev ? cb_data.dev : "null"); + pa_stream_connect_record(pa_str, cb_data.dev, &bufattr, PA_STREAM_ADJUST_LATENCY); + free(cb_data.dev); + + pa_stream_set_read_callback(pa_str, pa_read_cb, &actual_fft_size); + } + return false; } void ckb_init(ckb_runctx* context){ - config = kiss_fft_alloc(2048, 0, NULL, NULL); + key_lit = calloc(context->keycount, sizeof(*key_lit)); + config = kiss_fft_alloc(FFT_SIZE, 0, NULL, NULL); -#ifdef USE_PORTAUDIO -#ifdef __linux__ - putenv("PULSE_LATENCY_MSEC=90"); -#endif - PaError err = Pa_Initialize(); - if(err != paNoError){ - fprintf(stderr, "Error initialising PortAudio %d\n", err); - } -#else - // We do not need to wait for the device id on pulseaudio - initCapture(); -#endif + for (int i = 0; i < FFT_SIZE; i++) + hann_res[i] = (1.f - cosf(2.f * M_PI_F * i / (float)(FFT_SIZE-1))) * 0.5f; +} - for (int i = 0; i < 2048; i++) - hann_res[i] = (1 - cos(2 * M_PI * i / 2048)) * 0.5; +static void stream_state_cb(pa_stream* s, void* userdata) { + (*(pa_stream_state_t*)userdata) = pa_stream_get_state(s); } void ckb_parameter(ckb_runctx* context, const char* name, const char* value){ CKB_PARSE_AGRADIENT("color", &color){}; CKB_PARSE_LONG("v_offset", &v_offset){}; CKB_PARSE_DOUBLE("norm_f", &norm_f){ - norm_f /= 100; + norm_f /= 120.0; }; CKB_PARSE_DOUBLE("div_fact", &div_fact){ - div_fact = 101 - div_fact; - div_fact *= 100000.0; + div_fact = (101.0 - div_fact) * 12.0; }; CKB_PARSE_AGRADIENT("color", &color){}; CKB_PARSE_ARGB("bgcolour", &bg_a, &bg_r, &bg_g, &bg_b){}; - CKB_PARSE_LONG("range", &range){}; CKB_PARSE_BOOL("singlecol", &single_colour){}; + CKB_PARSE_LONG("default_device", &default_device){ + // If we have an existing stream, destroy it and recreate it + if(pa_str && old_default_device != default_device){ + DBG("Disconnecting PA stream"); + // pa_stream_disconnect sometimes randomly fails with -15, so if that happens, keep retrying... + // If we don't do this, we'll end up with multiple streams that have not been cleaned up + pa_stream_state_t state = PA_STREAM_READY; + pa_stream_set_state_callback(pa_str, stream_state_cb, &state); + int disconnect = pa_stream_disconnect(pa_str); -// CKB_PARSE_BOOL("linear", &linear){}; + while(state == PA_STREAM_READY){ + DBG("Stream state: %d", state); + pa_mainloop_iterate(pa_ml, 1, NULL); + if(disconnect == -PA_ERR_BADSTATE){ + DBG("pa_stream_disconnect() returned Bad State. Retrying after loop iteration..."); + disconnect = pa_stream_disconnect(pa_str); + } + } -#ifdef USE_PORTAUDIO - CKB_PARSE_LONG("devid", &devid){ - // 0 is considered a valid value for portaudio, we use 0 for default - devid -= 1; - // If we were already running, close the previous stream - destroyCapture(); - // Create a new stream - initCapture(); + pa_stream_unref(pa_str); + pa_str = NULL; + + mviz_init_capture(); + old_default_device = default_device; + } }; -#endif } void ckb_keypress(ckb_runctx* context, ckb_key* key, int x, int y, int state){ @@ -206,85 +380,109 @@ void ckb_keypress(ckb_runctx* context, ckb_key* key, int x, int y, int state){ } void ckb_start(ckb_runctx* context, int state){ - return; + mviz_init_capture(); } void ckb_time(ckb_runctx* context, double delta){ - return; + ckb_key* keys = context->keys; + + for(unsigned int i = 0; i < context->keycount; i++){ + unsigned int sub = round(delta*50.0*(context->height-keys[i].y+10)); + if(key_lit[i] >= sub) + key_lit[i] -= sub; + else + key_lit[i] = 0; + } } -void getFreqDec(){ - int16_t data[2048] = { 0 }; +#define BIN_FN (height * ((translated_frame[bin]) / (float)div_fact) * ((float)norm_f * (i + 1) + 2)) +// If we want to average +// (height * ((translated_frame[bin] / step) / (float)div_fact) * ((float)norm_f * (i + 1) + 2)) +static size_t lin_to_log(unsigned int height) +{ + size_t bin = 0; + size_t step = 1; + size_t i; + for(i = 1; i <= actual_fft_size; i++) { - if(stream == NULL) - return; + if(powers[i - 1] > translated_frame[bin]) + translated_frame[bin] = powers[i - 1]; + // If we want to average + //translated_frame[bin] += powers[i - 1]; -#ifdef USE_PORTAUDIO - Pa_ReadStream(stream, data, 2048); -#else - pa_simple_read(stream, data, sizeof(data), NULL); -#endif + // Runs every time a multiple of step is hit + if(!(i % step)) + { + // Scale it to the height of the device + translated_frame[bin] = BIN_FN; + bin++; + } - for(int i = 0; i < 2048; i++){ - inbuf[i].r = data[i] * hann_res[i]; - inbuf[i].i = 0; + // Check if we have to change the step + // The loop starts from 1 so that this doesn't trigger on 0 + if (!(i % (step * 10))) + step *= 10; } - kiss_fft(config, inbuf, outbuf); + translated_frame[bin] = BIN_FN; - for(int j = 0; j < 1024; j++) - powers[j] = sqrt(outbuf[j].r*outbuf[j].r + outbuf[j].i*outbuf[j].i); + //DBG("Total number of bins %zu\n", bin); + return bin + 1; } -void translate_frame_vertically(int height){ - for(int i = 0; i < range; i++) - translated_frame[i] = round(height * (powers[i]/(div_fact * 3)) * (norm_f * (i + 1) + 2)); -} - -int get_key_value(int width, int x){ - int cur_pos = range * x / width; - cur_pos = (cur_pos > range - 1 ? range - 1 : cur_pos); - int newval = translated_frame[cur_pos]; - // Each key takes 18 nearby values from the array - // Pick the largest one for this key. - if(cur_pos > 17){ - for(int i = 0; i < 18; i++) - if(translated_frame[cur_pos - i] > newval) - newval = translated_frame[cur_pos - i]; - } +static inline float get_key_value(unsigned int width, int x, size_t bins){ + size_t cur_pos = bins * x / width; + cur_pos = (cur_pos > bins - 1 ? 1 - 1 : cur_pos); + float newval = translated_frame[cur_pos]; + // Check this position and one nearby one, and pick the max + if(cur_pos > 0 && translated_frame[cur_pos - 1] > newval) + newval = translated_frame[cur_pos - 1]; return newval; } -void grad_to_key(ckb_runctx* context, ckb_key* key, int key_y){ - // Get colour for this key - // Map the LEDs to the gradient, bottom (0) is at 0%, top (height) is at 100% - float key_colour = (100.f / context->height) * key_y; - float a, r, g, b; - ckb_grad_color(&a, &r, &g, &b, &color, key_colour); - key->a = roundf(a); - key->r = roundf(r); - key->g = roundf(g); - key->b = roundf(b); -} - int ckb_frame(ckb_runctx* context){ - CKB_KEYCLEAR(context); + // Don't process anything if the main loop hasn't been initialised yet + if(!pa_ml) + return 0; + + if(pa_state == PA_CONTEXT_FAILED){ + if(pa_str){ + pa_stream_disconnect(pa_str); + pa_stream_unref(pa_str); + } + if(pa_ctx){ + pa_context_disconnect(pa_ctx); + pa_context_unref(pa_ctx); + } + pa_str = NULL; + pa_ctx = NULL; + pa_state = PA_CONTEXT_UNCONNECTED; + clock_nanosleep(CLOCK_MONOTONIC, 0, &(struct timespec) {.tv_sec = 1}, NULL); + mviz_init_capture(); + } + + if(pa_state != PA_CONTEXT_READY) + return 0; + + for(int i = 0; i < 5 && pa_mainloop_iterate(pa_ml, 0, NULL) > 0; i++) + clock_nanosleep(CLOCK_MONOTONIC, 0, &(struct timespec) {.tv_nsec = 500000}, NULL); + ckb_key* keys = context->keys; ckb_key* maxkey = keys + context->keycount; - getFreqDec(); + memset(translated_frame, 0, sizeof(translated_frame)); if(single_colour){ - translate_frame_vertically(100); + CKB_KEYCLEAR(context); + const size_t bins = lin_to_log(100); // Find the max value - int max_light = translated_frame[0]; - for(int i = 0; i < range; i++) + float max_light = translated_frame[0]; + for(size_t i = 0; i < bins; i++) if(translated_frame[i] > max_light) max_light = translated_frame[i]; float a, r, g, b; ckb_grad_color(&a, &r, &g, &b, &color, max_light); for(ckb_key* key = keys; key < maxkey; key++){ - // TODO DEDUP key->a = roundf(a); key->r = roundf(r); key->g = roundf(g); @@ -294,13 +492,24 @@ int ckb_frame(ckb_runctx* context){ return 0; } + const size_t bins = lin_to_log(context->height); for(ckb_key* key = keys; key < maxkey; key++){ - translate_frame_vertically(context->height); int key_y = context->height - key->y; - // Blank out the key if it is not supposed to be on - if(key_y + v_offset <= get_key_value(context->width, key->x)){ - grad_to_key(context, key, key_y); - } else { + // If the key is supposed to be on, light it up and reset its counter + // If the key's counter is 0, then turn it off + if(key_y + v_offset <= get_key_value(context->width, key->x, bins)){ + // Get colour for this key + // Map the LEDs to the gradient, bottom (0) is at 0%, top (height) is at 100% + float key_colour = (100.f / context->height) * key_y; + float a, r, g, b; + ckb_grad_color(&a, &r, &g, &b, &color, key_colour); + key->a = roundf(a); + key->r = roundf(r); + key->g = roundf(g); + key->b = roundf(b); + + key_lit[key - keys] = 200; + } else if(key_lit[key - keys] == 0){ key->a = bg_a; key->r = bg_r; key->g = bg_g; diff --git a/src/daemon/bragi_proto.h b/src/daemon/bragi_proto.h index 3364a54a..890304ea 100644 --- a/src/daemon/bragi_proto.h +++ b/src/daemon/bragi_proto.h @@ -18,10 +18,10 @@ #define BRAGI_BRIGHTNESS 0x02 #define BRAGI_MODE 0x03 #define BRAGI_ANGLE_SNAP 0x07 -#define BRAG_APP_VER 0x13 -#define BRAG_BLD_VER 0x14 -#define BRAG_RADIO_APP_VER 0x15 -#define BRAG_RADIO_BLD_VER 0x16 +#define BRAGI_APP_VER 0x13 +#define BRAGI_BLD_VER 0x14 +#define BRAGI_RADIO_APP_VER 0x15 +#define BRAGI_RADIO_BLD_VER 0x16 #define BRAGI_DPI_INDEX 0x1E #define BRAGI_DPI_MASK 0x1F #define BRAGI_DPI_X 0x21 @@ -30,6 +30,8 @@ #define BRAGI_DPI1_COLOR 0x30 #define BRAGI_DPI2_COLOR 0x31 #define BRAGI_HWLAYOUT 0x41 +#define BRAGI_BRIGHTNESS_COARSE 0x44 + // Untested. Only known because CUE complained about it in its logs #define BRAGI_MAX_POLLRATE 0x96 @@ -56,6 +58,7 @@ // Resources (used to get handles) #define BRAGI_RES_LIGHTING 0x01 +#define BRAGI_RES_LIGHTING_MONOCHROME 0x10 #define BRAGI_RES_PAIRINGID 0x05 // ckb-specific macros diff --git a/src/daemon/device_bragi.c b/src/daemon/device_bragi.c index 313e669e..39784a84 100644 --- a/src/daemon/device_bragi.c +++ b/src/daemon/device_bragi.c @@ -75,7 +75,10 @@ static int setactive_bragi(usbdevice* kb, int active){ } // The daemon will always send RGB data through handle 0 (), so go ahead and open it - int light = bragi_open_handle(kb, BRAGI_LIGHTING_HANDLE, BRAGI_RES_LIGHTING); + uchar res = BRAGI_RES_LIGHTING; + if(IS_MONOCHROME_DEV(kb)) + res = BRAGI_RES_LIGHTING_MONOCHROME; + int light = bragi_open_handle(kb, BRAGI_LIGHTING_HANDLE, res); if(light < 0) return light; @@ -115,19 +118,19 @@ static int start_bragi_common(usbdevice* kb){ // Read FW versions kb->fwversion = kb->bldversion = kb->radioappversion = kb->radiobldversion = UINT32_MAX; - prop = bragi_get_property(kb, BRAG_APP_VER); + prop = bragi_get_property(kb, BRAGI_APP_VER); if(prop >= 0) kb->fwversion = bragi_fwver_bswap(prop); - prop = bragi_get_property(kb, BRAG_BLD_VER); + prop = bragi_get_property(kb, BRAGI_BLD_VER); if(prop >= 0) kb->bldversion = bragi_fwver_bswap(prop); - prop = bragi_get_property(kb, BRAG_RADIO_APP_VER); + prop = bragi_get_property(kb, BRAGI_RADIO_APP_VER); if(prop >= 0) kb->radioappversion = bragi_fwver_bswap(prop); - prop = bragi_get_property(kb, BRAG_RADIO_BLD_VER); + prop = bragi_get_property(kb, BRAGI_RADIO_BLD_VER); if(prop >= 0) kb->radiobldversion = bragi_fwver_bswap(prop); @@ -146,6 +149,12 @@ static int start_bragi_common(usbdevice* kb){ kb->usbdelay = USB_DELAY_DEFAULT; + // Check if the device supports fine or coarse brightness + if(bragi_get_property(kb, BRAGI_BRIGHTNESS) >= 0) + kb->brightness_mode = BRIGHTNESS_HARDWARE_FINE; + else if(bragi_get_property(kb, BRAGI_BRIGHTNESS_COARSE) >= 0) + kb->brightness_mode = BRIGHTNESS_HARDWARE_COARSE; + // Read pairing ID if(IS_WIRELESS_DEV(kb)){ int pair = bragi_open_handle(kb, BRAGI_GENERIC_HANDLE, BRAGI_RES_PAIRINGID); diff --git a/src/daemon/devnode.c b/src/daemon/devnode.c index d92892f7..78a8ecb7 100644 --- a/src/daemon/devnode.c +++ b/src/daemon/devnode.c @@ -339,6 +339,10 @@ static int _mkdevpath(usbdevice* kb){ if(HAS_FEATURES(kb, FEAT_BATTERY)) fputs(" battery", ffile); + if(kb->brightness_mode == BRIGHTNESS_HARDWARE_COARSE) + fputs(" hwbright_coarse", ffile); + else if(kb->brightness_mode == BRIGHTNESS_HARDWARE_FINE) + fputs(" hwbright_fine", ffile); fputc('\n', ffile); fclose(ffile); check_chmod(fpath, S_GID_READ); diff --git a/src/daemon/led_bragi.c b/src/daemon/led_bragi.c index ffc6ce3e..b49f5060 100644 --- a/src/daemon/led_bragi.c +++ b/src/daemon/led_bragi.c @@ -71,16 +71,25 @@ static int updatergb_bragi(usbdevice* kb, int force, const size_t led_offset){ int laston = memcmp(lastlight->r + led_offset, pkt, CPY_SZ(r)) || memcmp(lastlight->g + led_offset, pkt, CPY_SZ(g)) || memcmp(lastlight->b + led_offset, pkt, CPY_SZ(b)); - if (newon != laston || force) - bragi_set_property(kb, BRAGI_BRIGHTNESS, newon ? 1000 : 0); + if (newon != laston || force){ + if(kb->brightness_mode == BRIGHTNESS_HARDWARE_COARSE) + bragi_set_property(kb, BRAGI_BRIGHTNESS_COARSE, newon ? 3 : 0); + else if(kb->brightness_mode == BRIGHTNESS_HARDWARE_FINE) + bragi_set_property(kb, BRAGI_BRIGHTNESS, newon ? 1000 : 0); + } static_assert(sizeof(pkt) >= 7 + N_KEYS_EXTENDED * 3, "Bragi RGB packet must be large enough to fit all possible zones in the keymap"); - memcpy(pkt + 7, newlight->r + led_offset, CPY_SZ(r)); - memcpy(pkt + 7 + CPY_SZ(r), newlight->g + led_offset, CPY_SZ(g)); - memcpy(pkt + 7 + CPY_SZ(r) + CPY_SZ(g), newlight->b + led_offset, CPY_SZ(b)); + size_t bytes = zones; - if(bragi_write_to_handle(kb, pkt, BRAGI_LIGHTING_HANDLE, sizeof(pkt), 3 * zones)) + memcpy(pkt + 7, newlight->r + led_offset, CPY_SZ(r)); + if(!IS_MONOCHROME_DEV(kb)) { + bytes *= 3; // 3 channels + memcpy(pkt + 7 + CPY_SZ(r), newlight->g + led_offset, CPY_SZ(g)); + memcpy(pkt + 7 + CPY_SZ(r) + CPY_SZ(g), newlight->b + led_offset, CPY_SZ(b)); + } + + if(bragi_write_to_handle(kb, pkt, BRAGI_LIGHTING_HANDLE, sizeof(pkt), bytes)) return 1; lastlight->forceupdate = newlight->forceupdate = 0; diff --git a/src/daemon/structures.h b/src/daemon/structures.h index 414fe533..49b8ae3b 100644 --- a/src/daemon/structures.h +++ b/src/daemon/structures.h @@ -378,6 +378,11 @@ typedef struct usbdevice_ { uchar bragi_in_ep; uchar wl_pairing_id[PAIR_ID_SIZE]; bool needs_fw_update; + enum { + BRIGHTNESS_SOFTWARE, + BRIGHTNESS_HARDWARE_FINE, + BRIGHTNESS_HARDWARE_COARSE, + } brightness_mode; } usbdevice; #endif // STRUCTURES_H diff --git a/src/daemon/usb.c b/src/daemon/usb.c index a183b120..a77d59f8 100644 --- a/src/daemon/usb.c +++ b/src/daemon/usb.c @@ -46,9 +46,12 @@ const dpi_list mouse_dpi_list[] = { const device_desc models[] = { // Keyboards { V_CORSAIR, P_K55, }, + { V_CORSAIR, P_K55_PRO, }, + { V_CORSAIR, P_K55_PRO_XT, }, { V_CORSAIR, P_K60_PRO_RGB, }, { V_CORSAIR, P_K60_PRO_RGB_LP, }, { V_CORSAIR, P_K60_PRO_RGB_SE, }, + { V_CORSAIR, P_K60_PRO_MONO, }, { V_CORSAIR, P_K63_NRGB, }, { V_CORSAIR, P_K63_NRGB_WL, }, { V_CORSAIR, P_K63_NRGB_WL2, }, @@ -200,12 +203,14 @@ const char* product_str(ushort product){ return "k63"; if(product == P_K63_NRGB_WL || product == P_K63_NRGB_WL2 || product == P_K63_NRGB_WL3 || product == P_K63_NRGB_WL4) return "k63_wireless"; - if(product == P_K60_PRO_RGB || product == P_K60_PRO_RGB_LP || product == P_K60_PRO_RGB_SE) + if(product == P_K60_PRO_RGB || product == P_K60_PRO_RGB_LP || product == P_K60_PRO_RGB_SE || product == P_K60_PRO_MONO) return "k60"; - if(product == P_K57_U || product == P_K57_D) + if(product == P_K57_U || product == P_K57_D || product == P_K55_PRO_XT) return "k57_wireless"; if(product == P_K55) return "k55"; + if(product == P_K55_PRO) + return "k55pro"; if(product == P_STRAFE || product == P_STRAFE_NRGB || product == P_STRAFE_NRGB_2) return "strafe"; if(product == P_STRAFE_MK2) diff --git a/src/daemon/usb.h b/src/daemon/usb.h index f3ca1f8e..6d1b6c72 100644 --- a/src/daemon/usb.h +++ b/src/daemon/usb.h @@ -47,6 +47,7 @@ #define P_K55_PRO_XT 0x1ba1 #define IS_K55(kb) ((kb)->vendor == V_CORSAIR && ((kb)->product == P_K55 || (kb)->product == P_K55_PRO || (kb)->product == P_K55_PRO_XT)) + #define P_K57_D 0x1b62 #define P_K57_U 0x1b6e @@ -211,7 +212,7 @@ const char* product_str(ushort product); /// The difference between non RGB and monochrome is, that monochrome has lights, but just in one color. /// nonRGB has no lights. /// Change this if new \b monochrome devices are added -#define IS_MONOCHROME(vendor, product) ((vendor) == (V_CORSAIR) && ((product) == (P_K63_NRGB) || (product) == (P_K63_NRGB_WL) || (product) == (P_K63_NRGB_WL2) || (product) == (P_K63_NRGB_WL3) || (product) == (P_K63_NRGB_WL4) || (product) == (P_K68_NRGB) || (product) == (P_STRAFE_NRGB) || (product) == (P_STRAFE_NRGB_2))) +#define IS_MONOCHROME(vendor, product) ((vendor) == (V_CORSAIR) && ((product) == (P_K63_NRGB) || (product) == (P_K63_NRGB_WL) || (product) == (P_K63_NRGB_WL2) || (product) == (P_K63_NRGB_WL3) || (product) == (P_K63_NRGB_WL4) || (product) == (P_K68_NRGB) || (product) == (P_STRAFE_NRGB) || (product) == (P_STRAFE_NRGB_2) || (product) == (P_K60_PRO_MONO))) /// For calling with a usbdevice*, vendor and product are extracted and IS_LEGACY() is returned. #define IS_LEGACY_DEV(kb) IS_LEGACY((kb)->vendor, (kb)->product) @@ -265,13 +266,13 @@ const char* product_str(ushort product); #define USES_FILE_HWSAVE(kb) ((kb)->product == P_K95_PLATINUM || (kb)->product == P_K70_MK2 || (kb)->product == P_K70_MK2SE || (kb)->product == P_STRAFE_MK2 || (kb)->product == P_GLAIVE || (kb)->product == P_SCIMITAR_PRO || (kb)->product == P_SCIMITAR_ELITE || (kb)->product == P_K70_MK2LP || (kb)->product == P_M65_RGB_ELITE || (kb)->product == P_IRONCLAW || (kb)->product == P_HARPOON_PRO || IS_K63_WL(kb) || IS_DARK_CORE_NXP(kb) || (kb)->product == P_IRONCLAW_W_U || (kb)->product == P_IRONCLAW_W_D || (kb)->product == P_NIGHTSWORD) /// Devices here support setting the pollrate through software -#define SUPPORTS_ADJRATE(kb) ((kb)->product == P_K60_PRO_RGB || (kb)->product == P_K60_PRO_RGB_LP || (kb)->product == P_K60_PRO_RGB_SE || (kb)->product == P_K63_NRGB || (kb)->product == P_K66 || (kb)->product == P_K68 || (kb)->product == P_K68_NRGB || (kb)->product == P_K70_MK2 || (kb)->product == P_K70_MK2SE || (kb)->product == P_K70_MK2LP || (kb)->product == P_K95_PLATINUM || (kb)->product == P_STRAFE || (kb)->product == P_STRAFE_NRGB || (kb)->product == P_STRAFE_NRGB_2 || (kb)->product == P_STRAFE_MK2 || (kb)->product == P_M55_RGB_PRO || (kb)->product == P_M65 || (kb)->product == P_M65_PRO || (kb)->product == P_M65_RGB_ELITE || (kb)->product == P_M95 || (kb)->product == P_SABRE_O || (kb)->product == P_SABRE_L || (kb)->product == P_SABRE_N || (kb)->product == P_SABRE_O2 || (kb)->product == P_SCIMITAR || (kb)->product == P_SCIMITAR_ELITE || (kb)->product == P_SCIMITAR_PRO || (kb)->product == P_HARPOON || (kb)->product == P_HARPOON_PRO || (kb)->product == P_GLAIVE || (kb)->product == P_KATAR || (kb)->product == P_KATAR_PRO_XT || (kb)->product == P_KATAR_PRO || (kb)->product == P_NIGHTSWORD || IS_DARK_CORE_NXP(kb) || (kb)->product == P_K95_PLATINUM_XT || (kb)->product == P_GLAIVE_PRO) +#define SUPPORTS_ADJRATE(kb) ((kb)->product == P_K60_PRO_RGB || (kb)->product == P_K60_PRO_MONO || (kb)->product == P_K60_PRO_RGB_LP || (kb)->product == P_K60_PRO_RGB_SE || (kb)->product == P_K63_NRGB || (kb)->product == P_K66 || (kb)->product == P_K68 || (kb)->product == P_K68_NRGB || (kb)->product == P_K70_MK2 || (kb)->product == P_K70_MK2SE || (kb)->product == P_K70_MK2LP || (kb)->product == P_K95_PLATINUM || (kb)->product == P_STRAFE || (kb)->product == P_STRAFE_NRGB || (kb)->product == P_STRAFE_NRGB_2 || (kb)->product == P_STRAFE_MK2 || (kb)->product == P_M55_RGB_PRO || (kb)->product == P_M65 || (kb)->product == P_M65_PRO || (kb)->product == P_M65_RGB_ELITE || (kb)->product == P_M95 || (kb)->product == P_SABRE_O || (kb)->product == P_SABRE_L || (kb)->product == P_SABRE_N || (kb)->product == P_SABRE_O2 || (kb)->product == P_SCIMITAR || (kb)->product == P_SCIMITAR_ELITE || (kb)->product == P_SCIMITAR_PRO || (kb)->product == P_HARPOON || (kb)->product == P_HARPOON_PRO || (kb)->product == P_GLAIVE || (kb)->product == P_KATAR || (kb)->product == P_KATAR_PRO_XT || (kb)->product == P_KATAR_PRO || (kb)->product == P_NIGHTSWORD || IS_DARK_CORE_NXP(kb) || (kb)->product == P_K95_PLATINUM_XT || (kb)->product == P_GLAIVE_PRO || (kb)->product == P_K55_PRO || (kb)->product == P_K55_PRO_XT) // Bragi devices that use macros for media keys. Currently used to apply a terrible workaround. #define BRAGI_HAS_MEDIA_MACRO(kb) (IS_K60PRORGB(kb)) // Devices that have volume wheels (or don't) -#define DEV_HAS_VOLWHEEL(kb) (!(IS_K65(kb) || IS_K63(kb) || (kb)->product == P_K57_U || BRAGI_HAS_MEDIA_MACRO(kb))) +#define DEV_HAS_VOLWHEEL(kb) (!(IS_K65(kb) || IS_K63(kb) || (kb)->product == P_K57_U || IS_K55(kb) || BRAGI_HAS_MEDIA_MACRO(kb))) // Devices that use the NXP protocol and have the DPI stage RGB data in the DPI packet #define NXP_RGB_IN_DPI_PKT(kb) ((kb)->vendor == V_CORSAIR && ((kb)->product == P_GLAIVE_PRO || IS_DARK_CORE_NXP(kb))) diff --git a/src/daemon/usb_bragi.c b/src/daemon/usb_bragi.c index aa64be1b..afd82cd3 100644 --- a/src/daemon/usb_bragi.c +++ b/src/daemon/usb_bragi.c @@ -28,6 +28,8 @@ void bragi_fill_input_eps(usbdevice* kb) kb->bragi_in_ep = 0x82; break; case P_K57_U: + case P_K55_PRO: + case P_K55_PRO_XT: kb->bragi_out_ep = 0x1; kb->bragi_in_ep = 0x82; break; diff --git a/src/gui/kb.cpp b/src/gui/kb.cpp index ceb9aea3..92869aef 100644 --- a/src/gui/kb.cpp +++ b/src/gui/kb.cpp @@ -344,6 +344,8 @@ void Kb::load(){ demoProfile = ":/txt/demoprofile_st100.ini"; else if(map.model() == KeyMap::NIGHTSWORD) demoProfile = ":/txt/demoprofile_nightsword.ini"; + else if(map.model() == KeyMap::K55PRO) + demoProfile = ":/txt/demoprofile_k55pro.ini"; QSettings demoSettings(demoProfile, QSettings::IniFormat, this); CkbDemoSettings cSettings(demoSettings); KbProfile* demo = new KbProfile(this, map, cSettings, "{BA7FC152-2D51-4C26-A7A6-A036CC93D924}"); diff --git a/src/gui/kblightwidget.cpp b/src/gui/kblightwidget.cpp index f79b9a24..41d4afd8 100644 --- a/src/gui/kblightwidget.cpp +++ b/src/gui/kblightwidget.cpp @@ -108,12 +108,12 @@ void KbLightWidget::newSelection(const QStringList& selection){ ui->selLabel->setText(tr("Click to select")); } else if(count == 1) { // This is done this way to aid translation - if(!light->map().isKeyboard() || light->map().model() == KeyMap::K55) + if(!light->map().isKeyboard() || light->map().model() == KeyMap::K55 || light->map().model() == KeyMap::K55PRO) ui->selLabel->setText(tr("1 zone selected")); else ui->selLabel->setText(tr("1 key selected")); } else { - if(!light->map().isKeyboard() || light->map().model() == KeyMap::K55) + if(!light->map().isKeyboard() || light->map().model() == KeyMap::K55 || light->map().model() == KeyMap::K55PRO) ui->selLabel->setText(tr("%1 zones selected").arg(count)); else ui->selLabel->setText(tr("%1 keys selected").arg(count)); diff --git a/src/gui/keymap.cpp b/src/gui/keymap.cpp index 2315016a..fc66a7da 100644 --- a/src/gui/keymap.cpp +++ b/src/gui/keymap.cpp @@ -11,6 +11,9 @@ // K55 Zone Size #define ZS 97, 75 +// K55 PRO Zone Size +#define ZSP 59, 75 + // Key positions (K95 - English) // This is the master key map that includes ANSI, ISO and JP-106 layouts - use patchANSI(), patchISO() or patchJP106() to finalize it static const Key K95Keys[] = { @@ -264,6 +267,14 @@ static const Key K55Zones[] = { }; #define K55_ZONES (sizeof(K55Zones) / sizeof(Key)) +static const Key K55PROZones[] = { + {nullptr, "Zone 1", "zone1", 26, 45, ZSP, true, false}, + {nullptr, "Zone 2", "zone2", 83, 45, ZSP, true, false}, + {nullptr, "Zone 3", "zone3", 140, 45, ZSP, true, false}, + {nullptr, "Zone 4", "zone4", 197, 45, ZSP, true, false}, + {nullptr, "Zone 5", "zone5", 254, 45, ZSP, true, false}, +}; +#define K55PRO_ZONES (sizeof(K55PROZones) / sizeof(Key)) // Strafe has side lights #define KSTRAFE_X_START 12 @@ -1008,6 +1019,15 @@ static QHash getMap(KeyMap::Model model, KeyMap::Layout layout){ // Done! break; } + case KeyMap::K55PRO:{ + // Keys are the same as the K55, but has 5 zones instead + map = getMap(KeyMap::K55, layout); + + // Overwrite the zones + for(const Key* key = K55PROZones; key < K55PROZones + K55PRO_ZONES; key++) + map[key->name] = *key; + break; + } case KeyMap::M65:{ // M65 isn't a keyboard; all mouse maps are unique. for(const Key* key = M65Keys; key < M65Keys + KEYCOUNT_M65; key++){ @@ -1456,6 +1476,8 @@ KeyMap::Model KeyMap::getModel(const QString& name){ return K95L; if(lower == "glaivepro") return GLAIVEPRO; + if(lower == "k55pro") + return K55PRO; return NO_MODEL; } @@ -1463,6 +1485,8 @@ QString KeyMap::getModel(KeyMap::Model model){ switch(model){ case K55: return "k55"; + case K55PRO: + return "k55pro"; case K57_WL: return "k57_wireless"; case K60: @@ -1557,6 +1581,8 @@ int KeyMap::modelWidth(Model model){ case K95: case K95L: return K95_WIDTH; + case K55PRO: + return K95P_WIDTH + 1; // FIXME case K95P: case K55: case K57_WL: @@ -1593,6 +1619,7 @@ int KeyMap::modelWidth(Model model){ int KeyMap::modelHeight(Model model){ switch(model){ case K55: + case K55PRO: case K57_WL: case K63: case K63_WL: diff --git a/src/gui/keywidget.cpp b/src/gui/keywidget.cpp index 66a5520a..58d1b29f 100644 --- a/src/gui/keywidget.cpp +++ b/src/gui/keywidget.cpp @@ -413,7 +413,7 @@ void KeyWidget::paintEvent(QPaintEvent*){ drawTopLeftCorner(&decPainter, kx, ky, kw, kh, scale); } else decPainter.drawRect(QRectF(kx * scale, ky * scale, kw * scale, kh * scale)); - } else if (model == KeyMap::K55) + } else if (model == KeyMap::K55 || model == KeyMap::K55PRO) decPainter.drawRect(QRectF(x * scale, y * scale, w * scale, h * scale)); else if ((model == KeyMap::K70MK2 || model == KeyMap::STRAFE_MK2) && key.friendlyName() == "Logo 1") drawLogo(&key, &decPainter, offX , offY, scale); diff --git a/src/gui/resources/demos/demoprofile_k55pro.ini b/src/gui/resources/demos/demoprofile_k55pro.ini new file mode 100644 index 00000000..4333688f --- /dev/null +++ b/src/gui/resources/demos/demoprofile_k55pro.ini @@ -0,0 +1,729 @@ +[%7BBA7FC152-2D51-4C26-A7A6-A036CC93D924%7D] +0\Binding\KeyMap=K55PRO US +0\Binding\UseRealNames=true +0\Binding\WinLock=false +0\GUID={11C11AE3-3195-4DFC-B8AC-2FEA703414E5} +0\HwModified=2a5c3bfd +0\Lighting\Animations\List={B60A7D3A-AAC6-4676-8471-D602455A1134} +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\BlendMode=Normal +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Keys=zone3, zone1, zone2, zone4, zone5, rwin, bslash_iso, g10, g11, g12, g13, g14, g15, g16, g17, g18, g7, g8, g9, hash, front, wheel, dpi, thumb, back +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Name=Wave +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Opacity=1 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\angle=120 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\color=0:ffff0000 17:ffffff00 33:ff00ff00 50:ff00ffff 67:ff0000ff 83:ffff00ff 100:ffff0000 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\delay=0 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\duration=3 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\kpdelay=0 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\kpmode=0 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\kpmodestop=false +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\kprelease=false +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\kprepeat=3 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\kpstop=0 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\kptrigger=0 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\length=100.000000 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\repeat=3 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\stop=-1 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\symmetric=0 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\Parameters\trigger=1 +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\ScriptGuid={E0BBA19E-C328-4C0E-8E3C-A06D5722B4FC} +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\ScriptName=Wave +0\Lighting\Animations\%7BB60A7D3A-AAC6-4676-8471-D602455A1134%7D\UseRealNames=true +0\Lighting\Brightness=0 +0\Lighting\KeyMap=K55PRO US +0\Lighting\Keys\0=#ffffff +0\Lighting\Keys\1=#ffffff +0\Lighting\Keys\2=#ffffff +0\Lighting\Keys\3=#ffffff +0\Lighting\Keys\4=#ffffff +0\Lighting\Keys\5=#ffffff +0\Lighting\Keys\6=#ffffff +0\Lighting\Keys\7=#ffffff +0\Lighting\Keys\8=#ffffff +0\Lighting\Keys\9=#ffffff +0\Lighting\Keys\a=#ffffff +0\Lighting\Keys\b=#ffffff +0\Lighting\Keys\back=#ffffff +0\Lighting\Keys\bslash=#ffffff +0\Lighting\Keys\bslash_iso=#ffffff +0\Lighting\Keys\bspace=#ffffff +0\Lighting\Keys\c=#ffffff +0\Lighting\Keys\caps=#ffffff +0\Lighting\Keys\colon=#ffffff +0\Lighting\Keys\comma=#ffffff +0\Lighting\Keys\d=#ffffff +0\Lighting\Keys\del=#ffffff +0\Lighting\Keys\dot=#ffffff +0\Lighting\Keys\down=#ffffff +0\Lighting\Keys\dpi=#ffffff +0\Lighting\Keys\e=#ffffff +0\Lighting\Keys\end=#ffffff +0\Lighting\Keys\enter=#ffffff +0\Lighting\Keys\equal=#ffffff +0\Lighting\Keys\esc=#ffffff +0\Lighting\Keys\f=#ffffff +0\Lighting\Keys\f1=#ffffff +0\Lighting\Keys\f10=#ffffff +0\Lighting\Keys\f11=#ffffff +0\Lighting\Keys\f12=#ffffff +0\Lighting\Keys\f2=#ffffff +0\Lighting\Keys\f3=#ffffff +0\Lighting\Keys\f4=#ffffff +0\Lighting\Keys\f5=#ffffff +0\Lighting\Keys\f6=#ffffff +0\Lighting\Keys\f7=#ffffff +0\Lighting\Keys\f8=#ffffff +0\Lighting\Keys\f9=#ffffff +0\Lighting\Keys\fn=#ffffff +0\Lighting\Keys\front=#ffffff +0\Lighting\Keys\g=#ffffff +0\Lighting\Keys\g1=#ffffff +0\Lighting\Keys\g10=#ffffff +0\Lighting\Keys\g11=#ffffff +0\Lighting\Keys\g12=#ffffff +0\Lighting\Keys\g13=#ffffff +0\Lighting\Keys\g14=#ffffff +0\Lighting\Keys\g15=#ffffff +0\Lighting\Keys\g16=#ffffff +0\Lighting\Keys\g17=#ffffff +0\Lighting\Keys\g18=#ffffff +0\Lighting\Keys\g2=#ffffff +0\Lighting\Keys\g3=#ffffff +0\Lighting\Keys\g4=#ffffff +0\Lighting\Keys\g5=#ffffff +0\Lighting\Keys\g6=#ffffff +0\Lighting\Keys\g7=#ffffff +0\Lighting\Keys\g8=#ffffff +0\Lighting\Keys\g9=#ffffff +0\Lighting\Keys\grave=#ffffff +0\Lighting\Keys\h=#ffffff +0\Lighting\Keys\hash=#ffffff +0\Lighting\Keys\home=#ffffff +0\Lighting\Keys\i=#ffffff +0\Lighting\Keys\ins=#ffffff +0\Lighting\Keys\j=#ffffff +0\Lighting\Keys\k=#ffffff +0\Lighting\Keys\l=#ffffff +0\Lighting\Keys\lalt=#ffffff +0\Lighting\Keys\lbrace=#ffffff +0\Lighting\Keys\lctrl=#ffffff +0\Lighting\Keys\left=#ffffff +0\Lighting\Keys\light=#ffffff +0\Lighting\Keys\lock=#ffffff +0\Lighting\Keys\lshift=#ffffff +0\Lighting\Keys\lwin=#ffffff +0\Lighting\Keys\m=#ffffff +0\Lighting\Keys\m1=#ffffff +0\Lighting\Keys\m2=#ffffff +0\Lighting\Keys\m3=#ffffff +0\Lighting\Keys\minus=#ffffff +0\Lighting\Keys\mr=#ffffff +0\Lighting\Keys\mute=#ffffff +0\Lighting\Keys\n=#ffffff +0\Lighting\Keys\next=#ffffff +0\Lighting\Keys\num0=#ffffff +0\Lighting\Keys\num1=#ffffff +0\Lighting\Keys\num2=#ffffff +0\Lighting\Keys\num3=#ffffff +0\Lighting\Keys\num4=#ffffff +0\Lighting\Keys\num5=#ffffff +0\Lighting\Keys\num6=#ffffff +0\Lighting\Keys\num7=#ffffff +0\Lighting\Keys\num8=#ffffff +0\Lighting\Keys\num9=#ffffff +0\Lighting\Keys\numdot=#ffffff +0\Lighting\Keys\numenter=#ffffff +0\Lighting\Keys\numlock=#ffffff +0\Lighting\Keys\numminus=#ffffff +0\Lighting\Keys\numplus=#ffffff +0\Lighting\Keys\numslash=#ffffff +0\Lighting\Keys\numstar=#ffffff +0\Lighting\Keys\o=#ffffff +0\Lighting\Keys\p=#ffffff +0\Lighting\Keys\pause=#ffffff +0\Lighting\Keys\pgdn=#ffffff +0\Lighting\Keys\pgup=#ffffff +0\Lighting\Keys\play=#ffffff +0\Lighting\Keys\prev=#ffffff +0\Lighting\Keys\prtscn=#ffffff +0\Lighting\Keys\q=#ffffff +0\Lighting\Keys\quote=#ffffff +0\Lighting\Keys\r=#ffffff +0\Lighting\Keys\ralt=#ffffff +0\Lighting\Keys\rbrace=#ffffff +0\Lighting\Keys\rctrl=#ffffff +0\Lighting\Keys\right=#ffffff +0\Lighting\Keys\rmenu=#ffffff +0\Lighting\Keys\rshift=#ffffff +0\Lighting\Keys\rwin=#ffffff +0\Lighting\Keys\s=#ffffff +0\Lighting\Keys\scroll=#ffffff +0\Lighting\Keys\slash=#ffffff +0\Lighting\Keys\space=#ffffff +0\Lighting\Keys\stop=#ffffff +0\Lighting\Keys\t=#ffffff +0\Lighting\Keys\tab=#ffffff +0\Lighting\Keys\u=#ffffff +0\Lighting\Keys\up=#ffffff +0\Lighting\Keys\v=#ffffff +0\Lighting\Keys\voldn=#ffffff +0\Lighting\Keys\volup=#ffffff +0\Lighting\Keys\w=#ffffff +0\Lighting\Keys\x=#ffffff +0\Lighting\Keys\y=#ffffff +0\Lighting\Keys\z=#ffffff +0\Lighting\Keys\zone1=#ffffff +0\Lighting\Keys\zone2=#ffffff +0\Lighting\Keys\zone3=#ffffff +0\Lighting\Keys\zone4=#ffffff +0\Lighting\Keys\zone5=#ffffff +0\Lighting\UseRealNames=true +0\Modified=6ec7ad79 +0\Name=Rainbow +0\Performance\AngleSnap=false +0\Performance\DPI\0=@Point(400 400) +0\Performance\DPI\0RGB=#ffff0000 +0\Performance\DPI\1=@Point(450 450) +0\Performance\DPI\1Disabled=false +0\Performance\DPI\1RGB=#ffffc000 +0\Performance\DPI\2=@Point(800 800) +0\Performance\DPI\2Disabled=false +0\Performance\DPI\2RGB=#ffffff00 +0\Performance\DPI\3=@Point(1500 1500) +0\Performance\DPI\3Disabled=false +0\Performance\DPI\3RGB=#ff00ff00 +0\Performance\DPI\4=@Point(3000 3000) +0\Performance\DPI\4Disabled=false +0\Performance\DPI\4RGB=#ff00ffff +0\Performance\DPI\5=@Point(6000 6000) +0\Performance\DPI\5Disabled=false +0\Performance\DPI\5RGB=#ffffffff +0\Performance\DPI\6RGB=#ffc0c0c0 +0\Performance\DPI\CurIdx=1 +0\Performance\Indicators\0\Enable=false +0\Performance\Indicators\0\Hardware=0 +0\Performance\Indicators\0\RGB0=#ff00ff00 +0\Performance\Indicators\0\RGB1=#ff000000 +0\Performance\Indicators\1\Enable=false +0\Performance\Indicators\1\Hardware=0 +0\Performance\Indicators\1\RGB0=#ff00ff00 +0\Performance\Indicators\1\RGB1=#ff000000 +0\Performance\Indicators\2\Enable=false +0\Performance\Indicators\2\Hardware=0 +0\Performance\Indicators\2\RGB0=#ff00ff00 +0\Performance\Indicators\2\RGB1=#ff000000 +0\Performance\Indicators\3\Enable=true +0\Performance\Indicators\3\RGB0=#00ffffff +0\Performance\Indicators\3\RGB1=#ff000000 +0\Performance\Indicators\4\Enable=true +0\Performance\Indicators\4\RGB0=#ffff0000 +0\Performance\Indicators\4\RGB1=#ff000000 +0\Performance\Indicators\5\Enable=true +0\Performance\Indicators\5\RGB0=#ffff0000 +0\Performance\Indicators\5\RGB1=#ffffff00 +0\Performance\Indicators\5\RGB2=#ffffffff +0\Performance\Indicators\6\Enable=true +0\Performance\Indicators\6\RGB0=#ffffffff +0\Performance\Indicators\6\RGB1=#ff000000 +0\Performance\Indicators\7\Enable=true +0\Performance\Indicators\7\RGB0=#00ffffff +0\Performance\Indicators\7\RGB1=#ff000000 +0\Performance\Indicators\7\RGB2=#ff000000 +0\Performance\Indicators\DPI=true +0\Performance\LiftHeight=3 +0\Performance\MuteDevice=sink +0\WindowInfo\enabled=false +1\Binding\KeyMap=K55PRO US +1\Binding\UseRealNames=true +1\Binding\WinLock=false +1\GUID={982F6B59-8399-4FA6-8AF4-3BC63A62D9AD} +1\HwModified=d6674c5 +1\Lighting\Animations\List={9EA4750E-790A-4DCE-90D0-CAAB36CCA693} +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\BlendMode=Normal +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Keys=zone3, zone2, zone4, bslash_iso, hash, front, wheel, dpi, thumb, back +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Name=Breath +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Opacity=1 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\color=0:00ffffff 50:ffffffff 100:00ffffff +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\delay=0 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\duration=5 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\kpdelay=0 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\kphold=1 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\kpmode=0 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\kpmodestop=false +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\kprelease=false +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\kprepeat=5 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\kpstop=0 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\kptrigger=0 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\randomize=0 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\repeat=5 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\stop=-1 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\Parameters\trigger=1 +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\ScriptGuid={54DD2975-E192-457D-BCFC-D912A24E33B4} +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\ScriptName=Gradient +1\Lighting\Animations\%7B9EA4750E-790A-4DCE-90D0-CAAB36CCA693%7D\UseRealNames=true +1\Lighting\Brightness=0 +1\Lighting\KeyMap=K55PRO US +1\Lighting\Keys\0=#ffffff +1\Lighting\Keys\1=#ffffff +1\Lighting\Keys\2=#ffffff +1\Lighting\Keys\3=#ffffff +1\Lighting\Keys\4=#ffffff +1\Lighting\Keys\5=#ffffff +1\Lighting\Keys\6=#ffffff +1\Lighting\Keys\7=#ffffff +1\Lighting\Keys\8=#ffffff +1\Lighting\Keys\9=#ffffff +1\Lighting\Keys\a=#ff0000 +1\Lighting\Keys\b=#ff0000 +1\Lighting\Keys\back=#ff0000 +1\Lighting\Keys\bslash=#ff0000 +1\Lighting\Keys\bslash_iso=#ff0000 +1\Lighting\Keys\bspace=#ffffff +1\Lighting\Keys\c=#ff0000 +1\Lighting\Keys\caps=#ffffff +1\Lighting\Keys\colon=#ff0000 +1\Lighting\Keys\comma=#ff0000 +1\Lighting\Keys\d=#ff0000 +1\Lighting\Keys\del=#ffffff +1\Lighting\Keys\dot=#ff0000 +1\Lighting\Keys\down=#ff0000 +1\Lighting\Keys\dpi=#ff0000 +1\Lighting\Keys\e=#ff0000 +1\Lighting\Keys\end=#ffffff +1\Lighting\Keys\enter=#ffffff +1\Lighting\Keys\equal=#ffffff +1\Lighting\Keys\esc=#ffffff +1\Lighting\Keys\f=#ff0000 +1\Lighting\Keys\f1=#ffffff +1\Lighting\Keys\f10=#ffffff +1\Lighting\Keys\f11=#ffffff +1\Lighting\Keys\f12=#ffffff +1\Lighting\Keys\f2=#ffffff +1\Lighting\Keys\f3=#ffffff +1\Lighting\Keys\f4=#ffffff +1\Lighting\Keys\f5=#ffffff +1\Lighting\Keys\f6=#ffffff +1\Lighting\Keys\f7=#ffffff +1\Lighting\Keys\f8=#ffffff +1\Lighting\Keys\f9=#ffffff +1\Lighting\Keys\fn=#ffffff +1\Lighting\Keys\front=#ff0000 +1\Lighting\Keys\g=#ff0000 +1\Lighting\Keys\g1=#ff8080 +1\Lighting\Keys\g10=#ff8080 +1\Lighting\Keys\g11=#ff8080 +1\Lighting\Keys\g12=#ff8080 +1\Lighting\Keys\g13=#ff8080 +1\Lighting\Keys\g14=#ff8080 +1\Lighting\Keys\g15=#ff8080 +1\Lighting\Keys\g16=#ff8080 +1\Lighting\Keys\g17=#ff8080 +1\Lighting\Keys\g18=#ff8080 +1\Lighting\Keys\g2=#ff8080 +1\Lighting\Keys\g3=#ff8080 +1\Lighting\Keys\g4=#ff8080 +1\Lighting\Keys\g5=#ff8080 +1\Lighting\Keys\g6=#ff8080 +1\Lighting\Keys\g7=#ff8080 +1\Lighting\Keys\g8=#ff8080 +1\Lighting\Keys\g9=#ff8080 +1\Lighting\Keys\grave=#ffffff +1\Lighting\Keys\h=#ff0000 +1\Lighting\Keys\hash=#ff0000 +1\Lighting\Keys\home=#ffffff +1\Lighting\Keys\i=#ff0000 +1\Lighting\Keys\ins=#ffffff +1\Lighting\Keys\j=#ff0000 +1\Lighting\Keys\k=#ff0000 +1\Lighting\Keys\l=#ff0000 +1\Lighting\Keys\lalt=#ffffff +1\Lighting\Keys\lbrace=#ff0000 +1\Lighting\Keys\lctrl=#ffffff +1\Lighting\Keys\left=#ff0000 +1\Lighting\Keys\light=#ffffff +1\Lighting\Keys\lock=#ffffff +1\Lighting\Keys\lshift=#ffffff +1\Lighting\Keys\lwin=#ffffff +1\Lighting\Keys\m=#ff0000 +1\Lighting\Keys\m1=#ffffff +1\Lighting\Keys\m2=#ffffff +1\Lighting\Keys\m3=#ffffff +1\Lighting\Keys\minus=#ffffff +1\Lighting\Keys\mr=#ffffff +1\Lighting\Keys\mute=#ffffff +1\Lighting\Keys\n=#ff0000 +1\Lighting\Keys\next=#ff8080 +1\Lighting\Keys\num0=#ff8080 +1\Lighting\Keys\num1=#ff8080 +1\Lighting\Keys\num2=#ff8080 +1\Lighting\Keys\num3=#ff8080 +1\Lighting\Keys\num4=#ff8080 +1\Lighting\Keys\num5=#ff8080 +1\Lighting\Keys\num6=#ff8080 +1\Lighting\Keys\num7=#ff8080 +1\Lighting\Keys\num8=#ff8080 +1\Lighting\Keys\num9=#ff8080 +1\Lighting\Keys\numdot=#ff8080 +1\Lighting\Keys\numenter=#ff8080 +1\Lighting\Keys\numlock=#ff8080 +1\Lighting\Keys\numminus=#ff8080 +1\Lighting\Keys\numplus=#ff8080 +1\Lighting\Keys\numslash=#ff8080 +1\Lighting\Keys\numstar=#ff8080 +1\Lighting\Keys\o=#ff0000 +1\Lighting\Keys\p=#ff0000 +1\Lighting\Keys\pause=#ffffff +1\Lighting\Keys\pgdn=#ffffff +1\Lighting\Keys\pgup=#ffffff +1\Lighting\Keys\play=#ff8080 +1\Lighting\Keys\prev=#ff8080 +1\Lighting\Keys\prtscn=#ffffff +1\Lighting\Keys\q=#ff0000 +1\Lighting\Keys\quote=#ff0000 +1\Lighting\Keys\r=#ff0000 +1\Lighting\Keys\ralt=#ffffff +1\Lighting\Keys\rbrace=#ff0000 +1\Lighting\Keys\rctrl=#ffffff +1\Lighting\Keys\right=#ff0000 +1\Lighting\Keys\rmenu=#ffffff +1\Lighting\Keys\rshift=#ffffff +1\Lighting\Keys\rwin=#ffffff +1\Lighting\Keys\s=#ff0000 +1\Lighting\Keys\scroll=#ffffff +1\Lighting\Keys\slash=#ff0000 +1\Lighting\Keys\space=#ff0000 +1\Lighting\Keys\stop=#ff8080 +1\Lighting\Keys\t=#ff0000 +1\Lighting\Keys\tab=#ffffff +1\Lighting\Keys\thumb=#ff0000 +1\Lighting\Keys\u=#ff0000 +1\Lighting\Keys\up=#ff0000 +1\Lighting\Keys\v=#ff0000 +1\Lighting\Keys\voldn=#ffffff +1\Lighting\Keys\volup=#ffffff +1\Lighting\Keys\w=#ff0000 +1\Lighting\Keys\wheel=#ff0000 +1\Lighting\Keys\x=#ff0000 +1\Lighting\Keys\y=#ff0000 +1\Lighting\Keys\z=#ff0000 +1\Lighting\Keys\zone1=#ffffff +1\Lighting\Keys\zone2=#ff0000 +1\Lighting\Keys\zone3=#ff0000 +1\Lighting\Keys\zone4=#ff0000 +1\Lighting\Keys\zone5=#ffffff +1\Lighting\UseRealNames=true +1\Modified=7e9c7b68 +1\Name=Breathing +1\Performance\AngleSnap=false +1\Performance\DPI\0=@Point(400 400) +1\Performance\DPI\0RGB=#ffff0000 +1\Performance\DPI\1=@Point(450 450) +1\Performance\DPI\1Disabled=false +1\Performance\DPI\1RGB=#ffffc000 +1\Performance\DPI\2=@Point(800 800) +1\Performance\DPI\2Disabled=false +1\Performance\DPI\2RGB=#ffffff00 +1\Performance\DPI\3=@Point(1500 1500) +1\Performance\DPI\3Disabled=false +1\Performance\DPI\3RGB=#ff00ff00 +1\Performance\DPI\4=@Point(3000 3000) +1\Performance\DPI\4Disabled=false +1\Performance\DPI\4RGB=#ff00ffff +1\Performance\DPI\5=@Point(6000 6000) +1\Performance\DPI\5Disabled=false +1\Performance\DPI\5RGB=#ffffffff +1\Performance\DPI\6RGB=#ffc0c0c0 +1\Performance\DPI\CurIdx=1 +1\Performance\Indicators\0\Enable=false +1\Performance\Indicators\0\Hardware=0 +1\Performance\Indicators\0\RGB0=#ff00ff00 +1\Performance\Indicators\0\RGB1=#ff000000 +1\Performance\Indicators\1\Enable=false +1\Performance\Indicators\1\Hardware=0 +1\Performance\Indicators\1\RGB0=#ff00ff00 +1\Performance\Indicators\1\RGB1=#ff000000 +1\Performance\Indicators\2\Enable=false +1\Performance\Indicators\2\Hardware=0 +1\Performance\Indicators\2\RGB0=#ff00ff00 +1\Performance\Indicators\2\RGB1=#ff000000 +1\Performance\Indicators\3\Enable=true +1\Performance\Indicators\3\RGB0=#00ffffff +1\Performance\Indicators\3\RGB1=#ff000000 +1\Performance\Indicators\4\Enable=true +1\Performance\Indicators\4\RGB0=#ffff0000 +1\Performance\Indicators\4\RGB1=#ff000000 +1\Performance\Indicators\5\Enable=true +1\Performance\Indicators\5\RGB0=#ffff0000 +1\Performance\Indicators\5\RGB1=#ffffff00 +1\Performance\Indicators\5\RGB2=#ffffffff +1\Performance\Indicators\6\Enable=true +1\Performance\Indicators\6\RGB0=#ffffffff +1\Performance\Indicators\6\RGB1=#ff000000 +1\Performance\Indicators\7\Enable=true +1\Performance\Indicators\7\RGB0=#00ffffff +1\Performance\Indicators\7\RGB1=#ff000000 +1\Performance\Indicators\7\RGB2=#ff000000 +1\Performance\Indicators\DPI=true +1\Performance\LiftHeight=3 +1\Performance\MuteDevice=sink +1\WindowInfo\enabled=false +2\Binding\KeyMap=K55PRO US +2\Binding\UseRealNames=true +2\Binding\WinLock=false +2\GUID={57C94A3A-80E6-4182-B067-6D4A14AD497C} +2\HwModified=417c3862 +2\Lighting\Animations\List={51E79A62-3C5C-4732-925C-098499E81A21}, {913D4ABD-246C-4008-9CFB-F4B91CC991F1} +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\BlendMode=Multiply +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Keys=zone3, zone1, zone2, zone4, zone5, rwin, bslash_iso, m1, m2, m3, g10, g11, g12, g13, g14, g15, g16, g17, g18, g7, g8, g9, hash, front, wheel, dpi, thumb, back +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Name=Random +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Opacity=1 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\delay=0 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\duration=0.5 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\fade=0 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\kpdelay=0 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\kpmode=1 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\kpmodestop=false +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\kprelease=false +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\kpstop=-1 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\kptrigger=0 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\stop=-1 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\trigger=1 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\Parameters\useopacity=0 +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\ScriptGuid={22418DA4-A181-4B93-A4D3-03682BA283D2} +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\ScriptName=Random +2\Lighting\Animations\%7B51E79A62-3C5C-4732-925C-098499E81A21%7D\UseRealNames=true +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\BlendMode=Add +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Keys=zone3, zone1, zone2, zone4, zone5, rwin, bslash_iso, g10, g11, g12, g13, g14, g15, g16, g17, g18, g7, g8, g9, hash +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Name=Ripple +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Opacity=1 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\color=0:ffff007f 100:00ff007f +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\delay=0 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\duration=1 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\kpdelay=0 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\kpmode=0 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\kpmodestop=false +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\kprelease=false +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\kprepeat=1 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\kpstop=0 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\kptrigger=1 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\length=10 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\randomize=0 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\repeat=1 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\stop=-1 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\symmetric=1 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\trigger=0 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\x_offset=0.000000 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\Parameters\y_offset=0.000000 +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\ScriptGuid={097D69F0-70B2-48B8-AFE2-25A1CDB02C9D} +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\ScriptName=Ripple +2\Lighting\Animations\%7B913D4ABD-246C-4008-9CFB-F4B91CC991F1%7D\UseRealNames=true +2\Lighting\Brightness=0 +2\Lighting\KeyMap=K55PRO US +2\Lighting\Keys\0=#aa00ff +2\Lighting\Keys\1=#aa00ff +2\Lighting\Keys\2=#aa00ff +2\Lighting\Keys\3=#aa00ff +2\Lighting\Keys\4=#aa00ff +2\Lighting\Keys\5=#aa00ff +2\Lighting\Keys\6=#aa00ff +2\Lighting\Keys\7=#aa00ff +2\Lighting\Keys\8=#aa00ff +2\Lighting\Keys\9=#aa00ff +2\Lighting\Keys\a=#aa00ff +2\Lighting\Keys\b=#aa00ff +2\Lighting\Keys\back=#aa00ff +2\Lighting\Keys\bslash=#aa00ff +2\Lighting\Keys\bslash_iso=#9700ff +2\Lighting\Keys\bspace=#aa00ff +2\Lighting\Keys\c=#aa00ff +2\Lighting\Keys\caps=#aa00ff +2\Lighting\Keys\colon=#9700ff +2\Lighting\Keys\comma=#aa00ff +2\Lighting\Keys\d=#aa00ff +2\Lighting\Keys\del=#aa00ff +2\Lighting\Keys\dot=#aa00ff +2\Lighting\Keys\down=#aa00ff +2\Lighting\Keys\dpi=#aa00ff +2\Lighting\Keys\e=#aa00ff +2\Lighting\Keys\end=#aa00ff +2\Lighting\Keys\enter=#aa00ff +2\Lighting\Keys\equal=#aa00ff +2\Lighting\Keys\esc=#aa00ff +2\Lighting\Keys\f=#aa00ff +2\Lighting\Keys\f1=#aa00ff +2\Lighting\Keys\f10=#aa00ff +2\Lighting\Keys\f11=#aa00ff +2\Lighting\Keys\f12=#aa00ff +2\Lighting\Keys\f2=#aa00ff +2\Lighting\Keys\f3=#aa00ff +2\Lighting\Keys\f4=#aa00ff +2\Lighting\Keys\f5=#aa00ff +2\Lighting\Keys\f6=#aa00ff +2\Lighting\Keys\f7=#aa00ff +2\Lighting\Keys\f8=#aa00ff +2\Lighting\Keys\f9=#aa00ff +2\Lighting\Keys\fn=#ffffff +2\Lighting\Keys\front=#aa00ff +2\Lighting\Keys\g=#aa00ff +2\Lighting\Keys\g1=#aa00ff +2\Lighting\Keys\g10=#aa00ff +2\Lighting\Keys\g11=#aa00ff +2\Lighting\Keys\g12=#aa00ff +2\Lighting\Keys\g13=#aa00ff +2\Lighting\Keys\g14=#aa00ff +2\Lighting\Keys\g15=#aa00ff +2\Lighting\Keys\g16=#aa00ff +2\Lighting\Keys\g17=#aa00ff +2\Lighting\Keys\g18=#aa00ff +2\Lighting\Keys\g2=#aa00ff +2\Lighting\Keys\g3=#aa00ff +2\Lighting\Keys\g4=#aa00ff +2\Lighting\Keys\g5=#aa00ff +2\Lighting\Keys\g6=#aa00ff +2\Lighting\Keys\g7=#aa00ff +2\Lighting\Keys\g8=#aa00ff +2\Lighting\Keys\g9=#aa00ff +2\Lighting\Keys\grave=#aa00ff +2\Lighting\Keys\h=#aa00ff +2\Lighting\Keys\hash=#9700ff +2\Lighting\Keys\home=#aa00ff +2\Lighting\Keys\i=#aa00ff +2\Lighting\Keys\ins=#aa00ff +2\Lighting\Keys\j=#aa00ff +2\Lighting\Keys\k=#aa00ff +2\Lighting\Keys\l=#aa00ff +2\Lighting\Keys\lalt=#aa00ff +2\Lighting\Keys\lbrace=#aa00ff +2\Lighting\Keys\lctrl=#aa00ff +2\Lighting\Keys\left=#aa00ff +2\Lighting\Keys\light=#aa00ff +2\Lighting\Keys\lock=#aa00ff +2\Lighting\Keys\lshift=#aa00ff +2\Lighting\Keys\lwin=#aa00ff +2\Lighting\Keys\m=#aa00ff +2\Lighting\Keys\m1=#aa00ff +2\Lighting\Keys\m2=#aa00ff +2\Lighting\Keys\m3=#aa00ff +2\Lighting\Keys\minus=#aa00ff +2\Lighting\Keys\mr=#aa00ff +2\Lighting\Keys\mute=#aa00ff +2\Lighting\Keys\n=#aa00ff +2\Lighting\Keys\next=#aa00ff +2\Lighting\Keys\num0=#aa00ff +2\Lighting\Keys\num1=#aa00ff +2\Lighting\Keys\num2=#aa00ff +2\Lighting\Keys\num3=#aa00ff +2\Lighting\Keys\num4=#aa00ff +2\Lighting\Keys\num5=#aa00ff +2\Lighting\Keys\num6=#aa00ff +2\Lighting\Keys\num7=#aa00ff +2\Lighting\Keys\num8=#aa00ff +2\Lighting\Keys\num9=#aa00ff +2\Lighting\Keys\numdot=#aa00ff +2\Lighting\Keys\numenter=#aa00ff +2\Lighting\Keys\numlock=#aa00ff +2\Lighting\Keys\numminus=#aa00ff +2\Lighting\Keys\numplus=#aa00ff +2\Lighting\Keys\numslash=#aa00ff +2\Lighting\Keys\numstar=#aa00ff +2\Lighting\Keys\o=#aa00ff +2\Lighting\Keys\p=#aa00ff +2\Lighting\Keys\pause=#aa00ff +2\Lighting\Keys\pgdn=#aa00ff +2\Lighting\Keys\pgup=#aa00ff +2\Lighting\Keys\play=#aa00ff +2\Lighting\Keys\prev=#aa00ff +2\Lighting\Keys\prtscn=#aa00ff +2\Lighting\Keys\q=#aa00ff +2\Lighting\Keys\quote=#aa00ff +2\Lighting\Keys\r=#aa00ff +2\Lighting\Keys\ralt=#aa00ff +2\Lighting\Keys\rbrace=#aa00ff +2\Lighting\Keys\rctrl=#aa00ff +2\Lighting\Keys\right=#aa00ff +2\Lighting\Keys\rmenu=#aa00ff +2\Lighting\Keys\rshift=#aa00ff +2\Lighting\Keys\rwin=#aa00ff +2\Lighting\Keys\s=#aa00ff +2\Lighting\Keys\scroll=#aa00ff +2\Lighting\Keys\slash=#aa00ff +2\Lighting\Keys\space=#aa00ff +2\Lighting\Keys\stop=#aa00ff +2\Lighting\Keys\t=#aa00ff +2\Lighting\Keys\tab=#aa00ff +2\Lighting\Keys\thumb=#aa00ff +2\Lighting\Keys\u=#aa00ff +2\Lighting\Keys\up=#aa00ff +2\Lighting\Keys\v=#aa00ff +2\Lighting\Keys\voldn=#ffffff +2\Lighting\Keys\volup=#ffffff +2\Lighting\Keys\w=#aa00ff +2\Lighting\Keys\wheel=#aa00ff +2\Lighting\Keys\x=#aa00ff +2\Lighting\Keys\y=#aa00ff +2\Lighting\Keys\z=#aa00ff +2\Lighting\Keys\zone1=#a900fe +2\Lighting\Keys\zone2=#a900fe +2\Lighting\Keys\zone3=#a900fe +2\Lighting\Keys\zone4=#a900fe +2\Lighting\Keys\zone5=#a900fe +2\Lighting\UseRealNames=true +2\Modified=191a9162 +2\Name=Trippy +2\Performance\AngleSnap=false +2\Performance\DPI\0=@Point(400 400) +2\Performance\DPI\0RGB=#ffff0000 +2\Performance\DPI\1=@Point(450 450) +2\Performance\DPI\1Disabled=false +2\Performance\DPI\1RGB=#ff4000ff +2\Performance\DPI\2=@Point(800 800) +2\Performance\DPI\2Disabled=false +2\Performance\DPI\2RGB=#ff8000ff +2\Performance\DPI\3=@Point(1500 1500) +2\Performance\DPI\3Disabled=false +2\Performance\DPI\3RGB=#ffff00ff +2\Performance\DPI\4=@Point(3000 3000) +2\Performance\DPI\4Disabled=false +2\Performance\DPI\4RGB=#ffff80ff +2\Performance\DPI\5=@Point(6000 6000) +2\Performance\DPI\5Disabled=false +2\Performance\DPI\5RGB=#ffffffff +2\Performance\DPI\6RGB=#ff000000 +2\Performance\DPI\CurIdx=1 +2\Performance\Indicators\0\Enable=false +2\Performance\Indicators\0\Hardware=0 +2\Performance\Indicators\0\RGB0=#ff00ff00 +2\Performance\Indicators\0\RGB1=#ff000000 +2\Performance\Indicators\1\Enable=false +2\Performance\Indicators\1\Hardware=0 +2\Performance\Indicators\1\RGB0=#ff00ff00 +2\Performance\Indicators\1\RGB1=#ff000000 +2\Performance\Indicators\2\Enable=false +2\Performance\Indicators\2\Hardware=0 +2\Performance\Indicators\2\RGB0=#ff00ff00 +2\Performance\Indicators\2\RGB1=#ff000000 +2\Performance\Indicators\3\Enable=true +2\Performance\Indicators\3\RGB0=#00ffffff +2\Performance\Indicators\3\RGB1=#ff000000 +2\Performance\Indicators\4\Enable=true +2\Performance\Indicators\4\RGB0=#ffff0000 +2\Performance\Indicators\4\RGB1=#ff000000 +2\Performance\Indicators\5\Enable=true +2\Performance\Indicators\5\RGB0=#ffff0000 +2\Performance\Indicators\5\RGB1=#ffffff00 +2\Performance\Indicators\5\RGB2=#ffffffff +2\Performance\Indicators\6\Enable=true +2\Performance\Indicators\6\RGB0=#ffffffff +2\Performance\Indicators\6\RGB1=#ff000000 +2\Performance\Indicators\7\Enable=true +2\Performance\Indicators\7\RGB0=#00ffffff +2\Performance\Indicators\7\RGB1=#ff000000 +2\Performance\Indicators\7\RGB2=#ff000000 +2\Performance\Indicators\DPI=true +2\Performance\LiftHeight=3 +2\Performance\MuteDevice=sink +2\WindowInfo\enabled=false +CurrentMode={11C11AE3-3195-4DFC-B8AC-2FEA703414E5} +HwModified=40b6f054 +ModeCount=3 +Modified=e1b94a7e +Name=Demo