Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6854fa68dc | ||
|
|
14b6e49692 | ||
|
|
e9cd3a96c2 | ||
|
|
8856c484b6 | ||
|
|
b97841fdae | ||
|
|
69143bed9c | ||
|
|
ebd0e3b762 | ||
|
|
ec8301ae62 | ||
|
|
3d4d78387e | ||
|
|
c2582729f2 | ||
|
|
9d66f5ff76 | ||
|
|
06ebed5537 |
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||
The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to the [UHK Versioning](VERSIONING.md) conventions.
|
||||
|
||||
## [8.2.1] - 2018-05-02
|
||||
|
||||
Device Protocol: 4.3.0 | Module Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Don't suppress modifier keys upon releasing a layer.
|
||||
- Restore Caps Lock indicator when saving the configuration.
|
||||
|
||||
## [8.2.0] - 2018-04-20
|
||||
|
||||
Device Protocol: 4.**3.0** | Module Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
Submodule lib/agent updated: b38b6fa294...3d9c83f9f4
@@ -3,8 +3,4 @@
|
||||
|
||||
// Macros:
|
||||
|
||||
#define I2C_WATCHDOG
|
||||
#define LED_DRIVERS_ENABLED
|
||||
// #define LED_DRIVER_STRESS_TEST
|
||||
|
||||
#endif
|
||||
|
||||
@@ -144,10 +144,9 @@ parser_error_t ParseConfig(config_buffer_t *buffer)
|
||||
IconsAndLayerTextsBrightness = iconsAndLayerTextsBrightness;
|
||||
AlphanumericSegmentsBrightness = alphanumericSegmentsBrightness;
|
||||
KeyBacklightBrightness = keyBacklightBrightness;
|
||||
#ifdef LED_DRIVERS_ENABLED
|
||||
Slaves[SlaveId_LeftLedDriver].isConnected = false;
|
||||
Slaves[SlaveId_RightLedDriver].isConnected = false;
|
||||
#endif
|
||||
|
||||
LedSlaveDriver_UpdateLeds();
|
||||
|
||||
// Update mouse key speeds
|
||||
|
||||
MouseMoveState.initialSpeed = mouseMoveInitialSpeed;
|
||||
|
||||
@@ -144,9 +144,7 @@ void InitPeripherals(void)
|
||||
initI2c();
|
||||
InitTestLed();
|
||||
LedPwm_Init();
|
||||
#ifdef I2C_WATCHDOG
|
||||
InitI2cWatchdog();
|
||||
#endif
|
||||
InitKeyDebouncer();
|
||||
EEPROM_Init();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ void SwitchKeymapById(uint8_t index)
|
||||
CurrentKeymapIndex = index;
|
||||
ValidatedUserConfigBuffer.offset = AllKeymaps[index].offset;
|
||||
ParseKeymap(&ValidatedUserConfigBuffer, index, AllKeymapsCount, AllMacrosCount);
|
||||
LedDisplay_SetCurrentKeymapText();
|
||||
LedDisplay_UpdateText();
|
||||
}
|
||||
|
||||
bool SwitchKeymapByAbbreviation(uint8_t length, char *abbrev)
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
uint8_t IconsAndLayerTextsBrightness = 0xff;
|
||||
uint8_t AlphanumericSegmentsBrightness = 0xff;
|
||||
bool ledIconStates[LedDisplayIcon_Last];
|
||||
|
||||
static const uint16_t capitalLetterToSegmentSet[] = {
|
||||
static const uint16_t capitalLetterToSegmentMap[] = {
|
||||
0b0000000011110111,
|
||||
0b0001001010001111,
|
||||
0b0000000000111001,
|
||||
@@ -35,7 +36,7 @@ static const uint16_t capitalLetterToSegmentSet[] = {
|
||||
0b0000110000001001,
|
||||
};
|
||||
|
||||
static const uint16_t digitToSegmentSet[] = {
|
||||
static const uint16_t digitToSegmentMap[] = {
|
||||
0b0000110000111111,
|
||||
0b0000010000000110,
|
||||
0b0000100010001011,
|
||||
@@ -48,13 +49,13 @@ static const uint16_t digitToSegmentSet[] = {
|
||||
0b0000000011101111,
|
||||
};
|
||||
|
||||
static uint16_t characterToSegmentSet(char character)
|
||||
static uint16_t characterToSegmentMap(char character)
|
||||
{
|
||||
switch (character) {
|
||||
case 'A' ... 'Z':
|
||||
return capitalLetterToSegmentSet[character - 'A'];
|
||||
return capitalLetterToSegmentMap[character - 'A'];
|
||||
case '0' ... '9':
|
||||
return digitToSegmentSet[character - '0'];
|
||||
return digitToSegmentMap[character - '0'];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -65,11 +66,11 @@ void LedDisplay_SetText(uint8_t length, const char* text)
|
||||
|
||||
switch (length) {
|
||||
case 3:
|
||||
allSegmentSets = (uint64_t)characterToSegmentSet(text[2]) << 28;
|
||||
allSegmentSets = (uint64_t)characterToSegmentMap(text[2]) << 28;
|
||||
case 2:
|
||||
allSegmentSets |= characterToSegmentSet(text[1]) << 14;
|
||||
allSegmentSets |= characterToSegmentMap(text[1]) << 14;
|
||||
case 1:
|
||||
allSegmentSets |= characterToSegmentSet(text[0]);
|
||||
allSegmentSets |= characterToSegmentMap(text[0]);
|
||||
}
|
||||
|
||||
LedDriverValues[LedDriverId_Left][11] = allSegmentSets & 0b00000001 ? AlphanumericSegmentsBrightness : 0;
|
||||
@@ -84,12 +85,6 @@ void LedDisplay_SetText(uint8_t length, const char* text)
|
||||
}
|
||||
}
|
||||
|
||||
void LedDisplay_SetCurrentKeymapText(void)
|
||||
{
|
||||
keymap_reference_t *currentKeymap = AllKeymaps + CurrentKeymapIndex;
|
||||
LedDisplay_SetText(currentKeymap->abbreviationLen, currentKeymap->abbreviation);
|
||||
}
|
||||
|
||||
void LedDisplay_SetLayer(layer_id_t layerId)
|
||||
{
|
||||
for (uint8_t i = 13; i <= 45; i += 16) {
|
||||
@@ -108,5 +103,25 @@ bool LedDisplay_GetIcon(led_display_icon_t icon)
|
||||
|
||||
void LedDisplay_SetIcon(led_display_icon_t icon, bool isEnabled)
|
||||
{
|
||||
LedDriverValues[LedDriverId_Left][8 + icon] = isEnabled ? IconsAndLayerTextsBrightness : 0;
|
||||
ledIconStates[icon] = isEnabled;
|
||||
LedDriverValues[LedDriverId_Left][icon + 8] = isEnabled ? IconsAndLayerTextsBrightness : 0;
|
||||
}
|
||||
|
||||
void LedDisplay_UpdateIcons(void)
|
||||
{
|
||||
for (uint8_t i=0; i<=LedDisplayIcon_Last; i++) {
|
||||
LedDisplay_SetIcon(i, ledIconStates[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void LedDisplay_UpdateText(void)
|
||||
{
|
||||
keymap_reference_t *currentKeymap = AllKeymaps + CurrentKeymapIndex;
|
||||
LedDisplay_SetText(currentKeymap->abbreviationLen, currentKeymap->abbreviation);
|
||||
}
|
||||
|
||||
void LedDisplay_UpdateAll(void)
|
||||
{
|
||||
LedDisplay_UpdateIcons();
|
||||
LedDisplay_UpdateText();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
LedDisplayIcon_CapsLock,
|
||||
LedDisplayIcon_Agent,
|
||||
LedDisplayIcon_Adaptive,
|
||||
LedDisplayIcon_Last = LedDisplayIcon_Adaptive,
|
||||
} led_display_icon_t;
|
||||
|
||||
// Variables:
|
||||
@@ -23,10 +24,11 @@
|
||||
// Functions:
|
||||
|
||||
void LedDisplay_SetText(uint8_t length, const char* text);
|
||||
void LedDisplay_SetCurrentKeymapText(void);
|
||||
void LedDisplay_SetLayer(layer_id_t layerId);
|
||||
void LedDisplay_SetIcon(led_display_icon_t icon, bool isEnabled);
|
||||
|
||||
bool LedDisplay_GetIcon(led_display_icon_t icon);
|
||||
void LedDisplay_SetIcon(led_display_icon_t icon, bool isEnabled);
|
||||
void LedDisplay_UpdateIcons(void);
|
||||
void LedDisplay_UpdateText(void);
|
||||
void LedDisplay_UpdateAll(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -62,6 +62,22 @@ static uint8_t setShutdownModeNormalBuffer[] = {LED_DRIVER_REGISTER_SHUTDOWN, SH
|
||||
static uint8_t setFrame1Buffer[] = {LED_DRIVER_REGISTER_FRAME, LED_DRIVER_FRAME_1};
|
||||
static uint8_t updatePwmRegistersBuffer[PWM_REGISTER_BUFFER_LENGTH];
|
||||
|
||||
void LedSlaveDriver_DisableLeds(void)
|
||||
{
|
||||
for (uint8_t ledDriverId=0; ledDriverId<=LedDriverId_Last; ledDriverId++) {
|
||||
memset(LedDriverValues[ledDriverId], 0, LED_DRIVER_LED_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
void LedSlaveDriver_UpdateLeds(void)
|
||||
{
|
||||
for (uint8_t ledDriverId=0; ledDriverId<=LedDriverId_Last; ledDriverId++) {
|
||||
memset(LedDriverValues[ledDriverId], KeyBacklightBrightness, LED_DRIVER_LED_COUNT);
|
||||
}
|
||||
|
||||
LedDisplay_UpdateAll();
|
||||
}
|
||||
|
||||
void LedSlaveDriver_Init(uint8_t ledDriverId)
|
||||
{
|
||||
if (ledDriverId == ISO_KEY_LED_DRIVER_ID && IS_ISO) {
|
||||
@@ -74,10 +90,7 @@ void LedSlaveDriver_Init(uint8_t ledDriverId)
|
||||
memset(LedDriverValues[ledDriverId], KeyBacklightBrightness, LED_DRIVER_LED_COUNT);
|
||||
|
||||
if (ledDriverId == LedDriverId_Left) {
|
||||
LedDisplay_SetIcon(LedDisplayIcon_CapsLock, false);
|
||||
LedDisplay_SetIcon(LedDisplayIcon_Agent, false);
|
||||
LedDisplay_SetIcon(LedDisplayIcon_Adaptive, false);
|
||||
LedDisplay_SetCurrentKeymapText();
|
||||
LedDisplay_UpdateAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,10 +131,8 @@ status_t LedSlaveDriver_Update(uint8_t ledDriverId)
|
||||
*ledIndex += chunkSize;
|
||||
if (*ledIndex >= LED_DRIVER_LED_COUNT) {
|
||||
*ledIndex = 0;
|
||||
#ifndef LED_DRIVER_STRESS_TEST
|
||||
memcpy(currentLedDriverState->targetLedValues, ledValues, LED_DRIVER_LED_COUNT);
|
||||
*ledDriverPhase = LedDriverPhase_UpdateChangedLedValues;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case LedDriverPhase_UpdateChangedLedValues: {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
typedef enum {
|
||||
LedDriverId_Right,
|
||||
LedDriverId_Left,
|
||||
LedDriverId_Last = LedDriverId_Left,
|
||||
} led_driver_id_t;
|
||||
|
||||
typedef enum {
|
||||
@@ -49,6 +50,8 @@
|
||||
|
||||
// Functions:
|
||||
|
||||
void LedSlaveDriver_DisableLeds(void);
|
||||
void LedSlaveDriver_UpdateLeds(void);
|
||||
void LedSlaveDriver_Init(uint8_t ledDriverId);
|
||||
status_t LedSlaveDriver_Update(uint8_t ledDriverId);
|
||||
|
||||
|
||||
@@ -265,10 +265,8 @@ status_t UhkModuleSlaveDriver_Update(uint8_t uhkModuleDriverId)
|
||||
|
||||
void UhkModuleSlaveDriver_Disconnect(uint8_t uhkModuleDriverId)
|
||||
{
|
||||
#ifdef LED_DRIVERS_ENABLED
|
||||
if (uhkModuleDriverId == SlaveId_LeftKeyboardHalf) {
|
||||
Slaves[SlaveId_LeftLedDriver].isConnected = false;
|
||||
}
|
||||
UhkModuleStates[uhkModuleDriverId].moduleId = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ uhk_slave_t Slaves[] = {
|
||||
.update = UhkModuleSlaveDriver_Update,
|
||||
.perDriverId = UhkModuleDriverId_RightAddon,
|
||||
},
|
||||
#ifdef LED_DRIVERS_ENABLED
|
||||
{
|
||||
.init = LedSlaveDriver_Init,
|
||||
.update = LedSlaveDriver_Update,
|
||||
@@ -42,7 +41,6 @@ uhk_slave_t Slaves[] = {
|
||||
.update = LedSlaveDriver_Update,
|
||||
.perDriverId = LedDriverId_Left,
|
||||
},
|
||||
#endif
|
||||
{
|
||||
.init = KbootSlaveDriver_Init,
|
||||
.update = KbootSlaveDriver_Update,
|
||||
|
||||
@@ -19,10 +19,8 @@
|
||||
SlaveId_LeftKeyboardHalf,
|
||||
SlaveId_LeftAddon,
|
||||
SlaveId_RightAddon,
|
||||
#ifdef LED_DRIVERS_ENABLED
|
||||
SlaveId_RightLedDriver,
|
||||
SlaveId_LeftLedDriver,
|
||||
#endif
|
||||
SlaveId_KbootDriver,
|
||||
} slave_id_t;
|
||||
|
||||
|
||||
@@ -163,58 +163,21 @@ static usb_device_class_config_list_struct_t UsbDeviceCompositeConfigList = {
|
||||
}
|
||||
}};
|
||||
|
||||
static bool computerSleeping = false;
|
||||
#ifdef LED_DRIVERS_ENABLED
|
||||
static uint8_t oldKeyBacklightBrightness = 0xFF;
|
||||
static bool capsLockOn = false, agentOn = false, adaptiveOn = false;
|
||||
#endif
|
||||
bool IsHostSleeping = false;
|
||||
|
||||
bool IsComputerSleeping(void) {
|
||||
return computerSleeping;
|
||||
static void suspendHost(void) {
|
||||
IsHostSleeping = true;
|
||||
LedSlaveDriver_DisableLeds();
|
||||
}
|
||||
|
||||
static void ComputerIsSleeping(void) {
|
||||
computerSleeping = true;
|
||||
#ifdef LED_DRIVERS_ENABLED
|
||||
// Save the state of the icons
|
||||
capsLockOn = LedDisplay_GetIcon(LedDisplayIcon_CapsLock);
|
||||
agentOn = LedDisplay_GetIcon(LedDisplayIcon_Agent);
|
||||
adaptiveOn = LedDisplay_GetIcon(LedDisplayIcon_Adaptive);
|
||||
void WakeUpHost(bool sendResume) {
|
||||
if (sendResume) { // The device should wake up the host.
|
||||
// Send resume signal - this will call USB_DeviceKhciControl(khciHandle, kUSB_DeviceControlResume, NULL);
|
||||
USB_DeviceSetStatus(UsbCompositeDevice.deviceHandle, kUSB_DeviceStatusBus, NULL);
|
||||
}
|
||||
|
||||
// Disable keyboard backlight
|
||||
oldKeyBacklightBrightness = KeyBacklightBrightness;
|
||||
KeyBacklightBrightness = 0;
|
||||
LedSlaveDriver_Init(LedDriverId_Right);
|
||||
LedSlaveDriver_Init(LedDriverId_Left);
|
||||
|
||||
// Turn layer LEDs off
|
||||
LedDisplay_SetLayer(LayerId_Base);
|
||||
|
||||
// Clear the text
|
||||
LedDisplay_SetText(0, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void WakeupComputer(bool sendResume) {
|
||||
if (sendResume) // The device should wake up the computer
|
||||
USB_DeviceSetStatus(UsbCompositeDevice.deviceHandle, kUSB_DeviceStatusBus, NULL); // Send resume signal - this will call USB_DeviceKhciControl(khciHandle, kUSB_DeviceControlResume, NULL);
|
||||
|
||||
computerSleeping = false; // The computer is now awake
|
||||
|
||||
#ifdef LED_DRIVERS_ENABLED
|
||||
// Restore keyboard backlight and text
|
||||
KeyBacklightBrightness = oldKeyBacklightBrightness;
|
||||
LedSlaveDriver_Init(LedDriverId_Right);
|
||||
LedSlaveDriver_Init(LedDriverId_Left);
|
||||
|
||||
// Update the active layer
|
||||
LedDisplay_SetLayer(GetActiveLayer());
|
||||
|
||||
// Restore icon states
|
||||
LedDisplay_SetIcon(LedDisplayIcon_CapsLock, capsLockOn);
|
||||
LedDisplay_SetIcon(LedDisplayIcon_Agent, agentOn);
|
||||
LedDisplay_SetIcon(LedDisplayIcon_Adaptive, adaptiveOn);
|
||||
#endif
|
||||
IsHostSleeping = false;
|
||||
LedSlaveDriver_UpdateLeds();
|
||||
}
|
||||
|
||||
static usb_status_t usbDeviceCallback(usb_device_handle handle, uint32_t event, void *param)
|
||||
@@ -227,8 +190,9 @@ static usb_status_t usbDeviceCallback(usb_device_handle handle, uint32_t event,
|
||||
return status;
|
||||
}
|
||||
|
||||
if (computerSleeping)
|
||||
WakeupComputer(false); // Wake up the keyboard if there is any activity on the bus
|
||||
if (IsHostSleeping) {
|
||||
WakeUpHost(false); // Wake up the keyboard if there is any activity on the bus.
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case kUSB_DeviceEventBusReset:
|
||||
@@ -237,13 +201,13 @@ static usb_status_t usbDeviceCallback(usb_device_handle handle, uint32_t event,
|
||||
break;
|
||||
case kUSB_DeviceEventSuspend:
|
||||
if (UsbCompositeDevice.attach) {
|
||||
ComputerIsSleeping(); // The computer sends this event when it goes to sleep, so turn off all the LEDs
|
||||
suspendHost(); // The host sends this event when it goes to sleep, so turn off all the LEDs.
|
||||
status = kStatus_USB_Success;
|
||||
}
|
||||
break;
|
||||
case kUSB_DeviceEventResume:
|
||||
// We will just wake up the computer if there is any activity on the bus
|
||||
// The problem is that the computer won't send a resume event when it boots, so the lights will never come back on
|
||||
// We will just wake up the host if there is any activity on the bus.
|
||||
// The problem is that the host won't send a resume event when it boots, so the lights will never come back on.
|
||||
status = kStatus_USB_Success;
|
||||
break;
|
||||
case kUSB_DeviceEventSetConfiguration:
|
||||
|
||||
@@ -28,12 +28,12 @@
|
||||
|
||||
// Variables:
|
||||
|
||||
extern bool IsHostSleeping;
|
||||
extern usb_composite_device_t UsbCompositeDevice;
|
||||
|
||||
//Functions:
|
||||
|
||||
void InitUsb(void);
|
||||
bool IsComputerSleeping(void);
|
||||
void WakeupComputer(bool sendResume);
|
||||
void WakeUpHost(bool sendResume);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -288,7 +288,7 @@ static void updateActiveUsbReports(void)
|
||||
if (activeLayer == LayerId_Base) {
|
||||
activeLayer = GetActiveLayer();
|
||||
}
|
||||
bool suppressKeys = previousLayer != LayerId_Base && activeLayer == LayerId_Base;
|
||||
bool layerGotReleased = previousLayer != LayerId_Base && activeLayer == LayerId_Base;
|
||||
LedDisplay_SetLayer(activeLayer);
|
||||
|
||||
if (MacroPlaying) {
|
||||
@@ -305,7 +305,6 @@ static void updateActiveUsbReports(void)
|
||||
key_state_t *keyState = &KeyStates[slotId][keyId];
|
||||
key_action_t *action = &CurrentKeymap[activeLayer][slotId][keyId];
|
||||
|
||||
|
||||
if (keyState->debounceCounter < KEY_DEBOUNCER_TIMEOUT_MSEC) {
|
||||
keyState->current = keyState->previous;
|
||||
} else if (!keyState->previous && keyState->current) {
|
||||
@@ -313,7 +312,8 @@ static void updateActiveUsbReports(void)
|
||||
}
|
||||
|
||||
if (keyState->current) {
|
||||
if (suppressKeys) {
|
||||
key_action_t *baseAction = &CurrentKeymap[LayerId_Base][slotId][keyId];
|
||||
if (layerGotReleased && !(baseAction->type == KeyActionType_Keystroke && baseAction->keystroke.scancode == 0 && baseAction->keystroke.modifiers)) {
|
||||
keyState->suppressed = true;
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ void UpdateUsbReports(void)
|
||||
{
|
||||
UsbReportUpdateCounter++;
|
||||
|
||||
// Process the key inputs at a constant rate when moving the mouse, so the mouse speed is consistent
|
||||
// Process the key inputs at a constant rate when moving the mouse, so the mouse speed is consistent.
|
||||
bool hasActiveMouseState = false;
|
||||
for (uint8_t i=0; i<ACTIVE_MOUSE_STATES_COUNT; i++) {
|
||||
hasActiveMouseState = true;
|
||||
@@ -428,7 +428,7 @@ void UpdateUsbReports(void)
|
||||
IsUsbMouseReportSent = false;
|
||||
}
|
||||
|
||||
if ((previousLayer != LayerId_Base || !IsUsbBasicKeyboardReportSent || !IsUsbMediaKeyboardReportSent || !IsUsbSystemKeyboardReportSent || !IsUsbMouseReportSent) && IsComputerSleeping()) {
|
||||
WakeupComputer(true); // Wake up the computer if any key is pressed and the computer is sleeping
|
||||
if ((previousLayer != LayerId_Base || !IsUsbBasicKeyboardReportSent || !IsUsbMediaKeyboardReportSent || !IsUsbSystemKeyboardReportSent || !IsUsbMouseReportSent) && IsHostSleeping) {
|
||||
WakeUpHost(true); // Wake up the host if any key is pressed and the computer is sleeping.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"commander": "^2.11.0",
|
||||
"shelljs": "^0.7.8"
|
||||
},
|
||||
"firmwareVersion": "8.2.0",
|
||||
"firmwareVersion": "8.2.1",
|
||||
"deviceProtocolVersion": "4.3.0",
|
||||
"moduleProtocolVersion": "4.0.0",
|
||||
"userConfigVersion": "4.0.0",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#define FIRMWARE_MAJOR_VERSION 8
|
||||
#define FIRMWARE_MINOR_VERSION 2
|
||||
#define FIRMWARE_PATCH_VERSION 0
|
||||
#define FIRMWARE_PATCH_VERSION 1
|
||||
|
||||
#define DEVICE_PROTOCOL_MAJOR_VERSION 4
|
||||
#define DEVICE_PROTOCOL_MINOR_VERSION 3
|
||||
|
||||
Reference in New Issue
Block a user