mirror of
https://github.com/lights0123/n-link.git
synced 2026-08-07 18:23:28 +00:00
Reuses nlink-cli's already-migrated cli.rs rather than repeating the clap 3-beta -> 4 migration. Also copies NOTICE.txt alongside cli.rs since its include_str! path is relative to the file's own directory.
130 lines
3.4 KiB
Rust
130 lines
3.4 KiB
Rust
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use libnspire::{PID_CX2, VID};
|
|
use rusb::{GlobalContext, Hotplug, UsbContext};
|
|
use tauri::{Emitter, Manager, Runtime, WebviewWindow};
|
|
|
|
use crate::device::{add_device, AddDevice, DevId, DEVICES};
|
|
|
|
mod cli;
|
|
mod commands;
|
|
mod device;
|
|
mod error;
|
|
|
|
struct DeviceMon<R: Runtime> {
|
|
window: WebviewWindow<R>,
|
|
}
|
|
|
|
impl<R: Runtime> Hotplug<GlobalContext> for DeviceMon<R> {
|
|
fn device_arrived(&mut self, device: rusb::Device<GlobalContext>) {
|
|
let handle = self.window.clone();
|
|
let is_cx_ii = device
|
|
.device_descriptor()
|
|
.map(|d| d.product_id() == PID_CX2)
|
|
.unwrap_or(false);
|
|
let device = Arc::new(device);
|
|
// The calculator often reports Busy immediately after enumeration, so
|
|
// retry until it settles.
|
|
std::thread::spawn(move || loop {
|
|
match add_device(device.clone()) {
|
|
Ok(dev) => {
|
|
let name = (dev.1).name.clone();
|
|
let needs_drivers = (dev.1).needs_drivers;
|
|
DEVICES.write().unwrap().insert(dev.0, dev.1);
|
|
if let Err(msg) = handle.emit(
|
|
"addDevice",
|
|
AddDevice {
|
|
dev: DevId {
|
|
bus_number: (dev.0).0,
|
|
address: (dev.0).1,
|
|
},
|
|
name,
|
|
is_cx_ii,
|
|
needs_drivers,
|
|
},
|
|
) {
|
|
eprintln!("{}", msg);
|
|
};
|
|
return;
|
|
}
|
|
Err(rusb::Error::Busy) => {
|
|
println!("busy");
|
|
}
|
|
Err(e) => {
|
|
eprintln!("{}", e);
|
|
return;
|
|
}
|
|
}
|
|
std::thread::sleep(Duration::from_millis(250));
|
|
});
|
|
}
|
|
|
|
fn device_left(&mut self, device: rusb::Device<GlobalContext>) {
|
|
if let Some((dev, _)) = DEVICES
|
|
.write()
|
|
.unwrap()
|
|
.remove_entry(&(device.bus_number(), device.address()))
|
|
{
|
|
if let Err(msg) = self.window.emit(
|
|
"removeDevice",
|
|
DevId {
|
|
bus_number: dev.0,
|
|
address: dev.1,
|
|
},
|
|
) {
|
|
eprintln!("{}", msg);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
if cli::run() {
|
|
return;
|
|
}
|
|
tauri::Builder::default()
|
|
.plugin(tauri_plugin_dialog::init())
|
|
.plugin(tauri_plugin_shell::init())
|
|
.setup(|app| {
|
|
// Tauri 1 registered this in on_page_load, guarded by an AtomicBool to
|
|
// avoid double-registering on reload. In v2, setup runs exactly once,
|
|
// so the guard is unnecessary.
|
|
let window = app
|
|
.get_webview_window("main")
|
|
.expect("main window not found");
|
|
if rusb::has_hotplug() {
|
|
if let Err(msg) =
|
|
GlobalContext::default().register_callback(Some(VID), None, None, Box::new(DeviceMon { window }))
|
|
{
|
|
eprintln!("{}", msg);
|
|
};
|
|
std::thread::spawn(|| loop {
|
|
GlobalContext::default().handle_events(None).unwrap();
|
|
});
|
|
} else {
|
|
println!("no hotplug");
|
|
}
|
|
Ok(())
|
|
})
|
|
.invoke_handler(tauri::generate_handler![
|
|
commands::enumerate,
|
|
commands::open_device,
|
|
commands::close_device,
|
|
commands::update_device,
|
|
commands::list_dir,
|
|
commands::download_file,
|
|
commands::upload_file,
|
|
commands::upload_os,
|
|
commands::delete_file,
|
|
commands::delete_dir,
|
|
commands::create_nspire_dir,
|
|
commands::move_file,
|
|
commands::copy,
|
|
])
|
|
.run(tauri::generate_context!())
|
|
.expect("error while running tauri application");
|
|
}
|