mirror of
https://github.com/zmkfirmware/zmk.git
synced 2026-03-23 06:25:18 -05:00
Compare commits
3 Commits
dependabot
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
197f899777 | ||
|
|
a1aca03aa0 | ||
|
|
e4fb39d4a6 |
4
.github/workflows/ble-test.yml
vendored
4
.github/workflows/ble-test.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Find test directories
|
- name: Find test directories
|
||||||
id: test-dirs
|
id: test-dirs
|
||||||
run: |
|
run: |
|
||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
image: docker.io/zmkfirmware/zmk-build-arm:4.1
|
image: docker.io/zmkfirmware/zmk-build-arm:4.1
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Cache west modules
|
- name: Cache west modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
|
|||||||
4
.github/workflows/build-user-config.yml
vendored
4
.github/workflows/build-user-config.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
has_valid_build_matrix: ${{ steps.fetch.outputs.has_valid_build_matrix }}
|
has_valid_build_matrix: ${{ steps.fetch.outputs.has_valid_build_matrix }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Fetch Build Matrix
|
- name: Fetch Build Matrix
|
||||||
id: fetch
|
id: fetch
|
||||||
@@ -67,7 +67,7 @@ jobs:
|
|||||||
curl -fsSL https://deb.nodesource.com/setup_22.x | bash && apt install -y nodejs
|
curl -fsSL https://deb.nodesource.com/setup_22.x | bash && apt install -y nodejs
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Create build directory
|
- name: Create build directory
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
include: ${{ fromJSON(needs.compile-matrix.outputs.include-list) }}
|
include: ${{ fromJSON(needs.compile-matrix.outputs.include-list) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Cache west modules
|
- name: Cache west modules
|
||||||
@@ -187,7 +187,7 @@ jobs:
|
|||||||
core-include: ${{ steps.core-list.outputs.result }}
|
core-include: ${{ steps.core-list.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
@@ -218,7 +218,7 @@ jobs:
|
|||||||
boards-include: ${{ steps.boards-list.outputs.result }}
|
boards-include: ${{ steps.boards-list.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@@ -346,7 +346,7 @@ jobs:
|
|||||||
organized-metadata: ${{ steps.organize-metadata.outputs.result }}
|
organized-metadata: ${{ steps.organize-metadata.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
@@ -428,7 +428,7 @@ jobs:
|
|||||||
core-changes: ${{ steps.core-changes.outputs.result }}
|
core-changes: ${{ steps.core-changes.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: tj-actions/changed-files@9200e69727eb73eb060652b19946b8a2fdfb654b # pin to v45.0.8 due to https://github.com/tj-actions/changed-files/issues/2463 https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised
|
- uses: tj-actions/changed-files@9200e69727eb73eb060652b19946b8a2fdfb654b # pin to v45.0.8 due to https://github.com/tj-actions/changed-files/issues/2463 https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised
|
||||||
|
|||||||
4
.github/workflows/doc-checks.yml
vendored
4
.github/workflows/doc-checks.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
- uses: bahmutov/npm-install@v1
|
- uses: bahmutov/npm-install@v1
|
||||||
with:
|
with:
|
||||||
working-directory: docs
|
working-directory: docs
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
typecheck:
|
typecheck:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
- uses: bahmutov/npm-install@v1
|
- uses: bahmutov/npm-install@v1
|
||||||
with:
|
with:
|
||||||
working-directory: docs
|
working-directory: docs
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
container:
|
container:
|
||||||
image: docker.io/zmkfirmware/zmk-dev-arm:4.1
|
image: docker.io/zmkfirmware/zmk-dev-arm:4.1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install --break-system-packages -r app/scripts/requirements.txt
|
run: pip install --break-system-packages -r app/scripts/requirements.txt
|
||||||
- name: West init
|
- name: West init
|
||||||
|
|||||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
|||||||
pre-commit:
|
pre-commit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|||||||
2
.github/workflows/release-please.yml
vendored
2
.github/workflows/release-please.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
ZMK_RELEASE_PLEASE_TOKEN: ${{ secrets.ZMK_RELEASE_PLEASE_TOKEN }}
|
ZMK_RELEASE_PLEASE_TOKEN: ${{ secrets.ZMK_RELEASE_PLEASE_TOKEN }}
|
||||||
VERSION: v${{ needs.handle-commit.outputs.major }}.${{ needs.handle-commit.outputs.minor }}
|
VERSION: v${{ needs.handle-commit.outputs.major }}.${{ needs.handle-commit.outputs.minor }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Create major.minor branch
|
- name: Create major.minor branch
|
||||||
if: ${{ needs.handle-commit.outputs.patch == '0' }}
|
if: ${{ needs.handle-commit.outputs.patch == '0' }}
|
||||||
|
|||||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Find test directories
|
- name: Find test directories
|
||||||
id: test-dirs
|
id: test-dirs
|
||||||
run: |
|
run: |
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
image: docker.io/zmkfirmware/zmk-build-arm:4.1
|
image: docker.io/zmkfirmware/zmk-build-arm:4.1
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: Cache west modules
|
- name: Cache west modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/events/battery
|
|||||||
target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/battery.c)
|
target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/battery.c)
|
||||||
|
|
||||||
target_sources_ifdef(CONFIG_ZMK_HID_INDICATORS app PRIVATE src/events/hid_indicators_changed.c)
|
target_sources_ifdef(CONFIG_ZMK_HID_INDICATORS app PRIVATE src/events/hid_indicators_changed.c)
|
||||||
|
add_subdirectory_ifdef(CONFIG_ZMK_HID_INDICATORS src/indicators)
|
||||||
|
|
||||||
target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c)
|
target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c)
|
||||||
add_subdirectory_ifdef(CONFIG_ZMK_SPLIT src/split)
|
add_subdirectory_ifdef(CONFIG_ZMK_SPLIT src/split)
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ config ZMK_HID_INDICATORS
|
|||||||
Enable HID indicators, used for detecting state of Caps/Scroll/Num Lock,
|
Enable HID indicators, used for detecting state of Caps/Scroll/Num Lock,
|
||||||
Kata, and Compose.
|
Kata, and Compose.
|
||||||
|
|
||||||
|
rsource "src/indicators/Kconfig"
|
||||||
|
|
||||||
config ZMK_HID_SEPARATE_MOD_RELEASE_REPORT
|
config ZMK_HID_SEPARATE_MOD_RELEASE_REPORT
|
||||||
bool "Release Modifiers Separately"
|
bool "Release Modifiers Separately"
|
||||||
help
|
help
|
||||||
|
|||||||
5
app/boards/nordic/nrf52840dongle/Kconfig.nrf52840dongle
Normal file
5
app/boards/nordic/nrf52840dongle/Kconfig.nrf52840dongle
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Copyright (c) 2026 The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
config BOARD_NRF52840DONGLE
|
||||||
|
select ZMK_BOARD_COMPAT if BOARD_NRF52840DONGLE_NRF52840_ZMK
|
||||||
37
app/dts/bindings/indicators/zmk,indicator-leds.yaml
Normal file
37
app/dts/bindings/indicators/zmk,indicator-leds.yaml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Copyright (c) 2025, The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
description: Display the states of HID indicators using LEDs
|
||||||
|
|
||||||
|
compatible: "zmk,indicator-leds"
|
||||||
|
|
||||||
|
child-binding:
|
||||||
|
properties:
|
||||||
|
leds:
|
||||||
|
type: phandles
|
||||||
|
required: true
|
||||||
|
description: One or more LED devices to control
|
||||||
|
|
||||||
|
indicator:
|
||||||
|
type: int
|
||||||
|
required: true
|
||||||
|
description: HID_INDICATOR_* value to indicate (see dt-bindings/zmk/hid_indicators.h)
|
||||||
|
|
||||||
|
active-brightness:
|
||||||
|
type: int
|
||||||
|
description: LED brightness in percent when the indicator is active
|
||||||
|
default: 100
|
||||||
|
|
||||||
|
inactive-brightness:
|
||||||
|
type: int
|
||||||
|
description: LED brightness in percent when the indicator is not active
|
||||||
|
default: 0
|
||||||
|
|
||||||
|
disconnected-brightness:
|
||||||
|
type: int
|
||||||
|
description: LED brightness in percent when the keyboard is not connected to any device
|
||||||
|
default: 0
|
||||||
|
|
||||||
|
on-while-idle:
|
||||||
|
type: boolean
|
||||||
|
description: Keep LEDs enabled even when the keyboard is idle and on battery power
|
||||||
18
app/include/dt-bindings/zmk/hid_indicators.h
Normal file
18
app/include/dt-bindings/zmk/hid_indicators.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dt-bindings/zmk/hid_usage.h>
|
||||||
|
|
||||||
|
// hid.h defines HID_USAGE_LED_NUM_LOCK as the minimum value, so that is bit
|
||||||
|
// zero in the report, and all other indicators are relative to that.
|
||||||
|
|
||||||
|
#define HID_INDICATOR_NUM_LOCK (1 << (HID_USAGE_LED_NUM_LOCK - HID_USAGE_LED_NUM_LOCK))
|
||||||
|
#define HID_INDICATOR_CAPS_LOCK (1 << (HID_USAGE_LED_CAPS_LOCK - HID_USAGE_LED_NUM_LOCK))
|
||||||
|
#define HID_INDICATOR_SCROLL_LOCK (1 << (HID_USAGE_LED_SCROLL_LOCK - HID_USAGE_LED_NUM_LOCK))
|
||||||
|
#define HID_INDICATOR_COMPOSE (1 << (HID_USAGE_LED_COMPOSE - HID_USAGE_LED_NUM_LOCK))
|
||||||
|
#define HID_INDICATOR_KANA (1 << (HID_USAGE_LED_KANA - HID_USAGE_LED_NUM_LOCK))
|
||||||
4
app/src/indicators/CMakeLists.txt
Normal file
4
app/src/indicators/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Copyright (c) 2025 The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
target_sources_ifdef(CONFIG_ZMK_INDICATOR_LEDS app PRIVATE indicator_leds.c)
|
||||||
4
app/src/indicators/Kconfig
Normal file
4
app/src/indicators/Kconfig
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Copyright (c) 2026 The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
rsource "Kconfig.indicator_leds"
|
||||||
19
app/src/indicators/Kconfig.indicator_leds
Normal file
19
app/src/indicators/Kconfig.indicator_leds
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Copyright (c) 2025 The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
config ZMK_INDICATOR_LEDS
|
||||||
|
bool
|
||||||
|
default y
|
||||||
|
depends on DT_HAS_ZMK_INDICATOR_LEDS_ENABLED
|
||||||
|
select LED
|
||||||
|
select ZMK_HID_INDICATORS
|
||||||
|
|
||||||
|
if ZMK_INDICATOR_LEDS
|
||||||
|
|
||||||
|
config ZMK_INDICATOR_LEDS_INIT_PRIORITY
|
||||||
|
int "ZMK indicator LED initialization priority"
|
||||||
|
default 91
|
||||||
|
help
|
||||||
|
System initialization priority for ZMK indicator LEDs.
|
||||||
|
|
||||||
|
endif # ZMK_INDICATOR_LEDS
|
||||||
226
app/src/indicators/indicator_leds.c
Normal file
226
app/src/indicators/indicator_leds.c
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT zmk_indicator_leds
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/drivers/led.h>
|
||||||
|
#include <zephyr/pm/device.h>
|
||||||
|
|
||||||
|
#include <zmk/endpoints.h>
|
||||||
|
#include <zmk/event_manager.h>
|
||||||
|
#include <zmk/hid_indicators.h>
|
||||||
|
#include <zmk/usb.h>
|
||||||
|
#include <zmk/events/activity_state_changed.h>
|
||||||
|
#include <zmk/events/endpoint_changed.h>
|
||||||
|
#include <zmk/events/hid_indicators_changed.h>
|
||||||
|
#include <zmk/events/usb_conn_state_changed.h>
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
|
struct indicator_led_child_config {
|
||||||
|
size_t leds_len;
|
||||||
|
const struct led_dt_spec *leds;
|
||||||
|
|
||||||
|
zmk_hid_indicators_t indicator;
|
||||||
|
uint8_t active_brightness;
|
||||||
|
uint8_t inactive_brightness;
|
||||||
|
uint8_t disconnected_brightness;
|
||||||
|
bool on_while_idle;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct indicator_led_config {
|
||||||
|
size_t indicators_len;
|
||||||
|
const struct indicator_led_child_config *indicators;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct indicator_led_data {
|
||||||
|
enum zmk_activity_state activity_state;
|
||||||
|
zmk_hid_indicators_t indicators;
|
||||||
|
bool usb_powered;
|
||||||
|
bool pm_suspended;
|
||||||
|
bool endpoint_connected;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool is_led_disabled(const struct indicator_led_child_config *config,
|
||||||
|
const struct indicator_led_data *data) {
|
||||||
|
// LEDs should always be off if the device is suspended.
|
||||||
|
if (data->pm_suspended) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the keyboard is powered, LEDs don't need to be disabled to save power.
|
||||||
|
if (data->usb_powered) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data->activity_state) {
|
||||||
|
case ZMK_ACTIVITY_ACTIVE:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case ZMK_ACTIVITY_IDLE:
|
||||||
|
return !config->on_while_idle;
|
||||||
|
|
||||||
|
case ZMK_ACTIVITY_SLEEP:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERR("Unhandled activity state %d", data->activity_state);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t get_brightness(const struct indicator_led_child_config *config,
|
||||||
|
const struct indicator_led_data *data) {
|
||||||
|
if (is_led_disabled(config, data)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data->endpoint_connected) {
|
||||||
|
return config->disconnected_brightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool active = data->indicators & config->indicator;
|
||||||
|
return active ? config->active_brightness : config->inactive_brightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int update_indicator(const struct indicator_led_child_config *config,
|
||||||
|
const struct indicator_led_data *data) {
|
||||||
|
const uint8_t value = get_brightness(config, data);
|
||||||
|
|
||||||
|
for (int i = 0; i < config->leds_len; i++) {
|
||||||
|
const struct led_dt_spec *spec = &config->leds[i];
|
||||||
|
const int err = led_set_brightness_dt(spec, value);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to set %s %u to %u%%: %d", spec->dev->name, spec->index, value, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("Set %s %u to %u%%", spec->dev->name, spec->index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int update_device(const struct device *dev) {
|
||||||
|
const struct indicator_led_config *config = dev->config;
|
||||||
|
struct indicator_led_data *data = dev->data;
|
||||||
|
|
||||||
|
data->activity_state = zmk_activity_get_state();
|
||||||
|
data->indicators = zmk_hid_indicators_get_current_profile();
|
||||||
|
data->usb_powered = zmk_usb_is_powered();
|
||||||
|
data->endpoint_connected = zmk_endpoint_is_connected();
|
||||||
|
|
||||||
|
for (int i = 0; i < config->indicators_len; i++) {
|
||||||
|
const int err = update_indicator(&config->indicators[i], data);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define INST_DEV(n) DEVICE_DT_GET(DT_DRV_INST(n)),
|
||||||
|
static const struct device *all_instances[] = {DT_INST_FOREACH_STATUS_OKAY(INST_DEV)};
|
||||||
|
|
||||||
|
static void update_all_indicators(struct k_work *work) {
|
||||||
|
LOG_DBG("Updating indicator LEDs");
|
||||||
|
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(all_instances); i++) {
|
||||||
|
if (device_is_ready(all_instances[i])) {
|
||||||
|
update_device(all_instances[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We may get multiple events at the same time (e.g. endpoint changed will
|
||||||
|
// also trigger HID indicators changed), but we only need to update the LEDs
|
||||||
|
// once per batch of events, so defer the updates with a work item.
|
||||||
|
static K_WORK_DEFINE(update_all_indicators_work, update_all_indicators);
|
||||||
|
|
||||||
|
static int indicator_led_event_listener(const zmk_event_t *eh) {
|
||||||
|
k_work_submit(&update_all_indicators_work);
|
||||||
|
return ZMK_EV_EVENT_BUBBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int indicator_led_init(const struct device *dev) { return update_device(dev); }
|
||||||
|
|
||||||
|
ZMK_LISTENER(indicator_led, indicator_led_event_listener);
|
||||||
|
ZMK_SUBSCRIPTION(indicator_led, zmk_activity_state_changed);
|
||||||
|
ZMK_SUBSCRIPTION(indicator_led, zmk_hid_indicators_changed);
|
||||||
|
ZMK_SUBSCRIPTION(indicator_led, zmk_usb_conn_state_changed);
|
||||||
|
ZMK_SUBSCRIPTION(indicator_led, zmk_endpoint_changed);
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_PM_DEVICE)
|
||||||
|
|
||||||
|
static int indicator_led_init_pm_action(const struct device *dev, enum pm_device_action action) {
|
||||||
|
struct indicator_led_data *data = dev->data;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case PM_DEVICE_ACTION_SUSPEND:
|
||||||
|
data->pm_suspended = true;
|
||||||
|
return update_device(dev);
|
||||||
|
|
||||||
|
case PM_DEVICE_ACTION_RESUME:
|
||||||
|
data->pm_suspended = false;
|
||||||
|
return update_device(dev);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||||
|
|
||||||
|
#define LED_DT_SPEC_GET_BY_IDX(node_id, prop, idx) \
|
||||||
|
LED_DT_SPEC_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx))
|
||||||
|
|
||||||
|
#define CHILD_LEDS_ARRAY(inst) DT_CAT(indicator_led_dt_spec_, inst)
|
||||||
|
|
||||||
|
#define DEFINE_CHILD_LEDS(inst) \
|
||||||
|
static const struct led_dt_spec CHILD_LEDS_ARRAY(inst)[] = { \
|
||||||
|
DT_FOREACH_PROP_ELEM_SEP(inst, leds, LED_DT_SPEC_GET_BY_IDX, (, )), \
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CHILD_CONFIG(inst) \
|
||||||
|
{ \
|
||||||
|
.leds_len = ARRAY_SIZE(CHILD_LEDS_ARRAY(inst)), \
|
||||||
|
.leds = CHILD_LEDS_ARRAY(inst), \
|
||||||
|
.indicator = DT_PROP(inst, indicator), \
|
||||||
|
.active_brightness = DT_PROP_OR(inst, active_brightness, 100), \
|
||||||
|
.inactive_brightness = DT_PROP_OR(inst, inactive_brightness, 0), \
|
||||||
|
.disconnected_brightness = DT_PROP_OR(inst, disconnected_brightness, 0), \
|
||||||
|
.on_while_idle = DT_PROP_OR(inst, on_while_idle, false), \
|
||||||
|
},
|
||||||
|
|
||||||
|
#define INDICATOR_LED_DEVICE(n) \
|
||||||
|
DT_INST_FOREACH_CHILD(n, DEFINE_CHILD_LEDS) \
|
||||||
|
\
|
||||||
|
static const struct indicator_led_child_config indicator_led_children_##n[] = { \
|
||||||
|
DT_INST_FOREACH_CHILD(n, CHILD_CONFIG)}; \
|
||||||
|
\
|
||||||
|
static const struct indicator_led_config indicator_led_config_##n = { \
|
||||||
|
.indicators_len = ARRAY_SIZE(indicator_led_children_##n), \
|
||||||
|
.indicators = indicator_led_children_##n, \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
static struct indicator_led_data indicator_led_data_##n = { \
|
||||||
|
.activity_state = ZMK_ACTIVITY_ACTIVE, \
|
||||||
|
.indicators = 0, \
|
||||||
|
.usb_powered = true, \
|
||||||
|
.pm_suspended = false, \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
PM_DEVICE_DT_INST_DEFINE(n, indicator_led_init_pm_action); \
|
||||||
|
\
|
||||||
|
DEVICE_DT_INST_DEFINE(n, &indicator_led_init, PM_DEVICE_DT_INST_GET(n), \
|
||||||
|
&indicator_led_data_##n, &indicator_led_config_##n, POST_KERNEL, \
|
||||||
|
CONFIG_ZMK_INDICATOR_LEDS_INIT_PRIORITY, NULL);
|
||||||
|
|
||||||
|
DT_INST_FOREACH_STATUS_OKAY(INDICATOR_LED_DEVICE);
|
||||||
@@ -59,11 +59,20 @@ config ZMK_STUDIO_TRANSPORT_UART
|
|||||||
select RING_BUFFER
|
select RING_BUFFER
|
||||||
default y if $(dt_chosen_enabled,$(DT_CHOSEN_ZMK_STUDIO_RPC_UART))
|
default y if $(dt_chosen_enabled,$(DT_CHOSEN_ZMK_STUDIO_RPC_UART))
|
||||||
|
|
||||||
|
if ZMK_STUDIO_TRANSPORT_UART
|
||||||
|
|
||||||
config ZMK_STUDIO_TRANSPORT_UART_RX_STACK_SIZE
|
config ZMK_STUDIO_TRANSPORT_UART_RX_STACK_SIZE
|
||||||
int "RX Stack Size"
|
int "RX Stack Size"
|
||||||
depends on !UART_INTERRUPT_DRIVEN
|
depends on !UART_INTERRUPT_DRIVEN
|
||||||
default 512
|
default 512
|
||||||
|
|
||||||
|
config ZMK_STUDIO_TRANSPORT_UART_RX_PRIORITY
|
||||||
|
int "RX Thread Priority"
|
||||||
|
depends on !UART_INTERRUPT_DRIVEN
|
||||||
|
default 9
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
config ZMK_STUDIO_TRANSPORT_BLE
|
config ZMK_STUDIO_TRANSPORT_BLE
|
||||||
bool "BLE (GATT)"
|
bool "BLE (GATT)"
|
||||||
select RING_BUFFER
|
select RING_BUFFER
|
||||||
@@ -71,9 +80,12 @@ config ZMK_STUDIO_TRANSPORT_BLE
|
|||||||
depends on ZMK_BLE
|
depends on ZMK_BLE
|
||||||
default y
|
default y
|
||||||
|
|
||||||
|
|
||||||
config BT_CONN_TX_MAX
|
config BT_CONN_TX_MAX
|
||||||
default 64 if ZMK_STUDIO_TRANSPORT_BLE
|
default 64 if ZMK_STUDIO_TRANSPORT_BLE
|
||||||
|
|
||||||
|
if ZMK_STUDIO_TRANSPORT_BLE
|
||||||
|
|
||||||
config ZMK_STUDIO_TRANSPORT_BLE_PREF_LATENCY
|
config ZMK_STUDIO_TRANSPORT_BLE_PREF_LATENCY
|
||||||
int "BLE Transport preferred latency"
|
int "BLE Transport preferred latency"
|
||||||
default 10
|
default 10
|
||||||
@@ -81,6 +93,8 @@ config ZMK_STUDIO_TRANSPORT_BLE_PREF_LATENCY
|
|||||||
When the studio UI is connected, a lower latency can be requested in order
|
When the studio UI is connected, a lower latency can be requested in order
|
||||||
to make the interactions between keyboard and studio faster.
|
to make the interactions between keyboard and studio faster.
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
config ZMK_STUDIO_RPC_THREAD_STACK_SIZE
|
config ZMK_STUDIO_RPC_THREAD_STACK_SIZE
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ static void uart_rx_main(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
K_THREAD_DEFINE(uart_transport_read_thread, CONFIG_ZMK_STUDIO_TRANSPORT_UART_RX_STACK_SIZE,
|
K_THREAD_DEFINE(uart_transport_read_thread, CONFIG_ZMK_STUDIO_TRANSPORT_UART_RX_STACK_SIZE,
|
||||||
uart_rx_main, NULL, NULL, NULL, K_LOWEST_APPLICATION_THREAD_PRIO, 0, 0);
|
uart_rx_main, NULL, NULL, NULL, CONFIG_ZMK_STUDIO_TRANSPORT_UART_RX_PRIORITY, 0, 0);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
41
docs/docs/config/led-indicators.md
Normal file
41
docs/docs/config/led-indicators.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
title: LED Indicators Configuration
|
||||||
|
sidebar_label: LED Indicators
|
||||||
|
---
|
||||||
|
|
||||||
|
See the [LED indicators feature page](../features/led-indicators.md) for more details.
|
||||||
|
|
||||||
|
See [Configuration Overview](index.md) for instructions on how to change these settings.
|
||||||
|
|
||||||
|
## Kconfig
|
||||||
|
|
||||||
|
Definition files:
|
||||||
|
|
||||||
|
- [zmk/app/src/indicators/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/indicators/Kconfig)
|
||||||
|
|
||||||
|
| Config | Type | Description | Default |
|
||||||
|
| ----------------------------------------- | ---- | --------------------------------------------------- | ------- |
|
||||||
|
| `CONFIG_ZMK_INDICATOR_LEDS_INIT_PRIORITY` | int | Indicator LED device driver initialization priority | 91 |
|
||||||
|
|
||||||
|
`CONFIG_ZMK_INDICATOR_LEDS_INIT_PRIORITY` must be set to a larger value than `CONFIG_LED_INIT_PRIORITY`.
|
||||||
|
|
||||||
|
## Indicator LED Driver
|
||||||
|
|
||||||
|
This driver maps HID indicator states to [LED API](https://docs.zephyrproject.org/4.1.0/hardware/peripherals/led.html) devices.
|
||||||
|
|
||||||
|
### Devicetree
|
||||||
|
|
||||||
|
Applies to: `compatible = "zmk,indicator-leds"`
|
||||||
|
|
||||||
|
Definition file: [zmk/app/dts/bindings/indicators/zmk,indicator-leds.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/indicators/zmk%2Cindicator-leds.yaml)
|
||||||
|
|
||||||
|
| Property | Type | Description | Default |
|
||||||
|
| ------------------------- | -------- | --------------------------------------------------------------------- | ------- |
|
||||||
|
| `indicator` | int | The `HID_INDICATOR_*` value to indicate | |
|
||||||
|
| `leds` | phandles | One or more LED devices to control | |
|
||||||
|
| `active-brightness` | int | LED brightness in percent when the indicator is active | 100 |
|
||||||
|
| `inactive-brightness` | int | LED brightness in percent when the indicator is not active | 0 |
|
||||||
|
| `disconnected-brightness` | int | LED brightness in percent when the keyboard is not connected | 0 |
|
||||||
|
| `on-while-idle` | bool | Keep LEDs enabled even when the keyboard is idle and on battery power | false |
|
||||||
|
|
||||||
|
The `indicator` property must be one of the `HID_INDICATOR_*` values defined in [zmk/app/include/dt-bindings/zmk/hid_indicators.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/hid_indicators.h). You may also combine values with `|` to make the LED be lit when any of the indicator states are active.
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
title: LED Indicators
|
||||||
|
sidebar_label: LED Indicators
|
||||||
|
description: Lighting system that indicates HID states such as caps lock.
|
||||||
|
---
|
||||||
|
|
||||||
|
ZMK supports the following five LED indicator states from the HID specification:
|
||||||
|
|
||||||
|
- Num Lock
|
||||||
|
- Caps Lock
|
||||||
|
- Scroll Lock
|
||||||
|
- Compose
|
||||||
|
- Kana
|
||||||
|
|
||||||
|
To connect these indicator states to LEDs on a keyboard, you must do two things in your board or shield files:
|
||||||
|
|
||||||
|
1. Define an LED driver and one or more LEDs to control.
|
||||||
|
2. Configure ZMK to control those LEDs.
|
||||||
|
|
||||||
|
## LED Definitions
|
||||||
|
|
||||||
|
The most common setup is for LEDs to be driven directly from a GPIO pin. The examples on this page show how to controls these using the `gpio-leds` or `pwm-leds` drivers, but you can also use any other [LED driver](https://docs.zephyrproject.org/4.1.0/hardware/peripherals/led.html) from Zephyr.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
LED strip drivers (e.g. WS2812 LEDs) are not currently supported. More work is needed to support the separate API and avoid conflicts with the underglow system.
|
||||||
|
:::
|
||||||
|
|
||||||
|
In your shield's `.overlay` file or board's `.dts` file, create a `gpio-leds` node and define any LEDs that you want to be able to control. The following example defines two LEDs on `&gpio0 1` and `&gpio0 2` which are enabled by driving the pins high:
|
||||||
|
|
||||||
|
```dts
|
||||||
|
/ {
|
||||||
|
leds {
|
||||||
|
compatible = "gpio-leds";
|
||||||
|
|
||||||
|
num_lock_led: num_lock_led {
|
||||||
|
gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
|
caps_lock_led: caps_lock_led {
|
||||||
|
gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### PWM Brightness Control
|
||||||
|
|
||||||
|
The above example only supports LEDs being off or on at full brightness. If you want to be able to reduce the brightness or use multiple brightness levels, you must use `pwm-leds` instead of `gpio-leds`. Note that this will increase power usage slightly when LEDs are enabled compared to using `gpio-leds`.
|
||||||
|
|
||||||
|
See the [backlight hardware integration page](backlight.mdx) for an example of configuring PWM LEDs.
|
||||||
|
|
||||||
|
## Indicator Definitions
|
||||||
|
|
||||||
|
Now that you have some LEDs defined, you can configure ZMK to use them.
|
||||||
|
|
||||||
|
In your shield's `.overlay` file or board's `.dts` file, add the following include to the top of the file:
|
||||||
|
|
||||||
|
```dts
|
||||||
|
#include <dt-bindings/zmk/hid_indicators.h>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, add a `zmk,indicator-leds` node. This node can have any number of child nodes. Each child maps an indicator state to one or more LEDs:
|
||||||
|
|
||||||
|
```dts
|
||||||
|
/ {
|
||||||
|
leds {
|
||||||
|
compatible = "gpio-leds";
|
||||||
|
|
||||||
|
// LEDs as defined in the previous step
|
||||||
|
num_lock_led: num_lock_led { ... };
|
||||||
|
caps_lock_led: caps_lock_led { ... };
|
||||||
|
};
|
||||||
|
|
||||||
|
indicators {
|
||||||
|
compatible = "zmk,indicator-leds";
|
||||||
|
|
||||||
|
num_lock_indicator: num_lock {
|
||||||
|
indicator = <HID_INDICATOR_NUM_LOCK>;
|
||||||
|
leds = <&num_lock_led>;
|
||||||
|
};
|
||||||
|
|
||||||
|
caps_lock_indicator: caps_lock {
|
||||||
|
indicator = <HID_INDICATOR_CAPS_LOCK>;
|
||||||
|
leds = <&caps_lock_led>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The name of each child node is unimportant, but you should give each node a label so users can change their settings in `.keymap` files. Conventionally, you should use one of the following:
|
||||||
|
|
||||||
|
- `num_lock_indicator`
|
||||||
|
- `caps_lock_indicator`
|
||||||
|
- `scroll_lock_indicator`
|
||||||
|
- `compose_indicator`
|
||||||
|
- `kana_indicator`
|
||||||
|
|
||||||
|
(If you have a non-standard setup and need to use different labels, you should document this somewhere so users know how to configure them.)
|
||||||
|
|
||||||
|
Each child node must have an `indicator` property, which is set to one of the following:
|
||||||
|
|
||||||
|
- `HID_INDICATOR_NUM_LOCK`
|
||||||
|
- `HID_INDICATOR_CAPS_LOCK`
|
||||||
|
- `HID_INDICATOR_SCROLL_LOCK`
|
||||||
|
- `HID_INDICATOR_COMPOSE`
|
||||||
|
- `HID_INDICATOR_KANA`
|
||||||
|
|
||||||
|
Each child node must also have an `leds` property, which holds a list of LED nodes to control. In this example, the LEDs are labeled `num_lock_led` and `caps_lock_led`, so the `leds` properties refer to them with `&num_lock_led` and `&caps_lock_led`, respectively.
|
||||||
|
|
||||||
|
You can also control multiple LEDs from the same indicator:
|
||||||
|
|
||||||
|
```dts
|
||||||
|
leds = <&led_1 &led_2>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## LED Behavior
|
||||||
|
|
||||||
|
See the [feature page](../../../features/led-indicators.md) and [configuration page](../../../config/led-indicators.md) for details on configuring LED brightness according to the indicator state.
|
||||||
67
docs/docs/features/led-indicators.md
Normal file
67
docs/docs/features/led-indicators.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
title: LED Indicators
|
||||||
|
sidebar_label: LED Indicators
|
||||||
|
---
|
||||||
|
|
||||||
|
ZMK supports the following five LED indicator states from the HID specification:
|
||||||
|
|
||||||
|
- Num Lock
|
||||||
|
- Caps Lock
|
||||||
|
- Scroll Lock
|
||||||
|
- Compose
|
||||||
|
- Kana
|
||||||
|
|
||||||
|
If your keyboard supports this feature, ZMK will display some or all of these states on LEDs.
|
||||||
|
|
||||||
|
The default behavior for an indicator LED is as follows:
|
||||||
|
|
||||||
|
- If the keyboard is on battery power and idle, the LED is off.
|
||||||
|
- If the keyboard is not connected to any host, the LED is off.
|
||||||
|
- If the indicator state is active, the LED is on at full brightness.
|
||||||
|
- Otherwise, the LED is off.
|
||||||
|
|
||||||
|
If you want to change these behaviors, you can set properties on the devicetree nodes for each indicator in your `.keymap` file. The conventional node labels for indicators are as follows:
|
||||||
|
|
||||||
|
- `num_lock_indicator`
|
||||||
|
- `caps_lock_indicator`
|
||||||
|
- `scroll_lock_indicator`
|
||||||
|
- `compose_indicator`
|
||||||
|
- `kana_indicator`
|
||||||
|
|
||||||
|
The examples below all use `caps_lock_indicator`. To edit a different indicator, use the relevant label for that indicator instead.
|
||||||
|
|
||||||
|
## Idle Behavior
|
||||||
|
|
||||||
|
The `on-while-idle` property prevents the LED from turning off when the keyboard on battery power and idle:
|
||||||
|
|
||||||
|
```dts
|
||||||
|
&caps_lock_indicator {
|
||||||
|
on-while-idle;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## LED Brightness
|
||||||
|
|
||||||
|
The `active-brightness`, `inactive-brightness`, and `disconnected-brightness` properties control the brightness of the LED when the indicator is active, indicator is not active, and the keyboard is not connected to any host, respectively.
|
||||||
|
|
||||||
|
For example, if you want the LED to be off when the indicator is active, 100% brightness when inactive, and 50% brightness when not connected:
|
||||||
|
|
||||||
|
```dts
|
||||||
|
&caps_lock_indicator {
|
||||||
|
active-brightness = <0>;
|
||||||
|
inactive-brightness = <100>;
|
||||||
|
disconnected-brightness = <50>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
If the LED is not configured to support brightness control, any value greater than 0 will result in maximum brightness.
|
||||||
|
|
||||||
|
For most LEDs, you can enable PWM brightness control, though this will increase power usage slightly. See the [LED indicators hardware integration page](../development/hardware-integration/lighting/led-indicators.md) for details on configuring the LEDs.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Adding LED Indicator Support to a Keyboard
|
||||||
|
|
||||||
|
See the [LED indicators hardware integration page](../development/hardware-integration/lighting/led-indicators.md) for instructions to enable this feature on a keyboard.
|
||||||
@@ -38,6 +38,7 @@ module.exports = {
|
|||||||
"features/split-keyboards",
|
"features/split-keyboards",
|
||||||
"features/debouncing",
|
"features/debouncing",
|
||||||
"features/battery",
|
"features/battery",
|
||||||
|
"features/led-indicators",
|
||||||
"features/low-power-states",
|
"features/low-power-states",
|
||||||
"features/encoders",
|
"features/encoders",
|
||||||
"features/pointing",
|
"features/pointing",
|
||||||
@@ -126,6 +127,7 @@ module.exports = {
|
|||||||
"config/combos",
|
"config/combos",
|
||||||
"config/displays",
|
"config/displays",
|
||||||
"config/encoders",
|
"config/encoders",
|
||||||
|
"config/led-indicators",
|
||||||
"config/lighting",
|
"config/lighting",
|
||||||
"config/pointing",
|
"config/pointing",
|
||||||
"config/keymap",
|
"config/keymap",
|
||||||
@@ -187,6 +189,7 @@ module.exports = {
|
|||||||
items: [
|
items: [
|
||||||
"development/hardware-integration/lighting/underglow",
|
"development/hardware-integration/lighting/underglow",
|
||||||
"development/hardware-integration/lighting/backlight",
|
"development/hardware-integration/lighting/backlight",
|
||||||
|
"development/hardware-integration/lighting/led-indicators",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user