#![allow(unused)]
#[cfg(feature = "hardware")]
mod hardware;
#[cfg(feature = "hardware")]
pub use hardware::lk201::{LK201Hardware, ScanCell};
#[cfg(feature = "hardware")]
pub use hardware::lk250::{LK250Hardware, Lk250Mode};
mod software;
pub use software::{LK201, LK201Sender};
mod keys;
pub use keys::{ALL_KEYS, Key, SpecialKey};
use std::{collections::VecDeque, fmt, sync::mpsc};
use tracing::trace;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Led(u8);
impl Led {
pub fn new(byte: u8) -> Self {
Led(byte)
}
pub fn is_wait(&self) -> bool {
self.0 & 0x01 == 0x01
}
pub fn is_compose(&self) -> bool {
self.0 & 0x02 == 0x02
}
pub fn is_lock(&self) -> bool {
self.0 & 0x04 == 0x04
}
pub fn is_hold(&self) -> bool {
self.0 & 0x08 == 0x08
}
pub fn is_all(&self) -> bool {
self.0 & 0x0F == 0x0F
}
}
impl fmt::Debug for Led {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Led({:02X}=", self.0)?;
let mut first = true;
for led in [
("Wait", self.is_wait()),
("Compose", self.is_compose()),
("Lock", self.is_lock()),
("Hold", self.is_hold()),
] {
if led.1 {
if first {
first = false;
} else {
write!(f, "+")?;
}
write!(f, "{}", led.0)?;
}
}
write!(f, ")")?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyMode {
Down = 0x80,
AutoDown = 0x82,
Illegal = 0x84,
UpDown = 0x86,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Division(pub u8);
impl Division {
pub fn new(div: u8) -> Option<Self> {
if div <= 14 && div > 0 {
Some(Division(div))
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AutoRepeatRegister(pub u8);
impl AutoRepeatRegister {
pub fn new(reg: u8) -> Option<Self> {
if reg <= 3 {
Some(AutoRepeatRegister(reg))
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Volume(pub u8);
impl Volume {
pub fn new(level: u8) -> Option<Self> {
if level <= 7 {
Some(Volume(level))
} else {
None
}
}
pub fn as_param_byte(self) -> u8 {
0x80 | (self.0 & 0x7)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LK201Command {
LedEnable(Led),
LedDisable(Led),
KeyClickEnable(Volume),
CtrlKeyClickEnable,
KeyClickDisable,
CtrlKeyClickDisable,
SoundClick,
BellEnable(Volume),
BellDisable,
RingBell,
SetMode { mode: KeyMode, division: Division },
SetModeWithAutoRepeat {
mode: KeyMode,
division: Division,
register: AutoRepeatRegister,
},
RepeatToDown,
EnableRepeat,
DisableRepeat,
EnableLK401,
DisableLK401,
TempNoRepeat,
SetAutoRepeat {
register: AutoRepeatRegister,
timeout: u8,
rate: u8,
},
PowerUp,
RequestId,
SetDefaults,
TestMode,
TestExit,
Inhibit,
Resume,
Unknown(u8),
Unknown2(u8, u8),
Unknown3(u8, u8, u8),
}
impl LK201Command {
pub fn len(&self) -> usize {
match self {
LK201Command::LedEnable(_) => 2,
LK201Command::LedDisable(_) => 2,
LK201Command::KeyClickEnable(_) => 2,
LK201Command::CtrlKeyClickEnable => 1,
LK201Command::KeyClickDisable => 1,
LK201Command::CtrlKeyClickDisable => 1,
LK201Command::SoundClick => 1,
LK201Command::BellEnable(_) => 2,
LK201Command::BellDisable => 1,
LK201Command::RingBell => 1,
LK201Command::SetMode { .. } => 1,
LK201Command::SetModeWithAutoRepeat { .. } => 2,
LK201Command::RepeatToDown => 1,
LK201Command::EnableRepeat => 1,
LK201Command::DisableRepeat => 1,
LK201Command::EnableLK401 => 1,
LK201Command::DisableLK401 => 1,
LK201Command::TempNoRepeat => 1,
LK201Command::SetAutoRepeat { .. } => 3,
LK201Command::PowerUp => 1,
LK201Command::RequestId => 1,
LK201Command::SetDefaults => 1,
LK201Command::TestMode => 1,
LK201Command::TestExit => 1,
LK201Command::Inhibit => 1,
LK201Command::Resume => 1,
LK201Command::Unknown(_) => 1,
LK201Command::Unknown2(_, _) => 2,
LK201Command::Unknown3(_, _, _) => 3,
}
}
pub fn response(&self) -> Option<LK201Response> {
Some(match self {
LK201Command::PowerUp => LK201Response::PowerUpSelfTest {
keyboard_id_firmware: 0x01, keyboard_id_hardware: 0x00, error: PowerUpError::NoError,
keycode: 0,
},
LK201Command::RequestId => LK201Response::KeyboardId {
firmware_id: 0x01, hardware_id: 0x01, },
LK201Command::SetMode { .. } => LK201Response::ModeChangeAck,
LK201Command::SetModeWithAutoRepeat { .. } => LK201Response::ModeChangeAck,
LK201Command::TempNoRepeat => LK201Response::ModeChangeAck,
LK201Command::SetDefaults => LK201Response::ModeChangeAck,
LK201Command::TestMode => LK201Response::TestModeAck,
LK201Command::Inhibit => LK201Response::KeyboardLockAck,
LK201Command::Unknown(_) => LK201Response::InputError,
LK201Command::Unknown2(_, _) => LK201Response::InputError,
LK201Command::Unknown3(_, _, _) => LK201Response::InputError,
_ => return None,
})
}
}
impl TryFrom<&VecDeque<u8>> for LK201Command {
type Error = ();
fn try_from(value: &VecDeque<u8>) -> Result<Self, Self::Error> {
let Some(&byte0) = value.get(0) else {
return Err(());
};
match byte0 {
0x13 => {
let Some(&led_byte) = value.get(1) else {
return Err(());
};
let led = Led::new(led_byte);
Ok(LK201Command::LedEnable(led))
}
0x11 => {
let Some(&led_byte) = value.get(1) else {
return Err(());
};
let led = Led::new(led_byte);
Ok(LK201Command::LedDisable(led))
}
0x1B => {
let Some(&vol_byte) = value.get(1) else {
return Err(());
};
let Some(volume) = Volume::new((vol_byte & 0x7) as u8) else {
return Ok(LK201Command::Unknown2(0x1B, vol_byte));
};
Ok(LK201Command::KeyClickEnable(volume))
}
0xBB => Ok(LK201Command::CtrlKeyClickEnable),
0x99 => Ok(LK201Command::KeyClickDisable),
0xB9 => Ok(LK201Command::CtrlKeyClickDisable),
0x9F => Ok(LK201Command::SoundClick),
0x80 => Ok(LK201Command::TestExit),
0x23 => {
let Some(&vol_byte) = value.get(1) else {
return Err(());
};
let Some(volume) = Volume::new((vol_byte & 0x7) as u8) else {
return Ok(LK201Command::Unknown2(0x23, vol_byte));
};
Ok(LK201Command::BellEnable(volume))
}
0xA1 => Ok(LK201Command::BellDisable),
0xA7 => Ok(LK201Command::RingBell),
0x78..=0x7F if (byte0 >> 3) & 0xF == 0xF => {
let Some(&timeout) = value.get(1) else {
return Err(());
};
let Some(&rate) = value.get(2) else {
return Err(());
};
let Some(register) = AutoRepeatRegister::new((byte0 >> 1) & 0x3) else {
return Ok(LK201Command::Unknown2(0x78, byte0));
};
Ok(LK201Command::SetAutoRepeat {
register,
timeout: timeout & 0x7F,
rate: rate & 0x7F,
})
}
b if (b & 0x01) == 0 => {
let division_bits = (b >> 3) & 0xF;
let mode_bits = (b >> 1) & 0x3;
let has_param = (b & 0x80) == 0;
if division_bits <= 14 {
let Some(division) = Division::new(division_bits) else {
return Ok(LK201Command::Unknown(b));
};
let mode = match mode_bits {
0b00 => KeyMode::Down,
0b01 => KeyMode::AutoDown,
0b11 => KeyMode::UpDown,
_ => return Ok(LK201Command::Unknown(b)),
};
if has_param {
let Some(¶m_byte) = value.get(1) else {
return Err(());
};
let Some(register) = AutoRepeatRegister::new(param_byte & 0x3) else {
return Ok(LK201Command::Unknown2(b, param_byte));
};
return Ok(LK201Command::SetModeWithAutoRepeat {
mode,
division,
register,
});
} else {
return Ok(LK201Command::SetMode { mode, division });
}
}
Ok(LK201Command::Unknown(byte0))
}
0xC1 => Ok(LK201Command::TempNoRepeat),
0xD9 => Ok(LK201Command::RepeatToDown),
0xE1 => Ok(LK201Command::DisableRepeat),
0xE3 => Ok(LK201Command::EnableRepeat),
0xE9 => Ok(LK201Command::EnableLK401),
0xEB => Ok(LK201Command::DisableLK401),
0xFD => Ok(LK201Command::PowerUp),
0xAB => Ok(LK201Command::RequestId),
0xD3 => Ok(LK201Command::SetDefaults),
0xCB => Ok(LK201Command::TestMode),
0x8B => Ok(LK201Command::Resume),
0x89 => Ok(LK201Command::Inhibit),
_ => Ok(LK201Command::Unknown(byte0)),
}
}
}
impl Into<Vec<u8>> for LK201Command {
fn into(self) -> Vec<u8> {
match self {
LK201Command::LedEnable(led) => vec![0x13, led.0],
LK201Command::LedDisable(led) => vec![0x11, led.0],
LK201Command::KeyClickEnable(vol) => vec![0x1B, vol.as_param_byte()],
LK201Command::CtrlKeyClickEnable => vec![0xBB],
LK201Command::KeyClickDisable => vec![0x99],
LK201Command::CtrlKeyClickDisable => vec![0xB9],
LK201Command::SoundClick => vec![0x9F],
LK201Command::BellEnable(vol) => vec![0x23, vol.as_param_byte()],
LK201Command::BellDisable => vec![0xA1],
LK201Command::RingBell => vec![0xA7],
LK201Command::SetMode { mode, division } => {
let mode_bits = match mode {
KeyMode::Down => 0b00,
KeyMode::AutoDown => 0b01,
KeyMode::UpDown => 0b11,
KeyMode::Illegal => 0b10,
};
vec![(division.0 << 3) | (mode_bits << 1) | 0x80]
}
LK201Command::SetModeWithAutoRepeat {
mode,
division,
register,
} => {
let mode_bits = match mode {
KeyMode::Down => 0b00,
KeyMode::AutoDown => 0b01,
KeyMode::UpDown => 0b11,
KeyMode::Illegal => 0b10,
};
vec![
(division.0 << 3) | (mode_bits << 1),
0x80 | (register.0 & 0x3),
]
}
LK201Command::RepeatToDown => vec![0xD9],
LK201Command::EnableRepeat => vec![0xE3],
LK201Command::DisableRepeat => vec![0xE1],
LK201Command::EnableLK401 => vec![0xE9],
LK201Command::DisableLK401 => vec![0xEB],
LK201Command::TempNoRepeat => vec![0xC1],
LK201Command::SetAutoRepeat {
register,
timeout,
rate,
} => {
vec![
0b0_1111_000 | (register.0 << 1),
(timeout / 5) & 0x7F,
rate & 0x7F | 0x80,
]
}
LK201Command::PowerUp => vec![0xFD],
LK201Command::RequestId => vec![0xAB],
LK201Command::SetDefaults => vec![0xD3],
LK201Command::TestMode => vec![0xCB],
LK201Command::TestExit => vec![0x80],
LK201Command::Inhibit => vec![0x89],
LK201Command::Resume => vec![0x8B],
LK201Command::Unknown(b) => vec![b],
LK201Command::Unknown2(b1, b2) => vec![b1, b2],
LK201Command::Unknown3(b1, b2, b3) => vec![b1, b2, b3],
}
}
}
pub mod keycodes {
pub const KEY_LOCK: u8 = 0xB0;
pub const KEY_SHIFT: u8 = 0xAE;
pub const KEY_CTRL: u8 = 0xAF;
pub const KEY_COMP: u8 = 0xB1;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LK201Response {
PowerUpSelfTest {
keyboard_id_firmware: u8,
keyboard_id_hardware: u8,
error: PowerUpError,
keycode: u8,
},
KeyboardId { firmware_id: u8, hardware_id: u8 },
ModeChangeAck,
KeyboardLockAck,
TestModeAck,
InputError,
OutputError,
KeyDown(u8),
Repeat,
AllUp,
PrefixKeyDown(u8),
}
impl LK201Response {
pub fn to_bytes(&self) -> Vec<u8> {
match self {
LK201Response::PowerUpSelfTest {
keyboard_id_firmware,
keyboard_id_hardware,
error,
keycode,
} => {
vec![
*keyboard_id_firmware,
*keyboard_id_hardware,
(*error).into(),
*keycode,
]
}
LK201Response::KeyboardId {
firmware_id,
hardware_id,
} => {
vec![*firmware_id, *hardware_id]
}
LK201Response::ModeChangeAck => vec![0xBA],
LK201Response::TestModeAck => vec![0xB8],
LK201Response::KeyboardLockAck => vec![0xB7],
LK201Response::InputError => vec![0xB6],
LK201Response::OutputError => vec![0xB5],
LK201Response::KeyDown(keycode) => vec![*keycode],
LK201Response::Repeat => vec![0xB4],
LK201Response::AllUp => vec![0xB3],
LK201Response::PrefixKeyDown(keycode) => vec![0xB9, *keycode],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyboardType {
LK201 = 1,
LK401 = 2,
LK443 = 3,
LK421 = 4,
}
impl KeyboardType {
pub fn from_id(id: u8) -> Option<Self> {
match id {
1 => Some(KeyboardType::LK201),
2 => Some(KeyboardType::LK401),
3 => Some(KeyboardType::LK443),
4 => Some(KeyboardType::LK421),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PowerUpError {
NoError,
KeyDownError,
PowerError,
Unknown(u8),
}
impl From<u8> for PowerUpError {
fn from(byte: u8) -> Self {
match byte {
0x00 => PowerUpError::NoError,
0x3D => PowerUpError::KeyDownError,
0x3E => PowerUpError::PowerError,
other => PowerUpError::Unknown(other),
}
}
}
impl From<PowerUpError> for u8 {
fn from(error: PowerUpError) -> u8 {
match error {
PowerUpError::NoError => 0x00,
PowerUpError::KeyDownError => 0x3D,
PowerUpError::PowerError => 0x3E,
PowerUpError::Unknown(byte) => byte,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[track_caller]
fn test_parse(input: &[u8], expected: LK201Command) {
let queue = VecDeque::from_iter(input.iter().copied());
let command = LK201Command::try_from(&queue).unwrap();
assert_eq!(
command, expected,
"input: {:02X?}, expected: {:?}",
input, expected
);
assert_eq!(
input.len(),
command.len(),
"input: {:02X?}, expected: {:?}",
input,
expected
);
}
#[test]
fn test_mode_commands() {
test_parse(
&[0x0A, 0x80],
LK201Command::SetModeWithAutoRepeat {
division: Division(1),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(0),
},
);
test_parse(
&[0x12, 0x81],
LK201Command::SetModeWithAutoRepeat {
division: Division(2),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(1),
},
);
test_parse(
&[0x72, 0x82],
LK201Command::SetModeWithAutoRepeat {
division: Division(14),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(2),
},
);
test_parse(
&[0xA2],
LK201Command::SetMode {
mode: KeyMode::AutoDown,
division: Division(4),
},
);
}
#[test]
fn test_autorepeat_commands() {
test_parse(
&[0x78, 0x64, 0x9E],
LK201Command::SetAutoRepeat {
register: AutoRepeatRegister(0),
timeout: 0x64,
rate: 0x1E,
},
);
test_parse(
&[0x7A, 0x64, 0x9E],
LK201Command::SetAutoRepeat {
register: AutoRepeatRegister(1),
timeout: 0x64,
rate: 0x1E,
},
);
test_parse(
&[0x7C, 0x64, 0x9E],
LK201Command::SetAutoRepeat {
register: AutoRepeatRegister(2),
timeout: 0x64,
rate: 0x1E,
},
);
}
#[test]
fn test_led_commands() {
test_parse(&[0x13, 0x84], LK201Command::LedEnable(Led::new(0x84)));
test_parse(&[0x11, 0x88], LK201Command::LedDisable(Led::new(0x88)));
test_parse(&[0x13, 0x8F], LK201Command::LedEnable(Led::new(0x8F)));
test_parse(&[0x13, 0x81], LK201Command::LedEnable(Led::new(0x81)));
test_parse(&[0x13, 0x80], LK201Command::LedEnable(Led::new(0x80))); test_parse(&[0x13, 0x83], LK201Command::LedEnable(Led::new(0x83))); test_parse(&[0x13, 0x85], LK201Command::LedEnable(Led::new(0x85))); test_parse(&[0x13, 0x86], LK201Command::LedEnable(Led::new(0x86))); test_parse(&[0x13, 0x8C], LK201Command::LedEnable(Led::new(0x8C))); test_parse(&[0x11, 0x8D], LK201Command::LedDisable(Led::new(0x8D))); }
#[test]
fn test_bell_and_click_commands() {
test_parse(&[0x23, 0x80], LK201Command::BellEnable(Volume(0)));
test_parse(&[0xA1], LK201Command::BellDisable);
test_parse(&[0xA7], LK201Command::RingBell);
test_parse(&[0x1B, 0x80], LK201Command::KeyClickEnable(Volume(0)));
test_parse(&[0x99], LK201Command::KeyClickDisable);
test_parse(&[0xBB], LK201Command::CtrlKeyClickEnable);
test_parse(&[0xB9], LK201Command::CtrlKeyClickDisable);
}
#[test]
fn test_control_commands() {
test_parse(&[0xFD], LK201Command::PowerUp);
test_parse(&[0xAB], LK201Command::RequestId);
test_parse(&[0xE3], LK201Command::EnableRepeat);
test_parse(&[0xE1], LK201Command::DisableRepeat);
test_parse(&[0xD9], LK201Command::RepeatToDown);
test_parse(&[0xC1], LK201Command::TempNoRepeat);
test_parse(&[0xD3], LK201Command::SetDefaults);
test_parse(&[0xCB], LK201Command::TestMode);
test_parse(&[0x8B], LK201Command::Resume);
test_parse(&[0x89], LK201Command::Inhibit);
test_parse(&[0x80], LK201Command::TestExit);
}
#[test]
fn test_unknown_commands() {
test_parse(&[0x0D], LK201Command::Unknown(0x0D));
test_parse(&[0xFF], LK201Command::Unknown(0xFF));
test_parse(&[0x01], LK201Command::Unknown(0x01));
test_parse(&[0x55], LK201Command::Unknown(0x55));
let cmd = LK201Command::Unknown(0x0D);
let resp = cmd.response().unwrap();
assert_eq!(resp, LK201Response::InputError);
assert_eq!(resp.to_bytes(), vec![0xB6]);
}
#[test]
fn test_command_responses() {
let cmd = LK201Command::PowerUp;
let resp = cmd.response().unwrap();
assert_eq!(resp.to_bytes(), vec![0x01, 0x00, 0x00, 0x00]);
let cmd = LK201Command::RequestId;
let resp = cmd.response().unwrap();
assert_eq!(resp.to_bytes(), vec![0x01, 0x01]);
let cmd = LK201Command::SetMode {
mode: KeyMode::AutoDown,
division: Division(1),
};
let resp = cmd.response().unwrap();
assert_eq!(resp.to_bytes(), vec![0xBA]);
let cmd = LK201Command::SetModeWithAutoRepeat {
mode: KeyMode::AutoDown,
division: Division(1),
register: AutoRepeatRegister(0),
};
let resp = cmd.response().unwrap();
assert_eq!(resp.to_bytes(), vec![0xBA]);
let resp = LK201Command::SetDefaults.response().unwrap();
assert_eq!(resp.to_bytes(), vec![0xBA]);
let cmd = LK201Command::TestMode;
let resp = cmd.response().unwrap();
assert_eq!(resp.to_bytes(), vec![0xB8]);
let cmd = LK201Command::Inhibit;
let resp = cmd.response().unwrap();
assert_eq!(resp.to_bytes(), vec![0xB7]);
let cmd = LK201Command::Unknown(0xFF);
let resp = cmd.response().unwrap();
assert_eq!(resp.to_bytes(), vec![0xB6]);
assert!(LK201Command::LedEnable(Led::new(0x84)).response().is_none());
assert!(LK201Command::BellEnable(Volume(4)).response().is_none());
assert!(LK201Command::KeyClickDisable.response().is_none());
assert!(LK201Command::Resume.response().is_none());
}
#[test]
fn test_response_serialization() {
let resp = LK201Response::ModeChangeAck;
assert_eq!(resp.to_bytes(), vec![0xBA]);
let resp = LK201Response::KeyboardLockAck;
assert_eq!(resp.to_bytes(), vec![0xB7]);
let resp = LK201Response::TestModeAck;
assert_eq!(resp.to_bytes(), vec![0xB8]);
let resp = LK201Response::InputError;
assert_eq!(resp.to_bytes(), vec![0xB6]);
let resp = LK201Response::OutputError;
assert_eq!(resp.to_bytes(), vec![0xB5]);
let resp = LK201Response::Repeat;
assert_eq!(resp.to_bytes(), vec![0xB4]);
let resp = LK201Response::AllUp;
assert_eq!(resp.to_bytes(), vec![0xB3]);
let resp = LK201Response::KeyDown(0x42);
assert_eq!(resp.to_bytes(), vec![0x42]);
let resp = LK201Response::PrefixKeyDown(0x42);
assert_eq!(resp.to_bytes(), vec![0xB9, 0x42]);
}
#[test]
fn test_full_sequence() {
test_parse(
&[0x0A, 0x80],
LK201Command::SetModeWithAutoRepeat {
division: Division(1),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(0),
},
);
test_parse(
&[0x1A, 0x80],
LK201Command::SetModeWithAutoRepeat {
division: Division(3),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(0),
},
);
test_parse(
&[0x3A, 0x80],
LK201Command::SetModeWithAutoRepeat {
division: Division(7),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(0),
},
);
test_parse(
&[0x42, 0x81],
LK201Command::SetModeWithAutoRepeat {
division: Division(8),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(1),
},
);
test_parse(
&[0x4A, 0x82],
LK201Command::SetModeWithAutoRepeat {
division: Division(9),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(2),
},
);
test_parse(
&[0x5A, 0x82],
LK201Command::SetModeWithAutoRepeat {
division: Division(11),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(2),
},
);
test_parse(
&[0x62, 0x82],
LK201Command::SetModeWithAutoRepeat {
division: Division(12),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(2),
},
);
test_parse(
&[0x6A, 0x82],
LK201Command::SetModeWithAutoRepeat {
division: Division(13),
mode: KeyMode::AutoDown,
register: AutoRepeatRegister(2),
},
);
}
#[test]
fn test_lk201() {
test_parse(&[0xE9], LK201Command::EnableLK401);
test_parse(&[0xEB], LK201Command::DisableLK401);
}
}