use std::io;
use std::path::Path;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::{Duration, Instant};
use mlua::prelude::*;
use crate::Rgb;
use crate::virtual_keyboard::{Bind, KeyCode, Modifiers, VirtualKeyboard, string_to_code};
use crate::{DeviceHandler, HandledDeviceRef, Handler};
#[derive(Debug)]
pub struct LuaHandler {
keyboard: Arc<Mutex<VirtualKeyboard>>,
callback: LuaFunction,
lua: Rc<Lua>,
}
#[derive(Clone, Debug)]
pub struct LuaDeviceHandler {
keyboard: Arc<Mutex<VirtualKeyboard>>,
callback: LuaFunction,
lua: Rc<Lua>,
start: Arc<Instant>,
device_ref: HandledDeviceRef,
}
impl LuaHandler {
pub fn new(keyboard: Arc<Mutex<VirtualKeyboard>>, path: &Path) -> LuaResult<Self> {
let lua = Rc::new(Lua::new());
lua.load(path).exec()?;
let callback: LuaFunction = lua.globals().get("OnEvent")?;
Ok(LuaHandler {
keyboard,
callback,
lua,
})
}
}
impl Handler for LuaHandler {
#[allow(refining_impl_trait)]
fn handler_for_device(&self, device_ref: HandledDeviceRef) -> LuaDeviceHandler {
LuaDeviceHandler::new(self, device_ref)
}
}
impl LuaDeviceHandler {
fn new(handler: &LuaHandler, device_ref: HandledDeviceRef) -> Self {
let res = Self {
keyboard: handler.keyboard.clone(),
callback: handler.callback.clone(),
lua: handler.lua.clone(),
start: Arc::new(Instant::now()),
device_ref,
};
res.bind_functions().unwrap_or_else(|err| {
log::error!("Could not bind lua functions: {}", err);
});
res.on_event("PROFILE_ACTIVATED", LuaNil);
res
}
fn bind_function<A, R, F>(&self, name: &str, fun: F) -> LuaResult<()>
where
A: FromLuaMulti,
R: IntoLuaMulti,
F: LuaNativeFn<A, Output = LuaResult<R>> + 'static,
{
self.lua.globals().set(name, LuaFunction::wrap(fun))
}
fn bind_not_implemented(&self, name: &'static str) -> LuaResult<()> {
self.bind_function(name, create_lua_not_implemented(name))
}
fn mirror_native(&self, name: &str, tab_name: &str, fn_name: &str) -> LuaResult<()> {
let tab: LuaTable = self.lua.globals().get(tab_name)?;
let fun: LuaFunction = tab.get(fn_name)?;
self.lua.globals().set(name, fun)
}
fn bind_functions(&self) -> LuaResult<()> {
self.bind_function(
"PressKey",
create_key_fun(self.keyboard.clone(), VirtualKeyboard::keys_down),
)?;
self.bind_function(
"ReleaseKey",
create_key_fun(self.keyboard.clone(), VirtualKeyboard::keys_up),
)?;
self.bind_function(
"PressAndReleaseKey",
create_key_fun(self.keyboard.clone(), VirtualKeyboard::press_keys),
)?;
self.bind_function(
"PressMouseButton",
create_mouse_button_fun(self.keyboard.clone(), VirtualKeyboard::key_down),
)?;
self.bind_function(
"ReleaseMouseButton",
create_mouse_button_fun(self.keyboard.clone(), VirtualKeyboard::key_up),
)?;
self.bind_function(
"PressAndReleaseMouseButton",
create_mouse_button_fun(self.keyboard.clone(), VirtualKeyboard::press),
)?;
self.bind_function(
"GetMKeyState",
create_get_m_key_state(self.device_ref.clone()),
)?;
self.bind_function(
"SetMKeyState",
create_set_m_key_state(self.device_ref.clone()),
)?;
self.bind_function(
"SetBacklightColor",
create_set_backlight_color(self.device_ref.clone()),
)?;
self.bind_function("Sleep", lua_sleep)?;
self.mirror_native("GetDate", "os", "date")?;
self.bind_function(
"GetRunningTime",
create_get_running_time(self.start.clone()),
)?;
let string_tab: LuaTable = self.lua.globals().get("string")?;
let format_fn: LuaFunction = string_tab.get("format")?;
self.bind_function(
"OutputLogMessage",
create_output_log_message(format_fn.clone()),
)?;
self.bind_function(
"OutputDebugMessage",
create_output_debug_message(format_fn.clone()),
)?;
self.bind_not_implemented("OutputLCDMessage")?;
self.bind_not_implemented("ClearLCD")?;
self.bind_not_implemented("PlayMacro")?;
self.bind_not_implemented("AbortMacro")?;
self.bind_not_implemented("ClearLog")?;
self.bind_not_implemented("IsKeyLockOn")?;
self.bind_not_implemented("IsModifierPressed")?;
self.bind_not_implemented("IsMouseButtonPressed")?;
self.bind_not_implemented("MoveMouseTo")?;
self.bind_not_implemented("MoveMouseWheel")?;
self.bind_not_implemented("MoveMouseRelative")?;
self.bind_not_implemented("MoveMouseToVirtual")?;
self.bind_not_implemented("GetMousePosition")?;
self.bind_not_implemented("SetMouseDPITable")?;
self.bind_not_implemented("SetMouseDPITableIndex")?;
self.bind_not_implemented("EnablePrimaryMouseButtonEvents")?;
self.bind_not_implemented("SetMouseSpeed")?;
self.bind_not_implemented("GetMouseSpeed")?;
self.bind_not_implemented("IncrementMouseSpeed")?;
self.bind_not_implemented("DecrementMouseSpeed")?;
self.bind_not_implemented("SetSteeringWheelProperty")?;
Ok(())
}
fn on_event(&self, event: &str, args: impl IntoLua) {
self.callback
.call::<()>((event, args, "lhc"))
.unwrap_or_else(lua_error)
}
}
impl DeviceHandler for LuaDeviceHandler {
fn g_key_down(&mut self, key: u16) {
self.on_event("G_PRESSED", key);
}
fn g_key_up(&mut self, key: u16) {
self.on_event("G_RELEASED", key);
}
fn m_key_down(&mut self, key: u16) {
self.on_event("M_PRESSED", key);
}
fn m_key_up(&mut self, key: u16) {
self.on_event("M_RELEASED", key);
}
}
fn lua_error(err: LuaError) {
log::warn!("Calling lua caused an error: {}", err);
}
fn create_lua_not_implemented(name: &'static str) -> impl LuaNativeFn<(), Output = LuaResult<()>> {
move || {
log::warn!("Ignoring call to unimplemented lua function {}", name);
Ok(())
}
}
fn create_key_fun(
keyboard: Arc<Mutex<VirtualKeyboard>>,
fun: fn(&mut VirtualKeyboard, &[KeyCode]) -> io::Result<()>,
) -> impl LuaNativeFn<(LuaVariadic<LuaValue>,), Output = LuaResult<()>> {
move |args: LuaVariadic<LuaValue>| {
let mut kb = keyboard.lock().unwrap();
fun(&mut kb, &map_to_keycode(args))?;
Ok(())
}
}
fn map_to_keycode(args: LuaVariadic<LuaValue>) -> Vec<KeyCode> {
log::debug!("{:?}", args);
let res = args
.iter()
.filter_map(|val| match val {
LuaValue::Integer(scancode) => Some(KeyCode(*scancode as u16)),
LuaValue::String(keyname) => keyname.to_str().as_deref().ok().and_then(string_to_code),
_ => None,
})
.collect();
log::debug!("{:?}", res);
res
}
fn create_mouse_button_fun(
keyboard: Arc<Mutex<VirtualKeyboard>>,
fun: fn(&mut VirtualKeyboard, Bind) -> io::Result<()>,
) -> impl LuaNativeFn<(u16,), Output = LuaResult<()>> {
move |m: u16| {
if let Some(bind) = map_mouse_button(m) {
let mut kb = keyboard.lock().unwrap();
fun(&mut kb, bind)?;
} else {
log::warn!("Ignoring invalid mouse button: {}", m);
}
Ok(())
}
}
fn map_mouse_button(button: u16) -> Option<Bind> {
match button {
1 => Some("mouse1"),
2 => Some("mouse2"),
3 => Some("mouse3"),
4 => Some("mouse4"),
5 => Some("mouse5"),
_ => None,
}
.and_then(string_to_code)
.map(|button| (Modifiers::empty(), button))
}
fn create_get_m_key_state(
device_ref: HandledDeviceRef,
) -> impl LuaNativeFn<(), Output = LuaResult<u8>> {
move || Ok(device_ref.mode())
}
fn create_set_m_key_state(
device_ref: HandledDeviceRef,
) -> impl LuaNativeFn<(u16,), Output = LuaResult<()>> {
move |m: u16| {
device_ref.set_mode(m);
Ok(())
}
}
fn create_set_backlight_color(
device_ref: HandledDeviceRef,
) -> impl LuaNativeFn<(u8, u8, u8), Output = LuaResult<()>> {
move |r, g, b| {
device_ref
.set_backlight(Rgb(r, g, b))
.unwrap_or_else(|err| {
log::error!("Could not change backlight: {}", err);
});
Ok(())
}
}
fn lua_sleep(time: u64) -> LuaResult<()> {
sleep(Duration::from_millis(time));
Ok(())
}
fn create_get_running_time(start: Arc<Instant>) -> impl LuaNativeFn<(), Output = LuaResult<u128>> {
move || Ok(start.elapsed().as_millis())
}
fn call_lua_format_fn(format_fn: &LuaFunction, args: LuaMultiValue) -> Option<String> {
let res: LuaResult<LuaString> = format_fn.call(args);
match res {
Ok(s) => Some(s.to_string_lossy()),
Err(e) => {
log::warn!("Could not format lua string: {}", e);
None
}
}
}
fn create_output_log_message(
format_fn: LuaFunction,
) -> impl LuaNativeFn<(LuaMultiValue,), Output = LuaResult<()>> {
move |args| {
if let Some(s) = call_lua_format_fn(&format_fn, args) {
log::info!("[Lua Log] {}", s);
}
Ok(())
}
}
fn create_output_debug_message(
format_fn: LuaFunction,
) -> impl LuaNativeFn<(LuaMultiValue,), Output = LuaResult<()>> {
move |args| {
if let Some(s) = call_lua_format_fn(&format_fn, args) {
log::debug!("[Lua Log] {}", s);
}
Ok(())
}
}