Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b496e62bdc | ||
|
90f974344b |
@ -35,6 +35,7 @@ GENERIC_FEATURES = \
|
|||||||
HAPTIC \
|
HAPTIC \
|
||||||
KEY_LOCK \
|
KEY_LOCK \
|
||||||
KEY_OVERRIDE \
|
KEY_OVERRIDE \
|
||||||
|
LAMPARRAY \
|
||||||
LEADER \
|
LEADER \
|
||||||
MAGIC \
|
MAGIC \
|
||||||
MOUSEKEY \
|
MOUSEKEY \
|
||||||
|
@ -84,6 +84,7 @@
|
|||||||
* [EEPROM](feature_eeprom.md)
|
* [EEPROM](feature_eeprom.md)
|
||||||
* [Key Lock](feature_key_lock.md)
|
* [Key Lock](feature_key_lock.md)
|
||||||
* [Key Overrides](feature_key_overrides.md)
|
* [Key Overrides](feature_key_overrides.md)
|
||||||
|
* [LampArray](feature_lamparray.md)
|
||||||
* [Layers](feature_layers.md)
|
* [Layers](feature_layers.md)
|
||||||
* [One Shot Keys](one_shot_keys.md)
|
* [One Shot Keys](one_shot_keys.md)
|
||||||
* [OS Detection](feature_os_detection.md)
|
* [OS Detection](feature_os_detection.md)
|
||||||
|
36
docs/feature_lamparray.md
Normal file
36
docs/feature_lamparray.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# LampArray
|
||||||
|
|
||||||
|
Implements the open Human Interface Devices (HID) Lighting and Illumination standard.
|
||||||
|
|
||||||
|
> LampArray devices have one or more Lamps (i.e. lights/LEDs/bulbs, etc…) that can be directly manipulated; setting state (on/off), brightness and color (RGB).
|
||||||
|
|
||||||
|
Windows provides support for devices under [Dynamic Lighting](https://support.microsoft.com/en-us/windows/control-your-dynamic-lighting-devices-in-windows-8e8f22e3-e820-476c-8f9d-9ffc7b6ffcd2).
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Supported lighting frameworks:
|
||||||
|
|
||||||
|
* [RGB Matrix](feature_rgb_matrix.md)
|
||||||
|
|
||||||
|
Currently unsupported:
|
||||||
|
|
||||||
|
* Split keyboard
|
||||||
|
* VUSB
|
||||||
|
|
||||||
|
## Basic Configuration :id=basic-configuration
|
||||||
|
|
||||||
|
To enable this feature, add the following to your `rules.mk`:
|
||||||
|
|
||||||
|
LAMPARRAY_ENABLE = yes
|
||||||
|
|
||||||
|
This should provide an out of the box experience, inferred from the existing keyboard and lighting framework configuration.
|
||||||
|
|
||||||
|
## Advanced Configuration :id=advanced-configuration
|
||||||
|
|
||||||
|
To change the default behavior, you can use the following defines in your `config.h`
|
||||||
|
|
||||||
|
|Define |Default |Description |
|
||||||
|
|---------------|-------------|---------------|
|
||||||
|
|`X` |*Not defined*| |
|
||||||
|
|`Y` |*Not defined*| |
|
||||||
|
|`Z` |`7` | |
|
@ -85,6 +85,27 @@ def generate_matrix_masked(kb_info_json, config_h_lines):
|
|||||||
config_h_lines.append(generate_define('MATRIX_MASKED'))
|
config_h_lines.append(generate_define('MATRIX_MASKED'))
|
||||||
|
|
||||||
|
|
||||||
|
def generate_estimated_dimensions(kb_info_json, config_h_lines):
|
||||||
|
"""Try and guess physical keyboard dimensions from the declared layouts
|
||||||
|
"""
|
||||||
|
if 'layouts' in kb_info_json:
|
||||||
|
width = 0
|
||||||
|
height = 0
|
||||||
|
for layout_data in kb_info_json['layouts'].values():
|
||||||
|
for key in layout_data['layout']:
|
||||||
|
x = key.get('x', 0)
|
||||||
|
y = key.get('y', 0)
|
||||||
|
w = key.get('w', 1)
|
||||||
|
h = key.get('h', 1)
|
||||||
|
|
||||||
|
width = max(width, x + w)
|
||||||
|
height = max(height, y + h)
|
||||||
|
|
||||||
|
# sizes are in micrometers - assume 1u = 19.05mm
|
||||||
|
config_h_lines.append(generate_define('ESTIMATED_KEYBOARD_WIDTH', width * 19050))
|
||||||
|
config_h_lines.append(generate_define('ESTIMATED_KEYBOARD_HEIGHT', height * 19050))
|
||||||
|
|
||||||
|
|
||||||
def generate_config_items(kb_info_json, config_h_lines):
|
def generate_config_items(kb_info_json, config_h_lines):
|
||||||
"""Iterate through the info_config map to generate basic config values.
|
"""Iterate through the info_config map to generate basic config values.
|
||||||
"""
|
"""
|
||||||
@ -202,6 +223,8 @@ def generate_config_h(cli):
|
|||||||
|
|
||||||
generate_matrix_masked(kb_info_json, config_h_lines)
|
generate_matrix_masked(kb_info_json, config_h_lines)
|
||||||
|
|
||||||
|
generate_estimated_dimensions(kb_info_json, config_h_lines)
|
||||||
|
|
||||||
if 'matrix_pins' in kb_info_json:
|
if 'matrix_pins' in kb_info_json:
|
||||||
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
|
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
|
||||||
|
|
||||||
|
@ -135,6 +135,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#ifdef WPM_ENABLE
|
#ifdef WPM_ENABLE
|
||||||
# include "wpm.h"
|
# include "wpm.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
# include "lamparray.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static uint32_t last_input_modification_time = 0;
|
static uint32_t last_input_modification_time = 0;
|
||||||
uint32_t last_input_activity_time(void) {
|
uint32_t last_input_activity_time(void) {
|
||||||
@ -407,6 +410,9 @@ void keyboard_init(void) {
|
|||||||
#ifdef SPLIT_KEYBOARD
|
#ifdef SPLIT_KEYBOARD
|
||||||
split_pre_init();
|
split_pre_init();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
lamparray_init();
|
||||||
|
#endif
|
||||||
#ifdef ENCODER_ENABLE
|
#ifdef ENCODER_ENABLE
|
||||||
encoder_init();
|
encoder_init();
|
||||||
#endif
|
#endif
|
||||||
@ -629,6 +635,10 @@ void quantum_task(void) {
|
|||||||
#ifdef SECURE_ENABLE
|
#ifdef SECURE_ENABLE
|
||||||
secure_task();
|
secure_task();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
lamparray_task();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \brief Main task that is repeatedly called as fast as possible. */
|
/** \brief Main task that is repeatedly called as fast as possible. */
|
||||||
|
219
quantum/lamparray/lamparray.c
Normal file
219
quantum/lamparray/lamparray.c
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// Copyright 2024 QMK
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include <string.h> // for memcpy
|
||||||
|
#include "lamparray.h"
|
||||||
|
#include "lamparray_surface.h"
|
||||||
|
#include "keycodes.h"
|
||||||
|
#include "keymap_introspection.h"
|
||||||
|
#include "action_layer.h"
|
||||||
|
|
||||||
|
// Defaults are generated from info.json layout content
|
||||||
|
#ifndef LAMPARRAY_WIDTH
|
||||||
|
# define LAMPARRAY_WIDTH ESTIMATED_KEYBOARD_WIDTH
|
||||||
|
#endif
|
||||||
|
#ifndef LAMPARRAY_HEIGHT
|
||||||
|
# define LAMPARRAY_HEIGHT ESTIMATED_KEYBOARD_HEIGHT
|
||||||
|
#endif
|
||||||
|
#ifndef LAMPARRAY_DEPTH
|
||||||
|
# define LAMPARRAY_DEPTH 30000
|
||||||
|
#endif
|
||||||
|
#ifndef LAMPARRAY_KIND
|
||||||
|
# define LAMPARRAY_KIND LAMPARRAY_KIND_KEYBOARD
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef RGB_MATRIX_ENABLE
|
||||||
|
# include "rgb_matrix.h"
|
||||||
|
# define LAMPARRAY_RED_LEVELS 255
|
||||||
|
# define LAMPARRAY_GREEN_LEVELS 255
|
||||||
|
# define LAMPARRAY_BLUE_LEVELS 255
|
||||||
|
# define LAMPARRAY_INTENSITY_LEVELS 1
|
||||||
|
# define LAMPARRAY_LAMP_COUNT RGB_MATRIX_LED_COUNT
|
||||||
|
# define LAMPARRAY_UPDATE_INTERVAL (RGB_MATRIX_LED_FLUSH_LIMIT * 1000)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//****************************************************************************
|
||||||
|
// utils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Query a HID usage for a given location
|
||||||
|
*
|
||||||
|
* This can be requested while the user is changing layers. This is mitigated somewhat by assuming the default layer changes infrequently.
|
||||||
|
* This is currently accepted as a limitation as there is no method to invalidate the hosts view of the data.
|
||||||
|
*/
|
||||||
|
uint8_t lamparray_binding_at_keymap_location(uint8_t row, uint8_t col) {
|
||||||
|
uint16_t keycode = keycode_at_keymap_location(get_highest_layer(default_layer_state), row, col);
|
||||||
|
(void)keycode;
|
||||||
|
#if LAMPARRAY_KIND == LAMPARRAY_KIND_KEYBOARD
|
||||||
|
// Basic QMK keycodes currently map directly to Keyboard UsagePage so safe to return without added indirection
|
||||||
|
// Mousekeys are ignored due to values overlap Keyboard UsagePage
|
||||||
|
if (IS_BASIC_KEYCODE(keycode) || IS_MODIFIER_KEYCODE(keycode)) {
|
||||||
|
return keycode;
|
||||||
|
}
|
||||||
|
#elif LAMPARRAY_KIND == LAMPARRAY_KIND_MOUSE
|
||||||
|
// Usages from the Button UsagePage (0x09) in the range of Button1 (0x01) to Button5 (0x05) inclusive
|
||||||
|
if ((code) >= KC_MS_BTN1 && (code) <= KC_MS_BTN5) {
|
||||||
|
return keycode - KC_MS_BTN1 + 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//****************************************************************************
|
||||||
|
// cache
|
||||||
|
|
||||||
|
static uint8_t input_binding_cache[LAMPARRAY_LAMP_COUNT];
|
||||||
|
|
||||||
|
void lamparray_update_cache(void) {
|
||||||
|
for (uint8_t lamp_id = 0; lamp_id < LAMPARRAY_LAMP_COUNT; lamp_id++) {
|
||||||
|
input_binding_cache[lamp_id] = lamparray_get_lamp_binding_impl(lamp_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t lamparray_get_lamp_binding(uint16_t lamp_id) {
|
||||||
|
return input_binding_cache[lamp_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
//****************************************************************************
|
||||||
|
// queue
|
||||||
|
|
||||||
|
#ifndef LAMPARRAY_REQUEST_QUEUE_SIZE
|
||||||
|
# define LAMPARRAY_REQUEST_QUEUE_SIZE 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
universal_lamparray_response_t request_queue[LAMPARRAY_REQUEST_QUEUE_SIZE] = {0};
|
||||||
|
uint8_t queue_size = 0;
|
||||||
|
|
||||||
|
void lamparray_queue_request(universal_lamparray_response_t* report) {
|
||||||
|
// get next slot
|
||||||
|
universal_lamparray_response_t* target = &request_queue[queue_size++];
|
||||||
|
|
||||||
|
// copy data
|
||||||
|
memcpy(target, report, sizeof(universal_lamparray_response_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void lamparray_handle_queue(void) {
|
||||||
|
for (uint8_t id = 0; id < queue_size; id++) {
|
||||||
|
universal_lamparray_response_t* report = &request_queue[id];
|
||||||
|
switch (report->report_id) {
|
||||||
|
case LAMPARRAY_REPORT_ID_RANGE_UPDATE:
|
||||||
|
lamparray_set_range(&report->range_update);
|
||||||
|
break;
|
||||||
|
case LAMPARRAY_REPORT_ID_MULTI_UPDATE:
|
||||||
|
lamparray_set_items(&report->multi_update);
|
||||||
|
break;
|
||||||
|
case LAMPARRAY_REPORT_ID_CONTROL:
|
||||||
|
lamparray_set_control_response(report->autonomous);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queue_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//****************************************************************************
|
||||||
|
// impl
|
||||||
|
|
||||||
|
static uint16_t cur_lamp_id = 0;
|
||||||
|
static bool is_autonomous = true;
|
||||||
|
|
||||||
|
void lamparray_get_attributes(lamparray_attributes_t* data) {
|
||||||
|
data->lamp_count = LAMPARRAY_LAMP_COUNT;
|
||||||
|
data->update_interval = LAMPARRAY_UPDATE_INTERVAL;
|
||||||
|
data->kind = LAMPARRAY_KIND;
|
||||||
|
data->bounds.width = LAMPARRAY_WIDTH;
|
||||||
|
data->bounds.height = LAMPARRAY_HEIGHT;
|
||||||
|
data->bounds.depth = LAMPARRAY_DEPTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lamparray_get_attributes_response(lamparray_attributes_response_t* data) {
|
||||||
|
data->lamp_id = cur_lamp_id;
|
||||||
|
data->update_latency = 1000;
|
||||||
|
data->is_programmable = 1;
|
||||||
|
data->input_binding = lamparray_get_lamp_binding(cur_lamp_id);
|
||||||
|
|
||||||
|
data->levels.red = LAMPARRAY_RED_LEVELS;
|
||||||
|
data->levels.green = LAMPARRAY_GREEN_LEVELS;
|
||||||
|
data->levels.blue = LAMPARRAY_BLUE_LEVELS;
|
||||||
|
data->levels.intensity = LAMPARRAY_INTENSITY_LEVELS;
|
||||||
|
|
||||||
|
lamparray_get_lamp_impl(cur_lamp_id, data);
|
||||||
|
|
||||||
|
// Automatic address pointer incrementing - 26.8.1 LampAttributesRequestReport
|
||||||
|
cur_lamp_id++;
|
||||||
|
if (cur_lamp_id >= LAMPARRAY_LAMP_COUNT) cur_lamp_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lamparray_set_attributes_response(uint16_t lamp_id) {
|
||||||
|
cur_lamp_id = lamp_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lamparray_set_control_response(uint8_t autonomous) {
|
||||||
|
is_autonomous = !!autonomous;
|
||||||
|
|
||||||
|
lamparray_surface_enable(!autonomous);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lamparray_set_range(lamparray_range_update_t* data) {
|
||||||
|
// Any Lamp*UpdateReports can be ignored - 26.10.1 AutonomousMode
|
||||||
|
if (is_autonomous) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure IDs are within bounds
|
||||||
|
if ((data->start >= LAMPARRAY_LAMP_COUNT) || (data->end >= LAMPARRAY_LAMP_COUNT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t index = data->start; index <= data->end; index++) {
|
||||||
|
lamparray_surface_set_item(index, data->color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch update complete - 26.11 Updating Lamp State
|
||||||
|
if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) {
|
||||||
|
lamparray_surface_update_finished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lamparray_set_items(lamparray_multi_update_t* data) {
|
||||||
|
// Any Lamp*UpdateReports can be ignored - 26.10.1 AutonomousMode
|
||||||
|
if (is_autonomous) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure data is within bounds
|
||||||
|
if (data->count > LAMP_MULTI_UPDATE_LAMP_COUNT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < data->count; i++) {
|
||||||
|
// Ensure IDs are within bounds
|
||||||
|
if (data->ids[i] >= LAMPARRAY_LAMP_COUNT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lamparray_surface_set_item(data->ids[i], data->colors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch update complete - 26.11 Updating Lamp State
|
||||||
|
if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) {
|
||||||
|
lamparray_surface_update_finished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//****************************************************************************
|
||||||
|
// feature hooks
|
||||||
|
|
||||||
|
void lamparray_init(void) {
|
||||||
|
lamparray_update_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
void lamparray_task(void) {
|
||||||
|
lamparray_handle_queue();
|
||||||
|
|
||||||
|
// TODO: regen cache if dynamic keymap updated?
|
||||||
|
uint16_t temp = 0;
|
||||||
|
if (!++temp) lamparray_update_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: SRC += ...
|
||||||
|
#ifdef RGB_MATRIX_ENABLE
|
||||||
|
# include "lamparray_rgb_matrix.c"
|
||||||
|
#endif
|
159
quantum/lamparray/lamparray.h
Normal file
159
quantum/lamparray/lamparray.h
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2024 QMK
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "util.h" // PACKED
|
||||||
|
|
||||||
|
#define LAMPARRAY_REPORT_ID_ATTRIBUTES 0x01
|
||||||
|
#define LAMPARRAY_REPORT_ID_ATTRIBUTES_REQUEST 0x02
|
||||||
|
#define LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE 0x03
|
||||||
|
#define LAMPARRAY_REPORT_ID_MULTI_UPDATE 0x04
|
||||||
|
#define LAMPARRAY_REPORT_ID_RANGE_UPDATE 0x05
|
||||||
|
#define LAMPARRAY_REPORT_ID_CONTROL 0x06
|
||||||
|
|
||||||
|
// 26.2.1 LampArrayKind Values
|
||||||
|
#define LAMPARRAY_KIND_UNDEFINED 0x00
|
||||||
|
#define LAMPARRAY_KIND_KEYBOARD 0x01
|
||||||
|
#define LAMPARRAY_KIND_MOUSE 0x02
|
||||||
|
#define LAMPARRAY_KIND_GAMECONTROLLER 0x03
|
||||||
|
#define LAMPARRAY_KIND_PERIPHERAL 0x04
|
||||||
|
#define LAMPARRAY_KIND_SCENE 0x05
|
||||||
|
#define LAMPARRAY_KIND_NOTIFICATION 0x06
|
||||||
|
#define LAMPARRAY_KIND_CHASSIS 0x07
|
||||||
|
#define LAMPARRAY_KIND_WEARABLE 0x08
|
||||||
|
#define LAMPARRAY_KIND_FURNITURE 0x09
|
||||||
|
#define LAMPARRAY_KIND_ART 0x0A
|
||||||
|
|
||||||
|
// 26.3.1 LampPurposes Flags
|
||||||
|
#define LAMP_PURPOSE_CONTROL 0x01
|
||||||
|
#define LAMP_PURPOSE_ACCENT 0x02
|
||||||
|
#define LAMP_PURPOSE_BRANDING 0x04
|
||||||
|
#define LAMP_PURPOSE_STATUS 0x08
|
||||||
|
#define LAMP_PURPOSE_ILLUMINATION 0x10
|
||||||
|
#define LAMP_PURPOSE_PRESENTATION 0x20
|
||||||
|
|
||||||
|
// 26.4.1 LampUpdate Flags
|
||||||
|
#define LAMP_UPDATE_FLAG_COMPLETE 0x01
|
||||||
|
|
||||||
|
typedef struct PACKED {
|
||||||
|
uint8_t red;
|
||||||
|
uint8_t green;
|
||||||
|
uint8_t blue;
|
||||||
|
uint8_t intensity;
|
||||||
|
} lamp_state_t;
|
||||||
|
|
||||||
|
typedef struct PACKED {
|
||||||
|
uint16_t lamp_count;
|
||||||
|
struct {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t depth;
|
||||||
|
} bounds;
|
||||||
|
uint32_t kind;
|
||||||
|
uint32_t update_interval;
|
||||||
|
} lamparray_attributes_t;
|
||||||
|
|
||||||
|
typedef struct PACKED {
|
||||||
|
uint16_t lamp_id;
|
||||||
|
struct {
|
||||||
|
int32_t x;
|
||||||
|
int32_t y;
|
||||||
|
int32_t z;
|
||||||
|
} position;
|
||||||
|
int32_t update_latency;
|
||||||
|
int32_t purposes;
|
||||||
|
lamp_state_t levels;
|
||||||
|
uint8_t is_programmable;
|
||||||
|
uint8_t input_binding;
|
||||||
|
} lamparray_attributes_response_t;
|
||||||
|
|
||||||
|
typedef struct PACKED {
|
||||||
|
uint8_t flags;
|
||||||
|
uint16_t start;
|
||||||
|
uint16_t end;
|
||||||
|
lamp_state_t color;
|
||||||
|
} lamparray_range_update_t;
|
||||||
|
|
||||||
|
#define LAMP_MULTI_UPDATE_LAMP_COUNT 8
|
||||||
|
typedef struct PACKED {
|
||||||
|
uint8_t count;
|
||||||
|
uint8_t flags;
|
||||||
|
uint16_t ids[LAMP_MULTI_UPDATE_LAMP_COUNT];
|
||||||
|
lamp_state_t colors[LAMP_MULTI_UPDATE_LAMP_COUNT];
|
||||||
|
} lamparray_multi_update_t;
|
||||||
|
|
||||||
|
typedef struct PACKED universal_lamparray_response_t {
|
||||||
|
uint8_t report_id;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint16_t lamp_id;
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
uint8_t autonomous;
|
||||||
|
};
|
||||||
|
lamparray_range_update_t range_update;
|
||||||
|
lamparray_multi_update_t multi_update;
|
||||||
|
};
|
||||||
|
} universal_lamparray_response_t;
|
||||||
|
|
||||||
|
typedef struct PACKED lamparray_attributes_report_t {
|
||||||
|
uint8_t report_id;
|
||||||
|
lamparray_attributes_t attributes;
|
||||||
|
} lamparray_attributes_report_t;
|
||||||
|
|
||||||
|
typedef struct PACKED lamparray_attributes_response_report_t {
|
||||||
|
uint8_t report_id;
|
||||||
|
lamparray_attributes_response_t attributes_response;
|
||||||
|
} lamparray_attributes_response_report_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Gets LampArrayAttributesReport data
|
||||||
|
*/
|
||||||
|
void lamparray_get_attributes(lamparray_attributes_t* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Sets LampAttributesRequestReport data
|
||||||
|
*/
|
||||||
|
void lamparray_set_attributes_response(uint16_t lamp_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Gets LampAttributesResponseReport data
|
||||||
|
*/
|
||||||
|
void lamparray_get_attributes_response(lamparray_attributes_response_t* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Sets LampRangeUpdateReport data
|
||||||
|
*/
|
||||||
|
void lamparray_set_range(lamparray_range_update_t* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Sets LampMultiUpdateReport data
|
||||||
|
*/
|
||||||
|
void lamparray_set_items(lamparray_multi_update_t* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Sets LampArrayControlReport data
|
||||||
|
*/
|
||||||
|
void lamparray_set_control_response(uint8_t autonomous);
|
||||||
|
|
||||||
|
//****************************************************************************
|
||||||
|
// utils
|
||||||
|
|
||||||
|
uint8_t lamparray_binding_at_keymap_location(uint8_t row, uint8_t col);
|
||||||
|
|
||||||
|
void lamparray_queue_request(universal_lamparray_response_t* report);
|
||||||
|
|
||||||
|
//****************************************************************************
|
||||||
|
// feature hooks
|
||||||
|
|
||||||
|
void lamparray_init(void);
|
||||||
|
|
||||||
|
void lamparray_task(void);
|
||||||
|
|
||||||
|
//****************************************************************************
|
||||||
|
// lighting framework bindings
|
||||||
|
|
||||||
|
void lamparray_get_lamp_impl(uint16_t lamp_id, lamparray_attributes_response_t* data);
|
||||||
|
uint8_t lamparray_get_lamp_binding_impl(uint16_t lamp_id);
|
56
quantum/lamparray/lamparray_rgb_matrix.c
Normal file
56
quantum/lamparray/lamparray_rgb_matrix.c
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2024 QMK
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include "lamparray.h"
|
||||||
|
#include "lamparray_surface.h"
|
||||||
|
#include "rgb_matrix.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Get feature specific lamp info.
|
||||||
|
*
|
||||||
|
* Scales the LED config with the assumed RGB Matrix dimensions (224x64), for simplicity, as a completely flat device.
|
||||||
|
* Assumes all keys are either on the top or bottom of the resulting rectangle.
|
||||||
|
*/
|
||||||
|
__attribute__((weak)) void lamparray_get_lamp_impl(uint16_t lamp_id, lamparray_attributes_response_t* data) {
|
||||||
|
data->position.x = (LAMPARRAY_WIDTH / 224) * g_led_config.point[lamp_id].x;
|
||||||
|
data->position.y = (LAMPARRAY_HEIGHT / 64) * g_led_config.point[lamp_id].y;
|
||||||
|
|
||||||
|
if (g_led_config.flags[lamp_id] & LED_FLAG_UNDERGLOW) {
|
||||||
|
data->position.z = LAMPARRAY_DEPTH;
|
||||||
|
data->purposes = LAMP_PURPOSE_ACCENT;
|
||||||
|
} else if (g_led_config.flags[lamp_id] & LED_FLAG_INDICATOR) {
|
||||||
|
data->position.z = 0;
|
||||||
|
data->purposes = LAMP_PURPOSE_STATUS;
|
||||||
|
} else {
|
||||||
|
data->position.z = 0;
|
||||||
|
data->purposes = LAMP_PURPOSE_CONTROL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Query a HID usage for a given lamp
|
||||||
|
*/
|
||||||
|
__attribute__((weak)) uint8_t lamparray_get_lamp_binding_impl(uint16_t lamp_id) {
|
||||||
|
for (uint8_t i_row = 0; i_row < MATRIX_ROWS; i_row++) {
|
||||||
|
for (uint8_t i_col = 0; i_col < MATRIX_COLS; i_col++) {
|
||||||
|
if (g_led_config.matrix_co[i_row][i_col] == lamp_id) {
|
||||||
|
return lamparray_binding_at_keymap_location(i_row, i_col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: temporay binding of storage and render
|
||||||
|
#include "rgb_matrix/overlay.c"
|
||||||
|
|
||||||
|
void lamparray_surface_enable(bool enable) {
|
||||||
|
rgb_matrix_overlay_enable(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lamparray_surface_set_item(uint16_t index, lamp_state_t color) {
|
||||||
|
rgb_matrix_overlay_set(index, (rgba_t){color.red, color.green, color.blue, color.intensity ? 0xFF : 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
void lamparray_surface_update_finished(void) {
|
||||||
|
rgb_matrix_overlay_flush();
|
||||||
|
}
|
18
quantum/lamparray/lamparray_surface.h
Normal file
18
quantum/lamparray/lamparray_surface.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2023 QMK
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief lamparray_surface_enable
|
||||||
|
*/
|
||||||
|
void lamparray_surface_enable(bool enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief lamparray_surface_set_item
|
||||||
|
*/
|
||||||
|
void lamparray_surface_set_item(uint16_t index, lamp_state_t color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief lamparray_surface_update_finished
|
||||||
|
*/
|
||||||
|
void lamparray_surface_update_finished(void);
|
@ -233,6 +233,10 @@ extern layer_state_t layer_state;
|
|||||||
# include "process_repeat_key.h"
|
# include "process_repeat_key.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
# include "lamparray.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
void set_single_persistent_default_layer(uint8_t default_layer);
|
void set_single_persistent_default_layer(uint8_t default_layer);
|
||||||
|
|
||||||
#define IS_LAYER_ON(layer) layer_state_is(layer)
|
#define IS_LAYER_ON(layer) layer_state_is(layer)
|
||||||
|
59
quantum/rgb_matrix/overlay.c
Normal file
59
quantum/rgb_matrix/overlay.c
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2023 QMK
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// TODO: Pack "enabled" of single LED more efficiently than as alpha channels
|
||||||
|
typedef struct PACKED rgba_t {
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
uint8_t a;
|
||||||
|
} rgba_t;
|
||||||
|
|
||||||
|
#define OVERLAY_DOUBLE_BUFFER
|
||||||
|
#ifdef OVERLAY_DOUBLE_BUFFER
|
||||||
|
rgba_t overlay_buffer_a[RGB_MATRIX_LED_COUNT] = {0};
|
||||||
|
rgba_t overlay_buffer_b[RGB_MATRIX_LED_COUNT] = {0};
|
||||||
|
|
||||||
|
rgba_t* overlay_buffer_render = overlay_buffer_a;
|
||||||
|
rgba_t* overlay_buffer_write = overlay_buffer_b;
|
||||||
|
#else
|
||||||
|
rgba_t overlay_buffer[RGB_MATRIX_LED_COUNT] = {0};
|
||||||
|
|
||||||
|
rgba_t* overlay_buffer_render = overlay_buffer;
|
||||||
|
rgba_t* overlay_buffer_write = overlay_buffer;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool is_enabled = false;
|
||||||
|
|
||||||
|
void rgb_matrix_overlay_enable(bool enable) {
|
||||||
|
is_enabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rgb_matrix_overlay_set(uint8_t index, rgba_t color) {
|
||||||
|
overlay_buffer_write[index] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rgb_matrix_overlay_flush(void) {
|
||||||
|
#ifdef OVERLAY_DOUBLE_BUFFER
|
||||||
|
memcpy(overlay_buffer_render, overlay_buffer_write, sizeof(rgba_t) * RGB_MATRIX_LED_COUNT);
|
||||||
|
|
||||||
|
rgba_t* buffer_tmp = overlay_buffer_render;
|
||||||
|
overlay_buffer_render = overlay_buffer_write;
|
||||||
|
overlay_buffer_write = buffer_tmp;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rgb_matrix_indicators_user(void) {
|
||||||
|
if (!is_enabled) return true;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < RGB_MATRIX_LED_COUNT; i++) {
|
||||||
|
rgba_t* led = &overlay_buffer_render[i];
|
||||||
|
|
||||||
|
// Allow "transparent" to running effect?
|
||||||
|
if (led->a) rgb_matrix_set_color(i, led->r, led->g, led->b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
@ -51,6 +51,10 @@
|
|||||||
extern keymap_config_t keymap_config;
|
extern keymap_config_t keymap_config;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
# include "lamparray.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ---------------------------------------------------------
|
/* ---------------------------------------------------------
|
||||||
* Global interface variables and declarations
|
* Global interface variables and declarations
|
||||||
* ---------------------------------------------------------
|
* ---------------------------------------------------------
|
||||||
@ -217,6 +221,24 @@ static const USBEndpointConfig digitizer_ep_config = {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
/* LampArray endpoint state structure */
|
||||||
|
static USBInEndpointState lamparray_ep_state;
|
||||||
|
|
||||||
|
/* LampArray endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */
|
||||||
|
static const USBEndpointConfig lamparray_ep_config = {
|
||||||
|
USB_EP_MODE_TYPE_INTR, /* Interrupt EP */
|
||||||
|
NULL, /* SETUP packet notification callback */
|
||||||
|
dummy_usb_cb, /* IN notification callback */
|
||||||
|
NULL, /* OUT notification callback */
|
||||||
|
LAMPARRAY_EPSIZE, /* IN maximum packet size */
|
||||||
|
0, /* OUT maximum packet size */
|
||||||
|
&lamparray_ep_state, /* IN Endpoint state */
|
||||||
|
NULL, /* OUT endpoint state */
|
||||||
|
usb_lld_endpoint_fields /* USB driver specific endpoint fields */
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USB_ENDPOINTS_ARE_REORDERABLE
|
#ifdef USB_ENDPOINTS_ARE_REORDERABLE
|
||||||
typedef struct {
|
typedef struct {
|
||||||
size_t queue_capacity_in;
|
size_t queue_capacity_in;
|
||||||
@ -509,6 +531,9 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {
|
|||||||
#endif
|
#endif
|
||||||
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
|
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
|
||||||
usbInitEndpointI(usbp, DIGITIZER_IN_EPNUM, &digitizer_ep_config);
|
usbInitEndpointI(usbp, DIGITIZER_IN_EPNUM, &digitizer_ep_config);
|
||||||
|
#endif
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
usbInitEndpointI(usbp, LAMPARRAY_IN_EPNUM, &lamparray_ep_config);
|
||||||
#endif
|
#endif
|
||||||
for (int i = 0; i < NUM_USB_DRIVERS; i++) {
|
for (int i = 0; i < NUM_USB_DRIVERS; i++) {
|
||||||
#ifdef USB_ENDPOINTS_ARE_REORDERABLE
|
#ifdef USB_ENDPOINTS_ARE_REORDERABLE
|
||||||
@ -586,6 +611,19 @@ static void set_led_transfer_cb(USBDriver *usbp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
static universal_lamparray_response_t universal_lamparray_report_buf;
|
||||||
|
|
||||||
|
static void set_lamparray_transfer_cb(USBDriver *usbp) {
|
||||||
|
// handle directly to avoid sync issues with get
|
||||||
|
if (universal_lamparray_report_buf.report_id == LAMPARRAY_REPORT_ID_ATTRIBUTES_REQUEST) {
|
||||||
|
lamparray_set_attributes_response(universal_lamparray_report_buf.lamp_id);
|
||||||
|
} else {
|
||||||
|
lamparray_queue_request(&universal_lamparray_report_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool usb_requests_hook_cb(USBDriver *usbp) {
|
static bool usb_requests_hook_cb(USBDriver *usbp) {
|
||||||
usb_control_request_t *setup = (usb_control_request_t *)usbp->setup;
|
usb_control_request_t *setup = (usb_control_request_t *)usbp->setup;
|
||||||
|
|
||||||
@ -623,6 +661,24 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
|
|||||||
}
|
}
|
||||||
# endif
|
# endif
|
||||||
#endif /* SHARED_EP_ENABLE */
|
#endif /* SHARED_EP_ENABLE */
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
case LAMPARRAY_INTERFACE:
|
||||||
|
switch (setup->wValue.lbyte) {
|
||||||
|
case LAMPARRAY_REPORT_ID_ATTRIBUTES:
|
||||||
|
static lamparray_attributes_report_t ret = {.report_id = LAMPARRAY_REPORT_ID_ATTRIBUTES};
|
||||||
|
lamparray_get_attributes(&ret.attributes);
|
||||||
|
|
||||||
|
usbSetupTransfer(usbp, (uint8_t *)&ret, sizeof(ret), NULL);
|
||||||
|
return TRUE;
|
||||||
|
case LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE:
|
||||||
|
static lamparray_attributes_response_report_t res = {.report_id = LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE};
|
||||||
|
lamparray_get_attributes_response(&res.attributes_response);
|
||||||
|
|
||||||
|
usbSetupTransfer(usbp, (uint8_t *)&res, sizeof(res), NULL);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
universal_report_blank.report_id = setup->wValue.lbyte;
|
universal_report_blank.report_id = setup->wValue.lbyte;
|
||||||
usbSetupTransfer(usbp, (uint8_t *)&universal_report_blank, setup->wLength, NULL);
|
usbSetupTransfer(usbp, (uint8_t *)&universal_report_blank, setup->wLength, NULL);
|
||||||
@ -653,6 +709,18 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
|
|||||||
#endif
|
#endif
|
||||||
usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_led_transfer_cb);
|
usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_led_transfer_cb);
|
||||||
return true;
|
return true;
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
case LAMPARRAY_INTERFACE:
|
||||||
|
switch (setup->wValue.lbyte) {
|
||||||
|
case LAMPARRAY_REPORT_ID_ATTRIBUTES_REQUEST:
|
||||||
|
case LAMPARRAY_REPORT_ID_RANGE_UPDATE:
|
||||||
|
case LAMPARRAY_REPORT_ID_MULTI_UPDATE:
|
||||||
|
case LAMPARRAY_REPORT_ID_CONTROL:
|
||||||
|
usbSetupTransfer(usbp, (uint8_t *)&universal_lamparray_report_buf, sizeof(universal_lamparray_report_buf), set_lamparray_transfer_cb);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -67,6 +67,10 @@
|
|||||||
# include "raw_hid.h"
|
# include "raw_hid.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
# include "lamparray.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
uint8_t keyboard_idle = 0;
|
uint8_t keyboard_idle = 0;
|
||||||
/* 0: Boot Protocol, 1: Report Protocol(default) */
|
/* 0: Boot Protocol, 1: Report Protocol(default) */
|
||||||
uint8_t keyboard_protocol = 1;
|
uint8_t keyboard_protocol = 1;
|
||||||
@ -409,6 +413,11 @@ void EVENT_USB_Device_ConfigurationChanged(void) {
|
|||||||
ConfigSuccess &= Endpoint_ConfigureEndpoint((DIGITIZER_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, DIGITIZER_EPSIZE, 1);
|
ConfigSuccess &= Endpoint_ConfigureEndpoint((DIGITIZER_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, DIGITIZER_EPSIZE, 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
/* Setup LampArray endpoint */
|
||||||
|
ConfigSuccess &= Endpoint_ConfigureEndpoint((LAMPARRAY_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, LAMPARRAY_EPSIZE, 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
usb_device_state_set_configuration(USB_DeviceState == DEVICE_STATE_Configured, USB_Device_ConfigurationNumber);
|
usb_device_state_set_configuration(USB_DeviceState == DEVICE_STATE_Configured, USB_Device_ConfigurationNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,6 +455,26 @@ void EVENT_USB_Device_ControlRequest(void) {
|
|||||||
ReportData = (uint8_t *)&keyboard_report_sent;
|
ReportData = (uint8_t *)&keyboard_report_sent;
|
||||||
ReportSize = sizeof(keyboard_report_sent);
|
ReportSize = sizeof(keyboard_report_sent);
|
||||||
break;
|
break;
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
case LAMPARRAY_INTERFACE:
|
||||||
|
switch ((USB_ControlRequest.wValue & 0xFF)) {
|
||||||
|
case LAMPARRAY_REPORT_ID_ATTRIBUTES:
|
||||||
|
static lamparray_attributes_report_t ret = {.report_id = LAMPARRAY_REPORT_ID_ATTRIBUTES};
|
||||||
|
lamparray_get_attributes(&ret.attributes);
|
||||||
|
|
||||||
|
ReportData = (uint8_t *)&ret;
|
||||||
|
ReportSize = sizeof(ret);
|
||||||
|
break;
|
||||||
|
case LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE:
|
||||||
|
static lamparray_attributes_response_report_t res = {.report_id = LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE};
|
||||||
|
lamparray_get_attributes_response(&res.attributes_response);
|
||||||
|
|
||||||
|
ReportData = (uint8_t *)&res;
|
||||||
|
ReportSize = sizeof(res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write the report data to the control endpoint */
|
/* Write the report data to the control endpoint */
|
||||||
@ -481,6 +510,26 @@ void EVENT_USB_Device_ControlRequest(void) {
|
|||||||
Endpoint_ClearOUT();
|
Endpoint_ClearOUT();
|
||||||
Endpoint_ClearStatusStage();
|
Endpoint_ClearStatusStage();
|
||||||
break;
|
break;
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
case LAMPARRAY_INTERFACE:
|
||||||
|
Endpoint_ClearSETUP();
|
||||||
|
|
||||||
|
while (!(Endpoint_IsINReady()))
|
||||||
|
;
|
||||||
|
|
||||||
|
static universal_lamparray_response_t universal_lamparray_report_buf;
|
||||||
|
|
||||||
|
Endpoint_Read_Control_Stream_LE(&universal_lamparray_report_buf, USB_ControlRequest.wLength);
|
||||||
|
Endpoint_ClearIN();
|
||||||
|
|
||||||
|
// handle directly to avoid sync issues with get
|
||||||
|
if (universal_lamparray_report_buf.report_id == LAMPARRAY_REPORT_ID_ATTRIBUTES_REQUEST) {
|
||||||
|
lamparray_set_attributes_response(universal_lamparray_report_buf.lamp_id);
|
||||||
|
} else {
|
||||||
|
lamparray_queue_request(&universal_lamparray_report_buf);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,6 +432,173 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM ConsoleReport[] = {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
const USB_Descriptor_HIDReport_Datatype_t PROGMEM LampArrayReport[] = {
|
||||||
|
HID_RI_USAGE_PAGE(8, 0x59), // Usage Page (Lighting and Illumination)
|
||||||
|
HID_RI_USAGE(8, 0x01), // Usage (Lamp Array)
|
||||||
|
HID_RI_COLLECTION(8, 0x01), // Collection (Application)
|
||||||
|
HID_RI_REPORT_ID(8, 1), // Report ID (1)
|
||||||
|
HID_RI_USAGE(8, 0x02), // Usage (Lamp Array Attributes Report)
|
||||||
|
HID_RI_COLLECTION(8, 0x02), // Collection (Logical)
|
||||||
|
HID_RI_USAGE(8, 0x03), // Usage (Lamp Count)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1)
|
||||||
|
HID_RI_FEATURE(8, 0x03), // Feature (Cnst,Var,Abs)
|
||||||
|
HID_RI_USAGE(8, 0x04), // Usage (Bounding Box Width In Micrometers)
|
||||||
|
HID_RI_USAGE(8, 0x05), // Usage (Bounding Box Height In Micrometers)
|
||||||
|
HID_RI_USAGE(8, 0x06), // Usage (Bounding Box Depth In Micrometers)
|
||||||
|
HID_RI_USAGE(8, 0x07), // Usage (Lamp Array Kind)
|
||||||
|
HID_RI_USAGE(8, 0x08), // Usage (Min Update Interval In Microseconds)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(32, 0x7FFFFFFF), // Logical Maximum (2147483647)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x20), // Report Size (32)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x05), // Report Count (5)
|
||||||
|
HID_RI_FEATURE(8, 0x03), // Feature (Cnst,Var,Abs)
|
||||||
|
HID_RI_END_COLLECTION(0), // End Collection
|
||||||
|
HID_RI_REPORT_ID(8, 2), // Report ID (2)
|
||||||
|
HID_RI_USAGE(8, 0x20), // Usage (Lamp Attributes Request Report)
|
||||||
|
HID_RI_COLLECTION(8, 0x02), // Collection (Logical)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_END_COLLECTION(0), // End Collection
|
||||||
|
HID_RI_REPORT_ID(8, 3), // Report ID (3)
|
||||||
|
HID_RI_USAGE(8, 0x22), // Usage (Lamp Attributes Response Report)
|
||||||
|
HID_RI_COLLECTION(8, 0x02), // Collection (Logical)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_USAGE(8, 0x23), // Usage (Position X In Micrometers)
|
||||||
|
HID_RI_USAGE(8, 0x24), // Usage (Position Y In Micrometers)
|
||||||
|
HID_RI_USAGE(8, 0x25), // Usage (Position Z In Micrometers)
|
||||||
|
HID_RI_USAGE(8, 0x27), // Usage (Update Latency In Microseconds)
|
||||||
|
HID_RI_USAGE(8, 0x26), // Usage (Lamp Purposes)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(32, 0x7FFFFFFF), // Logical Maximum (2147483647)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x20), // Report Size (32)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x05), // Report Count (5)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_USAGE(8, 0x28), // Usage (Red Level Count)
|
||||||
|
HID_RI_USAGE(8, 0x29), // Usage (Green Level Count)
|
||||||
|
HID_RI_USAGE(8, 0x2a), // Usage (Blue Level Count)
|
||||||
|
HID_RI_USAGE(8, 0x2b), // Usage (Intensity Level Count)
|
||||||
|
HID_RI_USAGE(8, 0x2c), // Usage (Is Programmable)
|
||||||
|
HID_RI_USAGE(8, 0x2d), // Usage (Input Binding)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), // Logical Maximum (255)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x06), // Report Count (6)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_END_COLLECTION(0), // End Collection
|
||||||
|
HID_RI_REPORT_ID(8, 4), // Report ID (4)
|
||||||
|
HID_RI_USAGE(8, 0x50), // Usage (Lamp Multi Update Report)
|
||||||
|
HID_RI_COLLECTION(8, 0x02), // Collection (Logical)
|
||||||
|
HID_RI_USAGE(8, 0x03), // Usage (Lamp Count)
|
||||||
|
HID_RI_USAGE(8, 0x55), // Usage (Lamp Update Flags)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(8, 0x08), // Logical Maximum (8)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x02), // Report Count (2)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_USAGE(8, 0x21), // Usage (Lamp Id)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x08), // Report Count (8)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), // Logical Maximum (255)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x20), // Report Count (32)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_END_COLLECTION(0), // End Collection
|
||||||
|
HID_RI_REPORT_ID(8, 5), // Report ID (5)
|
||||||
|
HID_RI_USAGE(8, 0x60), // Usage (Lamp Range Update Report)
|
||||||
|
HID_RI_COLLECTION(8, 0x02), // Collection (Logical)
|
||||||
|
HID_RI_USAGE(8, 0x55), // Usage (Lamp Update Flags)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(8, 0x08), // Logical Maximum (8)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_USAGE(8, 0x61), // Usage (Lamp Id Start)
|
||||||
|
HID_RI_USAGE(8, 0x62), // Usage (Lamp Id End)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x02), // Report Count (2)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel)
|
||||||
|
HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), // Logical Maximum (255)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x04), // Report Count (4)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_END_COLLECTION(0), // End Collection
|
||||||
|
HID_RI_REPORT_ID(8, 6), // Report ID (6)
|
||||||
|
HID_RI_USAGE(8, 0x70), // Usage (Lamp Array Control Report)
|
||||||
|
HID_RI_COLLECTION(8, 0x02), // Collection (Logical)
|
||||||
|
HID_RI_USAGE(8, 0x71), // Usage (Autonomous Mode)
|
||||||
|
HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0)
|
||||||
|
HID_RI_LOGICAL_MAXIMUM(8, 0x01), // Logical Maximum (1)
|
||||||
|
HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8)
|
||||||
|
HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1)
|
||||||
|
HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs)
|
||||||
|
HID_RI_END_COLLECTION(0), // End Collection
|
||||||
|
HID_RI_END_COLLECTION(0) // End Collection
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Device descriptor
|
* Device descriptor
|
||||||
*/
|
*/
|
||||||
@ -1050,6 +1217,46 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = {
|
|||||||
.PollingIntervalMS = USB_POLLING_INTERVAL_MS
|
.PollingIntervalMS = USB_POLLING_INTERVAL_MS
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
/*
|
||||||
|
* LampArray
|
||||||
|
*/
|
||||||
|
.LampArray_Interface = {
|
||||||
|
.Header = {
|
||||||
|
.Size = sizeof(USB_Descriptor_Interface_t),
|
||||||
|
.Type = DTYPE_Interface
|
||||||
|
},
|
||||||
|
.InterfaceNumber = LAMPARRAY_INTERFACE,
|
||||||
|
.AlternateSetting = 0x00,
|
||||||
|
.TotalEndpoints = 1,
|
||||||
|
.Class = HID_CSCP_HIDClass,
|
||||||
|
.SubClass = HID_CSCP_NonBootSubclass,
|
||||||
|
.Protocol = HID_CSCP_NonBootProtocol,
|
||||||
|
.InterfaceStrIndex = NO_DESCRIPTOR
|
||||||
|
},
|
||||||
|
.LampArray_HID = {
|
||||||
|
.Header = {
|
||||||
|
.Size = sizeof(USB_HID_Descriptor_HID_t),
|
||||||
|
.Type = HID_DTYPE_HID
|
||||||
|
},
|
||||||
|
.HIDSpec = VERSION_BCD(1, 1, 1),
|
||||||
|
.CountryCode = 0x00,
|
||||||
|
.TotalReportDescriptors = 1,
|
||||||
|
.HIDReportType = HID_DTYPE_Report,
|
||||||
|
.HIDReportLength = sizeof(LampArrayReport)
|
||||||
|
},
|
||||||
|
.LampArray_INEndpoint = {
|
||||||
|
.Header = {
|
||||||
|
.Size = sizeof(USB_Descriptor_Endpoint_t),
|
||||||
|
.Type = DTYPE_Endpoint
|
||||||
|
},
|
||||||
|
.EndpointAddress = (ENDPOINT_DIR_IN | LAMPARRAY_IN_EPNUM),
|
||||||
|
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
|
||||||
|
.EndpointSize = LAMPARRAY_EPSIZE,
|
||||||
|
.PollingIntervalMS = 0x01
|
||||||
|
},
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1198,6 +1405,13 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
case LAMPARRAY_INTERFACE:
|
||||||
|
Address = &ConfigurationDescriptor.LampArray_HID;
|
||||||
|
Size = sizeof(USB_HID_Descriptor_HID_t);
|
||||||
|
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -1254,6 +1468,13 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
|
|||||||
Size = sizeof(DigitizerReport);
|
Size = sizeof(DigitizerReport);
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
case LAMPARRAY_INTERFACE:
|
||||||
|
Address = &LampArrayReport;
|
||||||
|
Size = sizeof(LampArrayReport);
|
||||||
|
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -145,6 +145,13 @@ typedef struct {
|
|||||||
USB_HID_Descriptor_HID_t Digitizer_HID;
|
USB_HID_Descriptor_HID_t Digitizer_HID;
|
||||||
USB_Descriptor_Endpoint_t Digitizer_INEndpoint;
|
USB_Descriptor_Endpoint_t Digitizer_INEndpoint;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
// HID LampArray Interface
|
||||||
|
USB_Descriptor_Interface_t LampArray_Interface;
|
||||||
|
USB_HID_Descriptor_HID_t LampArray_HID;
|
||||||
|
USB_Descriptor_Endpoint_t LampArray_INEndpoint;
|
||||||
|
#endif
|
||||||
} USB_Descriptor_Configuration_t;
|
} USB_Descriptor_Configuration_t;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -194,6 +201,11 @@ enum usb_interfaces {
|
|||||||
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
|
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
|
||||||
DIGITIZER_INTERFACE,
|
DIGITIZER_INTERFACE,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
LAMPARRAY_INTERFACE,
|
||||||
|
#endif
|
||||||
|
|
||||||
TOTAL_INTERFACES
|
TOTAL_INTERFACES
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -281,6 +293,10 @@ enum usb_endpoints {
|
|||||||
# define DIGITIZER_IN_EPNUM SHARED_IN_EPNUM
|
# define DIGITIZER_IN_EPNUM SHARED_IN_EPNUM
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef LAMPARRAY_ENABLE
|
||||||
|
LAMPARRAY_IN_EPNUM = NEXT_EPNUM,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef PROTOCOL_LUFA
|
#ifdef PROTOCOL_LUFA
|
||||||
@ -307,5 +323,6 @@ enum usb_endpoints {
|
|||||||
#define CDC_EPSIZE 16
|
#define CDC_EPSIZE 16
|
||||||
#define JOYSTICK_EPSIZE 8
|
#define JOYSTICK_EPSIZE 8
|
||||||
#define DIGITIZER_EPSIZE 8
|
#define DIGITIZER_EPSIZE 8
|
||||||
|
#define LAMPARRAY_EPSIZE 8
|
||||||
|
|
||||||
uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress);
|
uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user