diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/logitech.rs | 30 | ||||
| -rw-r--r-- | src/tray.rs | 79 |
2 files changed, 94 insertions, 15 deletions
diff --git a/src/logitech.rs b/src/logitech.rs index 28ff45c..7f0a058 100644 --- a/src/logitech.rs +++ b/src/logitech.rs @@ -20,6 +20,8 @@ use std::collections::HashMap; use serde_derive::{Deserialize, Serialize}; use tungstenite::{client::IntoClientRequest, Message}; +use crate::tray::quit; + #[derive(Serialize, Deserialize, Debug)] pub struct DeviceInfo { pub id: String, @@ -90,22 +92,31 @@ impl Device { fn connection() -> tungstenite::WebSocket< tungstenite::stream::MaybeTlsStream<std::net::TcpStream>, > { + // This will never fail because the URL is hardcoded let url = url::Url::parse("ws://localhost:9010").unwrap(); let (mut ws_stream, _) = tungstenite::connect({ + // This will never fail because the URL is valid let mut request = url.into_client_request().unwrap(); // https://github.com/snapview/tungstenite-rs/issues/279 // https://github.com/snapview/tungstenite-rs/issues/145#issuecomment-713581499 + // + // This unwrap will never fail either because we are parsing a hardcoded, + // known string request .headers_mut() .insert("Sec-WebSocket-Protocol", "json".parse().unwrap()); request }) - .unwrap(); + .unwrap_or_else(|_| { + quit("failed to connect to the logitech g hub websocket. is it running?"); + }); - ws_stream.read_message().unwrap(); + ws_stream.read_message().unwrap_or_else(|_| { + quit("failed to read message from the logitech g hub websocket"); + }); ws_stream } @@ -122,8 +133,12 @@ pub fn wireless_devices() -> HashMap<String, DeviceInfo> { }) .to_string(), )) - .unwrap(); + .unwrap_or_else(|_| { + quit("failed to write message to the logitech g hub websocket"); + }); + // This will never fail because if we even got this far, the `WebSocket` is + // working. let devices = serde_json::from_value::<DeviceList>( serde_json::from_str(&stream.read_message().unwrap().into_text().unwrap()) .unwrap(), @@ -166,14 +181,21 @@ pub fn device(display_name: &str) -> Device { stream .write_message(Message::binary( + // The `unwrap` for `get` should never fail because we know the devices that + // are available, and if the user unplugs one of their devices mid-battery state check, + // that's on them. :P serde_json::json!({ "path": format!("/battery/{}/state", wireless_devices().get(display_name).unwrap().id), "verb": "GET" }) .to_string(), )) - .unwrap(); + .unwrap_or_else(|_| { + quit("failed to write message to the logitech g hub websocket"); + }); + // Once again, this should never fail because if we even got this far, the + // `WebSocket` is working. serde_json::from_value::<Device>( serde_json::from_str(&stream.read_message().unwrap().into_text().unwrap()) .unwrap(), diff --git a/src/tray.rs b/src/tray.rs index a33d081..7a4f3b2 100644 --- a/src/tray.rs +++ b/src/tray.rs @@ -15,7 +15,12 @@ // Copyright (C) 2022-2022 Fuwn <[email protected]> // SPDX-License-Identifier: GPL-3.0-only -use std::sync::{Arc, Mutex}; +use std::{ + ffi::OsStr, + iter::once, + os::windows::ffi::OsStrExt, + sync::{Arc, Mutex}, +}; use tao::{ event::Event, event_loop::ControlFlow, menu, menu::CustomMenuItem, @@ -79,17 +84,22 @@ impl Tray { /// /// Useful for displaying informational icons fn force_icon(code: &str) -> Icon { - trace!("building forced icon: {}", code); + trace!("building forced icon '{}'", code); let image = image::load_from_memory(&crate::ascii_art::number_to_image( + // This will never fail because we as the author provide a code that is + // always a number in string form. code.parse().unwrap(), )) - .unwrap() + .unwrap_or_else(|_| quit(&format!("failed to load forced icon '{code}'"))) .into_rgba8(); let (width, height) = image.dimensions(); - let icon = Icon::from_rgba(image.into_raw(), width, height).unwrap(); + let icon = + Icon::from_rgba(image.into_raw(), width, height).unwrap_or_else(|_| { + quit(&format!("failed to convert forced icon '{code}' to rgba")) + }); - trace!("built forced icon: {}", code); + trace!("built forced icon '{}'", code); icon } @@ -97,7 +107,7 @@ impl Tray { /// Create a tray icon compatible icon from a devices battery level fn icon(selected_device_display_name: &Option<String>) -> Icon { trace!( - "building icon for display name: {:?}", + "building icon for display name '{:?}'", selected_device_display_name ); @@ -117,13 +127,24 @@ impl Tray { .percentage(), ) }) - .unwrap() + .unwrap_or_else(|_| { + quit(&format!( + "failed to load icon for display name '{:?}'", + selected_device_display_name + )) + }) .into_rgba8(); let (width, height) = image.dimensions(); - let icon = Icon::from_rgba(image.into_raw(), width, height).unwrap(); + let icon = + Icon::from_rgba(image.into_raw(), width, height).unwrap_or_else(|_| { + quit(&format!( + "failed to convert icon for display name '{:?}' to rgba", + selected_device_display_name + )) + }); trace!( - "built icon for display name: {:?}", + "built icon for display name '{:?}'", selected_device_display_name ); @@ -217,7 +238,12 @@ impl Tray { // Making sure that the last device, the default device, is never the // dummy device + // + // There will always be a last device because the dummy device is always + // present. if devices.last().unwrap().display_name == "Dummy (Debug)" { + // We can always pop the last device because there will always be a + // last element even if there is only one. let last = devices.pop().unwrap(); devices.insert(0, last); @@ -254,7 +280,7 @@ impl Tray { .with_id(main_tray_id) .with_tooltip("elem") .build(&event_loop) - .unwrap(), + .unwrap_or_else(|_| self::quit("failed to build system tray")), )); let mut devices = local_self.lock().unwrap().devices.clone(); let icon_self = self.inner.clone(); @@ -307,7 +333,7 @@ impl Tray { if devices.iter().any(|d| d.clone().id() == menu_id) { for device in &mut devices { if menu_id == device.clone().id() { - debug!("selected device: {}", device.clone().title()); + debug!("selected device '{}'", device.clone().title()); device.set_selected(true); // Ellipsis icon to indicate background process system_tray @@ -365,3 +391,34 @@ impl Tray { }); } } + +pub fn quit(message: &str) -> ! { + message_box(message); + panic!("{}", message); +} + +pub fn message_box( + // title: &str, + message: &str, // buttons: u32, icon: u32 +) -> i32 { + let buttons = winuser::MB_OK; + let icon = winuser::MB_ICONEXCLAMATION; + let other_options = winuser::MB_SETFOREGROUND | winuser::MB_TOPMOST; + + unsafe { + winuser::MessageBoxW( + std::ptr::null_mut(), + OsStr::new(message) + .encode_wide() + .chain(once(0)) + .collect::<Vec<u16>>() + .as_ptr(), + OsStr::new("elem") // title + .encode_wide() + .chain(once(0)) + .collect::<Vec<u16>>() + .as_ptr(), + buttons | icon | other_options, + ) + } +} |