mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-01 07:39:11 +00:00
linux-rust: show device info in UI
This commit is contained in:
@@ -9,18 +9,13 @@ use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::path::PathBuf;
|
||||
use crate::utils::get_devices_path;
|
||||
|
||||
const PSM: u16 = 0x1001;
|
||||
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const POLL_INTERVAL: Duration = Duration::from_millis(200);
|
||||
const HEADER_BYTES: [u8; 4] = [0x04, 0x00, 0x04, 0x00];
|
||||
|
||||
fn get_devices_path() -> PathBuf {
|
||||
let data_dir = std::env::var("XDG_DATA_HOME")
|
||||
.unwrap_or_else(|_| format!("{}/.local/share", std::env::var("HOME").unwrap_or_default()));
|
||||
PathBuf::from(data_dir).join("librepods").join("devices.json")
|
||||
}
|
||||
|
||||
pub mod opcodes {
|
||||
pub const SET_FEATURE_FLAGS: u8 = 0x4D;
|
||||
pub const REQUEST_NOTIFICATIONS: u8 = 0x0F;
|
||||
@@ -268,11 +263,18 @@ pub struct AirPodsInformation {
|
||||
pub version3: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", content = "data")]
|
||||
pub enum DeviceInformation {
|
||||
AirPods(AirPodsInformation),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DeviceData {
|
||||
pub name: String,
|
||||
pub type_: DeviceType,
|
||||
pub le: LEData,
|
||||
pub information: Option<AirPodsInformation>,
|
||||
pub information: Option<DeviceInformation>,
|
||||
}
|
||||
|
||||
pub struct AACPManagerState {
|
||||
@@ -620,7 +622,8 @@ impl AACPManager {
|
||||
let mut state = self.state.lock().await;
|
||||
if let Some(mac) = state.airpods_mac {
|
||||
if let Some(device_data) = state.devices.get_mut(&mac.to_string()) {
|
||||
device_data.information = Some(info.clone());
|
||||
device_data.name = info.name.clone();
|
||||
device_data.information = Some(DeviceInformation::AirPods(info.clone()));
|
||||
}
|
||||
}
|
||||
let json = serde_json::to_string(&state.devices).unwrap();
|
||||
@@ -668,6 +671,7 @@ impl AACPManager {
|
||||
if let Some(mac) = state.airpods_mac {
|
||||
let mac_str = mac.to_string();
|
||||
let device_data = state.devices.entry(mac_str.clone()).or_insert(DeviceData {
|
||||
name: mac_str.clone(),
|
||||
type_: DeviceType::AirPods,
|
||||
le: LEData { irk: "".to_string(), enc_key: "".to_string() },
|
||||
information: None,
|
||||
|
||||
@@ -2,46 +2,20 @@ use std::cmp::PartialEq;
|
||||
use bluer::monitor::{Monitor, MonitorEvent, Pattern};
|
||||
use bluer::{Address, Session};
|
||||
use aes::Aes128;
|
||||
use aes::cipher::{BlockEncrypt, KeyInit, BlockDecrypt};
|
||||
use aes::cipher::{KeyInit, BlockDecrypt};
|
||||
use aes::cipher::generic_array::GenericArray;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use log::{info, debug};
|
||||
use serde_json;
|
||||
use crate::bluetooth::aacp::ProximityKeyType;
|
||||
use futures::StreamExt;
|
||||
use hex;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use crate::bluetooth::aacp::BatteryStatus;
|
||||
use crate::ui::tray::MyTray;
|
||||
use crate::bluetooth::aacp::{DeviceData, LEData, DeviceType};
|
||||
|
||||
fn get_devices_path() -> PathBuf {
|
||||
let data_dir = std::env::var("XDG_DATA_HOME")
|
||||
.unwrap_or_else(|_| format!("{}/.local/share", std::env::var("HOME").unwrap_or_default()));
|
||||
PathBuf::from(data_dir).join("librepods").join("devices.json")
|
||||
}
|
||||
|
||||
fn get_preferences_path() -> PathBuf {
|
||||
let config_dir = std::env::var("XDG_CONFIG_HOME")
|
||||
.unwrap_or_else(|_| format!("{}/.config", std::env::var("HOME").unwrap_or_default()));
|
||||
PathBuf::from(config_dir).join("librepods").join("preferences.json")
|
||||
}
|
||||
|
||||
fn e(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||
let mut swapped_key = *key;
|
||||
swapped_key.reverse();
|
||||
let mut swapped_data = *data;
|
||||
swapped_data.reverse();
|
||||
let cipher = Aes128::new(&GenericArray::from(swapped_key));
|
||||
let mut block = GenericArray::from(swapped_data);
|
||||
cipher.encrypt_block(&mut block);
|
||||
let mut result: [u8; 16] = block.into();
|
||||
result.reverse();
|
||||
result
|
||||
}
|
||||
use crate::bluetooth::aacp::{DeviceData, DeviceType};
|
||||
use crate::utils::{get_devices_path, get_preferences_path, ah};
|
||||
|
||||
fn decrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||
let cipher = Aes128::new(&GenericArray::from(*key));
|
||||
@@ -50,15 +24,6 @@ fn decrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||
block.into()
|
||||
}
|
||||
|
||||
fn ah(k: &[u8; 16], r: &[u8; 3]) -> [u8; 3] {
|
||||
let mut r_padded = [0u8; 16];
|
||||
r_padded[..3].copy_from_slice(r);
|
||||
let encrypted = e(k, &r_padded);
|
||||
let mut hash = [0u8; 3];
|
||||
hash.copy_from_slice(&encrypted[..3]);
|
||||
hash
|
||||
}
|
||||
|
||||
fn verify_rpa(addr: &str, irk: &[u8; 16]) -> bool {
|
||||
let rpa: Vec<u8> = addr.split(':')
|
||||
.map(|s| u8::from_str_radix(s, 16).unwrap())
|
||||
|
||||
@@ -2,6 +2,7 @@ mod bluetooth;
|
||||
mod airpods;
|
||||
mod media_controller;
|
||||
mod ui;
|
||||
mod utils;
|
||||
|
||||
use std::env;
|
||||
use log::info;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
use std::collections::HashMap;
|
||||
use iced::widget::button::Style;
|
||||
use iced::widget::{button, column, container, pane_grid, text, Space};
|
||||
use iced::{daemon, window, Background, Element, Length, Subscription, Task, Theme};
|
||||
use iced::widget::{button, column, container, pane_grid, text, Space, combo_box, row, text_input};
|
||||
use iced::{daemon, window, Background, Border, Center, Color, Element, Length, Subscription, Task, Theme};
|
||||
use std::sync::Arc;
|
||||
use log::debug;
|
||||
use iced::border::Radius;
|
||||
use iced::overlay::menu;
|
||||
use log::{debug, error};
|
||||
use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
|
||||
use crate::bluetooth::aacp::{DeviceData, DeviceInformation, DeviceType};
|
||||
use crate::utils::{get_devices_path, get_app_settings_path, MyTheme};
|
||||
|
||||
pub fn start_ui(ui_rx: UnboundedReceiver<()>, start_minimized: bool) -> iced::Result {
|
||||
daemon(App::title, App::update, App::view)
|
||||
@@ -17,6 +22,8 @@ pub struct App {
|
||||
panes: pane_grid::State<Pane>,
|
||||
selected_tab: Tab,
|
||||
ui_rx: Arc<Mutex<UnboundedReceiver<()>>>,
|
||||
theme_state: combo_box::State<MyTheme>,
|
||||
selected_theme: MyTheme,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -26,14 +33,13 @@ pub enum Message {
|
||||
Resized(pane_grid::ResizeEvent),
|
||||
SelectTab(Tab),
|
||||
OpenMainWindow,
|
||||
ThemeSelected(MyTheme),
|
||||
CopyToClipboard(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Tab {
|
||||
Device1,
|
||||
Device2,
|
||||
Device3,
|
||||
Device4,
|
||||
Device(String),
|
||||
Settings,
|
||||
}
|
||||
|
||||
@@ -43,6 +49,7 @@ pub enum Pane {
|
||||
Content,
|
||||
}
|
||||
|
||||
|
||||
impl App {
|
||||
pub fn new(ui_rx: UnboundedReceiver<()>, start_minimized: bool) -> (Self, Task<Message>) {
|
||||
let (mut panes, first_pane) = pane_grid::State::new(Pane::Sidebar);
|
||||
@@ -62,12 +69,45 @@ impl App {
|
||||
(Some(id), open.map(Message::WindowOpened))
|
||||
};
|
||||
|
||||
let app_settings_path = get_app_settings_path();
|
||||
let selected_theme = std::fs::read_to_string(&app_settings_path)
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
|
||||
.and_then(|v| v.get("theme").cloned())
|
||||
.and_then(|t| serde_json::from_value(t).ok())
|
||||
.unwrap_or(MyTheme::Dark);
|
||||
|
||||
(
|
||||
Self {
|
||||
window,
|
||||
panes,
|
||||
selected_tab: Tab::Device1,
|
||||
selected_tab: Tab::Device("none".to_string()),
|
||||
ui_rx,
|
||||
theme_state: combo_box::State::new(vec![
|
||||
MyTheme::Light,
|
||||
MyTheme::Dark,
|
||||
MyTheme::Dracula,
|
||||
MyTheme::Nord,
|
||||
MyTheme::SolarizedLight,
|
||||
MyTheme::SolarizedDark,
|
||||
MyTheme::GruvboxLight,
|
||||
MyTheme::GruvboxDark,
|
||||
MyTheme::CatppuccinLatte,
|
||||
MyTheme::CatppuccinFrappe,
|
||||
MyTheme::CatppuccinMacchiato,
|
||||
MyTheme::CatppuccinMocha,
|
||||
MyTheme::TokyoNight,
|
||||
MyTheme::TokyoNightStorm,
|
||||
MyTheme::TokyoNightLight,
|
||||
MyTheme::KanagawaWave,
|
||||
MyTheme::KanagawaDragon,
|
||||
MyTheme::KanagawaLotus,
|
||||
MyTheme::Moonfly,
|
||||
MyTheme::Nightfly,
|
||||
MyTheme::Oxocarbon,
|
||||
MyTheme::Ferra,
|
||||
]),
|
||||
selected_theme,
|
||||
},
|
||||
Task::batch(vec![open_task, wait_task]),
|
||||
)
|
||||
@@ -104,7 +144,6 @@ impl App {
|
||||
Message::OpenMainWindow => {
|
||||
if let Some(window_id) = self.window {
|
||||
Task::batch(vec![
|
||||
window::minimize(window_id, false),
|
||||
window::gain_focus(window_id),
|
||||
])
|
||||
} else {
|
||||
@@ -113,78 +152,369 @@ impl App {
|
||||
open_task.map(Message::WindowOpened)
|
||||
}
|
||||
}
|
||||
Message::ThemeSelected(theme) => {
|
||||
self.selected_theme = theme;
|
||||
let app_settings_path = get_app_settings_path();
|
||||
let settings = serde_json::json!({"theme": self.selected_theme});
|
||||
debug!("Writing settings to {}: {}", app_settings_path.to_str().unwrap() , settings);
|
||||
std::fs::write(app_settings_path, settings.to_string()).ok();
|
||||
Task::none()
|
||||
}
|
||||
Message::CopyToClipboard(data) => {
|
||||
iced::clipboard::write(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, _id: window::Id) -> Element<'_, Message> {
|
||||
let devices_json = std::fs::read_to_string(get_devices_path()).unwrap_or_else(|e| {
|
||||
error!("Failed to read devices file: {}", e);
|
||||
"{}".to_string()
|
||||
});
|
||||
let devices_list: HashMap<String, DeviceData> = serde_json::from_str(&devices_json).unwrap_or_else(|e| {
|
||||
error!("Deserialization failed: {}", e);
|
||||
HashMap::new()
|
||||
});
|
||||
let pane_grid = pane_grid::PaneGrid::new(&self.panes, |_pane_id, pane, _is_maximized| {
|
||||
match pane {
|
||||
Pane::Sidebar => {
|
||||
let create_tab_button = |tab: Tab, label: &str| -> Element<'_, Message> {
|
||||
let create_tab_button = |tab: Tab, label: &str, description: Option<&str>| -> Element<'_, Message> {
|
||||
let label = label.to_string();
|
||||
let description = description.map(|d| d.to_string());
|
||||
let is_selected = self.selected_tab == tab;
|
||||
let content = container(text(label).size(18)).padding(10);
|
||||
let mut col = column![text(label).size(18)];
|
||||
if let Some(desc) = description {
|
||||
col = col.push(text(desc).size(12));
|
||||
}
|
||||
let content = container(col)
|
||||
.padding(10);
|
||||
let style = move |theme: &Theme, _status| {
|
||||
if is_selected {
|
||||
Style::default()
|
||||
.with_background(Background::Color(theme.palette().primary))
|
||||
let mut style = Style::default()
|
||||
.with_background(theme.palette().primary);
|
||||
let mut border = Border::default();
|
||||
border.color = theme.palette().text;
|
||||
style.border = border.rounded(20);
|
||||
style
|
||||
} else {
|
||||
let mut style = Style::default();
|
||||
let mut style = Style::default()
|
||||
.with_background(theme.palette().primary.scale_alpha(0.1));
|
||||
let mut border = Border::default();
|
||||
border.color = theme.palette().primary.scale_alpha(0.1);
|
||||
style.border = border.rounded(10);
|
||||
style.text_color = theme.palette().text;
|
||||
style
|
||||
}
|
||||
};
|
||||
button(content)
|
||||
.style(style)
|
||||
.padding(5)
|
||||
.on_press(Message::SelectTab(tab))
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
};
|
||||
|
||||
let devices = column![
|
||||
create_tab_button(Tab::Device1, "Device 1"),
|
||||
create_tab_button(Tab::Device2, "Device 2"),
|
||||
create_tab_button(Tab::Device3, "Device 3"),
|
||||
create_tab_button(Tab::Device4, "Device 4")
|
||||
]
|
||||
.spacing(5);
|
||||
let mut devices = column!().spacing(4);
|
||||
let mut devices_vec: Vec<(String, DeviceData)> = devices_list.clone().into_iter().collect();
|
||||
devices_vec.sort_by(|a, b| a.1.name.cmp(&b.1.name));
|
||||
for (mac, device) in devices_vec {
|
||||
let name = device.name.clone();
|
||||
let tab_button = create_tab_button(
|
||||
Tab::Device(mac.clone()),
|
||||
&name,
|
||||
Some(&mac)
|
||||
);
|
||||
devices = devices.push(tab_button);
|
||||
}
|
||||
|
||||
let settings = create_tab_button(Tab::Settings, "Settings");
|
||||
let settings = create_tab_button(Tab::Settings, "Settings", None);
|
||||
|
||||
let content = column![
|
||||
devices,
|
||||
Space::with_height(Length::Fill),
|
||||
settings
|
||||
]
|
||||
.spacing(5);
|
||||
.padding(12);
|
||||
|
||||
pane_grid::Content::new(content)
|
||||
}
|
||||
|
||||
Pane::Content => {
|
||||
let content_text = match self.selected_tab {
|
||||
Tab::Device1 => "Content for Device 1",
|
||||
Tab::Device2 => "Content for Device 2",
|
||||
Tab::Device3 => "Content for Device 3",
|
||||
Tab::Device4 => "Content for Device 4",
|
||||
Tab::Settings => "Settings content",
|
||||
let content = match &self.selected_tab {
|
||||
Tab::Device(id) => {
|
||||
if id == "none" {
|
||||
container(
|
||||
text("Select a device".to_string()).size(16)
|
||||
)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
} else {
|
||||
let mut information_col = column![];
|
||||
|
||||
let device_type = devices_list.get(id)
|
||||
.map(|d| d.type_.clone()).unwrap();
|
||||
|
||||
if device_type == DeviceType::AirPods {
|
||||
let device_information = devices_list.get(id)
|
||||
.and_then(|d| d.information.clone());
|
||||
match device_information {
|
||||
Some(DeviceInformation::AirPods(ref airpods_information)) => {
|
||||
information_col = information_col
|
||||
.push(text("Device Information").size(18).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().primary);
|
||||
style
|
||||
}
|
||||
))
|
||||
.push(Space::with_height(Length::from(10)))
|
||||
.push(
|
||||
row![
|
||||
text("Model Number").size(16).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text);
|
||||
style
|
||||
}
|
||||
),
|
||||
Space::with_width(Length::Fill),
|
||||
text(airpods_information.model_number.clone()).size(16)
|
||||
]
|
||||
)
|
||||
.push(
|
||||
row![
|
||||
text("Manufacturer").size(16).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text);
|
||||
style
|
||||
}
|
||||
),
|
||||
Space::with_width(Length::Fill),
|
||||
text(airpods_information.manufacturer.clone()).size(16)
|
||||
]
|
||||
)
|
||||
.push(
|
||||
row![
|
||||
text("Serial Number").size(16).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text);
|
||||
style
|
||||
}
|
||||
),
|
||||
Space::with_width(Length::Fill),
|
||||
button(
|
||||
text(
|
||||
airpods_information.serial_number.clone()
|
||||
)
|
||||
.size(16)
|
||||
)
|
||||
.style(
|
||||
|theme: &Theme, _status| {
|
||||
let mut style = Style::default();
|
||||
style.text_color = theme.palette().text;
|
||||
style.background = Some(Background::Color(Color::TRANSPARENT));
|
||||
style
|
||||
}
|
||||
)
|
||||
.padding(0)
|
||||
.on_press(Message::CopyToClipboard(airpods_information.serial_number.clone()))
|
||||
]
|
||||
)
|
||||
.push(
|
||||
row![
|
||||
text("Left Serial Number").size(16).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text);
|
||||
style
|
||||
}
|
||||
),
|
||||
Space::with_width(Length::Fill),
|
||||
button(
|
||||
text(
|
||||
airpods_information.left_serial_number.clone()
|
||||
)
|
||||
.size(16)
|
||||
)
|
||||
.style(
|
||||
|theme: &Theme, _status| {
|
||||
let mut style = Style::default();
|
||||
style.text_color = theme.palette().text;
|
||||
style.background = Some(Background::Color(Color::TRANSPARENT));
|
||||
style
|
||||
}
|
||||
)
|
||||
.padding(0)
|
||||
.on_press(Message::CopyToClipboard(airpods_information.left_serial_number.clone()))
|
||||
]
|
||||
)
|
||||
.push(
|
||||
row![
|
||||
text("Right Serial Number").size(16).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text);
|
||||
style
|
||||
}
|
||||
),
|
||||
Space::with_width(Length::Fill),
|
||||
button(
|
||||
text(
|
||||
airpods_information.right_serial_number.clone()
|
||||
)
|
||||
.size(16)
|
||||
)
|
||||
.style(
|
||||
|theme: &Theme, _status| {
|
||||
let mut style = Style::default();
|
||||
style.text_color = theme.palette().text;
|
||||
style.background = Some(Background::Color(Color::TRANSPARENT));
|
||||
style
|
||||
}
|
||||
)
|
||||
.padding(0)
|
||||
.on_press(Message::CopyToClipboard(airpods_information.right_serial_number.clone()))
|
||||
]
|
||||
)
|
||||
.push(
|
||||
row![
|
||||
text("Version 1").size(16).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text);
|
||||
style
|
||||
}
|
||||
),
|
||||
Space::with_width(Length::Fill),
|
||||
text(airpods_information.version1.clone()).size(16)
|
||||
]
|
||||
)
|
||||
.push(
|
||||
row![
|
||||
text("Version 2").size(16).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text);
|
||||
style
|
||||
}
|
||||
),
|
||||
Space::with_width(Length::Fill),
|
||||
text(airpods_information.version2.clone()).size(16)
|
||||
]
|
||||
)
|
||||
.push(
|
||||
row![
|
||||
text("Version 3").size(16).style(
|
||||
|theme: &Theme| {
|
||||
let mut style = text::Style::default();
|
||||
style.color = Some(theme.palette().text);
|
||||
style
|
||||
}
|
||||
),
|
||||
Space::with_width(Length::Fill),
|
||||
text(airpods_information.version3.clone()).size(16)
|
||||
]
|
||||
);
|
||||
debug!("AirPods Information: {:?}", airpods_information);
|
||||
}
|
||||
_ => {
|
||||
error!("Expected AirPodsInformation, got something else: {:?}", device_information);
|
||||
},
|
||||
}
|
||||
}
|
||||
container(
|
||||
column![
|
||||
container(information_col)
|
||||
.style(
|
||||
|theme: &Theme| {
|
||||
let mut style = container::Style::default();
|
||||
style.background = Some(Background::Color(theme.palette().primary.scale_alpha(0.1)));
|
||||
let mut border = Border::default();
|
||||
border.color = theme.palette().text;
|
||||
style.border = border.rounded(20);
|
||||
style
|
||||
}
|
||||
)
|
||||
.padding(20)
|
||||
]
|
||||
)
|
||||
.padding(20)
|
||||
.center_x(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
}
|
||||
}
|
||||
Tab::Settings => {
|
||||
container(
|
||||
column![
|
||||
text("Settings").size(40),
|
||||
Space::with_height(Length::from(20)),
|
||||
row![
|
||||
text("Theme:")
|
||||
.size(16),
|
||||
Space::with_width(Length::from(10)),
|
||||
combo_box(
|
||||
&self.theme_state,
|
||||
"Select theme",
|
||||
Some(&self.selected_theme),
|
||||
Message::ThemeSelected
|
||||
)
|
||||
.input_style(
|
||||
|theme: &Theme, _status| {
|
||||
text_input::Style {
|
||||
background: Background::Color(Color::TRANSPARENT),
|
||||
border: Border {
|
||||
width: 0.5,
|
||||
color: theme.palette().text,
|
||||
radius: Radius::from(10.0),
|
||||
},
|
||||
icon: Default::default(),
|
||||
placeholder: theme.palette().text.scale_alpha(0.5),
|
||||
value: theme.palette().text,
|
||||
selection: theme.palette().primary
|
||||
}
|
||||
}
|
||||
)
|
||||
.menu_style(
|
||||
|theme: &Theme| {
|
||||
menu::Style {
|
||||
background: Background::Color(Color::TRANSPARENT),
|
||||
border: Border {
|
||||
width: 0.5,
|
||||
color: theme.palette().text,
|
||||
radius: Radius::from(10.0)
|
||||
},
|
||||
text_color: theme.palette().text,
|
||||
selected_text_color: theme.palette().text,
|
||||
selected_background: Background::Color(theme.palette().primary.scale_alpha(0.3)),
|
||||
}
|
||||
}
|
||||
)
|
||||
.width(Length::Fill)
|
||||
]
|
||||
.align_y(Center)
|
||||
]
|
||||
)
|
||||
.padding(20)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
},
|
||||
};
|
||||
let content = container(text(content_text).size(40))
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill);
|
||||
|
||||
pane_grid::Content::new(content)
|
||||
}
|
||||
}
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.on_resize(20, Message::Resized);
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.on_resize(20, Message::Resized);
|
||||
|
||||
container(pane_grid).into()
|
||||
}
|
||||
|
||||
fn theme(&self, _id: window::Id) -> Theme {
|
||||
Theme::Moonfly
|
||||
self.selected_theme.into()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
|
||||
130
linux-rust/src/utils.rs
Normal file
130
linux-rust/src/utils.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use aes::cipher::generic_array::GenericArray;
|
||||
use aes::cipher::{BlockEncrypt, KeyInit};
|
||||
use aes::Aes128;
|
||||
use iced::Theme;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn get_devices_path() -> PathBuf {
|
||||
let data_dir = std::env::var("XDG_DATA_HOME")
|
||||
.unwrap_or_else(|_| format!("{}/.local/share", std::env::var("HOME").unwrap_or_default()));
|
||||
PathBuf::from(data_dir).join("librepods").join("devices.json")
|
||||
}
|
||||
|
||||
pub fn get_preferences_path() -> PathBuf {
|
||||
let config_dir = std::env::var("XDG_CONFIG_HOME")
|
||||
.unwrap_or_else(|_| format!("{}/.local/share", std::env::var("HOME").unwrap_or_default()));
|
||||
PathBuf::from(config_dir).join("librepods").join("preferences.json")
|
||||
}
|
||||
|
||||
pub fn get_app_settings_path() -> PathBuf {
|
||||
let config_dir = std::env::var("XDG_CONFIG_HOME")
|
||||
.unwrap_or_else(|_| format!("{}/.local/share", std::env::var("HOME").unwrap_or_default()));
|
||||
PathBuf::from(config_dir).join("librepods").join("app_settings.json")
|
||||
}
|
||||
|
||||
fn e(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||
let mut swapped_key = *key;
|
||||
swapped_key.reverse();
|
||||
let mut swapped_data = *data;
|
||||
swapped_data.reverse();
|
||||
let cipher = Aes128::new(&GenericArray::from(swapped_key));
|
||||
let mut block = GenericArray::from(swapped_data);
|
||||
cipher.encrypt_block(&mut block);
|
||||
let mut result: [u8; 16] = block.into();
|
||||
result.reverse();
|
||||
result
|
||||
}
|
||||
|
||||
pub fn ah(k: &[u8; 16], r: &[u8; 3]) -> [u8; 3] {
|
||||
let mut r_padded = [0u8; 16];
|
||||
r_padded[..3].copy_from_slice(r);
|
||||
let encrypted = e(k, &r_padded);
|
||||
let mut hash = [0u8; 3];
|
||||
hash.copy_from_slice(&encrypted[..3]);
|
||||
hash
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MyTheme {
|
||||
Light,
|
||||
Dark,
|
||||
Dracula,
|
||||
Nord,
|
||||
SolarizedLight,
|
||||
SolarizedDark,
|
||||
GruvboxLight,
|
||||
GruvboxDark,
|
||||
CatppuccinLatte,
|
||||
CatppuccinFrappe,
|
||||
CatppuccinMacchiato,
|
||||
CatppuccinMocha,
|
||||
TokyoNight,
|
||||
TokyoNightStorm,
|
||||
TokyoNightLight,
|
||||
KanagawaWave,
|
||||
KanagawaDragon,
|
||||
KanagawaLotus,
|
||||
Moonfly,
|
||||
Nightfly,
|
||||
Oxocarbon,
|
||||
Ferra,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MyTheme {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Light => "Light",
|
||||
Self::Dark => "Dark",
|
||||
Self::Dracula => "Dracula",
|
||||
Self::Nord => "Nord",
|
||||
Self::SolarizedLight => "Solarized Light",
|
||||
Self::SolarizedDark => "Solarized Dark",
|
||||
Self::GruvboxLight => "Gruvbox Light",
|
||||
Self::GruvboxDark => "Gruvbox Dark",
|
||||
Self::CatppuccinLatte => "Catppuccin Latte",
|
||||
Self::CatppuccinFrappe => "Catppuccin Frappé",
|
||||
Self::CatppuccinMacchiato => "Catppuccin Macchiato",
|
||||
Self::CatppuccinMocha => "Catppuccin Mocha",
|
||||
Self::TokyoNight => "Tokyo Night",
|
||||
Self::TokyoNightStorm => "Tokyo Night Storm",
|
||||
Self::TokyoNightLight => "Tokyo Night Light",
|
||||
Self::KanagawaWave => "Kanagawa Wave",
|
||||
Self::KanagawaDragon => "Kanagawa Dragon",
|
||||
Self::KanagawaLotus => "Kanagawa Lotus",
|
||||
Self::Moonfly => "Moonfly",
|
||||
Self::Nightfly => "Nightfly",
|
||||
Self::Oxocarbon => "Oxocarbon",
|
||||
Self::Ferra => "Ferra",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MyTheme> for Theme {
|
||||
fn from(my_theme: MyTheme) -> Self {
|
||||
match my_theme {
|
||||
MyTheme::Light => Theme::Light,
|
||||
MyTheme::Dark => Theme::Dark,
|
||||
MyTheme::Dracula => Theme::Dracula,
|
||||
MyTheme::Nord => Theme::Nord,
|
||||
MyTheme::SolarizedLight => Theme::SolarizedLight,
|
||||
MyTheme::SolarizedDark => Theme::SolarizedDark,
|
||||
MyTheme::GruvboxLight => Theme::GruvboxLight,
|
||||
MyTheme::GruvboxDark => Theme::GruvboxDark,
|
||||
MyTheme::CatppuccinLatte => Theme::CatppuccinLatte,
|
||||
MyTheme::CatppuccinFrappe => Theme::CatppuccinFrappe,
|
||||
MyTheme::CatppuccinMacchiato => Theme::CatppuccinMacchiato,
|
||||
MyTheme::CatppuccinMocha => Theme::CatppuccinMocha,
|
||||
MyTheme::TokyoNight => Theme::TokyoNight,
|
||||
MyTheme::TokyoNightStorm => Theme::TokyoNightStorm,
|
||||
MyTheme::TokyoNightLight => Theme::TokyoNightLight,
|
||||
MyTheme::KanagawaWave => Theme::KanagawaWave,
|
||||
MyTheme::KanagawaDragon => Theme::KanagawaDragon,
|
||||
MyTheme::KanagawaLotus => Theme::KanagawaLotus,
|
||||
MyTheme::Moonfly => Theme::Moonfly,
|
||||
MyTheme::Nightfly => Theme::Nightfly,
|
||||
MyTheme::Oxocarbon => Theme::Oxocarbon,
|
||||
MyTheme::Ferra => Theme::Ferra,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user