Driver warnings

This commit is contained in:
lights0123 2020-09-27 13:42:14 -04:00
parent 50a5496e31
commit e60a0f5972
No known key found for this signature in database
GPG key ID: 28F315322E37972F
9 changed files with 239 additions and 88 deletions

10
src-tauri/Cargo.lock generated
View file

@ -21,6 +21,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "ahash"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0adac150c2dd5a9c864d054e07bda5e6bc010cd10036ea5f17e82a2f5867f735"
[[package]]
name = "aho-corasick"
version = "0.7.13"
@ -561,6 +567,9 @@ name = "hashbrown"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7"
dependencies = [
"ahash",
]
[[package]]
name = "heck"
@ -841,6 +850,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"hashbrown",
"indicatif",
"lazy_static",
"libnspire",

View file

@ -16,11 +16,12 @@ libnspire = "0.2.2"
lazy_static = "1.4.0"
rusb = "0.6.4"
serde = { version = "1.0", features = [ "derive" ] }
tauri = { version = "0.9", features = [ "event", "notification" ] }
tauri = { version = "0.9", features = [ "event", "notification", "open" ] }
native-dialog = "0.4.3"
clap = "3.0.0-beta.2"
indicatif = "0.15.0"
libusb1-sys = { version = "0.4.2", features = [ "vendored" ] }
hashbrown = "0.9.0"
[target."cfg(windows)".build-dependencies]
winres = "0.1"

View file

