azalea_shell/window/taskbar/widget/bluetooth/
mod.rs1use std::collections::HashMap;
2
3use azalea_service::{LocalListenerHandle, StaticServiceManager};
4use gtk::prelude::*;
5use relm4::{
6 Component, ComponentParts, ComponentSender, component, factory::FactoryHashMap, prelude::*,
7};
8
9use crate::{
10 factory, icon,
11 service::{self, dbus::bluez::Device},
12};
13
14crate::init! {
15 Model {
16 is_powered: bool,
17 devices_menu: FactoryHashMap<String, factory::bluetooth::device::Model>,
18 _event_listener_handle: LocalListenerHandle,
19 }
20
21 Config {}
22}
23
24#[derive(Debug)]
25pub enum Input {
26 Connect(String, bool),
27 Power(bool),
28 Bluez(service::dbus::bluez::Output),
29}
30
31#[derive(Debug)]
32pub enum CommandOutput {
33 SetDevices(HashMap<String, Device>),
34}
35
36#[component(pub)]
37impl Component for Model {
38 type Init = Init;
39 type Input = Input;
40 type Output = ();
41 type CommandOutput = CommandOutput;
42
43 view! {
44 gtk::MenuButton {
45 set_hexpand: false,
46 set_vexpand: false,
47 set_valign: gtk::Align::Center,
48
49 set_direction: gtk::ArrowType::Up,
50
51 #[watch]
52 set_icon_name: if model.is_powered { icon::BLUETOOTH } else { icon::BLUETOOTH_X },
53
54 #[wrap(Some)]
55 set_popover = >k::Popover {
56 set_position: gtk::PositionType::Right,
57
58 gtk::Box {
59 set_orientation: gtk::Orientation::Vertical,
60
61 gtk::Box {
62 gtk::Label::new(Some("Bluetooth")) {
63 inline_css: r#"
64 font-weight: bold;
65 "#,
66
67 #[watch]
68 set_css_classes: if model.is_powered {
69 &[ "azalea-primary-fg" ]
70 } else {
71 &[]
72 },
73
74 set_halign: gtk::Align::Start,
75 set_hexpand: true,
76 },
77 gtk::Switch {
78 set_halign: gtk::Align::End,
79
80 #[watch]
81 #[block_signal(toggle_state)]
82 set_active: model.is_powered,
83
84 connect_state_set[sender] => move |_, on| {
85 sender.input(Input::Power(on));
86 false.into()
87 } @toggle_state,
88 },
89 },
90
91 gtk::Separator {},
92
93 #[local_ref]
94 devices_widget -> gtk::Box {
95 set_orientation: gtk::Orientation::Vertical,
96 set_spacing: 5,
97 }
98 },
99 },
100 },
101 }
102
103 fn init(
104 _init: Self::Init,
105 _root: Self::Root,
106 sender: ComponentSender<Self>,
107 ) -> ComponentParts<Self> {
108 let model = Model {
109 is_powered: true,
110 devices_menu: FactoryHashMap::builder()
111 .launch(gtk::Box::default())
112 .forward(sender.input_sender(), |output| match output {
113 factory::bluetooth::device::Output::Connect(device, connect) => {
114 Input::Connect(device.address, connect)
115 }
116 }),
117 _event_listener_handle: service::dbus::bluez::Service::forward_local(
118 sender.input_sender().clone(),
119 Input::Bluez,
120 ),
121 };
122
123 let (tx, rx) = flume::bounded(1);
124 service::dbus::bluez::Service::send(service::dbus::bluez::Input::Devices(tx));
125 sender.oneshot_command(async move {
126 let devices = rx.recv_async().await.unwrap();
127 CommandOutput::SetDevices(devices)
128 });
129
130 let devices_widget = model.devices_menu.widget();
131 let widgets = view_output!();
132
133 ComponentParts { model, widgets }
134 }
135
136 fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>, _root: &Self::Root) {
137 match message {
138 Input::Connect(address, connect) => {
139 service::dbus::bluez::Service::send(service::dbus::bluez::Input::Connect(
140 address, connect,
141 ));
142 }
143 Input::Bluez(output) => match output {
144 service::dbus::bluez::Output::Connected(device_address, connected) => {
145 if let Some(mut menu_entry) = self.devices_menu.get_mut(&device_address) {
146 menu_entry.device.is_connected = connected;
147 }
148 }
149 service::dbus::bluez::Output::Powered(on) => {
150 self.is_powered = on;
151 }
152 },
153 Input::Power(on) => {
154 service::dbus::bluez::Service::send(service::dbus::bluez::Input::Power(on));
155 }
156 }
157 }
158
159 fn update_cmd(
160 &mut self,
161 message: Self::CommandOutput,
162 _sender: ComponentSender<Self>,
163 _root: &Self::Root,
164 ) {
165 match message {
166 CommandOutput::SetDevices(devices) => {
167 self.devices_menu.clear();
168 for (address, device) in devices.into_iter() {
169 self.devices_menu.insert(address, device);
170 }
171 }
172 }
173 }
174}