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}