1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/// Pluton: Tools for working with Looking Glass "holographic" displays
/// Ian Rees 2019

use bytes::{Bytes, BytesMut, BufMut};
use hid;
#[macro_use] extern crate serde_derive;
use serde_json;

use std::{error, fmt, str, time};

const LOOKING_GLASS_VID:u16 = 0x04D8;
const LOOKING_GLASS_PID:u16 = 0xEf7E;

pub struct LookingGlass {
    /// Serial number as reported from EEPROM, not HID.  Seems to be the "real one"
    pub serial: String,
    pub pitch: f32,
    pub slope: f32,
    pub center: f32,
    pub dpi: f32,
    pub screen_w: u32, // Width in pixels
    pub screen_h: u32, // Height in pixels
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Error {
    HIDError(String),
    ParseError(String)
}

pub type Result<T> = ::std::result::Result<T, Error>;

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(error::Error::description(self))
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::HIDError(ref err) => err,
            Error::ParseError(ref err) => err,
        }
    }
}

impl LookingGlass {
    /// Returns a Vec of all Looking Glass candidates: each is Result<LookingGlass>
    pub fn findall() -> Vec<Result<LookingGlass>> {
        let mut glasses = Vec::new();

        let hid_manager = match hid::init() {
            Ok(manager) => manager,
            Err(error) => {
                glasses.push(Err(Error::HIDError(error.to_string())));
                return glasses;
            }
        };

        for candidate in hid_manager.find(Some(LOOKING_GLASS_VID), Some(LOOKING_GLASS_PID)) {
            glasses.push(
                if let Some(_hid_serial) = candidate.serial_number() {
                    // Don't actually care about the serial reported to HID, as it is not unique

                    match get_json_string(candidate) {
                        Ok(string) => json_to_glass(string),
                        Err(error) => Err(Error::HIDError(error.to_string()))
                    }
                } else {
                    Err(Error::HIDError("Error reading - may lack permissions?".to_string()))
                }
            );
        }

        glasses
    }
}

/// Parses JSON config string and instantiates a LookingGlass as appropriate
fn json_to_glass(json_string: String) -> Result<LookingGlass>
{
    #[derive(Serialize, Deserialize)]
    struct JSONValueMap {
        value: f32
    }

    #[derive(Serialize, Deserialize)]
    #[allow(non_snake_case)]
    struct ConfigJSON {
        configVersion: String,
        serial: String,
        pitch: JSONValueMap,
        slope: JSONValueMap,
        center: JSONValueMap,
        viewCone: JSONValueMap,
        invView: JSONValueMap,
        verticalAngle: JSONValueMap,
        DPI: JSONValueMap,
        screenW: JSONValueMap,
        screenH: JSONValueMap,
        flipImageX: JSONValueMap,
        flipImageY: JSONValueMap,
        flipSubp: JSONValueMap,
    }

    match serde_json::from_str::<ConfigJSON>(&json_string) {
        Ok(config) => {
            if config.configVersion == "1.0" {
                Ok(LookingGlass {
                    serial: config.serial,
                    pitch: config.pitch.value,
                    slope: config.slope.value,
                    center: config.center.value,
                    dpi: config.DPI.value,
                    screen_w: config.screenW.value as u32,
                    screen_h: config.screenH.value as u32,
                })
            } else {
                Err(Error::ParseError(format!("Don't know how to read config version {}...",
                    config.configVersion)))
            }
        },
        Err(error) => {
            Err(Error::ParseError(format!("Error parsing JSON: {}", error.to_string())))
        }
    }
}

/// For whatever reason, we only get 64-byte results from read(), but device reports 68 per page...
fn hid_multiread(handle: &mut hid::Handle) -> hid::Result<BytesMut> {
    let mut ret_buf = BytesMut::with_capacity(0);
    loop {
        // On Ubuntu 18.04, we'll either need 64 or 68 bytes, apparently (based on Python
        // experiments) depending on whether libhidapi uses libusb or libhidraw.
        let mut this_read = BytesMut::with_capacity(128);
        this_read.resize(128, 0);

        // Magic number warning: the read timeout is just a guess
        match handle.data().read(&mut this_read, time::Duration::from_millis(10))? {
            Some(count) => {
                ret_buf.extend_from_slice(&this_read[..count]);
            },
            None => {
                break;
            },
        }
    }
    Ok(ret_buf)
}

