feat(bluetooth): add ability to rename Bluetooth devices (#2160)
This commit is contained in:
parent
4b4b688d5a
commit
9c1a7fbc82
4 changed files with 115 additions and 4 deletions
|
|
@ -37,6 +37,10 @@ enum Dialog {
|
|||
device: String,
|
||||
pincode: String,
|
||||
},
|
||||
RenameDevice {
|
||||
path: OwnedObjectPath,
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -353,6 +357,29 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
|
||||
Some(dialog)
|
||||
}
|
||||
|
||||
Dialog::RenameDevice { name, .. } => {
|
||||
let is_valid = is_valid_bluetooth_alias(name);
|
||||
let input = widget::text_input("", name)
|
||||
.on_input(|value| Message::RenameDeviceInput(value))
|
||||
.on_submit(|_| Message::RenameDeviceConfirm);
|
||||
|
||||
let rename_button =
|
||||
widget::button::suggested(fl!("rename")).on_press_maybe(is_valid.then_some(Message::RenameDeviceConfirm));
|
||||
|
||||
let cancel_button =
|
||||
widget::button::standard(fl!("cancel")).on_press(Message::RenameDeviceCancel);
|
||||
|
||||
let dialog = widget::dialog()
|
||||
.title(fl!("bluetooth-rename-device"))
|
||||
.control(input)
|
||||
.primary_action(rename_button)
|
||||
.secondary_action(cancel_button)
|
||||
.apply(Element::from)
|
||||
.map(Into::into);
|
||||
|
||||
Some(dialog)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -369,6 +396,10 @@ pub enum Message {
|
|||
PinConfirm,
|
||||
PopupDevice(Option<OwnedObjectPath>),
|
||||
PopupSetting(bool),
|
||||
RenameDevice(OwnedObjectPath),
|
||||
RenameDeviceInput(String),
|
||||
RenameDeviceConfirm,
|
||||
RenameDeviceCancel,
|
||||
SelectAdapter(Option<OwnedObjectPath>),
|
||||
ServiceActivate,
|
||||
ServiceEnable,
|
||||
|
|
@ -629,6 +660,10 @@ impl Page {
|
|||
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
|
||||
Event::DeviceRenameFailed(path) => {
|
||||
tracing::warn!("Failed to rename device {path}");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -836,6 +871,38 @@ impl Page {
|
|||
}
|
||||
}
|
||||
|
||||
Message::RenameDevice(path) => {
|
||||
self.model.popup_device = None;
|
||||
let name = self
|
||||
.model
|
||||
.devices
|
||||
.get(&path)
|
||||
.map_or_else(String::new, |d| d.alias_or_addr().to_owned());
|
||||
self.dialog = Some(Dialog::RenameDevice { path, name });
|
||||
}
|
||||
|
||||
Message::RenameDeviceInput(new_name) => {
|
||||
if let Some(Dialog::RenameDevice { name, .. }) = &mut self.dialog {
|
||||
*name = new_name;
|
||||
}
|
||||
}
|
||||
|
||||
Message::RenameDeviceCancel => {
|
||||
if matches!(self.dialog, Some(Dialog::RenameDevice { .. })) {
|
||||
self.dialog = None;
|
||||
}
|
||||
}
|
||||
|
||||
Message::RenameDeviceConfirm => {
|
||||
if let Some(Dialog::RenameDevice { path, name }) = self.dialog.take() {
|
||||
if let Some(connection) = self.connection.clone() {
|
||||
return cosmic::task::future(rename_device(connection, path, name.trim().into()));
|
||||
} else {
|
||||
tracing::warn!("No DBus connection ready");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Message::ServiceActivate => {
|
||||
let activate_future = self.service_manager.activate();
|
||||
return cosmic::task::future(async move {
|
||||
|
|
@ -934,6 +1001,11 @@ fn status() -> Section<crate::pages::Message> {
|
|||
})
|
||||
}
|
||||
|
||||
fn is_valid_bluetooth_alias(name: &str) -> bool {
|
||||
let trimmed = name.trim();
|
||||
!trimmed.is_empty() && trimmed.len() <= 248
|
||||
}
|
||||
|
||||
fn popup_button(message: Option<Message>, text: &str) -> Element<'_, Message> {
|
||||
let spacing = theme::spacing();
|
||||
widget::text::body(text)
|
||||
|
|
@ -954,6 +1026,7 @@ fn connected_devices() -> Section<crate::pages::Message> {
|
|||
device_connect = fl!("bluetooth", "connect");
|
||||
device_disconnect = fl!("bluetooth", "disconnect");
|
||||
device_forget = fl!("bluetooth", "forget");
|
||||
device_rename = fl!("rename");
|
||||
});
|
||||
|
||||
Section::default()
|
||||
|
|
@ -991,13 +1064,17 @@ fn connected_devices() -> Section<crate::pages::Message> {
|
|||
.position(widget::popover::Position::Bottom)
|
||||
.on_close(Message::PopupDevice(None))
|
||||
.popup(
|
||||
widget::column::with_capacity(2)
|
||||
widget::column::with_capacity(3)
|
||||
.push_maybe(device.is_connected().then(|| {
|
||||
popup_button(
|
||||
Some(Message::DisconnectDevice(path.clone())),
|
||||
&descriptions[device_disconnect],
|
||||
)
|
||||
}))
|
||||
.push(popup_button(
|
||||
Some(Message::RenameDevice(path.clone())),
|
||||
&descriptions[device_rename],
|
||||
))
|
||||
.push_maybe(device.paired.then(|| {
|
||||
popup_button(
|
||||
Some(Message::ForgetDevice(path.clone())),
|
||||
|
|
|
|||
3
i18n/en/cosmic_settings.ftl
vendored
3
i18n/en/cosmic_settings.ftl
vendored
|
|
@ -233,6 +233,7 @@ online-accounts = Online accounts
|
|||
activate = Activate
|
||||
confirm = Confirm
|
||||
enable = Enable
|
||||
rename = Rename
|
||||
|
||||
bluetooth = Bluetooth
|
||||
.status = This system is visible as { $aliases } while Bluetooth settings are open.
|
||||
|
|
@ -260,6 +261,8 @@ bluetooth-display-passkey = Bluetooth Pairing
|
|||
bluetooth-display-pin = Bluetooth Pairing
|
||||
.description = Please type the following PIN on { $device }, then press Enter
|
||||
|
||||
bluetooth-rename-device = Rename Device
|
||||
|
||||
bluetooth-available = Nearby devices
|
||||
|
||||
bluetooth-adapters = Bluetooth adapters
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ fn device_type_to_icon(device_type: &str) -> &'static str {
|
|||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Device {
|
||||
alias: Option<String>,
|
||||
pub alias: Option<String>,
|
||||
pub address: String,
|
||||
pub adapter: OwnedObjectPath,
|
||||
pub enabled: Active,
|
||||
|
|
@ -46,7 +46,7 @@ impl Device {
|
|||
let (address, adapter, alias) = join!(
|
||||
proxy.device.address(),
|
||||
proxy.device.adapter(),
|
||||
proxy.device.name()
|
||||
proxy.device.alias()
|
||||
);
|
||||
let address = address?;
|
||||
if address.is_empty() {
|
||||
|
|
@ -56,7 +56,10 @@ impl Device {
|
|||
if adapter.is_empty() {
|
||||
return Err(zbus::Error::Failure("Device has no adapter".to_owned()));
|
||||
}
|
||||
let alias = alias.ok();
|
||||
let alias = match alias {
|
||||
Ok(alias) if !alias.is_empty() && alias != address.replace(':', "-") => Some(alias),
|
||||
_ => proxy.device.name().await.ok(),
|
||||
};
|
||||
let device_type: String = proxy.icon().await;
|
||||
let paired = proxy.device.paired().await.unwrap_or(false);
|
||||
let enabled = if proxy.device.connected().await.unwrap_or(false) {
|
||||
|
|
@ -309,6 +312,33 @@ pub async fn forget_device(connection: zbus::Connection, device_path: OwnedObjec
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn rename_device(
|
||||
connection: zbus::Connection,
|
||||
device_path: OwnedObjectPath,
|
||||
name: String,
|
||||
) -> Event {
|
||||
let proxy = match bluez_zbus::get_device(&connection, device_path.clone()).await {
|
||||
Err(why) => {
|
||||
tracing::error!("Unable to get the device: {why}");
|
||||
return Event::DeviceFailed(device_path);
|
||||
}
|
||||
Ok(proxy) => proxy,
|
||||
};
|
||||
|
||||
for attempt in 1..5 {
|
||||
let result = proxy.device.set_alias(&name).await;
|
||||
|
||||
if let Err(why) = result {
|
||||
tracing::warn!("Unable to rename device: {why}");
|
||||
tokio::time::sleep(Duration::from_millis(1000 * attempt)).await;
|
||||
} else {
|
||||
return Event::Ok;
|
||||
}
|
||||
}
|
||||
|
||||
Event::DeviceRenameFailed(device_path)
|
||||
}
|
||||
|
||||
pub async fn get_devices(connection: zbus::Connection, adapter_path: OwnedObjectPath) -> Event {
|
||||
// TODO error handling
|
||||
let result: zbus::Result<HashMap<OwnedObjectPath, Device>> = async {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ pub enum Event {
|
|||
DBusError(zbus::Error),
|
||||
DBusServiceUnknown,
|
||||
DeviceFailed(OwnedObjectPath),
|
||||
DeviceRenameFailed(OwnedObjectPath),
|
||||
Ok,
|
||||
NameHasNoOwner,
|
||||
RemovedAdapter(OwnedObjectPath),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue