mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
servo: Add a BluetoothDeviceSelectionRequest to the API for selecting Bluetooth devices (#43580)
This change has 2 parts: - adding `BluetoothPickDeviceRequest`, similar to other request structs used to communicate with the embedder. - switch from a `Vec<String>` that expected 2 * <device count> strings to `Vec<BluetoothDeviceDescription>` which is easier to reason about. Testing: Manual testing with a build that has the `native-bluetooth` feature enabled. --------- Signed-off-by: webbeef <me@webbeef.org>
This commit is contained in:
@@ -14,7 +14,7 @@ use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use embedder_traits::{EmbedderMsg, EmbedderProxy};
|
||||
use embedder_traits::{BluetoothDeviceDescription, EmbedderMsg, EmbedderProxy};
|
||||
use log::warn;
|
||||
use rand::{self, Rng};
|
||||
#[cfg(not(feature = "native-bluetooth"))]
|
||||
@@ -485,7 +485,7 @@ impl BluetoothManager {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut dialog_rows: Vec<String> = vec![];
|
||||
let mut device_descriptions = Vec::with_capacity(devices.len());
|
||||
for device in devices {
|
||||
let address = device.get_address().unwrap_or_default();
|
||||
let name = device.get_name().await.unwrap_or_else(|_| {
|
||||
@@ -496,7 +496,7 @@ impl BluetoothManager {
|
||||
};
|
||||
format!("Unknown ({}...)", short)
|
||||
});
|
||||
dialog_rows.extend_from_slice(&[address, name]);
|
||||
device_descriptions.push(BluetoothDeviceDescription { address, name });
|
||||
}
|
||||
|
||||
let (ipc_sender, ipc_receiver) =
|
||||
@@ -504,7 +504,7 @@ impl BluetoothManager {
|
||||
self.embedder_proxy
|
||||
.send(EmbedderMsg::GetSelectedBluetoothDevice(
|
||||
webview_id,
|
||||
dialog_rows,
|
||||
device_descriptions,
|
||||
ipc_sender,
|
||||
));
|
||||
|
||||
|
||||
@@ -76,10 +76,10 @@ pub use crate::site_data_manager::{SiteData, SiteDataManager, StorageType};
|
||||
pub use crate::user_content_manager::UserContentManager;
|
||||
pub use crate::webview::{WebView, WebViewBuilder};
|
||||
pub use crate::webview_delegate::{
|
||||
AlertDialog, AllowOrDenyRequest, AuthenticationRequest, ColorPicker, ConfirmDialog,
|
||||
ContextMenu, CreateNewWebViewRequest, EmbedderControl, FilePicker, InputMethodControl,
|
||||
NavigationRequest, PermissionRequest, PromptDialog, SelectElement, SimpleDialog,
|
||||
WebResourceLoad, WebViewDelegate,
|
||||
AlertDialog, AllowOrDenyRequest, AuthenticationRequest, BluetoothDeviceSelectionRequest,
|
||||
ColorPicker, ConfirmDialog, ContextMenu, CreateNewWebViewRequest, EmbedderControl, FilePicker,
|
||||
InputMethodControl, NavigationRequest, PermissionRequest, PromptDialog, SelectElement,
|
||||
SimpleDialog, WebResourceLoad, WebViewDelegate,
|
||||
};
|
||||
|
||||
#[cfg(feature = "webxr")]
|
||||
|
||||
@@ -84,8 +84,8 @@ use crate::servo_delegate::{DefaultServoDelegate, ServoDelegate, ServoError};
|
||||
use crate::site_data_manager::SiteDataManager;
|
||||
use crate::webview::{MINIMUM_WEBVIEW_SIZE, WebView, WebViewInner};
|
||||
use crate::webview_delegate::{
|
||||
AllowOrDenyRequest, AuthenticationRequest, EmbedderControl, FilePicker, NavigationRequest,
|
||||
PermissionRequest, ProtocolHandlerRegistration, WebResourceLoad,
|
||||
AllowOrDenyRequest, AuthenticationRequest, BluetoothDeviceSelectionRequest, EmbedderControl,
|
||||
FilePicker, NavigationRequest, PermissionRequest, ProtocolHandlerRegistration, WebResourceLoad,
|
||||
};
|
||||
|
||||
#[cfg(feature = "media-gstreamer")]
|
||||
@@ -564,8 +564,7 @@ impl ServoInner {
|
||||
if let Some(webview) = self.get_webview_handle(webview_id) {
|
||||
webview.delegate().show_bluetooth_device_dialog(
|
||||
webview,
|
||||
items,
|
||||
response_sender,
|
||||
BluetoothDeviceSelectionRequest::new(items, response_sender),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,16 +8,16 @@ use std::rc::Rc;
|
||||
#[cfg(feature = "gamepad")]
|
||||
use embedder_traits::GamepadHapticEffectType;
|
||||
use embedder_traits::{
|
||||
AlertResponse, AllowOrDeny, AuthenticationResponse, ConfirmResponse, ConsoleLogLevel,
|
||||
ContextMenuAction, ContextMenuElementInformation, ContextMenuItem, Cursor, EmbedderControlId,
|
||||
EmbedderControlResponse, FilePickerRequest, FilterPattern, InputEventId, InputEventResult,
|
||||
InputMethodType, LoadStatus, MediaSessionEvent, NewWebViewDetails, Notification,
|
||||
PermissionFeature, PromptResponse, RgbColor, ScreenGeometry, SelectElementOptionOrOptgroup,
|
||||
SimpleDialogRequest, TraversalId, WebResourceRequest, WebResourceResponse,
|
||||
WebResourceResponseMsg,
|
||||
AlertResponse, AllowOrDeny, AuthenticationResponse, BluetoothDeviceDescription,
|
||||
ConfirmResponse, ConsoleLogLevel, ContextMenuAction, ContextMenuElementInformation,
|
||||
ContextMenuItem, Cursor, EmbedderControlId, EmbedderControlResponse, FilePickerRequest,
|
||||
FilterPattern, InputEventId, InputEventResult, InputMethodType, LoadStatus, MediaSessionEvent,
|
||||
NewWebViewDetails, Notification, PermissionFeature, PromptResponse, RgbColor, ScreenGeometry,
|
||||
SelectElementOptionOrOptgroup, SimpleDialogRequest, TraversalId, WebResourceRequest,
|
||||
WebResourceResponse, WebResourceResponseMsg,
|
||||
};
|
||||
use paint_api::rendering_context::RenderingContext;
|
||||
use servo_base::generic_channel::GenericSender;
|
||||
use servo_base::generic_channel::{GenericSender, SendError};
|
||||
use servo_base::id::PipelineId;
|
||||
use servo_constellation_traits::EmbedderToConstellationMessage;
|
||||
use tokio::sync::mpsc::UnboundedSender as TokioSender;
|
||||
@@ -126,6 +126,39 @@ pub struct ProtocolHandlerRegistration {
|
||||
pub register_or_unregister: RegisterOrUnregister,
|
||||
}
|
||||
|
||||
/// A request to let the user chose a Bluetooth device.
|
||||
pub struct BluetoothDeviceSelectionRequest {
|
||||
devices: Vec<BluetoothDeviceDescription>,
|
||||
responder: IpcResponder<Option<String>>,
|
||||
}
|
||||
|
||||
impl BluetoothDeviceSelectionRequest {
|
||||
pub(crate) fn new(
|
||||
devices: Vec<BluetoothDeviceDescription>,
|
||||
responder: GenericSender<Option<String>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
devices,
|
||||
responder: IpcResponder::new(responder, None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the device chosen by the user.
|
||||
pub fn pick_device(mut self, device: &BluetoothDeviceDescription) -> Result<(), SendError> {
|
||||
self.responder.send(Some(device.address.clone()))
|
||||
}
|
||||
|
||||
/// Cancel this request.
|
||||
pub fn cancel(mut self) -> Result<(), SendError> {
|
||||
self.responder.send(None)
|
||||
}
|
||||
|
||||
/// The set of devices that the user can chose from.
|
||||
pub fn devices(&self) -> &Vec<BluetoothDeviceDescription> {
|
||||
&self.devices
|
||||
}
|
||||
}
|
||||
|
||||
/// A request to authenticate a [`WebView`] navigation. Embedders may choose to prompt
|
||||
/// the user to enter credentials or simply ignore this request (in which case credentials
|
||||
/// will not be used).
|
||||
@@ -943,15 +976,7 @@ pub trait WebViewDelegate {
|
||||
}
|
||||
|
||||
/// Open dialog to select bluetooth device.
|
||||
/// TODO: This API needs to be reworked to match the new model of how responses are sent.
|
||||
fn show_bluetooth_device_dialog(
|
||||
&self,
|
||||
_webview: WebView,
|
||||
_: Vec<String>,
|
||||
response_sender: GenericSender<Option<String>>,
|
||||
) {
|
||||
let _ = response_sender.send(None);
|
||||
}
|
||||
fn show_bluetooth_device_dialog(&self, _webview: WebView, _: BluetoothDeviceSelectionRequest) {}
|
||||
|
||||
/// Request that the embedder show UI elements for form controls that are not integrated
|
||||
/// into page content, such as dropdowns for `<select>` elements.
|
||||
|
||||
@@ -424,6 +424,12 @@ impl From<ConsoleLogLevel> for log::Level {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct BluetoothDeviceDescription {
|
||||
pub address: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Messages towards the embedder.
|
||||
#[derive(Deserialize, IntoStaticStr, Serialize)]
|
||||
pub enum EmbedderMsg {
|
||||
@@ -483,7 +489,11 @@ pub enum EmbedderMsg {
|
||||
/// A pipeline panicked. First string is the reason, second one is the backtrace.
|
||||
Panic(WebViewId, String, Option<String>),
|
||||
/// Open dialog to select bluetooth device.
|
||||
GetSelectedBluetoothDevice(WebViewId, Vec<String>, GenericSender<Option<String>>),
|
||||
GetSelectedBluetoothDevice(
|
||||
WebViewId,
|
||||
Vec<BluetoothDeviceDescription>,
|
||||
GenericSender<Option<String>>,
|
||||
),
|
||||
/// Open interface to request permission specified by prompt.
|
||||
PromptPermission(WebViewId, PermissionFeature, GenericSender<AllowOrDeny>),
|
||||
/// Report a complete sampled profile
|
||||
|
||||
@@ -10,12 +10,12 @@ use egui::{
|
||||
};
|
||||
use egui_file_dialog::{DialogState, FileDialog as EguiFileDialog};
|
||||
use euclid::Length;
|
||||
use log::warn;
|
||||
use log::{error, warn};
|
||||
use servo::{
|
||||
AlertDialog, AuthenticationRequest, ColorPicker, ConfirmDialog, ContextMenu, ContextMenuItem,
|
||||
DeviceIndependentPixel, EmbedderControlId, FilePicker, GenericSender, PermissionRequest,
|
||||
PromptDialog, RgbColor, SelectElement, SelectElementOption, SelectElementOptionOrOptgroup,
|
||||
SimpleDialog,
|
||||
AlertDialog, AuthenticationRequest, BluetoothDeviceSelectionRequest, ColorPicker,
|
||||
ConfirmDialog, ContextMenu, ContextMenuItem, DeviceIndependentPixel, EmbedderControlId,
|
||||
FilePicker, PermissionRequest, PromptDialog, RgbColor, SelectElement, SelectElementOption,
|
||||
SelectElementOptionOrOptgroup, SimpleDialog,
|
||||
};
|
||||
|
||||
/// The minimum width of many UI elements including dialog boxes and menus,
|
||||
@@ -41,9 +41,8 @@ pub enum Dialog {
|
||||
request: Option<PermissionRequest>,
|
||||
},
|
||||
SelectDevice {
|
||||
devices: Vec<String>,
|
||||
request: Option<BluetoothDeviceSelectionRequest>,
|
||||
selected_device_index: usize,
|
||||
response_sender: GenericSender<Option<String>>,
|
||||
},
|
||||
SelectElement {
|
||||
maybe_prompt: Option<SelectElement>,
|
||||
@@ -113,14 +112,10 @@ impl Dialog {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_device_selection_dialog(
|
||||
devices: Vec<String>,
|
||||
response_sender: GenericSender<Option<String>>,
|
||||
) -> Self {
|
||||
pub fn new_device_selection_dialog(request: BluetoothDeviceSelectionRequest) -> Self {
|
||||
Dialog::SelectDevice {
|
||||
devices,
|
||||
request: Some(request),
|
||||
selected_device_index: 0,
|
||||
response_sender,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,29 +401,36 @@ impl Dialog {
|
||||
is_open
|
||||
},
|
||||
Dialog::SelectDevice {
|
||||
devices,
|
||||
request,
|
||||
selected_device_index,
|
||||
response_sender,
|
||||
} => {
|
||||
let mut is_open = true;
|
||||
let modal = Modal::new("device_picker".into());
|
||||
modal.show(ctx, |ui| {
|
||||
let mut frame = egui::Frame::default().inner_margin(10.0).begin(ui);
|
||||
frame.content_ui.set_min_width(MINIMUM_UI_ELEMENT_WIDTH);
|
||||
if let Some(request) = request {
|
||||
let mut frame = egui::Frame::default().inner_margin(10.0).begin(ui);
|
||||
frame.content_ui.set_min_width(MINIMUM_UI_ELEMENT_WIDTH);
|
||||
|
||||
frame.content_ui.heading("Choose a Device");
|
||||
frame.content_ui.add_space(10.0);
|
||||
frame.content_ui.heading("Choose a Device");
|
||||
frame.content_ui.add_space(10.0);
|
||||
|
||||
egui::ComboBox::from_label("")
|
||||
.selected_text(&devices[*selected_device_index + 1])
|
||||
.show_ui(&mut frame.content_ui, |ui| {
|
||||
for i in (0..devices.len() - 1).step_by(2) {
|
||||
let device_name = &devices[i + 1];
|
||||
ui.selectable_value(selected_device_index, i, device_name);
|
||||
}
|
||||
});
|
||||
let devices = request.devices();
|
||||
egui::ComboBox::from_label("")
|
||||
.selected_text(devices[*selected_device_index].name.clone())
|
||||
.show_ui(&mut frame.content_ui, |ui| {
|
||||
for (i, device) in devices.iter().enumerate() {
|
||||
ui.selectable_value(
|
||||
selected_device_index,
|
||||
i,
|
||||
device.name.clone(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
frame.end(ui);
|
||||
frame.end(ui);
|
||||
} else {
|
||||
error!("Unexpected: None SelectDevice while the dialog is open");
|
||||
}
|
||||
|
||||
egui::Sides::new().show(
|
||||
ui,
|
||||
@@ -437,18 +439,21 @@ impl Dialog {
|
||||
if ui.button("Ok").clicked() ||
|
||||
ui.input(|i| i.key_pressed(egui::Key::Enter))
|
||||
{
|
||||
if let Err(e) = response_sender
|
||||
.send(Some(devices[*selected_device_index].clone()))
|
||||
{
|
||||
warn!("Failed to send device selection: {}", e);
|
||||
let request =
|
||||
request.take().expect("non-None until dialog is closed");
|
||||
let choice = &request.devices()[*selected_device_index].clone();
|
||||
if let Err(error) = request.pick_device(choice) {
|
||||
warn!("Failed to send device selection: {error}");
|
||||
}
|
||||
is_open = false;
|
||||
}
|
||||
if ui.button("Cancel").clicked() ||
|
||||
ui.input(|i| i.key_pressed(egui::Key::Escape))
|
||||
{
|
||||
if let Err(e) = response_sender.send(None) {
|
||||
warn!("Failed to send cancellation: {}", e);
|
||||
let request =
|
||||
request.take().expect("non-None until dialog is closed");
|
||||
if let Err(error) = request.cancel() {
|
||||
warn!("Failed to send cancellation: {error}");
|
||||
}
|
||||
is_open = false;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ use keyboard_types::ShortcutMatcher;
|
||||
use log::{debug, info};
|
||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
|
||||
use servo::{
|
||||
AuthenticationRequest, Cursor, DeviceIndependentIntRect, DeviceIndependentPixel,
|
||||
DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel, DevicePoint, EmbedderControl,
|
||||
EmbedderControlId, GenericSender, ImeEvent, InputEvent, InputEventId, InputEventResult,
|
||||
AuthenticationRequest, BluetoothDeviceSelectionRequest, Cursor, DeviceIndependentIntRect,
|
||||
DeviceIndependentPixel, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel, DevicePoint,
|
||||
EmbedderControl, EmbedderControlId, ImeEvent, InputEvent, InputEventId, InputEventResult,
|
||||
InputMethodControl, Key, KeyState, KeyboardEvent, Modifiers, MouseButton as ServoMouseButton,
|
||||
MouseButtonAction, MouseButtonEvent, MouseLeftViewportEvent, MouseMoveEvent, NamedKey,
|
||||
OffscreenRenderingContext, PermissionRequest, RenderingContext, ScreenGeometry, Theme,
|
||||
@@ -1106,18 +1106,9 @@ impl PlatformWindow for HeadedWindow {
|
||||
fn show_bluetooth_device_dialog(
|
||||
&self,
|
||||
webview_id: WebViewId,
|
||||
devices: Vec<String>,
|
||||
response_sender: GenericSender<Option<String>>,
|
||||
request: BluetoothDeviceSelectionRequest,
|
||||
) {
|
||||
if devices.is_empty() {
|
||||
let _ = response_sender.send(None);
|
||||
return;
|
||||
}
|
||||
|
||||
self.add_dialog(
|
||||
webview_id,
|
||||
Dialog::new_device_selection_dialog(devices, response_sender),
|
||||
);
|
||||
self.add_dialog(webview_id, Dialog::new_device_selection_dialog(request));
|
||||
}
|
||||
|
||||
fn show_permission_dialog(&self, webview_id: WebViewId, permission_request: PermissionRequest) {
|
||||
|
||||
@@ -19,13 +19,13 @@ use image::{DynamicImage, ImageFormat, RgbaImage};
|
||||
use libc::c_char;
|
||||
use log::{error, info, warn};
|
||||
use servo::{
|
||||
AllowOrDenyRequest, AuthenticationRequest, CSSPixel, ConsoleLogLevel, CreateNewWebViewRequest,
|
||||
DeviceIntPoint, DeviceIntSize, EmbedderControl, EmbedderControlId, EventLoopWaker,
|
||||
GenericSender, InputEvent, InputEventId, InputEventResult, JSValue, LoadStatus,
|
||||
MediaSessionEvent, PermissionRequest, PrefValue, Preferences, ScreenshotCaptureError, Servo,
|
||||
ServoDelegate, ServoError, TraversalId, UserContentManager, WebDriverCommandMsg,
|
||||
WebDriverJSResult, WebDriverLoadStatus, WebDriverScriptCommand, WebDriverSenders, WebView,
|
||||
WebViewDelegate, WebViewId, pref,
|
||||
AllowOrDenyRequest, AuthenticationRequest, BluetoothDeviceSelectionRequest, CSSPixel,
|
||||
ConsoleLogLevel, CreateNewWebViewRequest, DeviceIntPoint, DeviceIntSize, EmbedderControl,
|
||||
EmbedderControlId, EventLoopWaker, GenericSender, InputEvent, InputEventId, InputEventResult,
|
||||
JSValue, LoadStatus, MediaSessionEvent, PermissionRequest, PrefValue, Preferences,
|
||||
ScreenshotCaptureError, Servo, ServoDelegate, ServoError, TraversalId, UserContentManager,
|
||||
WebDriverCommandMsg, WebDriverJSResult, WebDriverLoadStatus, WebDriverScriptCommand,
|
||||
WebDriverSenders, WebView, WebViewDelegate, WebViewId, pref,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
@@ -779,11 +779,10 @@ impl WebViewDelegate for RunningAppState {
|
||||
fn show_bluetooth_device_dialog(
|
||||
&self,
|
||||
webview: WebView,
|
||||
devices: Vec<String>,
|
||||
response_sender: GenericSender<Option<String>>,
|
||||
request: BluetoothDeviceSelectionRequest,
|
||||
) {
|
||||
self.platform_window_for_webview_id(webview.id())
|
||||
.show_bluetooth_device_dialog(webview.id(), devices, response_sender);
|
||||
.show_bluetooth_device_dialog(webview.id(), request);
|
||||
}
|
||||
|
||||
fn request_permission(&self, webview: WebView, permission_request: PermissionRequest) {
|
||||
|
||||
@@ -8,9 +8,9 @@ use std::rc::Rc;
|
||||
use euclid::Scale;
|
||||
use log::warn;
|
||||
use servo::{
|
||||
AuthenticationRequest, ConsoleLogLevel, Cursor, DeviceIndependentIntRect,
|
||||
DeviceIndependentPixel, DeviceIntPoint, DeviceIntSize, DevicePixel, EmbedderControl,
|
||||
EmbedderControlId, GenericSender, InputEventId, InputEventResult, MediaSessionEvent,
|
||||
AuthenticationRequest, BluetoothDeviceSelectionRequest, ConsoleLogLevel, Cursor,
|
||||
DeviceIndependentIntRect, DeviceIndependentPixel, DeviceIntPoint, DeviceIntSize, DevicePixel,
|
||||
EmbedderControl, EmbedderControlId, InputEventId, InputEventResult, MediaSessionEvent,
|
||||
PermissionRequest, RenderingContext, ScreenGeometry, WebView, WebViewBuilder, WebViewId,
|
||||
};
|
||||
use url::Url;
|
||||
@@ -412,8 +412,7 @@ pub(crate) trait PlatformWindow {
|
||||
fn show_bluetooth_device_dialog(
|
||||
&self,
|
||||
_: WebViewId,
|
||||
_devices: Vec<String>,
|
||||
_: GenericSender<Option<String>>,
|
||||
_request: BluetoothDeviceSelectionRequest,
|
||||
) {
|
||||
}
|
||||
fn show_permission_dialog(&self, _: WebViewId, _: PermissionRequest) {}
|
||||
|
||||
Reference in New Issue
Block a user