Compare commits

..

No commits in common. "iced-gui" and "master" have entirely different histories.

5 changed files with 507 additions and 2455 deletions

2492
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,4 +9,4 @@ edition = "2021"
libusb = "0.3.0"
colored = "2.0.0"
json = "0.12.4"
iced = "0.7.0"
gtk = { version = "0.4.8", package = "gtk4" }

View File

@ -1,78 +0,0 @@
extern crate json;
extern crate colored;
use std::path::Path;
#[derive(Debug)]
pub struct KeyboardLighting {
pub lighting_type: String,
pub rows: u16,
pub cols: u16
}
#[derive(Debug, Clone)]
pub struct KeyboardLayout {
pub rows: u16,
pub cols: u16,
pub keys: Vec<String>
}
#[derive(Debug)]
pub struct KeyboardConfig {
pub name: String,
pub vendor_id: u16,
pub product_id: u16,
pub layout: KeyboardLayout,
pub lighting: KeyboardLighting
}
pub fn parse_config(config_path: &Path) -> KeyboardConfig{
let mut parsed_json = json::parse(&std::fs::read_to_string(config_path).unwrap()).unwrap();
// General info
let extracted_name: String = parsed_json["name"].as_str().expect("ERROR: Failed to parse name").to_string();
let extracted_vendor_id: u16 = u16::from_str_radix(parsed_json["vendor_id"].as_str().expect("ERROR: Failed to parse name"), 16).unwrap();
let extracted_product_id: u16 = u16::from_str_radix(parsed_json["product_id"].as_str().expect("ERROR: Failed to parse name"), 16).unwrap();
// Layout
let extracted_layout_rows: u16 = parsed_json["layout"]["rows"].as_u16().expect("ERROR: Failed to parse layout rows");
let extracted_layout_cols: u16 = parsed_json["layout"]["cols"].as_u16().expect("ERROR: Failed to parse layout cols");
let mut extracted_layout_keys: Vec<String> = Vec::new();
let mut keys: std::collections::VecDeque<String> = std::collections::VecDeque::new();
for i in 0..extracted_layout_rows {
let mut row: json::JsonValue = parsed_json["layout"]["keys"].pop();
for j in 0..extracted_layout_cols {
keys.push_front(row.pop().as_str().expect("ERROR: Failed to extract key names").to_string());
}
}
for i in 0..keys.len() {
extracted_layout_keys.push(keys.pop_front().expect("ERROR: Empty key value"));
}
let keyboard_layout = KeyboardLayout {
rows: extracted_layout_rows,
cols: extracted_layout_cols,
keys: extracted_layout_keys
};
// Lighting
let extracted_lighting_type: String = parsed_json["lighting"]["type"].as_str().expect("ERROR: Failed to parse lighting type").to_string();
let extracted_lighting_rows: u16 = parsed_json["lighting"]["layout"]["rows"].as_u16().expect("ERROR: Failed to parse lighting rows");
let extracted_lighting_cols: u16 = parsed_json["lighting"]["layout"]["cols"].as_u16().expect("ERROR: Failed to parse lighting cols");
let keyboard_lighting = KeyboardLighting {
lighting_type: extracted_lighting_type,
rows: extracted_lighting_rows,
cols: extracted_lighting_cols
};
let keyboard_config = KeyboardConfig {
name: extracted_name,
vendor_id: extracted_vendor_id,
product_id: extracted_product_id,
layout: keyboard_layout,
lighting: keyboard_lighting
};
return keyboard_config;
}

View File

@ -1,16 +1,38 @@
extern crate libusb;
extern crate colored;
extern crate json;
// Config
mod config_parsing;
use config_parsing::*;
use std::path::Path;
use std::{cell::Cell, rc::Rc};
// USB
mod usb_communications;
use usb_communications::*;
use colored::Colorize;
use iced::{Sandbox, Settings, Vector};
use iced::widget::{Row, Column, Button, Text, Slider};
use gtk::glib::clone;
use gtk::{prelude::*};
use gtk::{Application, ApplicationWindow, Button};
#[derive(Debug)]
struct KeyboardLighting {
lighting_type: String,
rows: u16,
cols: u16
}
#[derive(Debug)]
struct KeyboardLayout {
rows: u16,
cols: u16,
keys: Vec<String>
}
#[derive(Debug)]
struct KeyboardConfig {
name: String,
vendor_id: u16,
product_id: u16,
layout: KeyboardLayout,
lighting: KeyboardLighting
}
/*
#[derive(Debug)]
@ -23,168 +45,175 @@ struct LightingMessage {
}
*/
struct SliderValues {
slider_r: u8,
slider_g: u8,
slider_b: u8
fn parse_config(config_path: &Path) -> KeyboardConfig{
let mut parsed_json = json::parse(&std::fs::read_to_string(config_path).unwrap()).unwrap();
// General info
let extracted_name: String = parsed_json["name"].as_str().expect("ERROR: Failed to parse name").to_string();
let extracted_vendor_id: u16 = u16::from_str_radix(parsed_json["vendor_id"].as_str().expect("ERROR: Failed to parse name"), 16).unwrap();
let extracted_product_id: u16 = u16::from_str_radix(parsed_json["product_id"].as_str().expect("ERROR: Failed to parse name"), 16).unwrap();
// Layout
let extracted_layout_rows: u16 = parsed_json["layout"]["rows"].as_u16().expect("ERROR: Failed to parse layout rows");
let extracted_layout_cols: u16 = parsed_json["layout"]["cols"].as_u16().expect("ERROR: Failed to parse layout cols");
let mut extracted_layout_keys: Vec<String> = Vec::new();
let mut keys: std::collections::VecDeque<String> = std::collections::VecDeque::new();
for i in 0..extracted_layout_rows {
let mut row: json::JsonValue = parsed_json["layout"]["keys"].pop();
for j in 0..extracted_layout_cols {
keys.push_front(row.pop().as_str().expect("ERROR: Failed to extract key names").to_string());
}
}
for i in 0..keys.len() {
extracted_layout_keys.push(keys.pop_front().expect("ERROR: Empty key value"));
}
struct MainPage {
config: KeyboardConfig,
btn_appearance: iced::widget::button::Appearance,
btn_size: u16,
slider_values: SliderValues,
let keyboard_layout = KeyboardLayout {
rows: extracted_layout_rows,
cols: extracted_layout_cols,
keys: extracted_layout_keys
};
// Lighting
let extracted_lighting_type: String = parsed_json["lighting"]["type"].as_str().expect("ERROR: Failed to parse lighting type").to_string();
let extracted_lighting_rows: u16 = parsed_json["lighting"]["layout"]["rows"].as_u16().expect("ERROR: Failed to parse lighting rows");
let extracted_lighting_cols: u16 = parsed_json["lighting"]["layout"]["cols"].as_u16().expect("ERROR: Failed to parse lighting cols");
let keyboard_lighting = KeyboardLighting {
lighting_type: extracted_lighting_type,
rows: extracted_lighting_rows,
cols: extracted_lighting_cols
};
let keyboard_config = KeyboardConfig {
name: extracted_name,
vendor_id: extracted_vendor_id,
product_id: extracted_product_id,
layout: keyboard_layout,
lighting: keyboard_lighting
};
return keyboard_config;
}
#[derive(Debug, Clone, Copy)]
enum MainPageMessages {
UpdateColor {
row: u8,
col: u8
},
UpdatedSliderRed(u8),
UpdatedSliderGreen(u8),
UpdatedSliderBlue(u8)
}
fn main() -> Result<(), iced::Error> {
fn main() {
// Printing the parsed config for debug purposes
let config: KeyboardConfig = parse_config(&Path::new("numpad_config.json"));
println!("{:#?}", config);
// Creating the application and displaying it
MainPage::run(Settings::default())
let app = Application::builder().application_id("org.sebaweb.keyboard-control-software").build();
app.connect_activate(build_ui);
app.run();
}
// USB Code
fn send_lighting_update(col: u8, row: u8, r: u8, g: u8, b: u8, _a: u8) {
let config: KeyboardConfig = parse_config(&Path::new("numpad_config.json"));
let context = libusb::Context::new().unwrap(); // Get a new USB context
for device in context.devices().unwrap().iter() { // Iterate through all the devices
let device_desc = device.device_descriptor().unwrap(); // Get a descriptor for the device
if device_desc.vendor_id() == config.vendor_id && device_desc.product_id() == config.product_id { // Check if the devie is the device we are looking for
println!("Supported keyboard discovered, claiming it.");
let mut device_handle: libusb::DeviceHandle = device.open().unwrap(); // Get a handle for the device
if device_handle.kernel_driver_active(0).unwrap() { // If the device has an active kernel driver, detach it
println!("Interface has kernel driver, detaching...");
device_handle.detach_kernel_driver(0).unwrap();
}
device_handle.claim_interface(0).unwrap(); // Now that the device is free we claim it
match device_handle.write_control(
libusb::request_type(libusb::Direction::Out, libusb::RequestType::Class, libusb::Recipient::Interface), // The request is outband and uses HID specific features tergeting an interface
0x09, 0x0301, 0, // We want to send a SET_REPORT request, that is a custom feature, with the feature being lighting, we want to send it to endpoint 0
&[r, g, b, row, col], std::time::Duration::from_secs(1)) { // First we send the row, then the column, then the Red, Green, Blue, and lastly the brightness
Ok(n) => {
println!("{}", format!("Successfully wrote {} bytes!", n).green());
},
Err(e) => {
eprintln!("{}{:?}", format!("Failed to write with error: ").red().bold(), e);
}
}
device_handle.release_interface(0).unwrap(); // Release the interface
device_handle.attach_kernel_driver(0).unwrap(); // Re-attach the kernel driver
}
}
}
// UI Code
impl Sandbox for MainPage {
type Message = MainPageMessages;
fn build_ui(app: &Application) {
fn new() -> Self {
println!("Creating UI");
MainPage {
config: parse_config(&Path::new("numpad_config.json")),
btn_appearance: iced::widget::button::Appearance {
shadow_offset: Vector {
x: 0.0,
y: 0.0
},
background: iced::Color::from_rgb8(0xA, 0xA, 0xA).into(),
border_radius: 10.0,
border_width: 10.0,
border_color: iced::Color::from_rgb8(0xFF, 0xFF, 0xFF),
text_color: iced::Color::from_rgb8(0xFF, 0xFF, 0xFF)
},
btn_size: 150,
slider_values: SliderValues { slider_r: 0, slider_g: 0, slider_b: 0 },
let config: KeyboardConfig = parse_config(&Path::new("numpad_config.json"));
let flow_box = gtk::FlowBox::builder()
.max_children_per_line(config.layout.cols.into())
.build();
for i in 0..config.layout.keys.len() {
let button = Button::builder()
.label(&config.layout.keys[i])
.focusable(true)
.build();
let cols = Rc::new(Cell::new(config.lighting.cols));
let rows = Rc::new(Cell::new(config.lighting.rows));
button.connect_clicked(clone!(@strong cols, @strong rows => move |button| {
let color = gtk::ColorChooserDialog::builder()
.default_width(400)
.default_height(200)
.name("Color picker")
.visible(true)
.use_alpha(true)
.use_header_bar(1)
.hide_on_close(true)
.build();
color.display();
color.connect_close(move |color| {
println!("The user requested to close the window with a keybind");
println!("{}", color.rgba());
});
let num = Rc::new(Cell::new(i));
color.connect_response(clone!(@strong num, @strong cols, @strong rows => move |color, response| {
println!("The user responded to the window with as cresponse type {}", response);
if response == gtk::ResponseType::Ok {
let red = color.rgba().red()*255f32;
let green = color.rgba().green()*255f32;
let blue = color.rgba().blue()*255f32;
let alpha = color.rgba().alpha()*255f32;
let keys_per_row = cols.get();
let row = (num.get() as f32 / keys_per_row as f32).floor();
let col = num.get() as f32 - row*keys_per_row as f32;
println!("i: {}, row: {}, col: {}, red: {}, green: {}, blue: {}, alpha: {}", i, row, col, red as u8, green as u8, blue as u8, alpha as u8);
send_lighting_update(col as u8, row as u8, red as u8, green as u8, blue as u8, alpha as u8);
}
color.destroy();
}));
println!("The label of the button is {}", button.label().unwrap());
}));
flow_box.insert(&button, i.try_into().unwrap());
}
fn title(&self) -> String {
String::from("Sebaweb Keyboard Control Software")
}
fn update(&mut self, message: Self::Message) {
match message {
MainPageMessages::UpdatedSliderRed(value) => { self.slider_values.slider_r = value; }
MainPageMessages::UpdatedSliderGreen(value) => { self.slider_values.slider_g = value; }
MainPageMessages::UpdatedSliderBlue(value) => { self.slider_values.slider_b = value; }
MainPageMessages::UpdateColor { row, col } => {
println!("{:?}", message);
send_lighting_update(col, row, self.slider_values.slider_r, self.slider_values.slider_g, self.slider_values.slider_b, 0x0);
}
_ => { println!("New message: {:?}", message); }
}
}
fn view(&self) -> iced::Element<'_, Self::Message> {
// TODO: Make dynamic layout
// Create the layout
let btn_num_lck = Button::new("Num\nLock").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 0, col: 0 });
let btn_divide = Button::new("/").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 0, col: 1 });
let btn_multiply = Button::new("*").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 0, col: 2 });
let btn_subtract = Button::new("-").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 0, col: 3 });
let row_1 = Row::new()
.push(btn_num_lck)
.push(btn_divide)
.push(btn_multiply)
.push(btn_subtract)
;
let btn_7 = Button::new("7").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 1, col: 0 });
let btn_8 = Button::new("8").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 1, col: 1 });
let btn_9= Button::new("9").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 1, col: 2 });
let btn_plus = Button::new("+").height(iced::Length::Units(self.btn_size*2)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 1, col: 3 });
let row_2 = Row::new()
.push(btn_7)
.push(btn_8)
.push(btn_9)
;
let btn_4 = Button::new("4").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 2, col: 0 });
let btn_5 = Button::new("5").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 2, col: 1 });
let btn_6= Button::new("6").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 2, col: 2 });
let row_3 = Row::new()
.push(btn_4)
.push(btn_5)
.push(btn_6)
;
let middle_buttons = Column::new().push(row_2).push(row_3);
let middle_buttons_with_plus = Row::new().push(middle_buttons).push(btn_plus);
let btn_1 = Button::new("1").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 3, col: 0 });
let btn_2 = Button::new("2").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 3, col: 1 });
let btn_3= Button::new("3").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 3, col: 2 });
let btn_enter = Button::new("Enter").height(iced::Length::Units(self.btn_size*2)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 3, col: 3 });
let row_4 = Row::new()
.push(btn_1)
.push(btn_2)
.push(btn_3)
;
let btn_0 = Button::new("0").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size*2)).on_press(MainPageMessages::UpdateColor { row: 4, col: 0 });
let btn_comma = Button::new(",").height(iced::Length::Units(self.btn_size)).width(iced::Length::Units(self.btn_size)).on_press(MainPageMessages::UpdateColor { row: 4, col: 1 });
let row_5 = Row::new()
.push(btn_0)
.push(btn_comma)
;
let bottom_buttons = Column::new().push(row_4).push(row_5);
let bottom_buttons_with_comma = Row::new().push(bottom_buttons).push(btn_enter);
let col = Column::new()
.push(row_1)
.push(middle_buttons_with_plus)
.push(bottom_buttons_with_comma)
.height(iced::Length::Fill).width(iced::Length::Fill);
// Create the color selector
let slider_r = Slider::new(0..=255, self.slider_values.slider_r, MainPageMessages::UpdatedSliderRed);
let slider_g = Slider::new(0..=255, self.slider_values.slider_g, MainPageMessages::UpdatedSliderGreen);
let slider_b = Slider::new(0..=255, self.slider_values.slider_b, MainPageMessages::UpdatedSliderBlue);
let color_selector_col = Column::new()
.push(slider_r)
.push(slider_g)
.push(slider_b)
.height(iced::Length::Fill).width(iced::Length::Fill);
let view_row = Row::new().height(iced::Length::Fill).width(iced::Length::Fill)
.push(col)
.push(color_selector_col);
view_row.into()
}
let window = ApplicationWindow::builder()
.application(app)
.title("Keyboard Control Software")
.default_width(1024)
.default_height(900)
.child(&flow_box)
.build();
window.present();
}

