1use rustix::ioctl::{self, opcode};
7use std::{
8 fs::{File, OpenOptions},
9 io::{self, Read, Result, Write},
10 thread,
11 time::Duration,
12};
13
14use usb_gadget::{
15 default_udc,
16 function::printer::{Printer, StatusFlags, GADGET_GET_PRINTER_STATUS, GADGET_SET_PRINTER_STATUS},
17 Class, Config, Gadget, Id, RegGadget, Strings, GADGET_IOC_MAGIC,
18};
19
20const BUF_SIZE: usize = 512;
22const DEV_PATH: &str = "/dev/g_printer0";
24const PRINT_EXIT_COUNT: u8 = 1;
26const DEFAULT_STATUS: StatusFlags =
28 StatusFlags::from_bits_truncate(StatusFlags::NOT_ERROR.bits() | StatusFlags::SELECTED.bits());
29
30fn ioctl_read_printer_status(file: &File) -> Result<u8> {
31 let getter = unsafe {
32 ioctl::Getter::<{ opcode::read::<u8>(GADGET_IOC_MAGIC, GADGET_GET_PRINTER_STATUS) }, u8>::new()
33 };
34 Ok(unsafe { ioctl::ioctl(file, getter) }?)
35}
36
37fn ioctl_write_printer_status(file: &File, status: u8) -> Result<()> {
38 let mut value = status;
39 unsafe {
40 ioctl::ioctl(
41 file,
42 ioctl::Updater::<{ opcode::read_write::<u8>(GADGET_IOC_MAGIC, GADGET_SET_PRINTER_STATUS) }, _>::new(
43 &mut value,
44 ),
45 )
46 }?;
47 Ok(())
48}
49
50fn create_printer_gadget() -> Result<RegGadget> {
51 usb_gadget::remove_all().expect("cannot remove all gadgets");
52
53 let udc = default_udc().expect("cannot get UDC");
54 let mut builder = Printer::builder();
55 builder.pnp_string = Some("Rust PNP".to_string());
56
57 let (_, func) = builder.build();
58 let reg = Gadget::new(
59 Class::INTERFACE_SPECIFIC,
60 Id::LINUX_FOUNDATION_COMPOSITE,
61 Strings::new("Clippy Manufacturer", "Rusty Printer", "RUST0123456"),
62 )
63 .with_config(Config::new("Config 1").with_function(func))
64 .bind(&udc)?;
65
66 Ok(reg)
67}
68
69fn read_printer_data(file: &mut File) -> Result<()> {
70 let mut buf = [0u8; BUF_SIZE];
71 let mut printed = 0;
72 println!("Will exit after printing {} pages...", PRINT_EXIT_COUNT);
73
74 loop {
75 let bytes_read = match file.read(&mut buf) {
76 Ok(bytes_read) if bytes_read > 0 => bytes_read,
77 _ => break,
78 };
79 io::stdout().write_all(&buf[..bytes_read])?;
80 io::stdout().flush()?;
81
82 if buf.windows(5).any(|w| w == b"%%EOF") {
84 printed += 1;
85 if printed == PRINT_EXIT_COUNT {
86 println!("Printed {} pages, exiting.", PRINT_EXIT_COUNT);
87 break;
88 }
89 }
90 }
91
92 Ok(())
93}
94
95fn set_printer_status(file: &File, flags: StatusFlags, clear: bool) -> Result<StatusFlags> {
96 let mut status = get_printer_status(file)?;
97 if clear {
98 status.remove(flags);
99 } else {
100 status.insert(flags);
101 }
102 let bits = status.bits();
103 log::debug!("Setting printer status: {:08b}", bits);
104 ioctl_write_printer_status(file, bits)?;
105 Ok(StatusFlags::from_bits_truncate(bits))
106}
107
108fn get_printer_status(file: &File) -> Result<StatusFlags> {
109 let status = ioctl_read_printer_status(file)?;
110 log::debug!("Got printer status: {:08b}", status);
111 let status = StatusFlags::from_bits_truncate(status);
112 Ok(status)
113}
114
115fn print_status(status: StatusFlags) {
116 println!("Printer status is:");
117 if status.contains(StatusFlags::SELECTED) {
118 println!(" Printer is Selected");
119 } else {
120 println!(" Printer is NOT Selected");
121 }
122 if status.contains(StatusFlags::PAPER_EMPTY) {
123 println!(" Paper is Out");
124 } else {
125 println!(" Paper is Loaded");
126 }
127 if status.contains(StatusFlags::NOT_ERROR) {
128 println!(" Printer OK");
129 } else {
130 println!(" Printer ERROR");
131 }
132}
133
134fn main() -> Result<()> {
135 env_logger::init();
136
137 let g_printer = create_printer_gadget().map_err(|e| {
139 eprintln!("Failed to create printer gadget: {e}");
140 e
141 })?;
142 println!("Printer gadget created: {}", g_printer.path().display());
143
144 println!("Attempt open device path: {DEV_PATH}");
146 let mut count = 0;
147 let mut file = loop {
148 thread::sleep(Duration::from_secs(1));
149
150 match OpenOptions::new().read(true).write(true).open(DEV_PATH) {
151 Ok(file) => break file,
152 Err(_) if count < 5 => count += 1,
153 Err(err) => {
154 return Err(io::Error::new(
155 io::ErrorKind::NotFound,
156 format!("Printer {DEV_PATH} not found or cannot open: {err}"),
157 ))
158 }
159 }
160 };
161
162 print_status(set_printer_status(&file, DEFAULT_STATUS, false)?);
163 if let Err(e) = read_printer_data(&mut file) {
164 return Err(io::Error::other(format!("Failed to read data from {DEV_PATH}: {e}")));
165 }
166
167 Ok(())
168}