logitech_lcd/
lib.rs

1#![warn(missing_docs)]
2
3//! logitech-lcd provides binding for the [Logitech Gaming LCD/Gamepanel SDK](http://gaming.logitech.com/en-us/developers).
4//!
5//! ## Overview
6//! The Logitech LCD/GamePanel SDK introduces second screen capability that allows GamePanel-enabled
7//! Logitech gaming keyboards to display in-game info, system statistics, and more.
8//! The SDK enables integration of GamePanel functionality within your code.
9//!
10//! ## Lcd Interface
11//! The SDK interface is implemented by the [Lcd struct](struct.Lcd.html). Create a new
12//! [Lcd](struct.Lcd.html) at start of program. Update the screen with the provided methods.
13//! The [Lcd](struct.Lcd.html) will automatically disconnect when the [Lcd](struct.Lcd.html)
14//! is dropped.
15//!
16//! ## Examples
17//!
18//! Monochrome:
19//!
20//! ```no_run
21//! let mut lcd = logitech_lcd::Lcd::init_mono("My Glorious Monochrome App").unwrap();
22//!
23//! for i in 0..{
24//!     lcd.set_mono_text(0, &format!("update:{}", i)[..]).unwrap();
25//!
26//!     lcd.update();
27//!
28//!     std::thread::sleep(std::time::Duration::from_millis(15));
29//! }
30//! ```
31//! Color:
32//!
33//! ```no_run
34//! let mut lcd = logitech_lcd::Lcd::init_color("My Glorious Color App").unwrap();
35//!
36//! for i in 0..{
37//!     lcd.set_color_text(0, &format!("update:{}", i)[..], i as u8,
38//!         (i >> 8) as u8, (i >> 16) as u8).unwrap();
39//!
40//!     lcd.update();
41//!
42//!     std::thread::sleep(std::time::Duration::from_millis(15));
43//! }
44//! ```
45//! Monochrome and Color:
46//!
47//! ```no_run
48//! let mut lcd = logitech_lcd::Lcd::init_either("My Glorious App").unwrap();
49//!
50//! for i in 0..{
51//!     lcd.set_mono_text(0,  &format!("update:{}", i)[..]).unwrap();
52//!
53//!     lcd.set_color_text(0, &format!("update:{}", i)[..], i as u8,
54//!         (i >> 8) as u8, (i >> 16) as u8).unwrap();
55//!
56//!     lcd.update();
57//!
58//!     std::thread::sleep(std::time::Duration::from_millis(15));
59//! }
60//! ```
61//!
62//! ## Error Handling
63//! The underling Logitech LCD/GamePanel SDK does unfortunately not return any info on error.
64//! We therefore only able report what function failed, but not why. See [Error](enum.Error.html)
65//!
66//! ## Do’s and Don’ts
67//! These are a few guidelines that may help you implement 'better' support in your application:
68//!
69//! - For color use a splash screen when the application starts up.
70//! - For color have a nice background image to take full advantage of the RGBA LCD.
71//! - Don’t just display information on the LCD that is already being displayed on main view of your
72//! application. Instead display information he can only see when hitting tab or going to the menu.
73//! - Use the LCD to unclutter the main view.
74//! - Write support for both the color and monochrome LCDs, as both have an important user base.
75//! - Text displayed on the LCD is fixed-width, so you can easily create multiple columns that
76//! always align correctly.
77//! - If you want to create custom screens, draw your own bitmaps and update the background LCD
78//! bitmap up to 60 times/second.
79//! - Use the buttons to create multiple pages or add functionality to the LCD.
80//!
81
82extern crate logitech_lcd_sys as sys;
83
84pub use sys::{
85    LcdButton, MONO_WIDTH, MONO_HEIGHT, COLOR_WIDTH, COLOR_HEIGHT,
86};
87
88use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
89use std::os::raw::c_int;
90
91static INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT;
92
93/// Main LCD interface
94///
95/// Initialize at start of your program. Can Be initialized with color support,
96/// monochrome support and both. Will automatically disconnect when the Lcd is dropped.
97pub struct Lcd {
98    type_flags: sys::LcdType,
99    lib: sys::LogitechLcd,
100}
101
102/// Runtime LCD error
103///
104/// The underling Logitech LCD/GamePanel SDK does unfortunately not return any info on error.
105/// We therefore only able report what function failed, but not why.
106#[derive(Debug)]
107pub enum Error {
108    /// A logitech LCD is not connected to the system.
109    NotConnected,
110    /// FFI call to LogiLcdInit() in LogitechLcd.dll has failed.
111    Initialization,
112    /// FFI call to LogiLcdMonoSetBackground() in LogitechLcd.dll has failed.
113    MonoBackground,
114    /// FFI call to LogiLcdMonoSetText() in LogitechLcd.dll has failed.
115    MonoText,
116    /// FFI call to LogiLcdColorSetBackground() in LogitechLcd.dll has failed.
117    ColorBackground,
118    /// FFI call to LogiLcdColorSetTitle() in LogitechLcd.dll has failed.
119    ColorTitle,
120    /// FFI call to LogiLcdColorSetText() in LogitechLcd.dll has failed.
121    ColorText,
122    /// Unexpected NULL character
123    NullCharacter,
124    /// Failed to load LogitechLcd.dll.
125    LoadLibrary(std::io::Error),
126}
127
128impl std::error::Error for Error {
129    fn description(&self) -> &str {
130        match *self {
131            Error::NotConnected    => "A logitech LCD is not connected to the system.",
132            Error::Initialization  => "FFI call to LogiLcdInit() in LogitechLcd.dll has failed.",
133            Error::MonoBackground  => "FFI call to LogiLcdMonoSetBackground() in LogitechLcd.dll has failed.",
134            Error::MonoText        => "FFI call to LogiLcdMonoSetText() in LogitechLcd.dll has failed.",
135            Error::ColorBackground => "FFI call to LogiLcdColorSetTitle() in LogitechLcd.dll has failed.",
136            Error::ColorTitle      => "FFI call to LogiLcdColorSetTitle() in LogitechLcd.dll has failed.",
137            Error::ColorText       => "FFI call to LogiLcdColorSetText() in LogitechLcd.dll has failed.",
138            Error::NullCharacter   => "Unexpected NULL character.",
139            Error::LoadLibrary(_)  => "Failed to load LogitechLcd.dll",
140        }
141    }
142
143    fn cause(&self) -> Option<&std::error::Error> {
144        match *self {
145            Error::LoadLibrary(ref e) => Some(e),
146            _ => None,
147        }
148    }
149}
150
151impl std::fmt::Display for Error {
152    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
153        use std::error::Error;
154        match self.cause() {
155            Some(c) => write!(f, "LcdError: {}, Cause: {}", self.description(), c.description()),
156            None => write!(f, "LcdError: {}", self.description()),
157        }
158    }
159}
160
161#[cfg(target_os = "windows")]
162fn str_to_wchar_checked(s: &str) -> Result<Vec<u16>, Error> {
163    use std::os::windows::ffi::OsStrExt;
164    use std::ffi::OsStr;
165
166    // Check for null character
167    if s.chars().any(|c| c as u32 == 0) {
168        return Err(Error::NullCharacter);
169    }
170
171    // Encode as widechar/utf-16 and terminate with \0\0
172    Ok(OsStr::new(s).encode_wide().chain(Some(0)).collect::<Vec<u16>>())
173}
174
175
176#[cfg(not(target_os = "windows"))]
177fn str_to_wchar_checked(_: &str) -> Result<Vec<u16>, Error> {
178    unimplemented!();
179}
180
181impl Lcd {
182    fn init(app_name: &str, type_flags: sys::LcdType) -> Result<Lcd, Error> {
183        assert_eq!(INITIALIZED.swap(true, Ordering::SeqCst), false);
184
185        let lib = sys::LogitechLcd::load().map_err(|e| Error::LoadLibrary(e))?;
186
187        let ws = str_to_wchar_checked(app_name)?;
188
189        let ret = unsafe {
190            match (lib.LogiLcdInit)(ws.as_ptr(), type_flags.bits()) {
191                true => {
192                    match (lib.LogiLcdIsConnected)(type_flags.bits()) {
193                        true => Ok(Lcd {
194                            type_flags: type_flags,
195                            lib: lib,
196                        }),
197                        false => Err(Error::NotConnected),
198                    }
199                },
200                false => Err(Error::Initialization),
201            }
202        };
203
204        if ret.is_err() {
205            INITIALIZED.store(false, Ordering::SeqCst);
206        }
207
208        ret
209    }
210
211    /// Initialize and connect to a monochrome lcd device.
212    ///
213    /// Parameters:
214    /// - app_name: The name of your applet.
215    ///
216    /// Panics:
217    /// - If another Lcd instance is alive.
218    ///
219    pub fn init_mono(app_name: &str) -> Result<Lcd, Error>  {
220        Self::init(app_name, sys::LcdType::MONO)
221    }
222
223    /// Initialize and connect to a color lcd device.
224    ///
225    /// Parameters:
226    /// - app_name: The name of your applet.
227    ///
228    /// Panics:
229    /// - If another Lcd instance is alive.
230    ///
231    pub fn init_color(app_name: &str) -> Result<Lcd, Error>  {
232        Self::init(app_name, sys::LcdType::COLOR)
233    }
234
235    /// Initialize and connect to either a monochrome or color lcd device.
236    ///
237    /// Parameters:
238    /// - app_name: The name of your applet.
239    ///
240    /// Panics:
241    /// - If another Lcd instance is alive.
242    ///
243    pub fn init_either(app_name: &str) -> Result<Lcd, Error> {
244        Self::init(app_name, sys::LcdType::EITHER)
245    }
246
247    /// Checks if the device is connected.
248    ///
249    /// Return value:
250    /// If a device supporting the lcd type specified is found, it returns `true`, otherwise `false`
251    ///
252    pub fn is_connected(&self) -> bool {
253        unsafe {
254            (self.lib.LogiLcdIsConnected)(self.type_flags.bits())
255        }
256    }
257
258    /// Updates the lcd display.
259    ///
260    /// You have to call this function every frame of your main loop, to keep the lcd updated.
261    ///
262    pub fn update(&mut self) {
263        unsafe {
264            (self.lib.LogiLcdUpdate)();
265        }
266    }
267
268    /// Checks if the buttons specified by the parameter are being pressed.
269    ///
270    /// If the buttons specified are being pressed it returns `true`, otherwise `false`.
271    /// The button will be considered pressed only if your applet is the one currently in the foreground.
272    ///
273    pub fn is_button_pressed(&self, buttons: LcdButton) -> bool {
274        unsafe {
275            (self.lib.LogiLcdIsButtonPressed)(buttons.bits())
276        }
277    }
278
279    /// Sets the specified image as background for the monochrome lcd device.
280    ///
281    /// Parameters:
282    /// - mono_bitmap: The image data is organized as a rectangular area, 160 bytes wide and 43
283    /// bytes high. Despite the display being monochrome, 8 bits per pixel are used
284    /// here for simple manipulation of individual pixels. A pixel will turn on the
285    /// if the value assigned to that byte is >= 128, it will remain off if the value is < 128.
286    ///
287    /// Panics:
288    /// - If mono_bitmap's length is not 160x43 bytes.
289    /// - If Lcd was initialized without mono support.
290    ///
291    pub fn set_mono_background(&mut self, mono_bitmap: &[u8]) -> Result<(), Error> {
292        assert!(!(self.type_flags | sys::LcdType::MONO).is_empty());
293        assert_eq!(mono_bitmap.len(), MONO_WIDTH * MONO_HEIGHT);
294
295        unsafe {
296            match (self.lib.LogiLcdMonoSetBackground)(mono_bitmap.as_ptr()) {
297                true => Ok(()),
298                false => Err(Error::MonoBackground),
299            }
300        }
301    }
302
303    /// Sets the specified text in the requested line on the monochrome lcd device.
304    ///
305    /// Parameters:
306    /// - line_number: The line on the screen you want the text to appear. The monochrome lcd display
307    ///   has 4 lines, so this parameter can be any number from 0 to 3.
308    /// - **text**: Defines the text you want to display
309    ///
310    /// Panics:
311    /// - If line_number larger than or equal to 4.
312    /// - If Lcd was initialized without mono support.
313    ///
314    pub fn set_mono_text(&mut self, line_number: usize, text: &str) -> Result<(), Error> {
315        assert!(!(self.type_flags | sys::LcdType::MONO).is_empty());
316
317        let ws = str_to_wchar_checked(text)?;
318        assert!(line_number < 4);
319
320        unsafe {
321            match (self.lib.LogiLcdMonoSetText)(line_number as c_int, ws.as_ptr()) {
322                true => Ok(()),
323                false => Err(Error::MonoText),
324            }
325        }
326    }
327
328    /// Sets the specified image as background for the color lcd device connected.
329    ///
330    /// Parameters:
331    /// - color_bitmap: ARGB color bitmap, full RGB gamma, 8-bit per channel,
332    /// 320 pixels wide and 240 pixels high, 32 bits per pixel(4 bytes).
333    ///
334    /// Panics:
335    /// - If color_bitmap's length is not 320x240x4 bytes.
336    /// - If Lcd was initialized without color support.
337    ///
338    pub fn set_color_background(&mut self, color_bitmap: &[u8]) -> Result<(), Error> {
339        assert!(!(self.type_flags | sys::LcdType::COLOR).is_empty());
340        assert_eq!(color_bitmap.len(), COLOR_WIDTH * COLOR_HEIGHT * 4);
341
342        unsafe {
343            match (self.lib.LogiLcdColorSetBackground)(color_bitmap.as_ptr()) {
344                true => Ok(()),
345                false => Err(Error::ColorBackground),
346            }
347        }
348    }
349
350    /// Sets the specified text in the first line on the color lcd device connected.
351    /// The font size that will be displayed is bigger than the one used in the other lines,
352    /// so you can use this function to set the title of your applet/page.
353    ///
354    /// Parameters:
355    /// - text: Defines the text you want to display as title.
356    /// - red, green, blue: The LCD can display a full RGB color, you can define the color
357    /// of your title using these parameters.
358    ///
359    /// Panics:
360    /// - If Lcd was initialized without color support.
361    ///
362    pub fn set_color_title(&mut self, text: &str, red: u8, green: u8, blue: u8)
363        -> Result<(), Error>
364    {
365        assert!(!(self.type_flags | sys::LcdType::COLOR).is_empty());
366        let ws = str_to_wchar_checked(text)?;
367
368        unsafe {
369            match (self.lib.LogiLcdColorSetTitle)(ws.as_ptr(), red as c_int,
370                green as c_int, blue as c_int)
371            {
372                true  => Ok(()),
373                false => Err(Error::ColorTitle),
374            }
375        }
376    }
377
378    /// Sets the specified text in the requested line on the color lcd device connected.
379    ///
380    /// Parameters:
381    /// - line_number: The line on the screen you want the text to appear. The color lcd display
382    /// has 8 lines, or standard text, so this parameter can be any number from 0 to 7
383    /// - text: Defines the text you want to display as title.
384    /// - red, green, blue: The LCD can display a full RGB color, you can define the color
385    /// of your title using these parameters.
386    ///
387    /// Panics:
388    /// - If line_number larger than or equal to 8.
389    /// - If Lcd was initialized without color support.
390    ///
391    pub fn set_color_text(&mut self, line_number: usize, text: &str,
392        red: u8, green: u8, blue: u8) -> Result<(), Error>
393    {
394        assert!(!(self.type_flags | sys::LcdType::COLOR).is_empty());
395
396        let ws = str_to_wchar_checked(text)?;
397        assert!(line_number < 8);
398
399        unsafe {
400            match (self.lib.LogiLcdColorSetText)(line_number as c_int,
401                ws.as_ptr(), red as c_int, green as c_int, blue as c_int)
402            {
403                true => Ok(()),
404                false => Err(Error::ColorText),
405            }
406        }
407    }
408}
409
410impl Drop for Lcd {
411    /// Kills the applet and frees memory used by the SDK
412    fn drop(&mut self) {
413        unsafe {
414            (self.lib.LogiLcdShutdown)();
415        }
416        INITIALIZED.store(false, Ordering::SeqCst);
417    }
418}