pub(crate) mod args;
#[cfg(all(target_os = "windows", feature = "gui"))]
pub(crate) mod win_gui;
#[cfg(all(target_os = "macos", not(feature = "gui")))]
pub(crate) fn list_devices_macos() {
use karabiner_driverkit::fetch_devices;
println!("Available keyboard devices:");
println!("===========================\n");
let kb_list = fetch_devices();
let print_header = || {
println!(
"{:<20} {:<10} {:<10} product_key",
"hash", "vendor_id", "product_id"
);
};
let print_line = || {
println!(
"{} {} {} {}",
"-".repeat(18),
"-".repeat(8),
"-".repeat(8),
"-".repeat(50)
);
};
print_header();
print_line();
for kb in kb_list.iter() {
println!("{}", kb);
}
print_line();
if kb_list.is_empty() {
println!("No devices found. Ensure Karabiner-VirtualHIDDevice driver is activated.");
return;
}
println!(
"\nTo address devices with empty names (product key), hash values can be used in the configuration!"
);
println!("\nConfiguration example:");
println!(" (defcfg");
println!(" macos-dev-names-include (");
for kb in kb_list
.iter()
.map(|k| k.product_key.clone())
.filter(|k| !k.trim().is_empty())
{
println!(" \"{}\"", kb.trim());
}
println!(" )");
println!(" or instead:");
println!(" macos-dev-names-include (");
for kb in kb_list.iter().map(|k| k.hash) {
println!(" \"0x{:<16X}\"", kb);
}
println!(" )");
println!(" )");
}
#[cfg(all(any(target_os = "linux", target_os = "android"), not(feature = "gui")))]
pub(crate) fn list_devices_linux() {
use crate::oskbd::discover_devices;
use kanata_parser::cfg::DeviceDetectMode;
println!("Available keyboard devices:");
println!("===========================");
let devices = discover_devices(None, None, DeviceDetectMode::KeyboardOnly);
if devices.is_empty() {
println!("No keyboard devices found.");
println!("\nTroubleshooting:");
println!(" 1. Check permissions: sudo usermod -a -G input $USER");
println!(" 2. Log out and back in for group changes to take effect");
println!(" 3. Ensure devices are connected and working");
return;
}
println!("Found {} keyboard device(s):\n", devices.len());
for (i, (device, path)) in devices.iter().enumerate() {
let name = device.name().unwrap_or("Unknown");
let input_id = device.input_id();
println!(" {}. \"{}\"", i + 1, name);
println!(" Path: {path}");
println!(
" Vendor ID: {} (0x{:04X}), Product ID: {} (0x{:04X})",
input_id.vendor(),
input_id.vendor(),
input_id.product(),
input_id.product()
);
println!();
}
println!("Configuration example:");
println!(" (defcfg");
println!(" linux-dev-names-include (");
for (device, _path) in devices.iter() {
println!(" \"{}\"", device.name().unwrap_or("Unknown"));
}
println!(" )");
println!(" )");
}
#[cfg(all(
target_os = "windows",
feature = "interception_driver",
not(feature = "gui")
))]
struct WindowsDeviceInfo {
display_name: String, raw_wide_bytes: Vec<u8>, hardware_id: Option<String>, }
#[cfg(all(
target_os = "windows",
feature = "interception_driver",
not(feature = "gui")
))]
fn get_device_info(device_handle: winapi::um::winnt::HANDLE) -> Option<WindowsDeviceInfo> {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::ptr::null_mut;
use winapi::shared::minwindef::{PUINT, UINT};
use winapi::um::winuser::{GetRawInputDeviceInfoW, RIDI_DEVICENAME};
unsafe {
let mut name_size: UINT = 0;
GetRawInputDeviceInfoW(
device_handle,
RIDI_DEVICENAME,
null_mut(),
&mut name_size as PUINT,
);
if name_size > 0 {
let mut name_buffer: Vec<u16> = vec![0; name_size as usize];
let result = GetRawInputDeviceInfoW(
device_handle,
RIDI_DEVICENAME,
name_buffer.as_mut_ptr() as *mut _,
&mut name_size as PUINT,
);
if result != u32::MAX {
let actual_char_count = (result / 2) as usize;
name_buffer.truncate(actual_char_count);
if let Some(&0) = name_buffer.last() {
name_buffer.pop();
}
let raw_wide_bytes: Vec<u8> =
name_buffer.iter().flat_map(|&c| c.to_le_bytes()).collect();
let os_string = OsString::from_wide(&name_buffer);
let display_name = os_string.to_string_lossy().into_owned();
let hardware_id = extract_hardware_id(&display_name);
return Some(WindowsDeviceInfo {
display_name,
raw_wide_bytes,
hardware_id,
});
}
}
}
None
}
#[cfg(all(
target_os = "windows",
feature = "interception_driver",
not(feature = "gui")
))]
pub(crate) fn list_devices_windows() {
use std::ptr::null_mut;
use winapi::shared::minwindef::{PUINT, UINT};
use winapi::um::winuser::{GetRawInputDeviceList, RAWINPUTDEVICELIST, RIM_TYPEKEYBOARD};
println!("Available keyboard devices:");
println!("===========================");
unsafe {
let mut num_devices: UINT = 0;
let result = GetRawInputDeviceList(
null_mut(),
&mut num_devices as PUINT,
std::mem::size_of::<RAWINPUTDEVICELIST>() as UINT,
);
if result == u32::MAX {
println!("Error: Failed to get device count");
return;
}
if num_devices == 0 {
println!("No input devices found.");
return;
}
let mut devices: Vec<RAWINPUTDEVICELIST> = vec![std::mem::zeroed(); num_devices as usize];
let result = GetRawInputDeviceList(
devices.as_mut_ptr(),
&mut num_devices as PUINT,
std::mem::size_of::<RAWINPUTDEVICELIST>() as UINT,
);
if result == u32::MAX {
println!("Error: Failed to get device list");
return;
}
let keyboards: Vec<&RAWINPUTDEVICELIST> = devices
.iter()
.filter(|device| device.dwType == RIM_TYPEKEYBOARD)
.collect();
if keyboards.is_empty() {
println!("No keyboard devices found.");
println!("\nTroubleshooting:");
println!(" 1. Ensure keyboards are connected and working");
println!(" 2. Try running as administrator if needed");
return;
}
println!("Found {} keyboard device(s):\n", keyboards.len());
for (i, device) in keyboards.iter().enumerate() {
if let Some(device_info) = get_device_info(device.hDevice) {
println!(" {}. Device: {}", i + 1, device_info.display_name);
if let Some(hwid) = &device_info.hardware_id {
println!(" Hardware ID: {hwid}");
}
println!(
" Raw wide string bytes: {:?}",
device_info.raw_wide_bytes
);
println!();
}
}
if !keyboards.is_empty() {
println!("Configuration example:");
println!(" (defcfg");
println!(" windows-interception-keyboard-hwids (");
for device in keyboards.iter() {
if let Some(device_info) = get_device_info(device.hDevice) {
print!(" {:?}", device_info.raw_wide_bytes);
if let Some(hwid) = &device_info.hardware_id {
println!(" ; {} ({})", hwid, device_info.display_name);
} else {
println!(" ; {}", device_info.display_name);
}
}
}
println!(" )");
println!(" )");
}
}
}
#[cfg(all(
target_os = "windows",
feature = "interception_driver",
not(feature = "gui")
))]
fn extract_hardware_id(device_name: &str) -> Option<String> {
if let Some(start) = device_name.find("HID#")
&& let Some(end) = device_name[start..].find('#')
{
let hwid_part = &device_name[start..start + end];
return Some(hwid_part.to_string());
}
None
}