mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-23 14:44:57 +00:00
linux-rust: add dummy UI
This commit is contained in:
2916
linux-rust/Cargo.lock
generated
2916
linux-rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ uuid = "1.18.1"
|
|||||||
log = "0.4.28"
|
log = "0.4.28"
|
||||||
dbus = "0.9.9"
|
dbus = "0.9.9"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
# iced = {version = "0.13.1", features = ["tokio", "auto-detect-theme"]}
|
iced = {version = "0.13.1", features = ["multi-window", "tokio", "advanced"]}
|
||||||
libpulse-binding = "2.30.1"
|
libpulse-binding = "2.30.1"
|
||||||
ksni = "0.3.1"
|
ksni = "0.3.1"
|
||||||
image = "0.25.8"
|
image = "0.25.8"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::bluetooth::aacp::{AACPManager, ProximityKeyType, AACPEvent};
|
use crate::bluetooth::aacp::{AACPManager, ProximityKeyType, AACPEvent};
|
||||||
use crate::bluetooth::aacp::ControlCommandIdentifiers;
|
use crate::bluetooth::aacp::ControlCommandIdentifiers;
|
||||||
use crate::bluetooth::att::ATTManager;
|
// use crate::bluetooth::att::ATTManager;
|
||||||
use crate::media_controller::MediaController;
|
use crate::media_controller::MediaController;
|
||||||
use bluer::Address;
|
use bluer::Address;
|
||||||
use log::{debug, info, error};
|
use log::{debug, info, error};
|
||||||
@@ -13,7 +13,7 @@ use crate::ui::tray::MyTray;
|
|||||||
pub struct AirPodsDevice {
|
pub struct AirPodsDevice {
|
||||||
pub mac_address: Address,
|
pub mac_address: Address,
|
||||||
pub aacp_manager: AACPManager,
|
pub aacp_manager: AACPManager,
|
||||||
pub att_manager: ATTManager,
|
// pub att_manager: ATTManager,
|
||||||
pub media_controller: Arc<Mutex<MediaController>>,
|
pub media_controller: Arc<Mutex<MediaController>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ impl AirPodsDevice {
|
|||||||
let mut aacp_manager = AACPManager::new();
|
let mut aacp_manager = AACPManager::new();
|
||||||
aacp_manager.connect(mac_address).await;
|
aacp_manager.connect(mac_address).await;
|
||||||
|
|
||||||
let mut att_manager = ATTManager::new();
|
// let mut att_manager = ATTManager::new();
|
||||||
att_manager.connect(mac_address).await.expect("Failed to connect ATT");
|
// att_manager.connect(mac_address).await.expect("Failed to connect ATT");
|
||||||
|
|
||||||
if let Some(handle) = &tray_handle {
|
if let Some(handle) = &tray_handle {
|
||||||
handle.update(|tray: &mut MyTray| tray.connected = true).await;
|
handle.update(|tray: &mut MyTray| tray.connected = true).await;
|
||||||
@@ -226,7 +226,7 @@ impl AirPodsDevice {
|
|||||||
AirPodsDevice {
|
AirPodsDevice {
|
||||||
mac_address,
|
mac_address,
|
||||||
aacp_manager,
|
aacp_manager,
|
||||||
att_manager,
|
// att_manager,
|
||||||
media_controller,
|
media_controller,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use ksni::TrayMethods;
|
|||||||
use crate::ui::tray::MyTray;
|
use crate::ui::tray::MyTray;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use crate::bluetooth::le::start_le_monitor;
|
use crate::bluetooth::le::start_le_monitor;
|
||||||
|
use tokio::sync::mpsc::unbounded_channel;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
@@ -26,8 +27,7 @@ struct Args {
|
|||||||
no_tray: bool,
|
no_tray: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
fn main() -> iced::Result {
|
||||||
async fn main() -> bluer::Result<()> {
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let log_level = if args.debug { "debug" } else { "info" };
|
let log_level = if args.debug { "debug" } else { "info" };
|
||||||
if env::var("RUST_LOG").is_err() {
|
if env::var("RUST_LOG").is_err() {
|
||||||
@@ -35,6 +35,19 @@ async fn main() -> bluer::Result<()> {
|
|||||||
}
|
}
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
|
let (ui_tx, ui_rx) = unbounded_channel::<()>();
|
||||||
|
std::thread::spawn(|| {
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
rt.block_on(async_main(ui_tx)).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
ui::window::start_ui(ui_rx)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn async_main(ui_tx: tokio::sync::mpsc::UnboundedSender<()>) -> bluer::Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
let tray_handle = if args.no_tray {
|
let tray_handle = if args.no_tray {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -50,6 +63,7 @@ async fn main() -> bluer::Result<()> {
|
|||||||
listening_mode: None,
|
listening_mode: None,
|
||||||
allow_off_option: None,
|
allow_off_option: None,
|
||||||
command_tx: None,
|
command_tx: None,
|
||||||
|
ui_tx: Some(ui_tx),
|
||||||
};
|
};
|
||||||
let handle = tray.spawn().await.unwrap();
|
let handle = tray.spawn().await.unwrap();
|
||||||
Some(handle)
|
Some(handle)
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
pub mod tray;
|
pub mod tray;
|
||||||
|
pub mod window;
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use ab_glyph::{Font, ScaleFont};
|
use ab_glyph::{Font, ScaleFont};
|
||||||
use ksni::{Icon, ToolTip};
|
use ksni::{Icon, ToolTip};
|
||||||
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use crate::bluetooth::aacp::ControlCommandIdentifiers;
|
use crate::bluetooth::aacp::ControlCommandIdentifiers;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ pub(crate) struct MyTray {
|
|||||||
pub(crate) listening_mode: Option<u8>,
|
pub(crate) listening_mode: Option<u8>,
|
||||||
pub(crate) allow_off_option: Option<u8>,
|
pub(crate) allow_off_option: Option<u8>,
|
||||||
pub(crate) command_tx: Option<tokio::sync::mpsc::UnboundedSender<(ControlCommandIdentifiers, Vec<u8>)>>,
|
pub(crate) command_tx: Option<tokio::sync::mpsc::UnboundedSender<(ControlCommandIdentifiers, Vec<u8>)>>,
|
||||||
|
pub(crate) ui_tx: Option<UnboundedSender<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ksni::Tray for MyTray {
|
impl ksni::Tray for MyTray {
|
||||||
@@ -106,6 +108,16 @@ impl ksni::Tray for MyTray {
|
|||||||
}).unwrap_or(0);
|
}).unwrap_or(0);
|
||||||
let options_clone = options.clone();
|
let options_clone = options.clone();
|
||||||
vec![
|
vec![
|
||||||
|
StandardItem {
|
||||||
|
label: "Open Window".into(),
|
||||||
|
icon_name: "window-new".into(),
|
||||||
|
activate: Box::new(|this: &mut Self| {
|
||||||
|
if let Some(tx) = &this.ui_tx {
|
||||||
|
let _ = tx.send(());
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}.into(),
|
||||||
RadioGroup {
|
RadioGroup {
|
||||||
selected,
|
selected,
|
||||||
select: Box::new(move |this: &mut Self, current| {
|
select: Box::new(move |this: &mut Self, current| {
|
||||||
|
|||||||
193
linux-rust/src/ui/window.rs
Normal file
193
linux-rust/src/ui/window.rs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
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 std::sync::Arc;
|
||||||
|
use log::debug;
|
||||||
|
use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
|
||||||
|
|
||||||
|
pub fn start_ui(ui_rx: UnboundedReceiver<()>) -> iced::Result {
|
||||||
|
daemon(App::title, App::update, App::view)
|
||||||
|
.subscription(App::subscription)
|
||||||
|
.theme(App::theme)
|
||||||
|
.run_with(|| App::new(ui_rx))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
window: Option<window::Id>,
|
||||||
|
panes: pane_grid::State<Pane>,
|
||||||
|
selected_tab: Tab,
|
||||||
|
ui_rx: Arc<Mutex<UnboundedReceiver<()>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
WindowOpened(window::Id),
|
||||||
|
WindowClosed(window::Id),
|
||||||
|
Resized(pane_grid::ResizeEvent),
|
||||||
|
SelectTab(Tab),
|
||||||
|
OpenMainWindow,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub enum Tab {
|
||||||
|
Device1,
|
||||||
|
Device2,
|
||||||
|
Device3,
|
||||||
|
Device4,
|
||||||
|
Settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Pane {
|
||||||
|
Sidebar,
|
||||||
|
Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new(ui_rx: UnboundedReceiver<()>) -> (Self, Task<Message>) {
|
||||||
|
let (mut panes, first_pane) = pane_grid::State::new(Pane::Sidebar);
|
||||||
|
let split = panes.split(pane_grid::Axis::Vertical, first_pane, Pane::Content);
|
||||||
|
panes.resize(split.unwrap().1, 0.2);
|
||||||
|
|
||||||
|
let (_, open) = window::open(window::Settings::default());
|
||||||
|
|
||||||
|
let ui_rx = Arc::new(Mutex::new(ui_rx));
|
||||||
|
let wait_task = Task::perform(
|
||||||
|
wait_for_message(Arc::clone(&ui_rx)),
|
||||||
|
|_| Message::OpenMainWindow,
|
||||||
|
);
|
||||||
|
(
|
||||||
|
Self {
|
||||||
|
window: None,
|
||||||
|
panes,
|
||||||
|
selected_tab: Tab::Device1,
|
||||||
|
ui_rx,
|
||||||
|
},
|
||||||
|
Task::batch(vec![open.map(Message::WindowOpened), wait_task]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self, _id: window::Id) -> String {
|
||||||
|
"LibrePods".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) -> Task<Message> {
|
||||||
|
match message {
|
||||||
|
Message::WindowOpened(id) => {
|
||||||
|
self.window = Some(id);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::WindowClosed(id) => {
|
||||||
|
if self.window == Some(id) {
|
||||||
|
self.window = None;
|
||||||
|
}
|
||||||
|
let wait_task = Task::perform(
|
||||||
|
wait_for_message(Arc::clone(&self.ui_rx)),
|
||||||
|
|_| Message::OpenMainWindow,
|
||||||
|
);
|
||||||
|
wait_task
|
||||||
|
}
|
||||||
|
Message::Resized(event) => {
|
||||||
|
self.panes.resize(event.split, event.ratio);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::SelectTab(tab) => {
|
||||||
|
self.selected_tab = tab;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::OpenMainWindow => {
|
||||||
|
if let Some(window_id) = self.window {
|
||||||
|
Task::batch(vec![
|
||||||
|
window::minimize(window_id, false),
|
||||||
|
window::gain_focus(window_id),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
let (new_window_task, open_task) = window::open(window::Settings::default());
|
||||||
|
self.window = Some(new_window_task);
|
||||||
|
open_task.map(Message::WindowOpened)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _id: window::Id) -> Element<'_, Message> {
|
||||||
|
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 label = label.to_string();
|
||||||
|
let is_selected = self.selected_tab == tab;
|
||||||
|
let content = container(text(label).size(18)).padding(10);
|
||||||
|
let style = move |theme: &Theme, _status| {
|
||||||
|
if is_selected {
|
||||||
|
Style::default()
|
||||||
|
.with_background(Background::Color(theme.palette().primary))
|
||||||
|
} else {
|
||||||
|
let mut style = Style::default();
|
||||||
|
style.text_color = theme.palette().text;
|
||||||
|
style
|
||||||
|
}
|
||||||
|
};
|
||||||
|
button(content)
|
||||||
|
.style(style)
|
||||||
|
.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 settings = create_tab_button(Tab::Settings, "Settings");
|
||||||
|
|
||||||
|
let content = column![
|
||||||
|
devices,
|
||||||
|
Space::with_height(Length::Fill),
|
||||||
|
settings
|
||||||
|
]
|
||||||
|
.spacing(5);
|
||||||
|
|
||||||
|
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 = 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);
|
||||||
|
|
||||||
|
container(pane_grid).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme(&self, _id: window::Id) -> Theme {
|
||||||
|
Theme::Moonfly
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
window::close_events().map(Message::WindowClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_message(rx: Arc<Mutex<UnboundedReceiver<()>>>) {
|
||||||
|
debug!("Waiting for message to open main window...");
|
||||||
|
let mut guard = rx.lock().await;
|
||||||
|
let _ = guard.recv().await;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user