use std::{io::Write, time::Duration};
use anyhow::Context;
use clap::{Parser, ValueEnum};
use rusb::{DeviceHandle, GlobalContext};
const M27QX_VID: u16 = 0x0BDA;
const M27QX_PID: u16 = 0x1100;
const INTERFACE_NUMBER: u8 = 0;
#[derive(Copy, Clone, Debug, ValueEnum)]
#[value(rename_all = "verbatim")]
enum PictureMode {
Standard = 0x00,
#[value(name = "FPS")]
Fps = 0x01,
#[value(name = "RTS/RPG")]
RtsRpg = 0x02,
Movie = 0x03,
Reader = 0x04,
#[value(name = "sRGB")]
Srgb = 0x05,
Custom1 = 0x06,
Custom2 = 0x07,
Custom3 = 0x08,
}
fn forall(cli: Cli) -> anyhow::Result<()> {
let mut found = 0;
for device in rusb::devices().context("listing USB devices")?.iter() {
let descriptor = device
.device_descriptor()
.context("Reading device descriptor")?;
if descriptor.vendor_id() == M27QX_VID && descriptor.product_id() == M27QX_PID {
found += 1;
let handle = device.open().context("Opening USB device")?;
let is_active = handle
.kernel_driver_active(INTERFACE_NUMBER)
.unwrap_or(false);
if is_active {
handle
.detach_kernel_driver(INTERFACE_NUMBER)
.context("Detaching kernel driver")?;
}
handle
.claim_interface(INTERFACE_NUMBER)
.context("Claiming interface")?;
if let Some(picture_mode) = cli.picture_mode {
set_picture_mode(&handle, picture_mode).context("Setting picture mode")?;
}
if cli.kvm {
kvm(&handle).context("Triggering KVM switch")?;
}
if is_active {
handle.attach_kernel_driver(INTERFACE_NUMBER).ok();
}
}
}
if found == 0 {
anyhow::bail!("No M27Q-X devices found");
}
Ok(())
}
fn set_configuration(handle: &DeviceHandle<GlobalContext>, command: &[u8]) -> anyhow::Result<()> {
const MYSTERY_PREFIX: [u8; 64] = [
0x40, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let checksum = command.iter().fold(0, |acc, &val| acc ^ val);
let mut buffer = [0u8; 192];
let mut cursor = &mut buffer[..];
cursor.write_all(&MYSTERY_PREFIX)?;
cursor.write_all(command)?;
cursor.write_all(&[checksum])?;
let request_type = rusb::request_type(
rusb::Direction::Out,
rusb::RequestType::Class,
rusb::Recipient::Interface,
);
handle
.write_control(
request_type,
rusb::constants::LIBUSB_REQUEST_SET_CONFIGURATION,
0x0200,
0,
&buffer,
Duration::from_secs(1),
)
.context("USB write_control")?;
Ok(())
}
fn send_command(
handle: &DeviceHandle<GlobalContext>,
command: u8,
value: u8,
) -> anyhow::Result<()> {
set_configuration(handle, &[0x51, 0x85, 0x03, 0xe0, command, 0x00, value])
}
fn set_picture_mode(handle: &DeviceHandle<GlobalContext>, mode: PictureMode) -> anyhow::Result<()> {
send_command(handle, 0x2c, mode as u8)
}
fn kvm(handle: &DeviceHandle<GlobalContext>) -> anyhow::Result<()> {
send_command(handle, 0x69, 0x01)
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(long, value_enum, ignore_case = true)]
picture_mode: Option<PictureMode>,
#[arg(long)]
kvm: bool,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
forall(cli)
}