@ -2,10 +2,12 @@ use std::sync::Arc;
use std::time::Duration;
use libnspire::{PID, PID_CX2, VID};
use rusb::{GlobalContext, Error};
use rusb::{DeviceHandle, Error, GlobalContext};
use serde::{Deserialize, Serialize};
use crate::{Device, DeviceState};
use tauri::WebviewMut;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Promise {
@ -150,32 +152,93 @@ pub fn add_device(dev: Arc<rusb::Device<GlobalContext>>) -> rusb::Result<((u8, u
if !(descriptor.vendor_id() == VID && matches!(descriptor.product_id(), PID | PID_CX2)) {
return Err(rusb::Error::Other);
}
let handle = dev.open()?;
let (name, needs_drivers) = match dev.open() {
Ok(handle) => (
handle.read_product_string(
handle.read_languages(Duration::from_millis(100))?[0],
&descriptor,
Duration::from_millis(100),
)?,
false,
),
Err(rusb::Error::NotSupported) => (
if descriptor.product_id() == PID_CX2 {
"TI Nspire CX II"
} else {
"TI Nspire"
}
.to_string(),
true,
),
Err(other) => return Err(other),
};
Ok((
(dev.bus_number(), dev.address()),
Device {
name: dbg!(handle.read_product_string(
handle.read_languages(Duration::from_millis(100))?[0],
&descriptor,
Duration::from_millis(100),
))?,
name,
device: dev,
state: DeviceState::Closed,
needs_drivers,
},
))
}
pub fn enumerate() -> Result<(), libnspire::Error> {
crate::DEVICES.write().unwrap().extend(
rusb::devices()?
.iter()
.filter_map(|dev| match add_device(Arc::new(dev)){
Ok(d) => Ok(d),
Err(e) => Err(dbg!(e)),
}.ok()),
);
Ok(())
pub fn enumerate(handle: &mut WebviewMut) -> Result<Vec<AddDevice>, libnspire::Error> {
let devices: Vec<_> = rusb::devices()?.iter().collect();
let mut map = crate::DEVICES.write().unwrap();
map
.drain_filter(|k, _v| {
devices
.iter()
.all(|d| d.bus_number() != k.0 || d.address() != k.1)
})
.for_each(|d| {
if let Err(msg) = tauri::event::emit(
handle,
"removeDevice",
Some(DevId {
bus_number: (d.0).0,
address: (d.0).1,
}),
) {
eprintln!("{}", msg);
}
});
let filtered: Vec<_> = devices
.into_iter()
.filter(|d| !map.contains_key(&(d.bus_number(), d.address())))
.collect();
Ok(
filtered
.into_iter()
.filter_map(|dev| {
match add_device(Arc::new(dev)) {
Ok(d) => Ok(d),
Err(e) => Err(dbg!(e)),
}
.ok()
})
.map(|dev| {
let msg = AddDevice {
dev: dbg!(DevId {
bus_number: (dev.0).0,
address: (dev.0).1,
}),
name: (dev.1).name.clone(),
is_cx_ii: (dev.1)
.device
.device_descriptor()
.map(|d| d.product_id() == PID_CX2)
.unwrap_or(false),
needs_drivers: (dev.1).needs_drivers,
};
map.insert(dev.0, dev.1);
msg
})
.collect(),
)
}
#[derive(Debug, Serialize)]
@ -185,6 +248,7 @@ pub struct AddDevice {
pub dev: DevId,
pub name: String,
pub is_cx_ii: bool,
pub needs_drivers: bool,
}
#[derive(Debug, Serialize)]

View file

@ -3,7 +3,7 @@
// windows_subsystem = "windows"
// )]
use std::collections::HashMap;
use hashbrown::HashMap;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
@ -35,6 +35,7 @@ pub struct Device {
name: String,
device: Arc<rusb::Device<GlobalContext>>,
state: DeviceState,
needs_drivers: bool,
}
lazy_static::lazy_static! {
static ref DEVICES: RwLock<HashMap<(u8, u8), Device>> = RwLock::new(HashMap::new());
@ -55,6 +56,7 @@ impl Hotplug<GlobalContext> for DeviceMon {
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) = tauri::event::emit(
&mut handle,
@ -66,6 +68,7 @@ impl Hotplug<GlobalContext> for DeviceMon {
},
name,
is_cx_ii,
needs_drivers,
}),
) {
eprintln!("{}", msg);
@ -85,23 +88,43 @@ impl Hotplug<GlobalContext> for DeviceMon {
}
fn device_left(&mut self, device: rusb::Device<GlobalContext>) {
if let Some((dev, _)) = DEVICES
// if let Some((dev, _)) = DEVICES
// .write()
// .unwrap()
// .remove_entry(&(device.bus_number(), device.address()))
// {
// if let Err(msg) = tauri::event::emit(
// &mut self.handle,
// "removeDevice",
// Some(DevId {
// bus_number: dev.0,
// address: dev.1,
// }),
// ) {
// eprintln!("{}", msg);
// };
// }
}
}
fn err_wrap<T>(
res: Result<T, libnspire::Error>,
dev: DevId,
handle: &mut WebviewMut,
) -> Result<T, libnspire::Error> {
if let Err(ref e) = res {
dbg!(e);
}
if let Err(libnspire::Error::NoDevice) = res {
DEVICES
.write()
.unwrap()
.remove_entry(&(device.bus_number(), device.address()))
{
if let Err(msg) = tauri::event::emit(
&mut self.handle,
"removeDevice",
Some(DevId {
bus_number: dev.0,
address: dev.1,
}),
) {
eprintln!("{}", msg);
};
}
.remove(&(dev.bus_number, dev.address));
if let Err(msg) = tauri::event::emit(handle, "removeDevice", Some(dev)) {
eprintln!("{}", msg);
};
}
res
}
fn progress_sender<'a>(
@ -180,28 +203,7 @@ fn main() {
}
promise_fn(
webview,
move || {
let _ = cmd::enumerate();
Ok(
DEVICES
.read()
.unwrap()
.iter()
.map(|dev| AddDevice {
dev: dbg!(DevId {
bus_number: (dev.0).0,
address: (dev.0).1,
}),
name: (dev.1).name.clone(),
is_cx_ii: (dev.1)
.device
.device_descriptor()
.map(|d| d.product_id() == PID_CX2)
.unwrap_or(false),
})
.collect::<Vec<_>>(),
)
},
move || Ok(cmd::enumerate(&mut wv_handle)?),
promise,
);
}
@ -253,8 +255,7 @@ fn main() {
move || {
let handle = get_open_dev(&dev)?;
let handle = handle.lock().unwrap();
let info = handle.info()?;
let info = err_wrap(handle.info(), dev, &mut wv_handle)?;
Ok(info)
},
promise,
@ -266,7 +267,7 @@ fn main() {
move || {
let handle = get_open_dev(&dev)?;
let handle = handle.lock().unwrap();
let dir = handle.list_dir(&path)?;
let dir = err_wrap(handle.list_dir(&path), dev, &mut wv_handle)?;
Ok(
dir
@ -296,10 +297,14 @@ fn main() {
let handle = get_open_dev(&dev)?;
let handle = handle.lock().unwrap();
let mut buf = vec![0; size as usize];
handle.read_file(
&file,
&mut buf,
&mut progress_sender(&mut wv_handle, dev, size as usize),
err_wrap(
handle.read_file(
&file,
&mut buf,
&mut progress_sender(&mut wv_handle.clone(), dev, size as usize),
),
dev,
&mut wv_handle,
)?;
if let Some(name) = file.split('/').last() {
File::create(dest.join(name))?.write_all(&buf)?;
@ -328,10 +333,14 @@ fn main() {
.ok_or_else(|| anyhow::anyhow!("Failed to get file name"))?
.to_string_lossy()
.to_string();
handle.write_file(
&format!("{}/{}", path, name),
&buf,
&mut progress_sender(&mut wv_handle, dev, buf.len()),
err_wrap(
handle.write_file(
&format!("{}/{}", path, name),
&buf,
&mut progress_sender(&mut wv_handle.clone(), dev, buf.len()),
),
dev,
&mut wv_handle,
)?;
Ok(())
},
@ -346,7 +355,14 @@ fn main() {
let handle = handle.lock().unwrap();
let mut buf = vec![];
File::open(&src)?.read_to_end(&mut buf)?;
handle.send_os(&buf, &mut progress_sender(&mut wv_handle, dev, buf.len()))?;
err_wrap(
handle.send_os(
&buf,
&mut progress_sender(&mut wv_handle.clone(), dev, buf.len()),
),
dev,
&mut wv_handle,
)?;
Ok(())
},
promise,
@ -358,7 +374,7 @@ fn main() {
move || {
let handle = get_open_dev(&dev)?;
let handle = handle.lock().unwrap();
handle.delete_file(&path)?;
err_wrap(handle.delete_file(&path), dev, &mut wv_handle)?;
Ok(())
},
promise,
@ -370,7 +386,7 @@ fn main() {
move || {
let handle = get_open_dev(&dev)?;
let handle = handle.lock().unwrap();
handle.delete_dir(&path)?;
err_wrap(handle.delete_dir(&path), dev, &mut wv_handle)?;
Ok(())
},
promise,
@ -382,7 +398,7 @@ fn main() {
move || {
let handle = get_open_dev(&dev)?;
let handle = handle.lock().unwrap();
handle.create_dir(&path)?;
err_wrap(handle.create_dir(&path), dev, &mut wv_handle)?;
Ok(())
},
promise,
@ -399,7 +415,7 @@ fn main() {
move || {
let handle = get_open_dev(&dev)?;
let handle = handle.lock().unwrap();
handle.move_file(&src, &dest)?;
err_wrap(handle.move_file(&src, &dest), dev, &mut wv_handle)?;
Ok(())
},
promise,
@ -416,7 +432,7 @@ fn main() {
move || {
let handle = get_open_dev(&dev)?;
let handle = handle.lock().unwrap();
handle.copy_file(&src, &dest)?;
err_wrap(handle.copy_file(&src, &dest), dev, &mut wv_handle)?;
Ok(())
},
promise,

View file

@ -30,7 +30,8 @@
},
"allowlist": {
"event": true,
"notification": true
"notification": true,
"open": true
},
"window": {
"title": "N-Link",

View file

@ -1,7 +1,10 @@
<template>
<div class="header border-b px-2 py-2">
<el-popover width="239" :visible-arrow="false" popper-class="focus:outline-none dev-select-pop" v-model="active">
<div slot="reference" class="inline-block relative w-full focus:outline-none">
<div class="header border-b px-2 py-2 flex w-full">
<button @click="$devices.enumerate()" class="flex-shrink-0 mr-2 focus:outline-none" :class="$devices.enumerating && 'cursor-not-allowed opacity-25'" :disabled="$devices.enumerating">
<img src="~feather-icons/dist/icons/refresh-cw.svg" class="w-5"/>
</button>
<el-popover width="239" :visible-arrow="false" popper-class="focus:outline-none dev-select-pop" v-model="active" class="w-full overflow-hidden">
<div slot="reference" class="relative w-full focus:outline-none">
<div
class="block w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-3/2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline h-8 truncate">
<span v-if="selectedCalculator">
@ -10,7 +13,7 @@
<span v-else> {{ calc.name }}</span>
</span>
<span v-else class="text-gray-700 text-sm">
Select...
Select a device...
</span>
</div>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">

View file

@ -16,7 +16,7 @@
<file-view v-if="!loading && files" :files="files" :show-hidden="showHidden" @nav="path = $event"
@select="selected = $event"/>
<div v-else-if="loading" class="flex items-center justify-center h-full">
<div class="lds-dual-ring" />
<div class="lds-dual-ring"/>
</div>
</div>
<div class="overflow-auto h-full px-4 pt-4 w-48 flex-shrink-0 border-l">
@ -98,6 +98,7 @@ export default class FileBrowser extends Vue {
width: 80px;
height: 80px;
}
.lds-dual-ring:after {
$color: theme('colors.gray.400');
content: " ";

View file

@ -50,7 +50,7 @@ export type PartialCmd = { action: 'download'; path: [string, number]; dest: str
export type Cmd = { id: number } & PartialCmd;
export type Device = { name: string; info?: Info; progress?: Progress; queue?: Cmd[]; running?: boolean };
export type Device = { name: string; isCxIi: boolean; needsDrivers: boolean; info?: Info; progress?: Progress; queue?: Cmd[]; running?: boolean };
async function downloadFile(dev: DevId | string, path: [string, number], dest: string) {
if (typeof dev === 'string') dev = stringToDev(dev);
@ -118,18 +118,16 @@ let queueId = 0;
@Component
class Devices extends Vue {
devices: Record<string, Device> = {};
enumerating = false;
created() {
promisified({cmd: 'enumerate'}).then(devs => {
for (const dev of devs as (Device & DevId)[]) {
this.$set(this.devices, devToString(dev as DevId), dev);
}
}, console.error);
this.enumerate().catch(console.error);
listen('addDevice', dev => {
const payload = dev.payload as Device & DevId;
const str = devToString(payload);
const existing = this.devices[str] || {};
this.$set(this.devices, str, {...existing, ...payload});
console.log(dev);
});
listen('removeDevice', dev => {
this.$delete(this.devices, devToString(dev.payload as DevId));
@ -193,6 +191,18 @@ class Devices extends Vue {
this.runQueue(dev);
}
async enumerate() {
this.enumerating = true;
try {
for (const dev of await promisified({cmd: 'enumerate'}) as (Device & DevId)[]) {
this.$set(this.devices, devToString(dev as DevId), dev);
console.log(dev);
}
}finally {
this.enumerating = false;
}
}
async open(dev: DevId | string) {
if (typeof dev === 'string') dev = stringToDev(dev);
const info = await promisified({...dev, cmd: 'openDevice'});

View file

@ -4,7 +4,17 @@
<div class="flex-shrink-0 border-r w-64">
<device-select :selected.sync="selectedCalculator"/>
<div class="overflow-auto h-full px-4 py-4">
<div v-if="calculator && calculator.info">
<div v-if="needsDrivers">
<h1 class="text-3xl">Drivers required</h1>
<p>The WinUSB driver is required to use this device.</p>
<p class="text-center mt-2">
<a href="#" @click.prevent="installDrivers" class="text-blue-600">See installation instructions</a>
</p>
</div>
<div v-else-if="calculator && !calculator.info" class="flex items-center justify-center h-full">
<div class="lds-dual-ring" />
</div>
<div v-else-if="calculator && calculator.info">
<calc-info :info="calculator.info" :dev="selectedCalculator"/>
<label class="inline-flex items-center cursor-pointer mr-2 mt-4">
<input type="checkbox" class="form-checkbox h-5 w-5 text-blue-600 cursor-pointer" v-model="showHidden">
@ -24,6 +34,7 @@
<script lang="ts">
import {Component, Vue, Watch} from 'vue-property-decorator';
import {open} from 'tauri/api/window';
import CalcInfo from '@/components/CalcInfo.vue';
import FileBrowser from '@/components/FileBrowser.vue';
import DeviceSelect from "@/components/DeviceSelect.vue";
@ -52,15 +63,49 @@ export default class Home extends Vue {
}
@Watch('selectedCalculator')
onSelectCalculator(dev: string | null) {
if (dev && !this.$devices.devices[dev].info) {
async onSelectCalculator(dev: string | null) {
if (dev && !this.$devices.devices[dev].info && !this.$devices.devices[dev].needsDrivers) {
console.log('open', dev);
this.$devices.open(dev);
try {
await this.$devices.open(dev);
}catch(e) {
console.error(e);
this.selectedCalculator = null;
}
}
}
get calculator() {
return this.selectedCalculator && this.$devices.devices[this.selectedCalculator];
}
get needsDrivers() {
return this.selectedCalculator && this.$devices.devices[this.selectedCalculator]?.needsDrivers;
}
installDrivers() {
open('https://github.com');
}
}
</script>
<style lang="scss" scoped>
.lds-dual-ring {
display: inline-block;
width: 80px;
height: 80px;
}
.lds-dual-ring:after {
$color: theme('colors.gray.400');
content: " ";
display: block;
width: 64px;
height: 64px;
margin: 8px;
border-radius: 50%;
border: 6px solid $color;
border-color: $color transparent $color transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
</style>