View File

@ -1,47 +0,0 @@
extern crate libusb;
extern crate colored;
use crate::config_parsing::*;
use std::path::Path;
use colored::Colorize;
// USB Code
pub fn send_lighting_update(col: u8, row: u8, r: u8, g: u8, b: u8, _a: u8) {
println!("Sending lighting update!");
let config: KeyboardConfig = parse_config(&Path::new("numpad_config.json"));
let context = libusb::Context::new().unwrap(); // Get a new USB context
for device in context.devices().unwrap().iter() { // Iterate through all the devices
let device_desc = device.device_descriptor().unwrap(); // Get a descriptor for the device
if device_desc.vendor_id() == config.vendor_id && device_desc.product_id() == config.product_id { // Check if the devie is the device we are looking for
println!("Supported keyboard discovered, claiming it.");
let mut device_handle: libusb::DeviceHandle = device.open().unwrap(); // Get a handle for the device
if device_handle.kernel_driver_active(0).unwrap() { // If the device has an active kernel driver, detach it
println!("Interface has kernel driver, detaching...");
device_handle.detach_kernel_driver(0).unwrap();
}
device_handle.claim_interface(0).unwrap(); // Now that the device is free we claim it
match device_handle.write_control(
libusb::request_type(libusb::Direction::Out, libusb::RequestType::Class, libusb::Recipient::Interface), // The request is outband and uses HID specific features tergeting an interface
0x09, 0x0301, 0, // We want to send a SET_REPORT request, that is a custom feature, with the feature being lighting, we want to send it to endpoint 0
&[r, g, b, row, col], std::time::Duration::from_secs(1)) { // First we send the row, then the column, then the Red, Green, Blue, and lastly the brightness
Ok(n) => {
println!("{}", format!("Successfully wrote {} bytes!", n).green());
},
Err(e) => {
eprintln!("{}{:?}", format!("Failed to write with error: ").red().bold(), e);
}
}
device_handle.release_interface(0).unwrap(); // Release the interface
device_handle.attach_kernel_driver(0).unwrap(); // Re-attach the kernel driver
}
}
}