/// Does a single write/read transaction to read a page of data from the LG's EEPROM
fn hid_query(handle: &mut hid::Handle, addr:u16) -> hid::Result<BytesMut> {
    let mut buf = BytesMut::with_capacity(512);

    // Flush the read buffer
    hid_multiread(handle)?;

    buf.put_u16_be(0); // First byte of this is HID "report ID"
    buf.put_u16_be(addr);
    buf.resize(68, 0); // Looking Glass needs a 68-Byte request, unclear why that is

    let count = handle.feature().send(&buf)?;
    if count != buf.len() {
        return Err(hid::Error::Write);
    }

    buf = hid_multiread(handle)?;

    // The first four bytes read should be the same as the four bytes written.
    // Note that the split_to() removes leading 4 bytes from buf.
    let confirm = [0, 0, (addr>>8 & 0xff) as u8, (addr & 0xFF) as u8];
    if buf.len() <= 4 ||
       buf.split_to(4) != Bytes::from(&confirm[..]) {
        println!("Confirm failed!");
        return Err(hid::Error::Read);
    }

    Ok(buf)
}

/// Extracts the JSON-formatted configuration string from LG's EEPROM via HID
fn get_json_string(candidate: hid::Device) -> hid::Result<String> {
    let mut handle = candidate.open()?;

    handle.blocking(true)?;

    // Data is organised in 64-byte pages.  Page 0 starts with 4B of length, followed by that many
    // bytes of JSON-formatted data.  First read will then have the length and some JSON data.
    let json_size_raw = hid_query(&mut handle, 0)?;

    // Can't see how to nicely turn a BytesMut in to a Buf to use get_u32_be()...
    let mut json_size = 0usize;
    for i in 0..4 {
        json_size <<= 8;
        json_size += json_size_raw[i] as usize;
    }

    // Keep the remaining bytes from page 0
    let mut json = Vec::from(&json_size_raw[4..]);

    // Then, read the remaining pages
    while json.len() < json_size {
        let last_page = (json.len() / 64) as u16;
        let this_read = hid_query(&mut handle, last_page + 1)?;

        json.extend_from_slice(&this_read);
    }

    json.truncate(json_size); // Lop off uninitialised garbage

    match str::from_utf8(&json) {
        Ok(yay) => Ok(yay.to_string()),
        Err(error) => Err(hid::Error::String(error.to_string()))
    }
}


#[cfg(test)]
mod tests {
    use super::*; // Brings in private methods too

    #[test]
    fn can_find() {
        let glasses = LookingGlass::findall();
        if glasses.is_empty() {
            println!("Didn't find any Looking Glasses");
        } else {
            println!("Looking Glasses found:");
        }

        let mut index = 0;
        for glass in &glasses {
            index += 1;
            println!("{} of {}", index, glasses.len());

            match glass {
                Ok(glass) => {
                    println!("\t Serial: {}", glass.serial);
                    println!("\t {}x{} pixels, {} DPI", glass.screen_w, glass.screen_h, glass.dpi);
                    println!("\t pitch: {}", glass.pitch);
                    println!("\t slope: {}", glass.slope);
                    println!("\t center: {}", glass.center);
                },
                Err(error) => {
                    println!("\t Error: {}", error.to_string());
                    assert!(false);
                }
            }
        }
    }

    #[test]
    fn test_json_to_glass_v1() {
        let json = concat!(
            r#"{"configVersion":"1.0","serial":"00297","pitch":{"value":49.81804275512695},"#,
            r#""slope":{"value":5.044347763061523},"center":{"value":0.176902174949646},"#,
            r#""viewCone":{"value":40.0},"invView":{"value":1.0},"verticalAngle":{"value":0.0},"#,
            r#""DPI":{"value":338.0},"screenW":{"value":2560.0},"screenH":{"value":1600.0},"#,
            r#""flipImageX":{"value":0.0},"flipImageY":{"value":0.0},"flipSubp":{"value":0.0}}"#
            ).to_string();

        match json_to_glass(json) {
            Ok(glass) => {
                // Not sure that comparing for equality with floats here is a great idea...
                assert_eq!(glass.serial, "00297");
                assert_eq!(glass.pitch, 49.81804275512695);
                assert_eq!(glass.slope, 5.044347763061523);
                assert_eq!(glass.center, 0.176902174949646);
                assert_eq!(glass.dpi, 338.0);
                assert_eq!(glass.screen_w, 2560);
                assert_eq!(glass.screen_h, 1600);
            },
            Err(..) => {
                assert!(false);
            }
        }
    }
}