repo_description: Improve keyboard comfort and usability with advanced customization
repo_topics: cross-platform interception-driver keyboard keyboard-layout linux macos mouse mouse-emulation rust windows
code: //! Redirects output to the function provided by the entity supplying simulated input (e.g., AHK)
  // todo: allow sharing numpad status to differentiate between vk enter and vk numpad enter
  // todo: only press/release_key is implemented
  use super::*;
  use anyhow::Result;
  use log::*;
  
  use crate::kanata::CalculatedMouseMove;
  use kanata_parser::custom_action::*;
  
  use std::io;
  
  #[cfg(not(any(target_os = "windows", target_os = "macos")))]
  use std::fmt;
  
  use std::sync::Arc;
  use std::sync::OnceLock;
  type CbOutEvFn = dyn Fn(i64, i64, i64) -> i64 + Send + Sync + 'static; // Rust wrapper func around external callback (transmuted into this) Ahk accept only i64 arguments (vk,sc,up)
  pub struct FnOutEvWrapper {
      pub cb: Arc<CbOutEvFn>,
  } // wrapper struct to store our callback in a thread-shareable manner
  pub static OUTEVWRAP: OnceLock<FnOutEvWrapper> = OnceLock::new(); // ensure that our wrapper struct is created once (thread-safe)
  
  use std::sync::mpsc::{SendError, Sender as ASender};
  /// Handle for writing keys to the simulated input provider.
  pub struct KbdOut {
      pub tx_kout: Option<ASender<InputEvent>>,
  }
  
  use std::io::{Error as IoErr, ErrorKind::NotConnected};
  impl KbdOut {
      #[cfg(not(any(target_os = "linux", target_os = "android")))]
      pub fn new() -> Result<Self, io::Error> {
          Ok(Self { tx_kout: None })
      }
      #[cfg(any(target_os = "linux", target_os = "android"))]
      pub fn new(
          _s: &Option<String>,
          _tp: bool,
          _name: &str,
          _bustype: evdev::BusType,
      ) -> Result<Self, io::Error> {
          Ok(Self { tx_kout: None })
      }
      #[cfg(any(target_os = "linux", target_os = "android"))]
      pub fn write_raw(&mut self, event: InputEvent) -> Result<(), io::Error> {
          trace!("out-raw:{event:?}");
          Ok(())
      }
      pub fn write(&mut self, event: InputEvent) -> Result<(), io::Error> {
          trace!("out:{event}");
          if let Some(tx_kout) = &self.tx_kout {
              // Send key event msg → main thread so it can be polled to try receiving it after processing external input events
              match tx_kout.send(event) {
                  // send won't block for an async channel
                  Ok(res) => {
                      debug!(
                          "✓ tx_kout → rx_kout@key_out(dll) ‘{event}’ from send_out_ev_msg@sim_passthru(oskbd)"
                      );
                      return Ok(res);
                  }
                  Err(SendError(event)) => {
                      error!(
                          "✗ tx_kout → rx_kout@key_out(dll) ‘{event}’ from send_out_ev_msg@sim_passthru(oskbd)"
                      );
                      return Err(IoErr::new(
                          NotConnected,
                          format!("Failed sending sending {event}"),
                      ));
                  }
              }
          } else {
              debug!("✗ tx_kout doesn't exist");
          }
          Ok(())
      }
      pub fn output_ready(&self) -> bool {
          true
      }
      pub fn wait_until_ready(&self, _timeout: Option<std::time::Duration>) -> bool {
          true
      }
      pub fn write_key(&mut self, key: OsCode, value: KeyValue) -> Result<(), io::Error> {
          let key_ev = KeyEvent::new(key, value);
          let event = {
              #[cfg(target_os = "macos")]
              {
                  key_ev.try_into().unwrap()
              }
              #[cfg(not(target_os = "macos"))]
              {
                  key_ev.into()
              }
          };
          self.write(event)
      }
      pub fn write_code(&mut self, code: u32, value: KeyValue) -> Result<(), io::Error> {
          trace!("out-code:{code};{value:?}");
          Ok(())
      }
      pub fn press_key(&mut self, key: OsCode) -> Result<(), io::Error> {
          self.write_key(key, KeyValue::Press)
      }
      pub fn release_key(&mut self, key: OsCode) -> Result<(), io::Error> {
          self.write_key(key, KeyValue::Release)
      }
      pub fn release_tracked_output_keys(&mut self, _reason: &str) {}
      pub fn send_unicode(&mut self, c: char) -> Result<(), io::Error> {
          trace!("outU:{c}");
          Ok(())
      }
      pub fn click_btn(&mut self, btn: Btn) -> Result<(), io::Error> {
          trace!("out🖰:↓{btn:?}");
          Ok(())
      }
      pub fn release_btn(&mut self, btn: Btn) -> Result<(), io::Error> {
          trace!("out🖰:↑{btn:?}");
          Ok(())
      }
      pub fn scroll(&mut self, direction: MWheelDirection, distance: u16) -> Result<(), io::Error> {
          trace!("scroll:{direction:?},{distance:?}");
          Ok(())
      }
      pub fn move_mouse(&mut self, mv: CalculatedMouseMove) -> Result<(), io::Error> {
          let (direction, distance) = (mv.direction, mv.distance);
          trace!("out🖰:move {direction:?},{distance:?}");
          Ok(())
      }
      pub fn move_mouse_many(&mut self, moves: &[CalculatedMouseMove]) -> Result<(), io::Error> {
          for mv in moves {
              let (direction, distance) = (&mv.direction, &mv.distance);
              trace!("out🖰:move {direction:?},{distance:?}");
          }
          Ok(())
      }
      pub fn set_mouse(&mut self, x: u16, y: u16) -> Result<(), io::Error> {
          log::info!("out🖰:@{x},{y}");
          Ok(())
      }
      pub fn tick(&mut self) {}
  }
  
  #[cfg(not(any(target_os = "windows", target_os = "macos")))]
  #[derive(Debug, Clone, Copy)]
  pub struct InputEvent {
      pub code: u32,
  
      /// Key was released
      pub up: bool,
  }
  #[cfg(not(any(target_os = "windows", target_os = "macos")))]
  impl fmt::Display for InputEvent {
      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
          use kanata_keyberon::key_code::KeyCode;
          let direction = if self.up { "↑" } else { "↓" };
          let key_name = KeyCode::from(OsCode::from(self.code));
          write!(f, "{}{:?}", direction, key_name)
      }
  }
  
  #[cfg(not(any(target_os = "windows", target_os = "macos")))]
  impl InputEvent {
      pub fn from_oscode(code: OsCode, val: KeyValue) -> Self {
          Self {
              code: code.into(),
              up: val.into(),
          }
      }
  }
  
  #[cfg(not(any(target_os = "windows", target_os = "macos")))]
  impl TryFrom<InputEvent> for KeyEvent {
      type Error = ();
      fn try_from(item: InputEvent) -> Result<Self, Self::Error> {
          Ok(Self {
              code: OsCode::from_u16(item.code as u16).ok_or(())?,
              value: match item.up {
                  true => KeyValue::Release,
                  false => KeyValue::Press,
              },
          })
      }
  }
  
  #[cfg(not(any(target_os = "windows", target_os = "macos")))]
  impl From<KeyEvent> for InputEvent {
      fn from(item: KeyEvent) -> Self {
          Self {
              code: item.code.into(),
              up: item.value.into(),
          }
      }
  }
  